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