mod parser;
pub(crate) use parser::NEWLINE;
pub use parser::PowerShellSession;
pub use parser::PsValue;
pub use parser::ScriptResult;
pub use parser::Token;
pub use parser::Variables;
pub use parser::{CommandToken, ExpressionToken, MethodToken, StringExpandableToken};
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::{ExpressionToken, StringExpandableToken};
#[test]
fn deobfuscation() {
let mut p = PowerShellSession::new();
let input = r#" $script:var = [char]([int]("9e4e" -replace "e")+3); [int]'a';$var"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), 'a'.into());
assert_eq!(
script_res.deobfuscated(),
vec!["$script:var = 'a'", "[int]'a'", "'a'"].join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 1);
assert_eq!(
script_res.errors()[0].to_string(),
"ValError: Failed to convert value \"a\" to type Int"
);
let mut p = PowerShellSession::new();
let input = r#" $global:var = [char]([int]("9e4e" -replace "e")+3) "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.errors().len(), 0);
let script_res = p.parse_script(" [int]'a';$var ").unwrap();
assert_eq!(
script_res.deobfuscated(),
vec!["[int]'a'", "'a'"].join(NEWLINE)
);
assert_eq!(script_res.output(), vec!["a"].join(NEWLINE));
assert_eq!(script_res.errors().len(), 1);
assert_eq!(
script_res.errors()[0].to_string(),
"ValError: Failed to convert value \"a\" to type Int"
);
}
#[test]
fn deobfuscation_non_existing_value() {
let mut p = PowerShellSession::new();
let input = r#" $local:var = $env:programfiles;[int]'a';$var"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Null);
assert_eq!(
script_res.deobfuscated(),
vec!["$local:var = $env:programfiles", "[int]'a'", "$var"].join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 3);
assert_eq!(
script_res.errors()[0].to_string(),
"VariableError: Variable \"programfiles\" is not defined"
);
assert_eq!(
script_res.errors()[1].to_string(),
"ValError: Failed to convert value \"a\" to type Int"
);
assert_eq!(
script_res.errors()[2].to_string(),
"VariableError: Variable \"var\" is not defined"
);
let mut p = PowerShellSession::new().with_variables(Variables::force_eval());
let input = r#" $local:var = $env:programfiles;[int]'a';$script:var"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Null);
assert_eq!(
script_res.deobfuscated(),
vec!["$local:var = $null", "[int]'a'"].join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 1);
}
#[test]
fn deobfuscation_env_value() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" $local:var = $env:programfiles;$var"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::String(std::env::var("PROGRAMFILES").unwrap())
);
let program_files = std::env::var("PROGRAMFILES").unwrap();
assert_eq!(
script_res.deobfuscated(),
vec![
format!("$local:var = \"{}\"", program_files),
format!("\"{}\"", program_files)
]
.join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 0);
}
#[test]
fn deobfuscation_from_base_64() {
let mut p = PowerShellSession::new();
let input = r#" $encoded = [syStem.texT.EncoDInG]::unIcoDe.geTstRiNg([char]97);$encoded"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), String::from("\u{FFFD}").into());
let input = r#" [syStem.texT.EncoDInG]::unIcoDe.geTstRiNg([SYSTem.cOnVERT]::froMbasE64striNg("ZABlAGMAbwBkAGUAZAA="))"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), String::from("decoded").into());
}
#[test]
fn hash_table() {
let mut p = PowerShellSession::new().with_variables(Variables::env().values_persist());
let input = r#"
$nestedData = @{
Users = @(
@{ Name = "Alice"; Age = 30; Skills = @("PowerShell", "Python") }
@{ Name = "Bob"; Age = 25; Skills = @("Java", "C#") }
)
Settings = @{
Theme = "Dark"
Language = "en-US"
}
}
"$nestedData"
"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::String("System.Collections.Hashtable".to_string())
);
assert_eq!(
p.parse_script("$nesteddata.settings").unwrap().result(),
PsValue::HashTable(HashMap::from([
("language".to_string(), PsValue::String("en-US".to_string())),
("theme".to_string(), PsValue::String("Dark".to_string())),
]))
);
assert_eq!(
p.safe_eval("$nesteddata.settings.theme").unwrap(),
"Dark".to_string()
);
assert_eq!(
p.parse_script("$nesteddata.users[0]").unwrap().result(),
PsValue::HashTable(HashMap::from([
(
"skills".to_string(),
PsValue::Array(vec![
PsValue::String("PowerShell".to_string()),
PsValue::String("Python".to_string().into())
])
),
("name".to_string(), PsValue::String("Alice".to_string())),
("age".to_string(), PsValue::Int(30)),
]))
);
assert_eq!(
p.safe_eval("$nesteddata.users[0]['name']").unwrap(),
"Alice".to_string()
);
assert_eq!(
p.safe_eval("$nesteddata.users[0].NAME").unwrap(),
"Alice".to_string()
);
let input = r#" $a=@{val = 4};$a.val"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Int(4));
assert_eq!(
script_res.deobfuscated(),
vec!["$a = @{", "\tval = 4", "}", "4"].join(NEWLINE)
);
}
#[test]
fn test_simple_arithmetic() {
let input = r#"
Write-Host "=== Test 3: Arithmetic Operations ===" -ForegroundColor Green
$a = 10
$b = 5
Write-Output "Addition: $(($a + $b))"
Write-Output "Subtraction: $(($a - $b))"
Write-Output "Multiplication: $(($a * $b))"
Write-Output "Division: $(($a / $b))"
Write-Output "Modulo: $(($a % $b))"
"#;
let script_result = PowerShellSession::new().parse_script(input).unwrap();
assert_eq!(script_result.result(), PsValue::String("Modulo: 0".into()));
assert_eq!(
script_result.output(),
vec![
r#"=== Test 3: Arithmetic Operations ==="#,
r#"Addition: 15"#,
r#"Subtraction: 5"#,
r#"Multiplication: 50"#,
r#"Division: 2"#,
r#"Modulo: 0"#
]
.join(NEWLINE)
);
assert_eq!(script_result.errors().len(), 0);
assert_eq!(script_result.tokens().expandable_strings().len(), 6);
assert_eq!(
script_result.tokens().expandable_strings()[1],
StringExpandableToken::new(
"\"Addition: $(($a + $b))\"".to_string(),
"Addition: 15".to_string()
)
);
assert_eq!(script_result.tokens().expressions().len(), 12);
assert_eq!(
script_result.tokens().expressions()[2],
ExpressionToken::new("$a + $b".to_string(), PsValue::Int(15))
);
}
#[test]
fn test_scripts() {
use std::fs;
let Ok(entries) = fs::read_dir("test_scripts") else {
panic!("Failed to read 'test_scripts' directory");
};
for entry in entries {
let dir_entry = entry.unwrap();
if std::fs::FileType::is_dir(&dir_entry.file_type().unwrap()) {
let input_script = dir_entry.path().join("input.ps1");
let expected_deobfuscated_script = dir_entry.path().join("deobfuscated.txt");
let expected_output_script = dir_entry.path().join("output.txt");
let Ok(input) = fs::read_to_string(&input_script) else {
panic!("Failed to read test file: {}", input_script.display());
};
let Ok(expected_deobfuscated) = fs::read_to_string(&expected_deobfuscated_script)
else {
panic!(
"Failed to read test file: {}",
expected_deobfuscated_script.display()
);
};
let Ok(expected_output) = fs::read_to_string(&expected_output_script) else {
panic!(
"Failed to read test file: {}",
expected_output_script.display()
);
};
let script_result = PowerShellSession::new()
.with_variables(Variables::env())
.parse_script(&input)
.unwrap();
let expected_deobfuscated_vec = expected_deobfuscated
.lines()
.map(|s| s.trim_end())
.collect::<Vec<&str>>();
let current_deobfuscated = script_result.deobfuscated();
let current_output = script_result.output();
let expected_output_vec = expected_output
.lines()
.map(|s| s.trim_end())
.collect::<Vec<&str>>();
let current_deobfuscated_vec = current_deobfuscated
.lines()
.map(|s| s.trim_end())
.collect::<Vec<&str>>();
let current_output_vec = current_output
.lines()
.map(|s| s.trim_end())
.collect::<Vec<&str>>();
for i in 0..expected_deobfuscated_vec.len() {
assert_eq!(
expected_deobfuscated_vec[i],
current_deobfuscated_vec[i],
"File: {}, Deobfuscated line: {}",
file_name(&dir_entry),
i + 1
);
}
for i in 0..expected_output_vec.len() {
assert_eq!(
expected_output_vec[i],
current_output_vec[i],
"File: {}, Output line: {}",
file_name(&dir_entry),
i + 1
);
}
}
}
}
fn file_name(dir_entry: &std::fs::DirEntry) -> String {
dir_entry
.path()
.components()
.last()
.unwrap()
.as_os_str()
.to_string_lossy()
.to_string()
}
#[allow(dead_code)]
fn save_files(dir_entry: &std::fs::DirEntry, deobfuscated: &str, output: &str) {
let name = file_name(dir_entry);
std::fs::write(format!("{}_deobfuscated.txt", name), deobfuscated).unwrap();
std::fs::write(format!("{}_output.txt", name), output).unwrap();
}
#[test]
fn test_range() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" $numbers = 1..10; $numbers"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.deobfuscated(),
vec![
"$numbers = @(1,2,3,4,5,6,7,8,9,10)",
"@(1,2,3,4,5,6,7,8,9,10)"
]
.join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 0);
}
#[test]
fn even_numbers() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" $numbers = 1..10; $evenNumbers = $numbers | Where-Object { $_ % 2 -eq 0 }; $evenNumbers"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![
PsValue::Int(2),
PsValue::Int(4),
PsValue::Int(6),
PsValue::Int(8),
PsValue::Int(10)
])
);
assert_eq!(
script_res.deobfuscated(),
vec![
"$numbers = @(1,2,3,4,5,6,7,8,9,10)",
"$evennumbers = @(2,4,6,8,10)",
"@(2,4,6,8,10)"
]
.join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 0);
}
#[test]
fn divisible_by_2_and_3() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" $numbers = 1..10; $numbers | Where { $_ % 2 -eq 0 } | ? { $_ % 3 -eq 0 }"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Int(6));
assert_eq!(
script_res.deobfuscated(),
vec!["$numbers = @(1,2,3,4,5,6,7,8,9,10)", "6"].join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 0);
}
fn _test_function() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
function Get-Square($number) {
return $number * $number
}
"Square of 5: $(Get-Square 5)" "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.deobfuscated(),
vec![
"function Get-Square($number) {",
" return $number * $number",
"}",
" \"Square of 5: $(Get-Square 5)\""
]
.join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 2);
}
#[test]
fn test_if() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
# Test 10: Conditional Statements
if ($true) {
$if_result = "condition true"
}
if ($false) {
$else_result = "false branch"
} else {
$else_result = "true branch"
}
$score = 85
if ($score -ge 90) {
$grade = "A"
} elseif ($score -ge 80) {
$grade = "B"
} else {
$grade = "C"
}
"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.deobfuscated(),
vec![
"$if_result = \"condition true\"",
"$else_result = \"true branch\"",
"$score = 85",
"$grade = \"B\""
]
.join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 0);
}
#[test]
fn format_operator() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" ("{5}{2}{0}{1}{3}{6}{4}" -f 'ut',('oma'+'t'+'ion.'),'.A',('Ems'+'iUt'),'ls',('S'+'ystem.'+'Danage'+'men'+'t'),'i')"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::String("System.Danagement.Automation.EmsiUtils".into())
);
assert_eq!(
script_res.deobfuscated(),
vec![r#""System.Danagement.Automation.EmsiUtils""#].join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 0);
}
#[test]
fn encod_command() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" ("{5}{2}{0}{1}{3}{6}{4}" -f 'ut',('oma'+'t'+'ion.'),'.A',('Ems'+'iUt'),'ls',('S'+'ystem.'+'Danage'+'men'+'t'),'i')"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::String("System.Danagement.Automation.EmsiUtils".into())
);
assert_eq!(
script_res.deobfuscated(),
vec![r#""System.Danagement.Automation.EmsiUtils""#].join(NEWLINE)
);
assert_eq!(script_res.errors().len(), 0);
}
#[test]
fn array_literals() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" $a = 1,2,3;$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3)])
);
assert_eq!(
script_res.deobfuscated(),
vec!["$a = @(1,2,3)", "@(1,2,3)"].join(NEWLINE)
);
let input = r#" $a = "x", 'yyy', "z";$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![
PsValue::String("x".into()),
PsValue::String("yyy".into()),
PsValue::String("z".into())
])
);
assert_eq!(
script_res.deobfuscated(),
vec![r#"$a = @("x","yyy","z")"#, r#"@("x","yyy","z")"#].join(NEWLINE)
);
let input = r#" $a = 1,2+ 3,[long]4;$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![
PsValue::Int(1),
PsValue::Int(2),
PsValue::Int(3),
PsValue::Int(4),
])
);
assert_eq!(
script_res.deobfuscated(),
vec!["$a = @(1,2,3,4)", "@(1,2,3,4)"].join(NEWLINE)
);
let input = r#" $x = 3; $a = $x, $x+1, "count=$x";$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![
PsValue::Int(3),
PsValue::Int(3),
PsValue::Int(1),
PsValue::String("count=3".into()),
])
);
assert_eq!(
script_res.deobfuscated(),
vec![
"$x = 3",
"$a = @(3,3,1,\"count=3\")",
"@(3,3,1,\"count=3\")"
]
.join(NEWLINE)
);
let input = r#" $a = (1, 2), (3, 4);$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2)]),
PsValue::Array(vec![PsValue::Int(3), PsValue::Int(4)]),
])
);
assert_eq!(
script_res.deobfuscated(),
vec!["$a = @(@(1,2),@(3,4))", "@(@(1,2),@(3,4))"].join(NEWLINE)
);
let input = r#" $a = 1, "two", 3.0, $false, (Get-Date);$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![
PsValue::Int(1),
PsValue::String("two".into()),
PsValue::Float(3.0),
PsValue::Bool(false),
PsValue::String("Get-Date".into()),
])
);
assert_eq!(
script_res.deobfuscated(),
vec![
"$a = @(1,\"two\",3,$false,Get-Date)",
"@(1,\"two\",3,$false,Get-Date)"
]
.join(NEWLINE)
);
let input = r#" $a = 1, 2,3;$b = $a,4,5;$b"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3)]),
PsValue::Int(4),
PsValue::Int(5),
])
);
assert_eq!(
script_res.deobfuscated(),
vec!["$a = @(1,2,3)", "$b = @(@(1,2,3),4,5)", "@(@(1,2,3),4,5)"].join(NEWLINE)
);
let input = r#" $a = 1,-2,(-3) | ForEach-Object { $_ * 2 };$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Int(2), PsValue::Int(-4), PsValue::Int(-6),])
);
let input = r#" $a = (1,2,3) | ForEach-Object { $_ };$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3),])
);
assert_eq!(
script_res.deobfuscated(),
vec!["$a = @(1,2,3)", "@(1,2,3)"].join(NEWLINE)
);
let input = r#" $a = @{
A = 1,2,3
B = (4,5),6
}
$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::HashTable(HashMap::from([
(
"a".into(),
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3),])
),
(
"b".into(),
PsValue::Array(vec![
PsValue::Array(vec![PsValue::Int(4), PsValue::Int(5)]),
PsValue::Int(6),
])
),
]))
);
let input = r#" function Foo($x) { $x.GetType().name + $x[2]};Foo(1,2,3)"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::String("Object[]3".into()));
let input = r#" [object[]](1,2,3)"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2), PsValue::Int(3)])
);
let input = r#" $a = ,(42,2);$a"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Array(vec![
PsValue::Int(42),
PsValue::Int(2)
])])
);
let input = r#" function Foo($x) { $x.GetType().name + $x[2]};Foo(1,2,3)"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::String("Object[]3".into()));
let input = r#" function b($x) {$x};b(1,2+3,4)"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![
PsValue::Int(1),
PsValue::Int(2),
PsValue::Int(3),
PsValue::Int(4),
])
);
let input =
r#" $a=@{val = 4};function b($x) {$x};b(1, [long]($a | Where-Object val -eq 4).val)"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(4)])
);
}
#[test]
fn cast_expression() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" $a=@{val = 4};[long]($a).val"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Int(4));
assert_eq!(
script_res.deobfuscated(),
vec!["$a = @{", "\tval = 4", "}", "4"].join(NEWLINE)
);
let input = r#" $a=@{val = 4};[long]($a | Where-Object Val -eq 4).val"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Int(4));
assert_eq!(
script_res.deobfuscated(),
vec!["$a = @{", "\tval = 4", "}", "4"].join(NEWLINE)
);
}
#[test]
fn as_expression() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" '1a1' -replace 'a' -as [int] "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Int(11));
let input = r#" '1a1' -replace ('a' -as [int])"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::String("1a1".into()));
let input = r#" '2' -as [int] -shl 1"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Int(4));
let input = r#" [system.text.encoding]::unicode -shl 1 "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Null);
assert_eq!(
script_res.errors()[0].to_string(),
String::from("BitwiseError: -shl not defined for UnicodeEncoding")
);
let input = r#" [int] -shl 1 "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Null);
assert_eq!(
script_res.errors()[0].to_string(),
String::from("BitwiseError: -shl not defined for Int32")
);
let input = r#" '2' -as ([string] -shl 1) "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Null);
assert_eq!(
script_res.errors()[0].to_string(),
String::from("BitwiseError: -shl not defined for String")
);
let input = r#" '2' -as ([int]) "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Int(2));
let input = r#" '2' -As ([int]) "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Int(2));
}
#[test]
fn cast_assignment() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" [int] $elo = "1"; $elo "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Int(1));
let input = r#" [int] $elo = "1a": $elo"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Null);
assert_eq!(
script_res.errors()[0].to_string(),
String::from("ValError: Failed to convert value \"1a\" to type Int")
);
let input = r#" [double] $elo = "1a": $elo"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::Null);
assert_eq!(
script_res.errors()[0].to_string(),
String::from("ValError: Failed to convert value \"1a\" to type Float")
);
let input = r#" [int[]] $elo = "1", "2"; $elo"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(2)])
);
let input = r#" [byte[]] $elo = "1", "2"; $elo"#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Char(49), PsValue::Char(50)])
);
}
#[test]
fn splatten_arg() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" $a = @{ elo= 2; name= "radek"}; write-output @a "#;
let script_res = p.parse_script(input).unwrap();
assert!(script_res.output().contains("-elo 2"));
assert!(script_res.output().contains("-name radek"));
}
#[test]
fn strange_assignment() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" @(1,2)[0] = 1 "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.errors()[0].to_string(), "Skip".to_string());
let input = r#" "elo"[0] = 1 "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.errors()[0].to_string(), "Skip".to_string());
let input = r#" $a = @(1,2); $a[1] = 5; $a "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(5)])
);
let input = r#" $a = @(1,@(2,3));$a[1] = 6;$a "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![PsValue::Int(1), PsValue::Int(6)])
);
let input = r#" $a = @(1,@(2,3));$a[1][1] = 6;$a "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::Array(vec![
PsValue::Int(1),
PsValue::Array(vec![PsValue::Int(2), PsValue::Int(6)])
])
);
}
#[test]
fn format_expresssion() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" "!{2}!","!{2}!{1}!{0}!" -f 1, "elo{0}", "{0}" -f 'q1' "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(
script_res.result(),
PsValue::String("!q1! !q1!eloq1!1!".into())
);
let input = r#" "{0:00} {1:000} {2:000000}" -f 7, 24, 365 "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::String("07 024 000365".into()));
let input = r#" "{0} vs. {{0}}" -f 'foo' "#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.result(), PsValue::String("foo vs. {0}".into()));
}
#[test]
fn script_param_block() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
[CmdletBinding(DefaultParameterSetName = "Path", HelpURI = "https://go.microsoft.com/fwlink/?LinkId=517145")]
param(
[Parameter(ParameterSetName="Path", Position = 0)]
[System.String[]]
$Path = [IntPtr]::Zero
)
begin
{
# Construct the strongly-typed crypto object
}
process
{
Write-output elo
}
"#;
let _script_res = p.parse_script(input).unwrap();
}
#[test]
fn line_escape() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
if (($OriginalImageBase -eq [Int64]$PEInfo.EffectivePEHandle) `
-or ($PEInfo.IMAGE_NT_HEADERS.OptionalHeader.BaseRelocationTable.Size -eq 0))
{
return
}
"#;
let _script_res = p.parse_script(input).unwrap();
}
#[test]
fn str_escape() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
$ExeArgs = "crypto::cng crypto::capi `"crypto::certificates /export`" `"crypto::certificates /export /systemstore:CERT_SYSTEM_STORE_LOCAL_MACHINE`" exit"
"#;
let result = r#"$exeargs = "crypto::cng crypto::capi "crypto::certificates /export" "crypto::certificates /export /systemstore:CERT_SYSTEM_STORE_LOCAL_MACHINE" exit""#;
let script_res = p.parse_script(input).unwrap();
assert_eq!(script_res.deobfuscated(), result);
}
#[test]
fn unknown_method_arg() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress', [reflection.bindingflags] "Public,Static", $null, [System.Reflection.CallingConventions]::Any, @((New-Object System.Runtime.InteropServices.HandleRef).GetType(), [string]), $null); "#;
let script_res = p.parse_script(input).unwrap();
assert!(script_res.tokens().string_set().contains("GetProcAddress"));
}
#[test]
fn command_args() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#" Get-ProcAddress kernel32.dll GetProcAddress "#;
let script_res = p.parse_script(input).unwrap();
assert!(script_res.tokens().string_set().contains("GetProcAddress"));
}
#[test]
fn emsi_scan_buffer() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
[IntPtr]$hModule = [Kernel32]::LoadLibrary("emsi.dll")
Write-Host "[+] EMSI DLL Handle: $hModule"
"#;
let script_res = p.parse_script(input).unwrap();
assert!(script_res.tokens().string_set().contains("emsi.dll"));
}
#[test]
fn disable_script_logging() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
$settings = [Ref].Assembly.GetType("System.Management.Automation.Utils").GetField("cachedGroupPolicySettings","NonPublic,Static").GetValue($null);
"#;
let script_res = p.parse_script(input).unwrap();
assert!(
script_res
.tokens()
.string_set()
.contains("System.Management.Automation.Utils")
);
}
#[test]
fn buffer_patch_in_memory() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
function MyPatch{
if(-not ([System.Management.Automation.PSTypeName]"Bypass.EMSI").Type) {
[Reflection.Assembly]::Load([byte[]]@(77, 90, 144, 0,0)) |
Out-Null;
Write-Output "DLL has been reflected";
}
[Bypass.EMSI]::Patch();
}
MyPatch;
Start-Sleep 1;
"#;
let script_res = p.parse_script(input).unwrap();
assert!(
script_res.tokens().ttypes().contains(
"System.Management.Automation.PSTypeName"
.to_ascii_lowercase()
.as_str()
)
);
}
#[test]
fn as_expression_with_value_access_on_the_right_side() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
$elo.Invoke('something',(('Non'+'Public,Static') -as [String].Assembly))
"#;
let script_res = p.parse_script(input).unwrap();
assert!(
script_res
.tokens()
.string_set()
.contains("NonPublic,Static")
);
}
#[test]
fn ends_with_emsi() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
[System.IO.File]::WriteAllBytes("$pwd\emsi.dll", $temp)
"#;
let script_res = p.parse_script(input).unwrap();
assert!(script_res.tokens().string_set().contains("$pwd\\emsi.dll"));
}
#[test]
fn switch_statement() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
switch ($var) {
"a" { Write-Output "A" }
1 { LoadLib("emsi.dll") }
default { Write-Output "Other" }
}
"#;
let script_res = p.parse_script(input).unwrap();
assert!(script_res.tokens().string_set().contains("emsi.dll"));
}
#[test]
fn format_obfuscation() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
"{6}{3}{1}{4}{2}{0}{5}" -f([chaR]117 +'t'+'il'),[char]97,([char]101+'m'+'si'), (("{0}{2}" -f '.M','an','an')+'age'+'men'+'t.'),('u'+'to'+("{0}{2}{1}" -f 'ma','.','tion')),'s',(("{1}{0}"-f 't','Sys')+'em')
"#;
let script_res = p.parse_script(input).unwrap();
assert!(
script_res
.tokens()
.string_set()
.contains("System.Management.automation.emsiutils")
);
}
#[test]
fn format_obfuscation_1() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
$a."AssEmbly"."GETTYPe"(
(
"{6}{3}{1}{4}{2}{0}{5}" -f([chaR]117 +'t'+'il'),[char]97,([char]101+'m'+'si'), (("{0}{2}" -f '.M','an','an')+'age'+'men'+'t.'),('u'+'to'+("{0}{2}{1}" -f 'ma','.','tion')),'s',(("{1}{0}"-f 't','Sys')+'em')
))
"#;
let script_res = p.parse_script(input).unwrap();
assert!(
script_res
.tokens()
.string_set()
.contains("System.Management.automation.emsiutils")
);
}
#[test]
fn format_obfuscation_2() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
"getfiElD"(
( "{0}{2}{1}" -f('e'+'msi'),'d',('I'+("{0}{1}" -f 'ni','tF')+("{1}{0}"-f 'ile','a')) ),
("{2}{4}{0}{1}{3}" -f ('S'+'tat'),'i',('Non'+("{1}{0}" -f'ubl','P')+'i'),'c','c,')
)
"#;
let script_res = p.parse_script(input).unwrap();
assert!(script_res.tokens().string_set().contains("emsiInitFailed"));
assert!(
script_res
.tokens()
.string_set()
.contains("NonPublic,Static")
);
}
#[test]
fn format_obfuscation_3() {
let mut p = PowerShellSession::new().with_variables(Variables::env());
let input = r#"
SeT-Item('V'+'aR' + 'IA' + (("{1}{0}"-f'1','blE:')+'q2') + ('uZ'+'x'))
([TYpE]("{1}{0}"-F'F','rE'));
(Get-varIABLE ( ('1Q'+'2U') +'zX' ) -VaL)."AssEmbly"."GETTYPe"(
(
"{6}{3}{1}{4}{2}{0}{5}" -f([chaR]117 +'t'+'il'),[char]97,([char]101+'m'+'si'), (("{0}{2}" -f '.M','an','an')+'age'+'men'+'t.'),('u'+'to'+("{0}{2}{1}" -f 'ma','.','tion')),'s',(("{1}{0}"-f 't','Sys')+'em')
))."getfiElD"(
( "{0}{2}{1}" -f('e'+'msi'),'d',('I'+("{0}{1}" -f 'ni','tF')+("{1}{0}"-f 'ile','a')) ),
("{2}{4}{0}{1}{3}" -f ('S'+'tat'),'i',('Non'+("{1}{0}" -f'ubl','P')+'i'),'c','c,')
)."sETVaLUE"( ${nULl},${tRuE} )
"#;
let script_res = p.parse_script(input).unwrap();
assert!(script_res.tokens().string_set().contains("emsiInitFailed"));
assert!(
script_res
.tokens()
.string_set()
.contains("System.Management.automation.emsiutils")
);
}
}