mod arithmetic;
mod bitwise;
mod comparison;
mod contain;
mod join;
mod logical;
mod replace;
mod split;
mod type_check;
pub(crate) use arithmetic::ArithmeticPred;
pub(crate) use bitwise::{BitwiseError, BitwisePred};
pub(crate) use comparison::ComparisonPred;
pub(crate) use contain::ContainPred;
pub(crate) use join::JoinPred;
pub(crate) use logical::LogicalPred;
pub(crate) use replace::ReplacePred;
pub(crate) use split::SplitPred;
use thiserror_no_std::Error;
pub(crate) use type_check::TypeCheckPred;
use super::{Val, ValResult, ValType, value::ValError};
const AS_PREDICATE: &str = "-as";
#[derive(Error, Debug, PartialEq, Clone)]
pub enum OpError {
#[error("The -ireplace operator allows only two elements to follow it, not {0}")]
ReplaceInvalidArgsNumber(usize),
#[error("-is and -isnot needs to have type on ther right side, not {0}")]
NotType(String),
#[error("ValError: {0}")]
ValError(ValError),
#[error("Unknown type: {0}")]
UnknownType(String),
}
impl From<ValError> for OpError {
fn from(value: ValError) -> Self {
Self::ValError(value)
}
}
type OpResult<T> = core::result::Result<T, OpError>;
pub(crate) type StringPredType = Box<dyn Fn(Val, Val) -> OpResult<Val>>;
pub(crate) struct StringPred;
impl StringPred {
pub(crate) fn get(name: &str) -> Option<StringPredType> {
let name_lowercase = name.to_ascii_lowercase();
//-as is very simple, thats why there is no single module for that
if name_lowercase.as_str() == AS_PREDICATE {
return Some(Box::new(move |v1, v2| Ok(v1.cast(&v2)?)));
}
if let Some(compare) = ComparisonPred::get(name_lowercase.as_str()) {
return Some(Box::new(move |v1, v2| Ok(Val::Bool(compare(v1, v2)))));
}
if let Some(replace) = ReplacePred::get(name_lowercase.as_str()) {
return Some(Box::new(move |v1, v2| {
let (from, to) = if let Val::Array(arr) = v2 {
if arr.len() == 1 {
(arr[0].clone(), Val::Null)
} else if arr.len() == 2 {
(arr[0].clone(), arr[1].clone())
} else {
Err(OpError::ReplaceInvalidArgsNumber(arr.len()))?
}
} else {
(v2, Val::Null)
};
Ok(Val::String(replace(v1, from, to).into()))
}));
}
if let Some(type_check) = TypeCheckPred::get(name_lowercase.as_str()) {
return Some(Box::new(move |v1, v2| {
if let Val::RuntimeType(rt) = &v2 {
return Ok(Val::Bool(type_check(v1, rt.type_definition())));
}
Err(OpError::NotType(v2.cast_to_string()))
}));
}
if let Some(join) = JoinPred::get(name_lowercase.as_str()) {
return Some(Box::new(move |v1, v2| Ok(Val::String(join(v1, v2).into()))));
}
if let Some(split) = SplitPred::get(name_lowercase.as_str()) {
return Some(Box::new(move |v1, v2| Ok(split(v1, v2))));
}
if let Some(contain) = ContainPred::get(name_lowercase.as_str()) {
return Some(Box::new(move |v1, v2| Ok(Val::Bool(contain(v1, v2)))));
}
None
}
}
#[cfg(test)]
mod tests {
use crate::{PowerShellSession, Variables};
#[test]
fn test_obfuscation() {
let mut p = PowerShellSession::new();
assert_eq!(p.safe_eval(r#" [CHAR](70+44-44)+[chaR](81+30)+[ChAR]([byTE]0x72)+[CHar]([byTE]0x6d)+[CHAR](68) "#).unwrap().as_str(), "FormD");
assert_eq!(p.safe_eval(r#" [Char]([BYte]0x5c)+[ChAr](112)+[chAR]([bYTE]0x7b)+[ChAr]([BYtE]0x4d)+[Char](110)+[CHar]([bYte]0x7d) "#).unwrap().as_str(), "\\p{Mn}");
assert_eq!(
p.safe_eval(r#" $(('WrìtêÍnt32').NoRMaLIZE("FormD") -replace "\p{Mn}") "#)
.unwrap()
.as_str(),
"WriteInt32"
);
assert_eq!(
p.safe_eval(r#" ("$(('WrìtêÍnt32').NoRMaLIZE("FormD") -replace "\p{Mn}")") "#)
.unwrap()
.as_str(),
"WriteInt32"
);
assert_eq!(p.safe_eval(r#" ("$(('Wrì'+'têÍ'+'nt3'+'2').NoRMaLIZE([CHAR](70+44-44)+[chaR](81+30)+[ChAR]([byTE]0x72)+[CHar]([byTE]0x6d)+[CHAR](68)) -replace [Char]([BYte]0x5c)+[ChAr](112)+[chAR]([bYTE]0x7b)+[ChAr]([BYtE]0x4d)+[Char](110)+[CHar]([bYte]0x7d))") "#).unwrap().as_str(), "WriteInt32");
assert_eq!(p.safe_eval(r#" $(('àmsìCónté'+'xt').normaliZE([Char]([byTe]0x46)+[cHAR]([BYTe]0x6f)+[cHaR](11+103)+[chAr](109*21/21)+[Char](68)) -replace [CHaR](92+6-6)+[CHAR](112*58/58)+[cHar]([bYte]0x7b)+[cHar](77*36/36)+[cHAR](110)+[char](125)) "#).unwrap().as_str(), "amsiContext");
assert_eq!(p.safe_eval(r#" $ykHjp2N3fNRJs="System.$([CHAr]([BYTe]0x4d)+[char](97*89/89)+[chAr](110*12/12)+[Char]([byTe]0x61)+[CHAR]([bYtE]0x67)+[ChAr]([BYTe]0x65)+[CHAr](109+54-54)+[cHAR]([ByTE]0x65)+[ChAR]([BYtE]0x6e)+[CHaR](45+71)).$([ChaR]([ByTE]0x41)+[cHAR](12+105)+[cHar]([ByTe]0x74)+[CHar]([byTE]0x6f)+[CHaR](109)+[ChAR]([byTe]0x61)+[cHaR]([byTE]0x74)+[cHAR](105*19/19)+[CHaR](111+20-20)+[cHAR]([BYTE]0x6e)).$(('ÂmsìÛ'+'tíls').NOrmaLiZE([ChAR]([byTE]0x46)+[cHaR](111+19-19)+[cHaR](114+36-36)+[char](109)+[chAr]([BytE]0x44)) -replace [CHar](92+37-37)+[ChAR](112)+[cHAR](123+45-45)+[cHAR](77*55/55)+[chAR]([BYtE]0x6e)+[CHar]([bYTe]0x7d))"; $ykHjp2N3fNRJs "#).unwrap().as_str(), "System.Management.Automation.AmsiUtils");
}
#[test]
fn test_range_with_float() {
let mut p = PowerShellSession::new();
assert_eq!(p.safe_eval(r#" [string](1..1.3) "#).unwrap().as_str(), "1");
assert_eq!(p.safe_eval(r#" [string](1...3) "#).unwrap().as_str(), "1 0");
assert_eq!(p.safe_eval(r#" [string]1...3 "#).unwrap().as_str(), "1\n0");
}
#[test]
fn test_unary() {
let mut p = PowerShellSession::new();
assert_eq!(p.safe_eval(r#" +5 "#).unwrap().as_str(), "5");
assert_eq!(p.safe_eval(r#" -5 "#).unwrap().as_str(), "-5");
}
#[test]
fn test_format_operator() {
let mut p = PowerShellSession::new();
assert_eq!(
p.safe_eval(r#" "Hello, {0}!" -f "world" "#)
.unwrap()
.as_str(),
"Hello, world!"
);
assert_eq!(
p.safe_eval(r#" "Hello, {0}!" -f "every{0}" -f "body" "#)
.unwrap()
.as_str(),
"Hello, everybody!"
);
assert_eq!(
p.safe_eval(r#" "{0} + {1} = {2}" -f 5, 7, (5 + 7) "#)
.unwrap()
.as_str(),
"5 + 7 = 12"
);
assert_eq!(
p.safe_eval(r#" "{0:N2}" -f 1234.56789 "#).unwrap().as_str(),
"1234.57"
);
assert_eq!(
p.safe_eval(r#" "|{0,10}|" -f "Hi" "#).unwrap().as_str(),
"| Hi|"
);
assert_eq!(
p.safe_eval(
r#" $level = "INFO";$message = "Disk space low";"{0}: {1}" -f $level, $message "#
)
.unwrap()
.as_str(),
"INFO: Disk space low"
);
assert_eq!(
p.safe_eval(r#" "{0:310100a0b00}" -f 578 "#)
.unwrap()
.as_str(),
"310100a5b78"
);
//veeeery strange cases
//assert_eq!(p.safe_eval(r#"
// "{0:31sdfg,0100a0b00000000000000000000000}" -f
// 57899999999999999999999999999 "#).unwrap().as_str(),
// "31sdfg578199a9b99999999999999999999999"); assert_eq!(p.
// safe_eval(r#" "{0:31sdfg,0100a0b00000000000000000000000}" -f
// 578999999999999999999999999999 "#).unwrap().as_str(),
// "31sdfg5790100a0b00000000000000000000000");
}
#[test]
fn test_strings() {
let mut p = PowerShellSession::new().with_variables(Variables::new().values_persist());
assert_eq!(
p.safe_eval(r#" 'It''s fine' "#).unwrap().as_str(),
"It''s fine"
);
assert_eq!(
p.safe_eval(r#" "Price is $" "#).unwrap().as_str(),
"Price is $"
);
assert_eq!(
p.safe_eval(r#" "Result: $(1+2)" "#).unwrap().as_str(),
"Result: 3"
);
assert_eq!(
p.safe_eval(r#" $name = "Radek";"Hello $name" "#)
.unwrap()
.as_str(),
"Hello Radek"
);
assert_eq!(
p.safe_eval(r#" "This is a quote: `"" "#).unwrap().as_str(),
"This is a quote: \""
);
assert_eq!(
p.safe_eval(r#" "A backtick `` and escaped quote `"" "#)
.unwrap()
.as_str(),
"A backtick ` and escaped quote \""
);
assert_eq!(
p.safe_eval(
r#" @"
Hello $name $
Multiline with $(1+2)
"@ "#
)
.unwrap()
.as_str(),
"Hello Radek $\nMultiline with 3"
);
assert_eq!(
p.safe_eval(
r#" @'
This is a
multi-line
here string
'@ "#
)
.unwrap()
.as_str(),
"This is a\nmulti-line\nhere string"
);
}
}