Skip to main content

ScriptResult

Struct ScriptResult 

Source
pub struct ScriptResult { /* private fields */ }
Expand description

Contains the complete result of parsing and evaluating a PowerShell script.

This structure holds the final result value, any output generated, parsing errors encountered, and the tokenized representation of the script. It’s particularly useful for debugging and deobfuscation purposes.

§Examples

use ps_parser::PowerShellSession;

let mut session = PowerShellSession::new();
let script_result = session.parse_command("$a = 42; $a").unwrap();

// Access different parts of the result
println!("Final value: {:?}", script_result.result());
println!("Output: {:?}", script_result.output());
println!("Errors: {:?}", script_result.errors());

Implementations§

Source§

impl ScriptResult

Source

pub fn result(&self) -> PsValue

Examples found in repository?
examples/panic_hunt.rs (line 201)
160fn depth_matrix() {
161    let patterns: &[(&str, &str, &str)] = &[
162        ("paren", "(", ")"),
163        ("block", "& {", "}"),
164        ("sub", "$(", ")"),
165        ("dq", "\"$(", ")\""),
166        ("idx", "$a[", "]"),
167        ("if", "if(1){", "}"),
168        ("ifelse", "if(0){1}else{", "}"),
169        ("arr", "@(", ")"),
170        ("hash", "@{a=", "}"),
171        ("neg", "-(", ")"),
172        ("method", "'a'.Replace((", "),'b')"),
173        ("plus", "+(", ")"),
174        ("not", "!(", ")"),
175        ("cast", "[int](", ")"),
176        ("sb", "{", "}"),
177        ("cmd", "echo (", ")"),
178    ];
179    let only = std::env::args().nth(2);
180    let depths: Vec<usize> = match std::env::args().nth(3) {
181        Some(d) => d.split(',').map(|d| d.parse().unwrap()).collect(),
182        None => vec![5, 10, 20, 40, 60, 80, 100, 300, 1500, 5000],
183    };
184    for n in depths {
185        println!("--- depth {n}");
186        let mut cases: Vec<(String, String)> = patterns
187            .iter()
188            .filter(|(name, _, _)| only.as_deref().is_none_or(|o| o == "all" || o == *name))
189            .map(|(name, o, c)| (name.to_string(), format!("$a=1;{}1{}", o.repeat(n), c.repeat(n))))
190            .collect();
191        cases.push((
192            "func".into(),
193            format!("function f($n) {{ if ($n -gt 0) {{ f ($n - 1) }} else {{ 'done' }} }}; f {n}"),
194        ));
195        for (name, script) in cases {
196            let (tx, rx) = std::sync::mpsc::channel();
197            std::thread::Builder::new()
198                .stack_size(1 << 20)
199                .spawn(move || {
200                    let t = std::time::Instant::now();
201                    let r = PowerShellSession::new().parse_script(&script).map(|r| r.result());
202                    let _ = tx.send(format!("{:?} [{} ms]", r, t.elapsed().as_millis()));
203                })
204                .unwrap();
205            let res = rx
206                .recv_timeout(std::time::Duration::from_secs(20))
207                .unwrap_or_else(|_| "TIMEOUT".into());
208            let short: String = res.chars().take(70).collect();
209            println!("{name:>7}: {short}");
210        }
211    }
212    std::process::exit(0);
213}
214
215fn run(script: &str) -> Option<String> {
216    LAST.with(|l| l.borrow_mut().clear());
217    let res = panic::catch_unwind(|| {
218        let mut ps = PowerShellSession::new();
219        let _ = ps.parse_script(script);
220        let _ = PowerShellSession::new().parse_command(script);
221    });
222    // the hook fires even when the parser catches the panic internally
223    let last = LAST.with(|l| l.borrow().clone());
224    (res.is_err() || !last.is_empty()).then_some(last)
225}
226
227fn main() {
228    panic::set_hook(Box::new(|info| {
229        let loc = info
230            .location()
231            .map(|l| format!("{}:{}", l.file(), l.line()))
232            .unwrap_or_default();
233        let msg = info
234            .payload()
235            .downcast_ref::<&str>()
236            .map(|s| s.to_string())
237            .or_else(|| info.payload().downcast_ref::<String>().cloned())
238            .unwrap_or_default();
239        LAST.with(|l| *l.borrow_mut() = format!("{loc} | {msg}"));
240    }));
241
242    if std::env::args().nth(1).as_deref() == Some("--depth") {
243        depth_matrix();
244        return;
245    }
246
247    if let Some(script) = std::env::args().nth(1) {
248        let (tx, rx) = std::sync::mpsc::channel();
249        std::thread::spawn(move || {
250            let t = std::time::Instant::now();
251            let r = PowerShellSession::new().parse_script(&script).map(|r| r.result());
252            let _ = tx.send(format!("[{} ms] {:?}", t.elapsed().as_millis(), r));
253        });
254        let res = rx
255            .recv_timeout(std::time::Duration::from_secs(15))
256            .unwrap_or_else(|_| "TIMEOUT".into());
257        println!("{}", res.chars().take(200).collect::<String>());
258        std::process::exit(0);
259    }
260
261    // location -> (count, shortest input)
262    let mut found: BTreeMap<String, (usize, String)> = BTreeMap::new();
263    let mut record = |input: &str, panic: String| {
264        let key = panic.split(" | ").next().unwrap_or_default().to_string();
265        let e = found.entry(key).or_insert((0, input.to_string()));
266        e.0 += 1;
267        if input.len() < e.1.len() {
268            e.1 = input.to_string();
269        }
270        let _ = panic;
271    };
272
273    let mut runs = 0;
274    for s in TARGETED {
275        runs += 1;
276        if let Some(p) = run(s) {
277            println!("TARGETED PANIC: {s:?}\n    {p}");
278            record(s, p);
279        }
280    }
281
282    let corpus = [
283        "test_scripts/comprehensive/input.ps1",
284        "test_scripts/parser_focused/input.ps1",
285        "test_scripts/stress/input.ps1",
286        "test_scripts/enc_oneliner.ps1",
287    ];
288    for path in corpus {
289        // local samples (e.g. enc_oneliner.ps1) are not in the repo
290        let Ok(text) = std::fs::read_to_string(path) else {
291            continue;
292        };
293        let lines: Vec<&str> = text.lines().collect();
294
295        // every single line on its own, and every line truncated at each char boundary
296        for line in &lines {
297            let idxs: Vec<usize> = line.char_indices().map(|(i, _)| i).chain([line.len()]).collect();
298            for &i in &idxs {
299                runs += 1;
300                let s = &line[..i];
301                if let Some(p) = run(s) {
302                    record(s, p);
303                }
304            }
305        }
306
307        // whole file truncated at every line boundary
308        for n in 0..=lines.len() {
309            runs += 1;
310            let s = lines[..n].join("\n");
311            if let Some(p) = run(&s) {
312                record(&s, p);
313            }
314        }
315    }
316
317    println!("\n=== {runs} runs, {} distinct panic locations ===", found.len());
318    for (loc, (count, input)) in &found {
319        let short: String = input.chars().take(160).collect();
320        println!("{loc}  (x{count})\n    shortest: {short:?}");
321    }
322}
Source

