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
impl ScriptResult
Sourcepub fn result(&self) -> PsValue
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}pub fn deobfuscated_lines(&self) -> Vec<String>
Sourcepub fn deobfuscated(&self) -> String
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}pub fn tokens(&self) -> Tokens
pub fn errors(&self) -> Vec<ParserError>
Sourcepub fn output(&self) -> String
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}pub fn output_lines(&self) -> Vec<String>
pub fn script_variables(&self) -> HashMap<String, PsValue>
Trait Implementations§
Auto Trait Implementations§
impl Freeze for ScriptResult
impl RefUnwindSafe for ScriptResult
impl Send for ScriptResult
impl Sync for ScriptResult
impl Unpin for ScriptResult
impl UnsafeUnpin for ScriptResult
impl UnwindSafe for ScriptResult
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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