Skip to content

Commit

Permalink
feat: implement export hashing for mach-o
Browse files Browse the repository at this point in the history
  • Loading branch information
latonis committed May 28, 2024
1 parent 464d049 commit efdecef
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 2 deletions.
35 changes: 33 additions & 2 deletions lib/src/modules/macho/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,13 +311,13 @@ fn entitlement_hash(ctx: &mut ScanContext) -> Option<RuntimeString> {
let mut md5_hash = Md5::new();
let mut entitlements_to_hash = &macho.entitlements;

// if there are not any entitlements in the main Macho, the dylibs of the
// if there are not any entitlements in the main Macho, the entitlements of the
// nested file should be hashed
if entitlements_to_hash.is_empty() && !macho.file.is_empty() {
entitlements_to_hash = &macho.file[0].entitlements;
}

// we need to check again as the nested file dylibs could be empty too
// we need to check again as the nested file entitlements could be empty too
if entitlements_to_hash.is_empty() {
return None;
}
Expand All @@ -335,6 +335,37 @@ fn entitlement_hash(ctx: &mut ScanContext) -> Option<RuntimeString> {
Some(RuntimeString::new(digest))
}

/// Returns an md5 hash of the export symbols in the mach-o binary
#[module_export]
fn export_hash(ctx: &mut ScanContext) -> Option<RuntimeString> {
let macho = ctx.module_output::<Macho>()?;
let mut md5_hash = Md5::new();
let mut exports_to_hash = &macho.exports;

// if there are not any exports in the main Macho, the exports of the
// nested file should be hashed
if exports_to_hash.is_empty() && !macho.file.is_empty() {
exports_to_hash = &macho.file[0].exports;
}

// we need to check again as the nested file exports could be empty too
if exports_to_hash.is_empty() {
return None;
}

let exports_str: String = exports_to_hash
.iter()
.map(|e| e.trim().to_lowercase())
.unique()
.sorted()
.join(",");

md5_hash.update(exports_str.as_bytes());

let digest = format!("{:x}", md5_hash.finalize());
Some(RuntimeString::new(digest))
}

#[module_main]
fn main(input: &[u8]) -> Macho {
match parser::MachO::parse(input) {
Expand Down
33 changes: 33 additions & 0 deletions lib/src/modules/macho/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,37 @@ fn test_macho_module() {
"#,
&[]
);

rule_true!(
r#"
import "macho"
rule macho_test {
condition:
macho.export_hash() == "7f3b75c82e3151fff6c0a55b51cd5b94"
}
"#,
&chess_macho_data
);

rule_true!(
r#"
import "macho"
rule macho_test {
condition:
not defined macho.export_hash()
}
"#,
&[]
);

rule_true!(
r#"
import "macho"
rule macho_test {
condition:
macho.export_hash() == "6bfc6e935c71039e6e6abf097830dceb"
}
"#,
&tiny_universal_macho_data
);
}

0 comments on commit efdecef

Please sign in to comment.