pub fn deobfuscated_lines(&self) -> Vec<String>

Source

pub fn deobfuscated(&self) -> String

Examples found in repository?
examples/enc_oneliner.rs (line 12)
3fn main() {
4    let Some(path) = std::env::args().nth(1) else {
5        eprintln!("usage: cargo run --example enc_oneliner -- <script.ps1>");
6        std::process::exit(1);
7    };
8    let script = std::fs::read_to_string(path).unwrap();
9
10    println!("=== parse_command ===");
11    match PowerShellSession::new().parse_command(script.trim()) {
12        Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
13        Err(e) => println!("error: {e:?}"),
14    }
15
16    println!("=== parse_script ===");
17    match PowerShellSession::new().parse_script(script.trim()) {
18        Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
19        Err(e) => println!("error: {e:?}"),
20    }
21}
Source

pub fn tokens(&self) -> Tokens

Source

pub fn errors(&self) -> Vec<ParserError>

Source

pub fn output(&self) -> String

Examples found in repository?
examples/enc_oneliner.rs (line 12)
3fn main() {
4    let Some(path) = std::env::args().nth(1) else {
5        eprintln!("usage: cargo run --example enc_oneliner -- <script.ps1>");
6        std::process::exit(1);
7    };
8    let script = std::fs::read_to_string(path).unwrap();
9
10    println!("=== parse_command ===");
11    match PowerShellSession::new().parse_command(script.trim()) {
12        Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
13        Err(e) => println!("error: {e:?}"),
14    }
15
16    println!("=== parse_script ===");
17    match PowerShellSession::new().parse_script(script.trim()) {
18        Ok(res) => println!("deob:\n{}\nout:\n{}", res.deobfuscated(), res.output()),
19        Err(e) => println!("error: {e:?}"),
20    }
21}
Source

pub fn output_lines(&self) -> Vec<String>

Source

pub fn script_variables(&self) -> HashMap<String, PsValue>

Trait Implementations§

Source§

impl Debug for ScriptResult

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> MaybeSendSync for T

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.