use std::any::Any;
use crate::{
compiler::{EventKind, EventLog},
deobfuscation::techniques::{
Detection, Detections, Evidence, Technique, TechniqueCategory, WorkingAssembly,
},
metadata::{
method::{MethodBody, MethodImplCodeType},
tables::MethodDefRaw,
token::Token,
},
CilObject, Result,
};
#[derive(Debug)]
pub struct HandlersFindings {
pub affected_methods: Vec<Token>,
}
pub struct GenericHandlers;
impl Technique for GenericHandlers {
fn id(&self) -> &'static str {
"generic.handlers"
}
fn name(&self) -> &'static str {
"Malformed Exception Handler Repair"
}
fn category(&self) -> TechniqueCategory {
TechniqueCategory::Metadata
}
fn detect(&self, assembly: &CilObject) -> Detection {
let Some(tables) = assembly.tables() else {
return Detection::new_empty();
};
let Some(method_table) = tables.table::<MethodDefRaw>() else {
return Detection::new_empty();
};
let file = assembly.file();
let mut affected = Vec::new();
for row in method_table {
if row.rva == 0 {
continue;
}
let code_type = MethodImplCodeType::from_impl_flags(row.impl_flags);
if code_type.contains(MethodImplCodeType::NATIVE)
|| code_type.contains(MethodImplCodeType::RUNTIME)
{
continue;
}
let Ok(offset) = file.rva_to_offset(row.rva as usize) else {
continue;
};
let available = file.data().len().saturating_sub(offset);
if available == 0 {
continue;
}
let body_data = &file.data()[offset..offset + available];
if let Ok((_, filtered)) = MethodBody::from_lenient(body_data) {
if filtered > 0 {
affected.push(row.token);
}
}
}
if affected.is_empty() {
return Detection::new_empty();
}
let count = affected.len();
let findings = HandlersFindings {
affected_methods: affected,
};
Detection::new_detected(
vec![Evidence::BytecodePattern(format!(
"{count} methods with malformed exception handlers"
))],
Some(Box::new(findings) as Box<dyn Any + Send + Sync>),
)
}
fn byte_transform(
&self,
assembly: &mut WorkingAssembly,
detection: &Detection,
_detections: &Detections,
) -> Option<Result<EventLog>> {
let events = EventLog::new();
let Some(findings) = detection.findings::<HandlersFindings>() else {
return Some(Ok(events));
};
if !findings.affected_methods.is_empty() {
events.record(EventKind::ArtifactRemoved)
.message(format!(
"Detected {} methods with malformed exception handlers — will be stripped on regeneration",
findings.affected_methods.len()
));
if assembly.has_pending() {
if let Err(e) = assembly.commit() {
return Some(Err(e));
}
}
}
Some(Ok(events))
}
fn requires_regeneration(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use crate::{deobfuscation::techniques::Technique, test::helpers::load_sample};
#[test]
fn test_detect_no_panic_on_obfuscated() {
let asm = load_sample("tests/samples/packers/confuserex/1.6.0/mkaring_normal.exe");
let technique = super::GenericHandlers;
let _detection = technique.detect(&asm);
}
#[test]
fn test_detect_negative() {
let asm = load_sample("tests/samples/packers/confuserex/1.6.0/original.exe");
let technique = super::GenericHandlers;
let detection = technique.detect(&asm);
assert!(!detection.is_detected());
}
}