Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement dylib and entitlement hashing for macho #93

Merged
merged 15 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 14 additions & 14 deletions lib/src/modules/add_modules.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
// File generated automatically by build.rs. Do not edit.
{
#[cfg(feature = "string-module")]
add_module!(modules, "string", string, "string.String", Some("string"), Some(string::__main__ as MainFn));
#[cfg(feature = "test_proto2-module")]
add_module!(modules, "test_proto2", test_proto2, "test_proto2.TestProto2", Some("test_proto2"), Some(test_proto2::__main__ as MainFn));
#[cfg(feature = "macho-module")]
add_module!(modules, "macho", macho, "macho.Macho", Some("macho"), Some(macho::__main__ as MainFn));
#[cfg(feature = "pe-module")]
add_module!(modules, "pe", pe, "pe.PE", Some("pe"), Some(pe::__main__ as MainFn));
#[cfg(feature = "elf-module")]
add_module!(modules, "elf", elf, "elf.ELF", Some("elf"), Some(elf::__main__ as MainFn));
#[cfg(feature = "text-module")]
add_module!(modules, "text", text, "text.Text", Some("text"), Some(text::__main__ as MainFn));
#[cfg(feature = "dotnet-module")]
add_module!(modules, "dotnet", dotnet, "dotnet.Dotnet", Some("dotnet"), Some(dotnet::__main__ as MainFn));
#[cfg(feature = "lnk-module")]
add_module!(modules, "lnk", lnk, "lnk.Lnk", Some("lnk"), Some(lnk::__main__ as MainFn));
#[cfg(feature = "hash-module")]
add_module!(modules, "hash", hash, "hash.Hash", Some("hash"), Some(hash::__main__ as MainFn));
#[cfg(feature = "magic-module")]
add_module!(modules, "magic", magic, "magic.Magic", Some("magic"), Some(magic::__main__ as MainFn));
#[cfg(feature = "math-module")]
add_module!(modules, "math", math, "math.Math", Some("math"), Some(math::__main__ as MainFn));
#[cfg(feature = "test_proto2-module")]
add_module!(modules, "test_proto2", test_proto2, "test_proto2.TestProto2", Some("test_proto2"), Some(test_proto2::__main__ as MainFn));
#[cfg(feature = "text-module")]
add_module!(modules, "text", text, "text.Text", Some("text"), Some(text::__main__ as MainFn));
#[cfg(feature = "time-module")]
add_module!(modules, "time", time, "time.Time", Some("time"), Some(time::__main__ as MainFn));
#[cfg(feature = "dotnet-module")]
add_module!(modules, "dotnet", dotnet, "dotnet.Dotnet", Some("dotnet"), Some(dotnet::__main__ as MainFn));
#[cfg(feature = "test_proto3-module")]
add_module!(modules, "test_proto3", test_proto3, "test_proto3.TestProto3", Some("test_proto3"), Some(test_proto3::__main__ as MainFn));
#[cfg(feature = "pe-module")]
add_module!(modules, "pe", pe, "pe.PE", Some("pe"), Some(pe::__main__ as MainFn));
#[cfg(feature = "string-module")]
add_module!(modules, "string", string, "string.String", Some("string"), Some(string::__main__ as MainFn));
#[cfg(feature = "console-module")]
add_module!(modules, "console", console, "console.Console", Some("console"), Some(console::__main__ as MainFn));
#[cfg(feature = "elf-module")]
add_module!(modules, "elf", elf, "elf.ELF", Some("elf"), Some(elf::__main__ as MainFn));
#[cfg(feature = "math-module")]
add_module!(modules, "math", math, "math.Math", Some("math"), Some(math::__main__ as MainFn));
}
67 changes: 67 additions & 0 deletions lib/src/modules/macho/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

use crate::modules::prelude::*;
use crate::modules::protos::macho::*;
use itertools::Itertools;
use md5::{Digest, Md5};

mod parser;
#[cfg(test)]
Expand Down Expand Up @@ -264,6 +266,71 @@ fn has_rpath(ctx: &ScanContext, rpath: RuntimeString) -> Option<bool> {
Some(false)
}

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

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

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

let dylibs_to_hash: String = dylibs_to_hash
.iter()
.map(|d| {
std::string::String::from_utf8(d.name.clone().unwrap())
latonis marked this conversation as resolved.
Show resolved Hide resolved
.unwrap()
.trim()
.to_lowercase()
})
.unique()
.sorted()
.join(",");

md5_hash.update(dylibs_to_hash.as_bytes());

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

/// Returns an md5 hash of the entitlements designated in the mach-o binary
#[module_export]
fn entitlement_hash(ctx: &mut ScanContext) -> Option<RuntimeString> {
let macho = ctx.module_output::<Macho>()?;
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 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
if entitlements_to_hash.is_empty() {
return None;
}

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

md5_hash.update(entitlements_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
55 changes: 55 additions & 0 deletions lib/src/modules/macho/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,4 +311,59 @@ fn test_macho_module() {
"#,
&chess_macho_data
);

rule_true!(
r#"
import "macho"
rule macho_test {
condition:
macho.dylib_hash() == "6813ec6aceb392c8a9abe9db8e25d847"
}
"#,
&chess_macho_data
);

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

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

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

rule_true!(
r#"
import "macho"
rule macho_test {
condition:
not defined macho.entitlement_hash()
}
"#,
&[]
);
}
30 changes: 15 additions & 15 deletions lib/src/modules/modules.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
// File generated automatically by build.rs. Do not edit.
#[cfg(feature = "string-module")]
mod string;
#[cfg(feature = "test_proto2-module")]
mod test_proto2;
#[cfg(feature = "macho-module")]
mod macho;
#[cfg(feature = "pe-module")]
mod pe;
#[cfg(feature = "elf-module")]
mod elf;
#[cfg(feature = "text-module")]
mod text;
#[cfg(feature = "dotnet-module")]
mod dotnet;
#[cfg(feature = "lnk-module")]
mod lnk;
#[cfg(feature = "hash-module")]
mod hash;
#[cfg(feature = "magic-module")]
mod magic;
#[cfg(feature = "math-module")]
mod math;
#[cfg(feature = "test_proto2-module")]
mod test_proto2;
#[cfg(feature = "text-module")]
mod text;
#[cfg(feature = "time-module")]
mod time;
#[cfg(feature = "dotnet-module")]
mod dotnet;
#[cfg(feature = "test_proto3-module")]
mod test_proto3;
#[cfg(feature = "pe-module")]
mod pe;
#[cfg(feature = "string-module")]
mod string;
#[cfg(feature = "console-module")]
mod console;
mod console;
#[cfg(feature = "elf-module")]
mod elf;
#[cfg(feature = "math-module")]
mod math;
8 changes: 4 additions & 4 deletions lib/src/modules/protos/macho.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ message Certificates {
}

message Dylib {
optional bytes name = 1;
optional uint32 timestamp = 2 [(yaml.field).fmt = "t"];
optional string compatibility_version = 3;
optional string current_version = 4;
required bytes name = 1;
required uint32 timestamp = 2 [(yaml.field).fmt = "t"];
required string compatibility_version = 3;
required string current_version = 4;
}

message DyldInfo {
Expand Down
Loading