fix: deobfuscator: handle global functions as well

This commit is contained in:
ThetaDev 2025-03-26 02:12:18 +01:00
parent b91fd97851
commit 06550c7a5a

View file

@ -195,20 +195,48 @@ fn extract_js_fn(js: &str, name: &str) -> Result<String, DeobfError> {
let mut end = 0usize; let mut end = 0usize;
let mut period_before = false; let mut period_before = false;
let mut last_ident = None; let mut function_before = false;
let mut idents: HashMap<String, bool> = HashMap::new(); let mut idents: HashMap<String, bool> = HashMap::new();
// Set if the current statement is a variable/function param definition // Set if the current statement is a variable/function param definition
// First value is the brace level, second is true if we are on the right hand side of an assignment // First value is the brace level, second is true if we are on the right hand side of an assignment
let mut var_def_stmt: Option<(Level, bool)> = None; let mut var_def_stmt: Option<(Level, bool)> = None;
let global_objects = [ let global_objects = [
"NaN", "Infinity", "Object", "Function", "Boolean", "Symbol", "Error", "Number", "BigInt", "globalThis",
"Math", "Date", "String", "RegExp", "Array", "Map", "Set", "NaN",
"undefined",
"Infinity",
"Object",
"Function",
"Boolean",
"Symbol",
"Error",
"Number",
"BigInt",
"Math",
"Date",
"String",
"RegExp",
"Array",
"Map",
"Set",
"eval",
"isFinite",
"isNaN",
"parseFloat",
"parseInt",
"decodeURI",
"decodeURIComponent",
"encodeURI",
"encodeURIComponent",
"escape",
"unescape",
]; ];
for item in scan { for item in scan {
let it = item?; let it = item?;
let token = it.token; let token = it.token;
match state { match state {
// Looking for fn name // Looking for fn name
0 => { 0 => {
@ -226,71 +254,72 @@ fn extract_js_fn(js: &str, name: &str) -> Result<String, DeobfError> {
} }
} }
2 => { 2 => {
if let Token::Punct(punct) = token { match &token {
let var_def_this_lvl = || { Token::Punct(punct) => {
var_def_stmt let var_def_this_lvl = || {
.as_ref() var_def_stmt
.map(|(x, _)| x == &level) .as_ref()
.unwrap_or_default() .map(|(x, _)| x == &level)
}; .unwrap_or_default()
};
match punct { match punct {
Punct::OpenBrace => { Punct::OpenBrace => {
level.brace += 1; level.brace += 1;
}
Punct::CloseBrace => {
if var_def_this_lvl() {
var_def_stmt = None;
} }
level.brace -= 1; Punct::CloseBrace => {
if var_def_this_lvl() {
var_def_stmt = None;
}
level.brace -= 1;
if level.brace == 0 { if level.brace == 0 {
end = it.span.end; end = it.span.end;
state = 3; state = 3;
break; break;
}
}
Punct::OpenParen => {
level.paren += 1;
}
Punct::CloseParen => {
if var_def_this_lvl() {
var_def_stmt = None;
}
level.paren -= 1;
}
Punct::OpenBracket => {
level.bracket += 1;
}
Punct::CloseBracket => {
if var_def_this_lvl() {
var_def_stmt = None;
}
level.bracket -= 1;
}
Punct::SemiColon => {
if var_def_this_lvl() {
var_def_stmt = None;
}
}
Punct::Comma => {
if let Some((lvl, rhs)) = &mut var_def_stmt {
if lvl == &level {
*rhs = false;
} }
} }
} Punct::OpenParen => {
Punct::Equal => { level.paren += 1;
if let Some((lvl, rhs)) = &mut var_def_stmt { }
if lvl == &level { Punct::CloseParen => {
*rhs = true; if var_def_this_lvl() {
var_def_stmt = None;
}
level.paren -= 1;
}
Punct::OpenBracket => {
level.bracket += 1;
}
Punct::CloseBracket => {
if var_def_this_lvl() {
var_def_stmt = None;
}
level.bracket -= 1;
}
Punct::SemiColon => {
if var_def_this_lvl() {
var_def_stmt = None;
} }
} }
Punct::Comma => {
if let Some((lvl, rhs)) = &mut var_def_stmt {
if lvl == &level {
*rhs = false;
}
}
}
Punct::Equal => {
if let Some((lvl, rhs)) = &mut var_def_stmt {
if lvl == &level {
*rhs = true;
}
}
}
_ => {}
} }
_ => {}
} }
} else if let Token::Keyword(kw) = &token { Token::Keyword(kw) => match kw {
match kw {
Keyword::Var(_) | Keyword::Let(_) | Keyword::Const(_) => { Keyword::Var(_) | Keyword::Let(_) | Keyword::Const(_) => {
var_def_stmt = Some((level.clone(), false)); var_def_stmt = Some((level.clone(), false));
} }
@ -300,33 +329,34 @@ fn extract_js_fn(js: &str, name: &str) -> Result<String, DeobfError> {
var_def_stmt = Some((l, false)); var_def_stmt = Some((l, false));
} }
_ => {} _ => {}
} },
} Token::Ident(id) => {
// Ignore object attributes and 1char long local vars
// Looking for variable names if !period_before
if let Token::Ident(id) = &token { && id.as_ref().len() > 1
// Ignore object attributes and 1char long local vars && !global_objects.contains(&id.as_ref())
if !period_before && id.as_ref().len() > 1 {
if var_def_stmt
.as_ref()
.map(|(lvl, rhs)| lvl == &level && !rhs)
.unwrap_or_default()
{ {
idents.insert(id.to_string(), true); // If we are on the left hand side of a variable definition statement
} else if !global_objects.contains(&id.as_ref()) { // or after "function", mark the variable name as defined
last_ident = Some(id.to_string()); if var_def_stmt
.as_ref()
.map(|(lvl, rhs)| lvl == &level && !rhs)
.unwrap_or_default()
|| function_before
{
idents.insert(id.to_string(), true);
} else {
idents.entry(id.to_string()).or_default();
}
} }
} }
} else if last_ident.is_some() && !token.matches_punct(Punct::OpenParen) { _ => {}
idents.entry(last_ident.unwrap()).or_default();
last_ident = None;
} else {
last_ident = None;
} }
} }
_ => break, _ => break,
}; };
period_before = token.matches_punct(Punct::Period); period_before = token.matches_punct(Punct::Period);
function_before = matches!(&token, Token::Keyword(Keyword::Function(_)));
} }
if state != 3 { if state != 3 {