1mod parser;
30pub(crate) use parser::NEWLINE;
31pub use parser::PowerShellSession;
54pub use parser::PsValue;
70pub use parser::ScriptResult;
90pub use parser::Token;
122pub use parser::Variables;
147
148#[cfg(test)]
149mod tests {
150 use std::collections::HashMap;
151
152 use super::*;
153 use crate::Token;
154
155 #[test]
156 fn obfuscation_1() {
157 let input = r#"
158$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
159"#;
160
161 let mut p = PowerShellSession::new();
162 assert_eq!(
163 p.safe_eval(input).unwrap().as_str(),
164 "System.Management.Automation.ArmiUtils"
165 );
166 }
167
168 #[test]
169 fn obfuscation_2() {
170 let input = r#"
171$(('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))
172"#;
173
174 let mut p = PowerShellSession::new();
175 assert_eq!(p.safe_eval(input).unwrap().as_str(), "WriteInt32");
176 }
177
178 #[test]
179 fn obfuscation_3() {
180 let input = r#"
181$([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))
182"#;
183
184 let mut p = PowerShellSession::new();
185 assert_eq!(p.safe_eval(input).unwrap().as_str(), "emsiContext");
186 }
187
188 #[test]
189 fn obfuscation_4() {
190 let input = r#"
191[syStem.texT.EncoDInG]::unIcoDe.geTstRiNg([SYSTem.cOnVERT]::froMbasE64striNg("WwBjAGgAYQByAF0AKABbAGkAbgB0AF0AKAAiADkAZQA0AGUAIgAgAC0AcgBlAHAAbABhAGMAZQAgACIAZQAiACkAKwAzACkA"))"#;
192
193 let mut p = PowerShellSession::new();
194 assert_eq!(
195 p.safe_eval(input).unwrap().as_str(),
196 r#"[char]([int]("9e4e" -replace "e")+3)"#
197 );
198 }
199
200 #[test]
201 fn deobfuscation() {
202 let mut p = PowerShellSession::new();
204 let input = r#" $global:var = [char]([int]("9e4e" -replace "e")+3); [int]'a';$var"#;
205 let script_res = p.parse_input(input).unwrap();
206 assert_eq!(script_res.result(), 'a'.into());
207 assert_eq!(
208 script_res.deobfuscated(),
209 vec!["$var = 'a'", "[int]'a'"].join(NEWLINE)
210 );
211 assert_eq!(script_res.errors().len(), 1);
212 assert_eq!(
213 script_res.errors()[0].to_string(),
214 "ValError: Cannot convert value \"String\" to type \"Int\""
215 );
216
217 let mut p = PowerShellSession::new();
219 let input = r#" $global:var = [char]([int]("9e4e" -replace "e")+3) "#;
220 let script_res = p.parse_input(input).unwrap();
221
222 assert_eq!(script_res.errors().len(), 0);
223
224 let script_res = p.parse_input(" [int]'a';$var ").unwrap();
225 assert_eq!(script_res.deobfuscated(), vec!["[int]'a'"].join(NEWLINE));
226 assert_eq!(script_res.output(), vec!["a"].join(NEWLINE));
227 assert_eq!(script_res.errors().len(), 1);
228 assert_eq!(
229 script_res.errors()[0].to_string(),
230 "ValError: Cannot convert value \"String\" to type \"Int\""
231 );
232 }
233
234 #[test]
235 fn deobfuscation_non_existing_value() {
236 let mut p = PowerShellSession::new();
238 let input = r#" $local:var = $env:programfiles;[int]'a';$var"#;
239 let script_res = p.parse_input(input).unwrap();
240 assert_eq!(script_res.result(), PsValue::Null);
241 assert_eq!(
242 script_res.deobfuscated(),
243 vec!["$local:var = $env:programfiles", "[int]'a'", "$var"].join(NEWLINE)
244 );
245 assert_eq!(script_res.errors().len(), 3);
246 assert_eq!(
247 script_res.errors()[0].to_string(),
248 "VariableError: Variable \"programfiles\" is not defined"
249 );
250 assert_eq!(
251 script_res.errors()[1].to_string(),
252 "ValError: Cannot convert value \"String\" to type \"Int\""
253 );
254 assert_eq!(
255 script_res.errors()[2].to_string(),
256 "VariableError: Variable \"var\" is not defined"
257 );
258
259 let mut p = PowerShellSession::new().with_variables(Variables::force_eval());
261 let input = r#" $global:var = $env:programfiles;[int]'a';$var"#;
262 let script_res = p.parse_input(input).unwrap();
263 assert_eq!(script_res.result(), PsValue::Null);
264 assert_eq!(
265 script_res.deobfuscated(),
266 vec!["$var = $null", "[int]'a'"].join(NEWLINE)
267 );
268 assert_eq!(script_res.errors().len(), 1);
269 }
270
271 #[test]
272 fn deobfuscation_env_value() {
273 let mut p = PowerShellSession::new().with_variables(Variables::env());
275 let input = r#" $global:var = $env:programfiles;$var"#;
276 let script_res = p.parse_input(input).unwrap();
277 assert_eq!(
278 script_res.result(),
279 PsValue::String(std::env::var("PROGRAMFILES").unwrap())
280 );
281 assert_eq!(
282 script_res.deobfuscated(),
283 vec![format!(
284 "$var = '{}'",
285 std::env::var("PROGRAMFILES").unwrap()
286 )]
287 .join(NEWLINE)
288 );
289 assert_eq!(script_res.errors().len(), 0);
290 }
291
292 #[test]
293 fn hash_table() {
294 let mut p = PowerShellSession::new().with_variables(Variables::env());
296 let input = r#"
297$nestedData = @{
298 Users = @(
299 @{ Name = "Alice"; Age = 30; Skills = @("PowerShell", "Python") }
300 @{ Name = "Bob"; Age = 25; Skills = @("Java", "C#") }
301 )
302 Settings = @{
303 Theme = "Dark"
304 Language = "en-US"
305 }
306}
307"$nestedData"
308 "#;
309 let script_res = p.parse_input(input).unwrap();
310 assert_eq!(
311 script_res.result(),
312 PsValue::String("System.Collections.Hashtable".to_string())
313 );
314
315 assert_eq!(
316 p.parse_input("$nesteddata.settings").unwrap().result(),
317 PsValue::HashTable(HashMap::from([
318 ("language".to_string(), PsValue::String("en-US".to_string())),
319 ("theme".to_string(), PsValue::String("Dark".to_string())),
320 ]))
321 );
322
323 assert_eq!(
324 p.safe_eval("$nesteddata.settings.theme").unwrap(),
325 "Dark".to_string()
326 );
327
328 assert_eq!(
329 p.parse_input("$nesteddata.users[0]").unwrap().result(),
330 PsValue::HashTable(HashMap::from([
331 (
332 "skills".to_string(),
333 PsValue::Array(vec![
334 PsValue::String("PowerShell".to_string()),
335 PsValue::String("Python".to_string().into())
336 ])
337 ),
338 ("name".to_string(), PsValue::String("Alice".to_string())),
339 ("age".to_string(), PsValue::Int(30)),
340 ]))
341 );
342
343 assert_eq!(
344 p.safe_eval("$nesteddata.users[0]['name']").unwrap(),
345 "Alice".to_string()
346 );
347
348 assert_eq!(
349 p.safe_eval("$nesteddata.users[0].NAME").unwrap(),
350 "Alice".to_string()
351 );
352 }
353
354 #[test]
355 fn test_simple_arithmetic() {
356 let input = r#"
357Write-Host "=== Test 3: Arithmetic Operations ===" -ForegroundColor Green
358$a = 10
359$b = 5
360Write-Output "Addition: $(($a + $b))"
361Write-Output "Subtraction: $(($a - $b))"
362Write-Output "Multiplication: $(($a * $b))"
363Write-Output "Division: $(($a / $b))"
364Write-Output "Modulo: $(($a % $b))"
365"#;
366
367 let script_result = PowerShellSession::new().parse_input(input).unwrap();
368
369 assert_eq!(script_result.result(), PsValue::String("Modulo: 0".into()));
370 assert_eq!(
371 script_result.output(),
372 vec![
373 r#"=== Test 3: Arithmetic Operations ==="#,
374 r#"Addition: 15"#,
375 r#"Subtraction: 5"#,
376 r#"Multiplication: 50"#,
377 r#"Division: 2"#,
378 r#"Modulo: 0"#
379 ]
380 .join(NEWLINE)
381 );
382 assert_eq!(script_result.errors().len(), 0);
383 assert_eq!(script_result.tokens().strings(), vec![]);
384 assert_eq!(script_result.tokens().expandable_strings().len(), 6);
385 assert_eq!(
386 script_result.tokens().expandable_strings()[1],
387 Token::StringExpandable(
388 "\"Addition: $(($a + $b))\"".to_string(),
389 "Addition: 15".to_string()
390 )
391 );
392 assert_eq!(script_result.tokens().expression().len(), 12);
393 assert_eq!(
394 script_result.tokens().expression()[2],
395 Token::Expression("$a + $b".to_string(), PsValue::Int(15))
396 );
397 }
398
399 #[test]
400 fn test_scripts() {
401 use std::fs;
402 let Ok(entries) = fs::read_dir("test_scripts") else {
403 panic!("Failed to read test files");
404 };
405 for entry in entries {
406 let dir_entry = entry.unwrap();
407 if std::fs::FileType::is_dir(&dir_entry.file_type().unwrap()) {
408 let input_script = dir_entry.path().join("input.ps1");
410 let deobfuscated = dir_entry.path().join("deobfuscated.txt");
411 let output = dir_entry.path().join("output.txt");
412
413 let Ok(content) = fs::read_to_string(&input_script) else {
414 panic!("Failed to read test files");
415 };
416
417 let Ok(deobfuscated) = fs::read_to_string(&deobfuscated) else {
418 panic!("Failed to read test files");
419 };
420
421 let Ok(output) = fs::read_to_string(&output) else {
422 panic!("Failed to read test files");
423 };
424
425 let script_result = PowerShellSession::new()
426 .with_variables(Variables::env())
427 .parse_input(&content)
428 .unwrap();
429
430 let deobfuscated_vec = deobfuscated
431 .lines()
432 .map(|s| s.trim_end())
433 .collect::<Vec<&str>>();
434
435 let script_deobfuscated = script_result.deobfuscated();
436
437 let output_vec = output.lines().map(|s| s.trim_end()).collect::<Vec<&str>>();
438
439 let script_output = script_result.output();
440
441 let _name = dir_entry
442 .path()
443 .components()
444 .last()
445 .unwrap()
446 .as_os_str()
447 .to_string_lossy()
448 .to_string();
449 let script_deobfuscated_vec = script_deobfuscated
457 .lines()
458 .map(|s| s.trim_end())
459 .collect::<Vec<&str>>();
460
461 let script_output_vec = script_output
462 .lines()
463 .map(|s| s.trim_end())
464 .collect::<Vec<&str>>();
465
466 for i in 0..deobfuscated_vec.len() {
467 assert_eq!(deobfuscated_vec[i], script_deobfuscated_vec[i]);
468 }
469
470 for i in 0..output_vec.len() {
471 assert_eq!(output_vec[i], script_output_vec[i]);
472 }
473 }
474 }
475 }
476
477 #[test]
478 fn test_range() {
479 let mut p = PowerShellSession::new().with_variables(Variables::env());
481 let input = r#" $numbers = 1..10; $numbers"#;
482 let script_res = p.parse_input(input).unwrap();
483 assert_eq!(
484 script_res.deobfuscated(),
485 vec!["$numbers = @(1,2,3,4,5,6,7,8,9,10)"].join(NEWLINE)
486 );
487 assert_eq!(script_res.errors().len(), 0);
488 }
489
490 #[test]
491 fn even_numbers() {
492 let mut p = PowerShellSession::new().with_variables(Variables::env());
494 let input = r#" $numbers = 1..10; $evenNumbers = $numbers | Where-Object { $_ % 2 -eq 0 }; $evenNumbers"#;
495 let script_res = p.parse_input(input).unwrap();
496 assert_eq!(
497 script_res.result(),
498 PsValue::Array(vec![
499 PsValue::Int(2),
500 PsValue::Int(4),
501 PsValue::Int(6),
502 PsValue::Int(8),
503 PsValue::Int(10)
504 ])
505 );
506 assert_eq!(
507 script_res.deobfuscated(),
508 vec![
509 "$numbers = @(1,2,3,4,5,6,7,8,9,10)",
510 "$evennumbers = @(2,4,6,8,10)"
511 ]
512 .join(NEWLINE)
513 );
514 assert_eq!(script_res.errors().len(), 0);
515 }
516
517 fn _test_function() {
519 let mut p = PowerShellSession::new().with_variables(Variables::env());
521 let input = r#"
522function Get-Square($number) {
523 return $number * $number
524}
525"Square of 5: $(Get-Square 5)" "#;
526 let script_res = p.parse_input(input).unwrap();
527 assert_eq!(
528 script_res.deobfuscated(),
529 vec![
530 "function Get-Square($number) {",
531 " return $number * $number",
532 "}",
533 " \"Square of 5: $(Get-Square 5)\""
534 ]
535 .join(NEWLINE)
536 );
537 assert_eq!(script_res.errors().len(), 2);
538 }
539
540 #[test]
541 fn test_if() {
542 let mut p = PowerShellSession::new().with_variables(Variables::env());
544 let input = r#"
545 # Test 10: Conditional Statements
546if ($true) {
547 $if_result = "condition true"
548}
549
550if ($false) {
551 $else_result = "false branch"
552} else {
553 $else_result = "true branch"
554}
555
556$score = 85
557if ($score -ge 90) {
558 $grade = "A"
559} elseif ($score -ge 80) {
560 $grade = "B"
561} else {
562 $grade = "C"
563}
564
565 "#;
566 let script_res = p.parse_input(input).unwrap();
567 assert_eq!(
568 script_res.deobfuscated(),
569 vec![
570 "$if_result = 'condition true'",
571 "$else_result = 'true branch'",
572 "$score = 85",
573 "$grade = 'B'"
574 ]
575 .join(NEWLINE)
576 );
577 assert_eq!(script_res.errors().len(), 0);
578 }
579}