diff --git a/Cargo.lock b/Cargo.lock index 84f603ec4..b79ffc649 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2548,9 +2548,9 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.10" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" dependencies = [ "memchr", "thiserror", diff --git a/lib/src/modules/add_modules.rs b/lib/src/modules/add_modules.rs index ca9e29331..6b3424ea9 100644 --- a/lib/src/modules/add_modules.rs +++ b/lib/src/modules/add_modules.rs @@ -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)); } \ No newline at end of file diff --git a/lib/src/modules/macho/mod.rs b/lib/src/modules/macho/mod.rs index 99b1ae7bf..36c14843c 100644 --- a/lib/src/modules/macho/mod.rs +++ b/lib/src/modules/macho/mod.rs @@ -7,6 +7,8 @@ use crate::modules::prelude::*; use crate::modules::protos::macho::*; +use itertools::Itertools; +use md5::{Digest, Md5}; mod parser; #[cfg(test)] @@ -264,6 +266,71 @@ fn has_rpath(ctx: &ScanContext, rpath: RuntimeString) -> Option { Some(false) } +/// Returns an md5 hash of the dylibs designated in the mach-o binary +#[module_export] +fn dylib_hash(ctx: &mut ScanContext) -> Option { + let macho = ctx.module_output::()?; + 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()) + .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 { + let macho = ctx.module_output::()?; + 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) { diff --git a/lib/src/modules/macho/tests/mod.rs b/lib/src/modules/macho/tests/mod.rs index cbbcb1887..646dc260f 100644 --- a/lib/src/modules/macho/tests/mod.rs +++ b/lib/src/modules/macho/tests/mod.rs @@ -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() + } + "#, + &[] + ); } diff --git a/lib/src/modules/modules.rs b/lib/src/modules/modules.rs index 7ad936dbd..016edda99 100644 --- a/lib/src/modules/modules.rs +++ b/lib/src/modules/modules.rs @@ -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; \ No newline at end of file +mod console; +#[cfg(feature = "elf-module")] +mod elf; +#[cfg(feature = "math-module")] +mod math; \ No newline at end of file diff --git a/lib/src/modules/protos/macho.proto b/lib/src/modules/protos/macho.proto index e1bf29739..dadc49a93 100644 --- a/lib/src/modules/protos/macho.proto +++ b/lib/src/modules/protos/macho.proto @@ -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 {