From 8151d1a15ecffdb6503c34ad8130c2f093fc3189 Mon Sep 17 00:00:00 2001 From: Jacob Latonis Date: Sun, 21 Jan 2024 21:17:10 -0600 Subject: [PATCH] feat: implement entitlement parsing for Mach-O --- Cargo.lock | 7 ++ yara-x/Cargo.toml | 1 + yara-x/src/modules/macho/mod.rs | 98 +++++++++++++------ .../modules/macho/tests/testdata/chess.out | 9 +- yara-x/src/modules/protos/macho.proto | 11 ++- 5 files changed, 90 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f367cbcf..b37adff5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3111,6 +3111,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4963,6 +4969,7 @@ dependencies = [ "protobuf-parse", "regex-automata 0.3.8", "regex-syntax 0.7.5 (git+https://github.com/plusvic/regex.git?rev=423493d)", + "roxmltree", "rustc-hash", "serde", "serde_json", diff --git a/yara-x/Cargo.toml b/yara-x/Cargo.toml index 801cec835..8ee6ac179 100644 --- a/yara-x/Cargo.toml +++ b/yara-x/Cargo.toml @@ -169,6 +169,7 @@ yara-x-proto = { workspace = true } lingua = { version = "1.6.0", optional = true, default-features = false, features = ["english", "german", "french", "spanish"] } cryptographic-message-syntax = "0.26.0" +roxmltree = "0.19.0" [build-dependencies] protobuf = { workspace = true } diff --git a/yara-x/src/modules/macho/mod.rs b/yara-x/src/modules/macho/mod.rs index 5bdad689a..3f87d7361 100644 --- a/yara-x/src/modules/macho/mod.rs +++ b/yara-x/src/modules/macho/mod.rs @@ -45,6 +45,7 @@ const _CS_MAGIC_CODEDIRECTORY: u32 = 0xfade0c02; const _CS_MAGIC_EMBEDDED_SIGNATURE: u32 = 0xfade0cc0; const _CS_MAGIC_DETACHED_SIGNATURE: u32 = 0xfade0cc1; const CS_MAGIC_BLOBWRAPPER: u32 = 0xfade0b01; +const CS_MAGIC_EMBEDDED_ENTITLEMENTS: u32 = 0xfade7171; /// Define Mach-O CPU type constants const CPU_TYPE_MC680X0: u32 = 0x00000006; @@ -3249,7 +3250,6 @@ fn parse_macho_code_signature( macho_file: &mut File, ) -> Result<(), MachoError> { if macho_file.code_signature_data.is_some() { - let certificates = macho_file.certificates.mut_or_insert_default(); let code_signature_data = macho_file.code_signature_data.as_mut().unwrap(); let data_offset = code_signature_data.dataoff() as usize; @@ -3261,35 +3261,72 @@ fn parse_macho_code_signature( .map_err(|e| MachoError::ParsingError(format!("{:?}", e)))?; for blob_index in super_blob.index { - if blob_index.blob.magic == CS_MAGIC_BLOBWRAPPER { - let offset = blob_index.offset as usize; - let length = blob_index.blob.length as usize; - let size_of_blob = std::mem::size_of::(); - - let signage = SignedData::parse_ber( - &super_data[offset + size_of_blob - ..offset + size_of_blob + length], - ) - .map_err(|e| { - MachoError::ParsingError(format!("{:?}", e)) - })?; - // .unwrap(); - - let signers = signage.signers(); - let certs = signage.certificates(); - - certs.for_each(|cert| { - let name = cert.subject_common_name().unwrap(); - certificates.common_names.push(name); - }); - - signers.for_each(|signer| { - let (name, _) = - signer.certificate_issuer_and_serial().unwrap(); - certificates.signer_names.push( - name.user_friendly_str().unwrap().to_string(), - ); - }); + let offset = blob_index.offset as usize; + let length = blob_index.blob.length as usize; + let size_of_blob = std::mem::size_of::(); + + match blob_index.blob.magic { + CS_MAGIC_BLOBWRAPPER => { + let certificates = + macho_file.certificates.mut_or_insert_default(); + + let signage = SignedData::parse_ber( + &super_data + [offset + size_of_blob..offset + length], + ) + .map_err(|e| { + MachoError::ParsingError(format!("{:?}", e)) + })?; + // .unwrap(); + + let signers = signage.signers(); + let certs = signage.certificates(); + + certs.for_each(|cert| { + let name = cert.subject_common_name().unwrap(); + certificates.common_names.push(name); + }); + + signers.for_each(|signer| { + let (name, _) = signer + .certificate_issuer_and_serial() + .unwrap(); + certificates.signer_names.push( + name.user_friendly_str().unwrap().to_string(), + ); + }); + } + CS_MAGIC_EMBEDDED_ENTITLEMENTS => { + let xml_data = &super_data + [offset + size_of_blob..offset + length]; + let xml_string = + std::str::from_utf8(xml_data).unwrap_or_default(); + + let opt = roxmltree::ParsingOptions { + allow_dtd: true, + ..roxmltree::ParsingOptions::default() + }; + + let parsed_xml = + roxmltree::Document::parse_with_options( + xml_string, opt, + ) + .map_err(|e| { + MachoError::ParsingError(format!("{:?}", e)) + })?; + + for node in parsed_xml + .descendants() + .filter(|n| n.has_tag_name("key")) + { + if let Some(entitlement) = node.text() { + macho_file + .entitlements + .push(entitlement.to_string()); + } + } + } + _ => {} } } } @@ -3931,6 +3968,7 @@ fn main(data: &[u8]) -> Macho { macho_proto.code_signature_data = file_data.code_signature_data; macho_proto.certificates = file_data.certificates; + macho_proto.entitlements = file_data.entitlements; macho_proto.entry_point = file_data.entry_point; macho_proto.stack_size = file_data.stack_size; } diff --git a/yara-x/src/modules/macho/tests/testdata/chess.out b/yara-x/src/modules/macho/tests/testdata/chess.out index 468bfdea3..d17780edd 100644 --- a/yara-x/src/modules/macho/tests/testdata/chess.out +++ b/yara-x/src/modules/macho/tests/testdata/chess.out @@ -838,4 +838,11 @@ certificates: - "Apple Root CA" - "Software Signing" signer_names: - - "CN=Apple Code Signing Certification Authority, OU=Apple Certification Authority, O=Apple Inc., C=US" \ No newline at end of file + - "CN=Apple Code Signing Certification Authority, OU=Apple Certification Authority, O=Apple Inc., C=US" +entitlements: + - "com.apple.developer.game-center" + - "com.apple.private.tcc.allow" + - "com.apple.security.app-sandbox" + - "com.apple.security.device.microphone" + - "com.apple.security.files.user-selected.read-write" + - "com.apple.security.network.client" \ No newline at end of file diff --git a/yara-x/src/modules/protos/macho.proto b/yara-x/src/modules/protos/macho.proto index 4c9a4111d..63faa11a8 100644 --- a/yara-x/src/modules/protos/macho.proto +++ b/yara-x/src/modules/protos/macho.proto @@ -144,7 +144,7 @@ message File { optional Symtab symtab = 19; optional LinkedItData code_signature_data = 20; optional CodeSignature certificates = 21; - + repeated string entitlements = 22; } message Macho { @@ -170,16 +170,17 @@ message Macho { optional Symtab symtab = 19; optional LinkedItData code_signature_data = 20; optional CodeSignature certificates = 21; + repeated string entitlements = 22; // Add fields for Mach-O fat binary header - optional uint32 fat_magic = 22 [(yaml.field).fmt = "x"]; - optional uint32 nfat_arch = 23; - repeated FatArch fat_arch = 24; + optional uint32 fat_magic = 23 [(yaml.field).fmt = "x"]; + optional uint32 nfat_arch = 24; + repeated FatArch fat_arch = 25; // Nested Mach-O files - repeated File file = 25; + repeated File file = 26; } enum HEADER {