use std::any::Any;
use crate::{
compiler::{EventKind, EventLog},
deobfuscation::techniques::{
Detection, Detections, Evidence, Technique, TechniqueCategory, WorkingAssembly,
},
metadata::tables::{AssemblyRaw, DeclSecurityRaw, ModuleRaw, TableId, TypeRefRaw},
CilObject, Result,
};
const KNOWN_SENTINEL_VALUES: &[u32] = &[
0x7fff_7fff, 0xffff_ffff, 0xdead_beef, ];
const KNOWN_SENTINEL_VALUES_16: &[u16] = &[
0x7fff, 0xffff, ];
#[derive(Debug)]
pub struct MetadataFindings {
pub invalid_module_rows: usize,
pub invalid_assembly_rows: usize,
pub invalid_declsecurity_rows: usize,
pub invalid_typeref_scopes: usize,
pub patches: Vec<MetadataPatch>,
}
#[derive(Debug, Clone)]
pub struct MetadataPatch {
pub offset: usize,
pub size: u8,
pub original: u32,
pub corrected: u32,
}
pub struct GenericMetadata;
impl Technique for GenericMetadata {
fn id(&self) -> &'static str {
"generic.metadata"
}
fn name(&self) -> &'static str {
"Generic Invalid Metadata Repair"
}
fn category(&self) -> TechniqueCategory {
TechniqueCategory::Metadata
}
fn detect(&self, assembly: &CilObject) -> Detection {
let Some(tables) = assembly.tables() else {
return Detection::new_empty();
};
let strings = assembly.strings();
let mut findings = MetadataFindings {
invalid_module_rows: 0,
invalid_assembly_rows: 0,
invalid_declsecurity_rows: 0,
invalid_typeref_scopes: 0,
patches: Vec::new(),
};
let mut evidence = Vec::new();
if let Some(module_table) = tables.table::<ModuleRaw>() {
let strings_size = strings.as_ref().map(|s| s.data().len()).unwrap_or(0);
for row in module_table {
let name_index = row.name as usize;
let is_sentinel = KNOWN_SENTINEL_VALUES.contains(&row.name);
if name_index >= strings_size || is_sentinel {
findings.invalid_module_rows += 1;
findings.patches.push(MetadataPatch {
offset: row.offset + 2, size: if strings_size > 0xFFFF { 4 } else { 2 },
original: row.name,
corrected: 0,
});
}
}
}
if let Some(assembly_table) = tables.table::<AssemblyRaw>() {
let strings_size = strings.as_ref().map(|s| s.data().len()).unwrap_or(0);
for row in assembly_table {
let is_sentinel = KNOWN_SENTINEL_VALUES.contains(&row.name);
if row.name as usize >= strings_size || is_sentinel {
findings.invalid_assembly_rows += 1;
}
}
}
if let Some(declsec_table) = tables.table::<DeclSecurityRaw>() {
for row in declsec_table {
let is_sentinel = KNOWN_SENTINEL_VALUES_16.contains(&row.action);
if row.action > 0x000E || is_sentinel {
findings.invalid_declsecurity_rows += 1;
}
}
}
if let Some(typeref_table) = tables.table::<TypeRefRaw>() {
for row in typeref_table {
if row.resolution_scope.tag == TableId::Module && row.resolution_scope.row == 0 {
findings.invalid_typeref_scopes += 1;
}
}
}
let total_invalid = findings.invalid_module_rows
+ findings.invalid_assembly_rows
+ findings.invalid_declsecurity_rows;
if total_invalid > 0 {
evidence.push(Evidence::MetadataPattern(format!(
"{} invalid metadata entries (Module: {}, Assembly: {}, DeclSecurity: {})",
total_invalid,
findings.invalid_module_rows,
findings.invalid_assembly_rows,
findings.invalid_declsecurity_rows,
)));
Detection::new_detected(
evidence,
Some(Box::new(findings) as Box<dyn Any + Send + Sync>),
)
} else {
Detection::new_empty()
}
}
fn byte_transform(
&self,
assembly: &mut WorkingAssembly,
detection: &Detection,
_detections: &Detections,
) -> Option<Result<EventLog>> {
let events = EventLog::new();
let Some(findings) = detection.findings::<MetadataFindings>() else {
return Some(Ok(events));
};
let mut patched = 0usize;
for patch in &findings.patches {
match patch.size {
2 => {
if let Err(e) = assembly.write_le::<u16>(patch.offset, patch.corrected as u16) {
return Some(Err(e));
}
patched += 1;
}
4 => {
if let Err(e) = assembly.write_le::<u32>(patch.offset, patch.corrected) {
return Some(Err(e));
}
patched += 1;
}
_ => {}
}
}
if patched > 0 {
events
.record(EventKind::ArtifactRemoved)
.message(format!("Patched {} invalid metadata entries", patched));
}
Some(Ok(events))
}
fn requires_regeneration(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use crate::deobfuscation::techniques::Technique;
use crate::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::GenericMetadata;
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::GenericMetadata;
let detection = technique.detect(&asm);
assert!(!detection.is_detected());
}
}