|
|
|
|
|
use crate::utils::error::{ChatGptError, Result}; |
|
|
use regex::Regex; |
|
|
use std::collections::HashMap; |
|
|
|
|
|
|
|
|
pub struct Parser; |
|
|
|
|
|
impl Parser { |
|
|
|
|
|
|
|
|
|
|
|
pub fn find_var_definition(var_name: &str, start_line: usize, code: &str) -> Option<String> { |
|
|
let lines: Vec<&str> = code.lines().collect(); |
|
|
|
|
|
|
|
|
for i in (0..start_line.min(lines.len())).rev() { |
|
|
let line = lines[i]; |
|
|
|
|
|
|
|
|
let var_pattern = format!(r"var\s+var_{}\s*=\s*(.+?);", regex::escape(var_name)); |
|
|
if let Ok(re) = Regex::new(&var_pattern) { |
|
|
if let Some(caps) = re.captures(line) { |
|
|
let value = caps.get(1)?.as_str().trim(); |
|
|
|
|
|
|
|
|
if value.contains("btoa") |
|
|
|| value.contains("XOR_STR") |
|
|
|| value.contains("doubleXOR") |
|
|
|| value.contains("singlebtoa") |
|
|
{ |
|
|
continue; |
|
|
} |
|
|
|
|
|
return Some(value.to_string()); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
None |
|
|
} |
|
|
|
|
|
|
|
|
pub fn parse_assignments(code: &str) -> HashMap<String, String> { |
|
|
let mut assignments = HashMap::new(); |
|
|
|
|
|
|
|
|
let assign_re = Regex::new(r"var\s+var_(\w+)\s*=\s*(.+?);").unwrap(); |
|
|
|
|
|
for caps in assign_re.captures_iter(code) { |
|
|
if let (Some(key), Some(value)) = (caps.get(1), caps.get(2)) { |
|
|
let key_str = key.as_str(); |
|
|
let value_str = value.as_str().trim(); |
|
|
|
|
|
|
|
|
if !value_str.contains('(') || value_str.starts_with('"') { |
|
|
assignments.insert(key_str.to_string(), value_str.to_string()); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
assignments |
|
|
} |
|
|
|
|
|
|
|
|
pub fn get_xor_key(js_code: &str) -> Option<String> { |
|
|
|
|
|
let xor_pattern = Regex::new(r#"XOR_STR\s*\([^,]+,\s*([^)]+)\)"#).unwrap(); |
|
|
|
|
|
for caps in xor_pattern.captures_iter(js_code) { |
|
|
if let Some(second_arg) = caps.get(1) { |
|
|
let arg = second_arg.as_str().trim(); |
|
|
|
|
|
|
|
|
if arg.starts_with('"') && arg.ends_with('"') { |
|
|
return Some(arg.trim_matches('"').to_string()); |
|
|
} |
|
|
|
|
|
|
|
|
if arg.starts_with("var_") { |
|
|
let var_name = arg.strip_prefix("var_").unwrap(); |
|
|
let var_pattern = |
|
|
format!(r#"var\s+var_{}\s*=\s*"([^"]+)";"#, regex::escape(var_name)); |
|
|
if let Ok(re) = Regex::new(&var_pattern) { |
|
|
if let Some(value_caps) = re.captures(js_code) |
|
|
{ |
|
|
return value_caps.get(1).map(|m| m.as_str().to_string()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
None |
|
|
} |
|
|
|
|
|
|
|
|
pub fn parse_keys(decompiled_code: &str) -> Result<(String, HashMap<String, String>)> { |
|
|
let assignments = Self::parse_assignments(decompiled_code); |
|
|
let xor_key = Self::get_xor_key(decompiled_code) |
|
|
.ok_or_else(|| ChatGptError::vm_execution("XOR key not found"))?; |
|
|
|
|
|
let mut parsed_keys = HashMap::new(); |
|
|
let mut random_index = 1; |
|
|
|
|
|
for (key, value) in assignments { |
|
|
let parsed_value = if value.starts_with("Array") && !value.contains("location") { |
|
|
|
|
|
if let Some(sum_part) = value.split(") : ").nth(1) { |
|
|
let numbers: Vec<f64> = sum_part |
|
|
.split(" + ") |
|
|
.filter_map(|s| s.trim().parse().ok()) |
|
|
.collect(); |
|
|
|
|
|
if numbers.len() >= 2 { |
|
|
(numbers[0] + numbers[1]).to_string() |
|
|
} else { |
|
|
value.clone() |
|
|
} |
|
|
} else { |
|
|
value.clone() |
|
|
} |
|
|
} else if value.contains("location") { |
|
|
"location".to_string() |
|
|
} else if value.contains("cfIpLongitude") { |
|
|
"ipinfo".to_string() |
|
|
} else if value.contains("maxTouchPoints") { |
|
|
"vendor".to_string() |
|
|
} else if value.contains("history") { |
|
|
"history".to_string() |
|
|
} else if value.contains(r#"window["Object"]["keys"]"#) { |
|
|
"localstorage".to_string() |
|
|
} else if value.contains("createElement") { |
|
|
"element".to_string() |
|
|
} else if value.chars().all(|c| c.is_ascii_digit() || c == '.') { |
|
|
value.clone() |
|
|
} else if value.contains("random") { |
|
|
let result = format!("random_{}", random_index); |
|
|
random_index += 1; |
|
|
result |
|
|
} else if value.contains("doublexor") || value.contains("singlebtoa") { |
|
|
value.clone() |
|
|
} else { |
|
|
continue; |
|
|
}; |
|
|
|
|
|
parsed_keys.insert(key, parsed_value); |
|
|
} |
|
|
|
|
|
Ok((xor_key, parsed_keys)) |
|
|
} |
|
|
|
|
|
|
|
|
pub fn analyze_dependencies(code: &str) -> HashMap<String, Vec<String>> { |
|
|
let mut dependencies = HashMap::new(); |
|
|
|
|
|
|
|
|
let var_re = Regex::new(r"var\s+var_(\w+)\s*=\s*(.+?);").unwrap(); |
|
|
let ref_re = Regex::new(r"var_(\w+)").unwrap(); |
|
|
|
|
|
for caps in var_re.captures_iter(code) { |
|
|
if let (Some(var_name), Some(expr)) = (caps.get(1), caps.get(2)) { |
|
|
let var = var_name.as_str().to_string(); |
|
|
let expression = expr.as_str(); |
|
|
|
|
|
|
|
|
let mut deps = Vec::new(); |
|
|
for ref_caps in ref_re.captures_iter(expression) { |
|
|
if let Some(ref_var) = ref_caps.get(1) { |
|
|
let ref_name = ref_var.as_str().to_string(); |
|
|
if ref_name != var { |
|
|
deps.push(ref_name); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
dependencies.insert(var, deps); |
|
|
} |
|
|
} |
|
|
|
|
|
dependencies |
|
|
} |
|
|
|
|
|
|
|
|
pub fn extract_strings(code: &str) -> Vec<String> { |
|
|
let mut strings = Vec::new(); |
|
|
let string_re = Regex::new(r#""([^"\\]*(\\.[^"\\]*)*)""#).unwrap(); |
|
|
|
|
|
for caps in string_re.captures_iter(code) { |
|
|
if let Some(s) = caps.get(1) { |
|
|
strings.push(s.as_str().to_string()); |
|
|
} |
|
|
} |
|
|
|
|
|
strings |
|
|
} |
|
|
|
|
|
|
|
|
pub fn contains_pattern(code: &str, pattern: &str) -> bool { |
|
|
if let Ok(re) = Regex::new(pattern) { |
|
|
re.is_match(code) |
|
|
} else { |
|
|
code.contains(pattern) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn count_pattern(code: &str, pattern: &str) -> usize { |
|
|
if let Ok(re) = Regex::new(pattern) { |
|
|
re.find_iter(code).count() |
|
|
} else { |
|
|
code.matches(pattern).count() |
|
|
} |
|
|
} |
|
|
} |
|
|
|