1mod parser;
30pub(crate) use parser::NEWLINE;
31pub use parser::PowerShellSession;
54pub use parser::PsValue;
70pub use parser::ScriptResult;
90pub use parser::Token;
118pub use parser::Variables;
143
144#[cfg(test)]
145mod tests {
146 use std::collections::HashMap;
147
148 use super::*;
149 use crate::Token;
150
151 #[test]
152 fn obfuscation_1() {
153 let input = r#"
154$ilryNQSTt="System.$([cHAR]([ByTE]0x4d)+[ChAR]([byte]0x61)+[chAr](110)+[cHar]([byTE]0x61)+[cHaR](103)+[cHar](101*64/64)+[chaR]([byTE]0x6d)+[cHAr](101)+[CHAr]([byTE]0x6e)+[Char](116*103/103)).$([Char]([ByTe]0x41)+[Char](117+70-70)+[CHAr]([ByTE]0x74)+[CHar]([bYte]0x6f)+[CHar]([bytE]0x6d)+[ChaR]([ByTe]0x61)+[CHar]([bYte]0x74)+[CHAR]([byte]0x69)+[Char](111*26/26)+[chAr]([BYTe]0x6e)).$(('Ârmí'+'Ùtìl'+'s').NORmalizE([ChAR](44+26)+[chAR](111*9/9)+[cHar](82+32)+[ChaR](109*34/34)+[cHaR](68+24-24)) -replace [ChAr](92)+[CHaR]([BYTe]0x70)+[Char]([BytE]0x7b)+[CHaR]([BYTe]0x4d)+[chAR](110)+[ChAr](15+110))";$ilryNQSTt
155"#;
156
157 let mut p = PowerShellSession::new();
158 assert_eq!(
159 p.safe_eval(input).unwrap().as_str(),
160 "System.Management.Automation.ArmiUtils"
161 );
162 }
163
164 #[test]
165 fn obfuscation_2() {
166 let input = r#"
167$(('W'+'r'+'î'+'t'+'é'+'Í'+'n'+'t'+'3'+'2').NormAlIzE([chaR]([bYTE]0x46)+[CHAR](111)+[ChAR]([Byte]0x72)+[CHAR]([BytE]0x6d)+[CHAr](64+4)) -replace [cHAr]([BytE]0x5c)+[char]([bYtE]0x70)+[ChAR]([byTe]0x7b)+[cHar]([bYtE]0x4d)+[Char]([bYte]0x6e)+[CHAR](125))
168"#;
169
170 let mut p = PowerShellSession::new();
171 assert_eq!(p.safe_eval(input).unwrap().as_str(), "WriteInt32");
172 }
173
174 #[test]
175 fn obfuscation_3() {
176 let input = r#"
177$([cHar]([BYte]0x65)+[chAr]([bYTE]0x6d)+[CHaR]([ByTe]0x73)+[char](105)+[CHAR]([bYTE]0x43)+[cHaR](111)+[chaR]([bYTE]0x6e)+[cHAr]([bYTe]0x74)+[cHAr](32+69)+[cHaR](120+30-30)+[cHAR]([bYte]0x74))
178"#;
179
180 let mut p = PowerShellSession::new();
181 assert_eq!(p.safe_eval(input).unwrap().as_str(), "emsiContext");
182 }
183
184 #[test]
185 fn obfuscation_4() {
186 let input = r#"
187[syStem.texT.EncoDInG]::unIcoDe.geTstRiNg([SYSTem.cOnVERT]::froMbasE64striNg("WwBjAGgAYQByAF0AKABbAGkAbgB0AF0AKAAiADkAZQA0AGUAIgAgAC0AcgBlAHAAbABhAGMAZQAgACIAZQAiACkAKwAzACkA"))"#;
188
189 let mut p = PowerShellSession::new();
190 assert_eq!(
191 p.safe_eval(input).unwrap().as_str(),
192 r#"[char]([int]("9e4e" -replace "e")+3)"#
193 );
194 }
195
196 #[test]
197 fn deobfuscation() {
198 let mut p = PowerShellSession::new();
200 let input = r#" $global:var = [char]([int]("9e4e" -replace "e")+3); [int]'a';$var"#;
201 let script_res = p.parse_input(input).unwrap();
202 assert_eq!(script_res.result(), 'a'.into());
203 assert_eq!(
204 script_res.deobfuscated(),
205 vec!["$var = 'a'", "[int]'a'"].join(NEWLINE)
206 );
207 assert_eq!(script_res.errors().len(), 1);
208 assert_eq!(
209 script_res.errors()[0].to_string(),
210 "ValError: Cannot convert value \"String\" to type \"Int\""
211 );
212
213 let mut p = PowerShellSession::new();
215 let input = r#" $global:var = [char]([int]("9e4e" -replace "e")+3) "#;
216 let script_res = p.parse_input(input).unwrap();
217
218 assert_eq!(script_res.errors().len(), 0);
219
220 let script_res = p.parse_input(" [int]'a';$var ").unwrap();
221 assert_eq!(script_res.deobfuscated(), vec!["[int]'a'"].join(NEWLINE));
222 assert_eq!(script_res.output(), vec!["a"].join(NEWLINE));
223 assert_eq!(script_res.errors().len(), 1);
224 assert_eq!(
225 script_res.errors()[0].to_string(),
226 "ValError: Cannot convert value \"String\" to type \"Int\""
227 );
228 }
229
230 #[test]
231 fn deobfuscation_non_existing_value() {
232 let mut p = PowerShellSession::new();
234 let input = r#" $local:var = $env:programfiles;[int]'a';$var"#;
235 let script_res = p.parse_input(input).unwrap();
236 assert_eq!(script_res.result(), PsValue::Null);
237 assert_eq!(
238 script_res.deobfuscated(),
239 vec!["$local:var = $env:programfiles", "[int]'a'", "$var"].join(NEWLINE)
240 );
241 assert_eq!(script_res.errors().len(), 3);
242 assert_eq!(
243 script_res.errors()[0].to_string(),
244 "VariableError: Variable \"programfiles\" is not defined"
245 );
246 assert_eq!(
247 script_res.errors()[1].to_string(),
248 "ValError: Cannot convert value \"String\" to type \"Int\""
249 );
250 assert_eq!(
251 script_res.errors()[2].to_string(),
252 "VariableError: Variable \"var\" is not defined"
253 );
254
255 let mut p = PowerShellSession::new().with_variables(Variables::force_eval());
257 let input = r#" $global:var = $env:programfiles;[int]'a';$var"#;
258 let script_res = p.parse_input(input).unwrap();
259 assert_eq!(script_res.result(), PsValue::Null);
260 assert_eq!(
261 script_res.deobfuscated(),
262 vec!["$var = $null", "[int]'a'"].join(NEWLINE)
263 );
264 assert_eq!(script_res.errors().len(), 1);
265 }
266
267 #[test]
268 fn deobfuscation_env_value() {
269 let mut p = PowerShellSession::new().with_variables(Variables::env());
271 let input = r#" $global:var = $env:programfiles;$var"#;
272 let script_res = p.parse_input(input).unwrap();
273 assert_eq!(
274 script_res.result(),
275 PsValue::String(std::env::var("PROGRAMFILES").unwrap())
276 );
277 assert_eq!(
278 script_res.deobfuscated(),
279 vec![format!(
280 "$var = '{}'",
281 std::env::var("PROGRAMFILES").unwrap()
282 )]
283 .join(NEWLINE)
284 );
285 assert_eq!(script_res.errors().len(), 0);
286 }
287
288 #[test]
289 fn hash_table() {
290 let mut p = PowerShellSession::new().with_variables(Variables::env());
292 let input = r#"
293$nestedData = @{
294 Users = @(
295 @{ Name = "Alice"; Age = 30; Skills = @("PowerShell", "Python") }
296 @{ Name = "Bob"; Age = 25; Skills = @("Java", "C#") }
297 )
298 Settings = @{
299 Theme = "Dark"
300 Language = "en-US"
301 }
302}
303"$nestedData"
304 "#;
305 let script_res = p.parse_input(input).unwrap();
306 assert_eq!(
307 script_res.result(),
308 PsValue::String("System.Collections.Hashtable".to_string())
309 );
310
311 assert_eq!(
312 p.parse_input("$nesteddata.settings").unwrap().result(),
313 PsValue::HashTable(HashMap::from([
314 ("language".to_string(), PsValue::String("en-US".to_string())),
315 ("theme".to_string(), PsValue::String("Dark".to_string())),
316 ]))
317 );
318
319 assert_eq!(
320 p.safe_eval("$nesteddata.settings.theme").unwrap(),
321 "Dark".to_string()
322 );
323
324 assert_eq!(
325 p.parse_input("$nesteddata.users[0]").unwrap().result(),
326 PsValue::HashTable(HashMap::from([
327 (
328 "skills".to_string(),
329 PsValue::Array(vec![
330 PsValue::String("PowerShell".to_string()),
331 PsValue::String("Python".to_string().into())
332 ])
333 ),
334 ("name".to_string(), PsValue::String("Alice".to_string())),
335 ("age".to_string(), PsValue::Int(30)),
336 ]))
337 );
338
339 assert_eq!(
340 p.safe_eval("$nesteddata.users[0]['name']").unwrap(),
341 "Alice".to_string()
342 );
343
344 assert_eq!(
345 p.safe_eval("$nesteddata.users[0].NAME").unwrap(),
346 "Alice".to_string()
347 );
348 }
349
350 #[test]
351 fn test_simple_arithmetic() {
352 let input = r#"
353Write-Host "=== Test 3: Arithmetic Operations ===" -ForegroundColor Green
354$a = 10
355$b = 5
356Write-Output "Addition: $(($a + $b))"
357Write-Output "Subtraction: $(($a - $b))"
358Write-Output "Multiplication: $(($a * $b))"
359Write-Output "Division: $(($a / $b))"
360Write-Output "Modulo: $(($a % $b))"
361"#;
362
363 let script_result = PowerShellSession::new().parse_input(input).unwrap();
364
365 assert_eq!(script_result.result(), PsValue::String("Modulo: 0".into()));
366 assert_eq!(
367 script_result.output(),
368 vec![
369 r#"=== Test 3: Arithmetic Operations ==="#,
370 r#"Addition: 15"#,
371 r#"Subtraction: 5"#,
372 r#"Multiplication: 50"#,
373 r#"Division: 2"#,
374 r#"Modulo: 0"#
375 ]
376 .join(NEWLINE)
377 );
378 assert_eq!(script_result.errors().len(), 0);
379 assert_eq!(script_result.tokens().strings(), vec![]);
380 assert_eq!(script_result.tokens().expandable_strings().len(), 6);
381 assert_eq!(
382 script_result.tokens().expandable_strings()[1],
383 Token::StringExpandable(
384 "\"Addition: $(($a + $b))\"".to_string(),
385 "Addition: 15".to_string()
386 )
387 );
388 assert_eq!(script_result.tokens().expression().len(), 12);
389 assert_eq!(
390 script_result.tokens().expression()[2],
391 Token::Expression("$a + $b".to_string(), PsValue::Int(15))
392 );
393 }
394
395 #[test]
396 fn test_scripts() {
397 use std::fs;
398 let Ok(entries) = fs::read_dir("test_scripts") else {
399 panic!("Failed to read test files");
400 };
401 for entry in entries {
402 let dir_entry = entry.unwrap();
403 if std::fs::FileType::is_dir(&dir_entry.file_type().unwrap()) {
404 let input_script = dir_entry.path().join("input.ps1");
406 let deobfuscated = dir_entry.path().join("deobfuscated.txt");
407 let output = dir_entry.path().join("output.txt");
408
409 let Ok(content) = fs::read_to_string(&input_script) else {
410 panic!("Failed to read test files");
411 };
412
413 let Ok(deobfuscated) = fs::read_to_string(&deobfuscated) else {
414 panic!("Failed to read test files");
415 };
416
417 let Ok(output) = fs::read_to_string(&output) else {
418 panic!("Failed to read test files");
419 };
420
421 let script_result = PowerShellSession::new()
422 .with_variables(Variables::env())
423 .parse_input(&content)
424 .unwrap();
425
426 let deobfuscated_vec = deobfuscated
427 .lines()
428 .map(|s| s.trim_end())
429 .collect::<Vec<&str>>();
430
431 let script_deobfuscated = script_result.deobfuscated();
432
433 let output_vec = output.lines().map(|s| s.trim_end()).collect::<Vec<&str>>();
434
435 let script_output = script_result.output();
436
437 let _name = dir_entry
438 .path()
439 .components()
440 .last()
441 .unwrap()
442 .as_os_str()
443 .to_string_lossy()
444 .to_string();
445 let script_deobfuscated_vec = script_deobfuscated
453 .lines()
454 .map(|s| s.trim_end())
455 .collect::<Vec<&str>>();
456
457 let script_output_vec = script_output
458 .lines()
459 .map(|s| s.trim_end())
460 .collect::<Vec<&str>>();
461
462 for i in 0..deobfuscated_vec.len() {
463 assert_eq!(deobfuscated_vec[i], script_deobfuscated_vec[i]);
464 }
465
466 for i in 0..output_vec.len() {
467 assert_eq!(output_vec[i], script_output_vec[i]);
468 }
469 }
470 }
471 }
472
473 #[test]
474 fn test_range() {
475 let mut p = PowerShellSession::new().with_variables(Variables::env());
477 let input = r#" $numbers = 1..10; $numbers"#;
478 let script_res = p.parse_input(input).unwrap();
479 assert_eq!(
480 script_res.deobfuscated(),
481 vec!["$numbers = @(1,2,3,4,5,6,7,8,9,10)"].join(NEWLINE)
482 );
483 assert_eq!(script_res.errors().len(), 0);
484 }
485
486 #[test]
487 fn even_numbers() {
488 let mut p = PowerShellSession::new().with_variables(Variables::env());
490 let input = r#" $numbers = 1..10; $evenNumbers = $numbers | Where-Object { $_ % 2 -eq 0 }; $evenNumbers"#;
491 let script_res = p.parse_input(input).unwrap();
492 assert_eq!(
493 script_res.result(),
494 PsValue::Array(vec![
495 PsValue::Int(2),
496 PsValue::Int(4),
497 PsValue::Int(6),
498 PsValue::Int(8),
499 PsValue::Int(10)
500 ])
501 );
502 assert_eq!(
503 script_res.deobfuscated(),
504 vec![
505 "$numbers = @(1,2,3,4,5,6,7,8,9,10)",
506 "$evennumbers = @(2,4,6,8,10)"
507 ]
508 .join(NEWLINE)
509 );
510 assert_eq!(script_res.errors().len(), 0);
511 }
512
513 fn _test_function() {
515 let mut p = PowerShellSession::new().with_variables(Variables::env());
517 let input = r#"
518function Get-Square($number) {
519 return $number * $number
520}
521"Square of 5: $(Get-Square 5)" "#;
522 let script_res = p.parse_input(input).unwrap();
523 assert_eq!(
524 script_res.deobfuscated(),
525 vec![
526 "function Get-Square($number) {",
527 " return $number * $number",
528 "}",
529 " \"Square of 5: $(Get-Square 5)\""
530 ]
531 .join(NEWLINE)
532 );
533 assert_eq!(script_res.errors().len(), 2);
534 }
535}