1mod parser;
2
3pub(crate) use parser::NEWLINE;
4pub use parser::{PowerShellSession, PsValue, ScriptResult, Token, Variables};
5
6#[cfg(test)]
7mod tests {
8 use std::collections::HashMap;
9
10 use super::*;
11 use crate::Token;
12
13 #[test]
14 fn obfuscation_1() {
15 let input = r#"
16$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
17"#;
18
19 let mut p = PowerShellSession::new();
20 assert_eq!(
21 p.safe_eval(input).unwrap().as_str(),
22 "System.Management.Automation.ArmiUtils"
23 );
24 }
25
26 #[test]
27 fn obfuscation_2() {
28 let input = r#"
29$(('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))
30"#;
31
32 let mut p = PowerShellSession::new();
33 assert_eq!(p.safe_eval(input).unwrap().as_str(), "WriteInt32");
34 }
35
36 #[test]
37 fn obfuscation_3() {
38 let input = r#"
39$([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))
40"#;
41
42 let mut p = PowerShellSession::new();
43 assert_eq!(p.safe_eval(input).unwrap().as_str(), "emsiContext");
44 }
45
46 #[test]
47 fn obfuscation_4() {
48 let input = r#"
49[syStem.texT.EncoDInG]::unIcoDe.geTstRiNg([SYSTem.cOnVERT]::froMbasE64striNg("WwBjAGgAYQByAF0AKABbAGkAbgB0AF0AKAAiADkAZQA0AGUAIgAgAC0AcgBlAHAAbABhAGMAZQAgACIAZQAiACkAKwAzACkA"))"#;
50
51 let mut p = PowerShellSession::new();
52 assert_eq!(
53 p.safe_eval(input).unwrap().as_str(),
54 r#"[char]([int]("9e4e" -replace "e")+3)"#
55 );
56 }
57
58 #[test]
59 fn deobfuscation() {
60 let mut p = PowerShellSession::new();
62 let input = r#" $global:var = [char]([int]("9e4e" -replace "e")+3); [int]'a';$var"#;
63 let script_res = p.parse_input(input).unwrap();
64 assert_eq!(script_res.result(), 'a'.into());
65 assert_eq!(
66 script_res.deobfuscated(),
67 vec!["$var = 'a'", "[int]'a'"].join(NEWLINE)
68 );
69 assert_eq!(script_res.errors().len(), 1);
70 assert_eq!(
71 script_res.errors()[0].to_string(),
72 "ValError: Cannot convert value \"String\" to type \"Int\""
73 );
74
75 let mut p = PowerShellSession::new();
77 let input = r#" $global:var = [char]([int]("9e4e" -replace "e")+3) "#;
78 let script_res = p.parse_input(input).unwrap();
79
80 assert_eq!(script_res.errors().len(), 0);
81
82 let script_res = p.parse_input(" [int]'a';$var ").unwrap();
83 assert_eq!(script_res.deobfuscated(), vec!["[int]'a'"].join(NEWLINE));
84 assert_eq!(script_res.output(), vec!["a"].join(NEWLINE));
85 assert_eq!(script_res.errors().len(), 1);
86 assert_eq!(
87 script_res.errors()[0].to_string(),
88 "ValError: Cannot convert value \"String\" to type \"Int\""
89 );
90 }
91
92 #[test]
93 fn deobfuscation_non_existing_value() {
94 let mut p = PowerShellSession::new();
96 let input = r#" $local:var = $env:programfiles;[int]'a';$var"#;
97 let script_res = p.parse_input(input).unwrap();
98 assert_eq!(script_res.result(), PsValue::Null);
99 assert_eq!(
100 script_res.deobfuscated(),
101 vec!["$local:var = $env:programfiles", "[int]'a'", "$var"].join(NEWLINE)
102 );
103 assert_eq!(script_res.errors().len(), 3);
104 assert_eq!(
105 script_res.errors()[0].to_string(),
106 "VariableError: Variable \"programfiles\" is not defined"
107 );
108 assert_eq!(
109 script_res.errors()[1].to_string(),
110 "ValError: Cannot convert value \"String\" to type \"Int\""
111 );
112 assert_eq!(
113 script_res.errors()[2].to_string(),
114 "VariableError: Variable \"var\" is not defined"
115 );
116
117 let mut p = PowerShellSession::new().with_variables(Variables::force_eval());
119 let input = r#" $global:var = $env:programfiles;[int]'a';$var"#;
120 let script_res = p.parse_input(input).unwrap();
121 assert_eq!(script_res.result(), PsValue::Null);
122 assert_eq!(
123 script_res.deobfuscated(),
124 vec!["$var = $null", "[int]'a'"].join(NEWLINE)
125 );
126 assert_eq!(script_res.errors().len(), 1);
127 }
128
129 #[test]
130 fn deobfuscation_env_value() {
131 let mut p = PowerShellSession::new().with_variables(Variables::env());
133 let input = r#" $global:var = $env:programfiles;$var"#;
134 let script_res = p.parse_input(input).unwrap();
135 assert_eq!(
136 script_res.result(),
137 PsValue::String(std::env::var("PROGRAMFILES").unwrap())
138 );
139 assert_eq!(
140 script_res.deobfuscated(),
141 vec![format!(
142 "$var = '{}'",
143 std::env::var("PROGRAMFILES").unwrap()
144 )]
145 .join(NEWLINE)
146 );
147 assert_eq!(script_res.errors().len(), 0);
148 }
149
150 #[test]
151 fn hash_table() {
152 let mut p = PowerShellSession::new().with_variables(Variables::env());
154 let input = r#"
155$nestedData = @{
156 Users = @(
157 @{ Name = "Alice"; Age = 30; Skills = @("PowerShell", "Python") }
158 @{ Name = "Bob"; Age = 25; Skills = @("Java", "C#") }
159 )
160 Settings = @{
161 Theme = "Dark"
162 Language = "en-US"
163 }
164}
165"$nestedData"
166 "#;
167 let script_res = p.parse_input(input).unwrap();
168 assert_eq!(
169 script_res.result(),
170 PsValue::String("System.Collections.Hashtable".to_string())
171 );
172
173 assert_eq!(
174 p.parse_input("$nesteddata.settings").unwrap().result(),
175 PsValue::HashTable(HashMap::from([
176 ("language".to_string(), PsValue::String("en-US".to_string())),
177 ("theme".to_string(), PsValue::String("Dark".to_string())),
178 ]))
179 );
180
181 assert_eq!(
182 p.safe_eval("$nesteddata.settings.theme").unwrap(),
183 "Dark".to_string()
184 );
185
186 assert_eq!(
187 p.parse_input("$nesteddata.users[0]").unwrap().result(),
188 PsValue::HashTable(HashMap::from([
189 (
190 "skills".to_string(),
191 PsValue::Array(vec![
192 PsValue::String("PowerShell".to_string()),
193 PsValue::String("Python".to_string().into())
194 ])
195 ),
196 ("name".to_string(), PsValue::String("Alice".to_string())),
197 ("age".to_string(), PsValue::Int(30)),
198 ]))
199 );
200
201 assert_eq!(
202 p.safe_eval("$nesteddata.users[0]['name']").unwrap(),
203 "Alice".to_string()
204 );
205
206 assert_eq!(
207 p.safe_eval("$nesteddata.users[0].NAME").unwrap(),
208 "Alice".to_string()
209 );
210 }
211
212 #[test]
213 fn test_simple_arithmetic() {
214 let input = r#"
215Write-Host "=== Test 3: Arithmetic Operations ===" -ForegroundColor Green
216$a = 10
217$b = 5
218Write-Output "Addition: $(($a + $b))"
219Write-Output "Subtraction: $(($a - $b))"
220Write-Output "Multiplication: $(($a * $b))"
221Write-Output "Division: $(($a / $b))"
222Write-Output "Modulo: $(($a % $b))"
223"#;
224
225 let script_result = PowerShellSession::new().parse_input(input).unwrap();
226
227 assert_eq!(script_result.result(), PsValue::String("Modulo: 0".into()));
228 assert_eq!(
229 script_result.output(),
230 vec![
231 r#"=== Test 3: Arithmetic Operations ==="#,
232 r#"Addition: 15"#,
233 r#"Subtraction: 5"#,
234 r#"Multiplication: 50"#,
235 r#"Division: 2"#,
236 r#"Modulo: 0"#
237 ]
238 .join(NEWLINE)
239 );
240 assert_eq!(script_result.errors().len(), 0);
241 assert_eq!(script_result.tokens().strings(), vec![]);
242 assert_eq!(script_result.tokens().expandable_strings().len(), 6);
243 assert_eq!(
244 script_result.tokens().expandable_strings()[1],
245 Token::StringExpandable(
246 "\"Addition: $(($a + $b))\"".to_string(),
247 "Addition: 15".to_string()
248 )
249 );
250 assert_eq!(script_result.tokens().expression().len(), 12);
251 assert_eq!(
252 script_result.tokens().expression()[2],
253 Token::Expression("$a + $b".to_string(), PsValue::Int(15))
254 );
255 }
256
257 #[test]
258 fn test_scripts() {
259 use std::fs;
260 let Ok(entries) = fs::read_dir("test_scripts") else {
261 panic!("Failed to read test files");
262 };
263 for entry in entries {
264 let dir_entry = entry.unwrap();
265 if std::fs::FileType::is_dir(&dir_entry.file_type().unwrap()) {
266 let input_script = dir_entry.path().join("input.ps1");
268 let deobfuscated = dir_entry.path().join("deobfuscated.txt");
269 let output = dir_entry.path().join("output.txt");
270
271 let Ok(content) = fs::read_to_string(&input_script) else {
272 panic!("Failed to read test files");
273 };
274
275 let Ok(deobfuscated) = fs::read_to_string(&deobfuscated) else {
276 panic!("Failed to read test files");
277 };
278
279 let Ok(output) = fs::read_to_string(&output) else {
280 panic!("Failed to read test files");
281 };
282
283 let script_result = PowerShellSession::new()
284 .with_variables(Variables::env())
285 .parse_input(&content)
286 .unwrap();
287
288 let deobfuscated_vec = deobfuscated
289 .lines()
290 .map(|s| s.trim_end())
291 .collect::<Vec<&str>>();
292
293 let script_deobfuscated = script_result.deobfuscated();
294
295 let output_vec = output.lines().map(|s| s.trim_end()).collect::<Vec<&str>>();
296
297 let script_output = script_result.output();
298
299 let script_deobfuscated_vec = script_deobfuscated
303 .lines()
304 .map(|s| s.trim_end())
305 .collect::<Vec<&str>>();
306
307 let script_output_vec = script_output
308 .lines()
309 .map(|s| s.trim_end())
310 .collect::<Vec<&str>>();
311
312 for i in 0..deobfuscated_vec.len() {
313 assert_eq!(deobfuscated_vec[i], script_deobfuscated_vec[i]);
314 }
315
316 for i in 0..output_vec.len() {
317 assert_eq!(output_vec[i], script_output_vec[i]);
318 }
319 }
320 }
321 }
322
323 #[test]
324 fn test_range() {
325 let mut p = PowerShellSession::new().with_variables(Variables::env());
327 let input = r#" $numbers = 1..10; $numbers"#;
328 let script_res = p.parse_input(input).unwrap();
329 assert_eq!(
330 script_res.deobfuscated(),
331 vec!["$numbers = @(1,2,3,4,5,6,7,8,9,10)"].join(NEWLINE)
332 );
333 assert_eq!(script_res.errors().len(), 0);
334 }
335
336 #[test]
337 fn even_numbers() {
338 let mut p = PowerShellSession::new().with_variables(Variables::env());
340 let input = r#" $numbers = 1..10; $evenNumbers = $numbers | Where-Object { $_ % 2 -eq 0 }; $evenNumbers"#;
341 let script_res = p.parse_input(input).unwrap();
342 assert_eq!(
343 script_res.result(),
344 PsValue::Array(vec![
345 PsValue::Int(2),
346 PsValue::Int(4),
347 PsValue::Int(6),
348 PsValue::Int(8),
349 PsValue::Int(10)
350 ])
351 );
352 assert_eq!(
353 script_res.deobfuscated(),
354 vec![
355 "$numbers = @(1,2,3,4,5,6,7,8,9,10)",
356 "$evennumbers = @(2,4,6,8,10)"
357 ]
358 .join(NEWLINE)
359 );
360 assert_eq!(script_res.errors().len(), 0);
361 }
362}