diff --git a/lib/src/modules/macho/mod.rs b/lib/src/modules/macho/mod.rs index 9365237e5..141a045f0 100644 --- a/lib/src/modules/macho/mod.rs +++ b/lib/src/modules/macho/mod.rs @@ -266,6 +266,56 @@ fn has_rpath(ctx: &ScanContext, rpath: RuntimeString) -> Option { Some(false) } +/// Returns true if the Mach-O parsed imports contain `import` +/// +/// `import` is case-insensitive +#[module_export] +fn has_import(ctx: &ScanContext, import: RuntimeString) -> Option { + let macho = ctx.module_output::()?; + let expected_import = import.as_bstr(ctx); + + for im in macho.imports.iter() { + if expected_import.eq_ignore_ascii_case(im.as_bytes()) { + return Some(true); + } + } + + for file in macho.file.iter() { + for im in file.imports.iter() { + if expected_import.eq_ignore_ascii_case(im.as_bytes()) { + return Some(true); + } + } + } + + Some(false) +} + +/// Returns true if the Mach-O parsed exports contain `export` +/// +/// `export` is case-insensitive +#[module_export] +fn has_export(ctx: &ScanContext, export: RuntimeString) -> Option { + let macho = ctx.module_output::()?; + let expected_export = export.as_bstr(ctx); + + for ex in macho.exports.iter() { + if expected_export.eq_ignore_ascii_case(ex.as_bytes()) { + return Some(true); + } + } + + for file in macho.file.iter() { + for ex in file.exports.iter() { + if expected_export.eq_ignore_ascii_case(ex.as_bytes()) { + return Some(true); + } + } + } + + Some(false) +} + /// Returns an md5 hash of the dylibs designated in the mach-o binary #[module_export] fn dylib_hash(ctx: &mut ScanContext) -> Option { @@ -366,6 +416,36 @@ fn export_hash(ctx: &mut ScanContext) -> Option { Some(RuntimeString::new(digest)) } +/// Returns an md5 hash of the imported symbols in the mach-o binary +#[module_export] +fn import_hash(ctx: &mut ScanContext) -> Option { + let macho = ctx.module_output::()?; + let mut md5_hash = Md5::new(); + let mut imports_to_hash = &macho.imports; + + // if there are not any imports in the main Macho, the imports of the + // nested file should be hashed + if imports_to_hash.is_empty() && !macho.file.is_empty() { + imports_to_hash = &macho.file[0].imports; + } + + // we need to check again as the nested file imports could be empty too + if imports_to_hash.is_empty() { + return None; + } + + let imports_str: String = imports_to_hash + .iter() + .map(|e| e.trim().to_lowercase()) + .unique() + .sorted() + .join(","); + md5_hash.update(imports_str.as_bytes()); + + let digest = format!("{:x}", md5_hash.finalize()); + Some(RuntimeString::new(digest)) +} + #[module_main] fn main(data: &[u8], _meta: Option<&[u8]>) -> Macho { match parser::MachO::parse(data) { diff --git a/lib/src/modules/macho/parser.rs b/lib/src/modules/macho/parser.rs index 3dbd49304..45571b7de 100644 --- a/lib/src/modules/macho/parser.rs +++ b/lib/src/modules/macho/parser.rs @@ -1,4 +1,5 @@ use std::collections::HashSet; +use std::mem; use crate::modules::protos; use bstr::{BStr, ByteSlice}; @@ -43,6 +44,23 @@ const EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION: u64 = 0x00000004; const EXPORT_SYMBOL_FLAGS_REEXPORT: u64 = 0x00000008; const EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER: u64 = 0x00000010; +/// Mach-O import opcode consants +const BIND_OPCODE_MASK: u8 = 0xF0; +const BIND_IMMEDIATE_MASK: u8 = 0x0F; +const _BIND_OPCODE_DONE: u8 = 0x00; +const _BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: u8 = 0x10; +const BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: u8 = 0x20; +const _BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: u8 = 0x30; +const BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: u8 = 0x40; +const _BIND_OPCODE_SET_TYPE_IMM: u8 = 0x50; +const BIND_OPCODE_SET_ADDEND_SLEB: u8 = 0x60; +const BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: u8 = 0x70; +const BIND_OPCODE_ADD_ADDR_ULEB: u8 = 0x80; +const _BIND_OPCODE_DO_BIND: u8 = 0x90; +const BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: u8 = 0xA0; +const _BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: u8 = 0xB0; +const BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: u8 = 0xC0; + /// Mach-O dynamic linker constant const LC_REQ_DYLD: u32 = 0x80000000; @@ -278,6 +296,7 @@ impl<'a> MachO<'a> { build_version: None, min_version: None, exports: Vec::new(), + imports: Vec::new(), }; for _ in 0..macho.header.ncmds as usize { @@ -355,6 +374,28 @@ impl<'a> MachO<'a> { } } + if let Some(ref dyld_info) = macho.dyld_info { + for (offset, size) in [ + (dyld_info.bind_off, dyld_info.bind_size), + (dyld_info.lazy_bind_off, dyld_info.lazy_bind_size), + (dyld_info.weak_bind_off, dyld_info.weak_bind_size), + ] { + let offset = offset as usize; + let size = size as usize; + if let Some(import_data) = + data.get(offset..offset.saturating_add(size)) + { + if let Err(_err) = macho.imports()(import_data) { + #[cfg(feature = "logging")] + error!("Error parsing Mach-O file: {:?}", _err); + // fail silently if it fails, data was not formatted + // correctly but parsing should still proceed for + // everything else + }; + } + } + } + Ok(macho) } } @@ -381,6 +422,7 @@ pub struct MachOFile<'a> { build_version: Option, min_version: Option, exports: Vec, + imports: Vec, } impl<'a> MachOFile<'a> { @@ -1028,6 +1070,53 @@ impl<'a> MachOFile<'a> { } } + /// Parser that parses the imports at the offsets defined within LC_DYLD_INFO and LC_DYLD_INFO_ONLY + fn imports( + &mut self, + ) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], u8> + '_ { + move |data: &'a [u8]| { + let mut remainder: &[u8] = data; + let mut entry: u8; + + while !remainder.is_empty() { + (remainder, entry) = u8(remainder)?; + let opcode = entry & BIND_OPCODE_MASK; + let _immediate = entry & BIND_IMMEDIATE_MASK; + match opcode { + BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB + | BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB + | BIND_OPCODE_ADD_ADDR_ULEB + | BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB => { + (remainder, _) = uleb128(remainder)?; + } + BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB => { + (remainder, _) = uleb128(remainder)?; + (remainder, _) = uleb128(remainder)?; + } + BIND_OPCODE_SET_ADDEND_SLEB => { + (remainder, _) = sleb128(remainder)?; + } + + BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { + let (import_remainder, strr) = map( + tuple((take_till(|b| b == b'\x00'), tag(b"\x00"))), + |(s, _)| s, + )( + remainder + )?; + remainder = import_remainder; + if let Ok(import) = strr.to_str() { + self.imports.push(import.to_string()); + } + } + _ => {} + } + } + + Ok((remainder, 0)) + } + } + /// Parser that parses a LC_ID_DYLINKER, LC_LOAD_DYLINKER or /// LC_DYLD_ENVIRONMENT command. fn dylinker_command( @@ -1483,6 +1572,32 @@ fn uleb128(input: &[u8]) -> IResult<&[u8], u64> { Ok((data, val)) } +/// Parser that reads SLEB128 +fn sleb128(input: &[u8]) -> IResult<&[u8], i64> { + let mut val: i64 = 0; + let mut shift: i64 = 0; + + let mut data = input; + let mut byte: u8; + + loop { + (data, byte) = u8(data)?; + + val |= ((byte & !(1 << 7)) as i64) << shift; + shift += 7; + + if byte & (1 << 7) == 0 { + break; + } + } + + if shift < 8 * mem::size_of::() as i64 && (byte & 1 << 6) != 0 { + val |= !0 << shift; + } + + Ok((data, val)) +} + /// Convert a decimal number representation to a version string representation. fn convert_to_version_string(decimal_number: u32) -> String { let major = decimal_number >> 16; @@ -1586,6 +1701,8 @@ impl From> for protos::macho::Macho { .extend(m.rpaths.iter().map(|rpath: &&[u8]| rpath.to_vec())); result.entitlements.extend(m.entitlements.clone()); result.exports.extend(m.exports.clone()); + result.imports.extend(m.imports.clone()); + result .set_number_of_segments(m.segments.len().try_into().unwrap()); } else { @@ -1665,6 +1782,7 @@ impl From<&MachOFile<'_>> for protos::macho::File { result.rpaths.extend(macho.rpaths.iter().map(|rpath| rpath.to_vec())); result.entitlements.extend(macho.entitlements.clone()); result.exports.extend(macho.exports.clone()); + result.imports.extend(macho.imports.clone()); result .set_number_of_segments(result.segments.len().try_into().unwrap()); @@ -1888,3 +2006,30 @@ fn test_uleb_parsing() { ]) .is_err()); } + +#[test] +fn test_sleb_parsing() { + let sleb_128_in = vec![0b1100_0111, 0b1001_1111, 0b111_1111]; + let (_remainder, result) = sleb128(&sleb_128_in).unwrap(); + assert_eq!(-12345, result); + + let sleb_128_in = vec![0b1001_1100, 0b111_1111]; + let (_remainder, result) = sleb128(&sleb_128_in).unwrap(); + assert_eq!(-100, result); + + let sleb_128_in = vec![0b1111_1111, 0b0]; + let (_remainder, result) = sleb128(&sleb_128_in).unwrap(); + assert_eq!(127, result); + + let sleb_128_in = vec![0b111_1111]; + let (_remainder, result) = sleb128(&sleb_128_in).unwrap(); + assert_eq!(-1, result); + + let sleb_128_in = vec![0b1111_1110, 0b0]; + let (_remainder, result) = sleb128(&sleb_128_in).unwrap(); + assert_eq!(126, result); + + let sleb_128_in = vec![0b000_0000]; + let (_remainder, result) = sleb128(&sleb_128_in).unwrap(); + assert_eq!(0, result); +} diff --git a/lib/src/modules/macho/tests/mod.rs b/lib/src/modules/macho/tests/mod.rs index 4d4449388..f5e716eb1 100644 --- a/lib/src/modules/macho/tests/mod.rs +++ b/lib/src/modules/macho/tests/mod.rs @@ -381,7 +381,7 @@ fn test_macho_module() { import "macho" rule macho_test { condition: - not defined macho.export_hash() + not defined macho.export_hash() } "#, &[] @@ -397,4 +397,81 @@ fn test_macho_module() { "#, &tiny_universal_macho_data ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + macho.import_hash() == "80524643c68b9cf5658e9c2ccc71bdda" + } + "#, + &tiny_universal_macho_data + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + not defined macho.import_hash() + } + "#, + &[] + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + macho.import_hash() == "35ea3b116d319851d93e26f7392e876e" + } + "#, + &chess_macho_data + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + macho.has_import("_NSEventTrackingRunLoopMode") + } + "#, + &chess_macho_data + ); + + rule_false!( + r#" + import "macho" + rule macho_test { + condition: + macho.has_import("_NventTrackingRunLoopMode") + } + "#, + &chess_macho_data + ); + + rule_true!( + r#" + import "macho" + rule macho_test { + condition: + macho.has_export("_factorial") + } + "#, + &tiny_universal_macho_data + ); + + rule_false!( + r#" + import "macho" + rule macho_test { + condition: + macho.has_export("__notfound_export") + } + "#, + &tiny_universal_macho_data + ); } diff --git a/lib/src/modules/macho/tests/testdata/01ac68a14f0ff5faa72bb33e768bfaae4d21de61f776e2405324c498ef52b21b.out b/lib/src/modules/macho/tests/testdata/01ac68a14f0ff5faa72bb33e768bfaae4d21de61f776e2405324c498ef52b21b.out index 88e96d95d..d384fb100 100644 --- a/lib/src/modules/macho/tests/testdata/01ac68a14f0ff5faa72bb33e768bfaae4d21de61f776e2405324c498ef52b21b.out +++ b/lib/src/modules/macho/tests/testdata/01ac68a14f0ff5faa72bb33e768bfaae4d21de61f776e2405324c498ef52b21b.out @@ -491,4 +491,97 @@ exports: - "__Z19CBB_SetEFControlBarP15CAPF_DataSourceP15CAPF_PluginInfoPv" - "__Z19CBB_GetResmanEventsP15CAPF_DataSourcePFvP20HarmonyResourceEventPvES3_P15HarmonyNBTicket" - "__Z18ACCT_GetFirstIndexPK15CAPF_DataSourceRl" - - "__Z18AllocateStringCopyRPcRK8wxString" \ No newline at end of file + - "__Z18AllocateStringCopyRPcRK8wxString" +imports: + - "__ZTV11CAPF_ReqObj" + - "_global_lpAPFInfo" + - "_ACCT_openSession" + - "_ATTR_openSession" + - "_AUTH_openSession" + - "_FONT_openSession" + - "_FT_openSession" + - "_JOBM_openSession" + - "_LP_openSession" + - "_GA_openSession" + - "_GLOBOBJ_openSession" + - "_MTX_openSession" + - "_RESMAN_openSession" + - "__ZN8wxString4nposE" + - "__ZTV12wxMBConvUTF8" + - "_wxConvUTF8Ptr" + - "_wxEmptyString" + - "_wxTheAssertHandler" + - "_wxTrapInAssert" + - "__ZNSt3__15ctypeIcE2idE" + - "__ZdlPv" + - "__Znwm" + - "___gxx_personality_v0" + - "dyld_stub_binder" + - "_ACCT_clearJobLog" + - "_ACCT_getEventsEx" + - "_ACCT_getFirstIndex" + - "_ACCT_getJobLog" + - "_ACCT_getJobLogLength" + - "_ACCT_getJobLogPortionEx" + - "_LOCL_getStringWithContext" + - "_LOCL_getStringsWithContext" + - "_NB_cancel_block_destroy" + - "_NB_checkTicket" + - "_NB_destroy" + - "_RESMAN_createResource" + - "_RESMAN_deleteResource" + - "_RESMAN_getEventsEx" + - "_RESMAN_getResourceTypes" + - "_RESMAN_getResourceWithSettingsInContainer" + - "__Unwind_Resume" + - "__Z10wxOnAssertPKciS0_S0_S0_" + - "__Z12wxMilliSleepm" + - "__Z19wxGet_wxConvUTF8Ptrv" + - "__Z20EFIAPF_ErrLogGen_MACPKclS0_S0_l" + - "__ZN11CAPF_ReqObj13GetInputParamERK8wxStringPPv" + - "__ZN11CAPF_ReqObj13GetInputParamERK8wxStringPl" + - "__ZN11CAPF_ReqObj13GetInputParamERK8wxStringRS0_" + - "__ZN11CAPF_ReqObj14SetOutputParamERK8wxStringPv" + - "__ZN11CAPF_ReqObj14SetOutputParamERK8wxStringl" + - "__ZN11CAPF_ReqObj20CAPF_ReqObj_CalleeExElPvP15CAPF_PluginInfo" + - "__ZN11CAPF_ReqObjD1Ev" + - "__ZN11wxStopWatch5StartEl" + - "__ZN12CAPF_Session9GetKeyIDAEPc" + - "__ZN8wxString10ConvertStrEPKcmRK8wxMBConv" + - "__ZN8wxString4TrimEb" + - "__ZN9wxPrivate18GetUntypedNullDataEv" + - "__ZNK11wxStopWatch11TimeInMicroEv" + - "__ZNK15CAPF_DataSource10getSessionEv" + - "__ZNK15CAPF_DataSource11getProtocolEv" + - "__ZNK15CAPF_DataSource14getDSWorkClassEi" + - "__ZNK8wxMBConv14DoConvertMB2WCEPKcm" + - "__ZNK8wxString6AsCharERK8wxMBConv" + - "__ZNK8wxString9CmpNoCaseERKS_" + - "__ZNKSt3__16locale9use_facetERNS0_2idE" + - "__ZNKSt3__18ios_base6getlocEv" + - "__ZNSt3__112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6__initEPKwm" + - "__ZNSt3__112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6assignEPKwm" + - "__ZNSt3__112basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEEaSERKS5_" + - "__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryC1ERS3_" + - "__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryD1Ev" + - "__ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEi" + - "__ZNSt3__16localeD1Ev" + - "__ZNSt3__18ios_base33__set_badbit_and_consider_rethrowEv" + - "__ZNSt3__18ios_base5clearEj" + - "__ZSt9terminatev" + - "___bzero" + - "___cxa_begin_catch" + - "___cxa_end_catch" + - "_calloc" + - "_free" + - "_freeHarmonyAttributes" + - "_freeStringList" + - "_harmony_calloc" + - "_harmony_free" + - "_harmony_malloc" + - "_memset" + - "_strcpy" + - "_strlen" + - "_wcslen" + - "__ZdlPv" + - "__Znwm" \ No newline at end of file diff --git a/lib/src/modules/macho/tests/testdata/a1da3d22c5ec85143faf5f69fb18d79cb0e7394c7b5065f74b7ce8c52ace75f1.out b/lib/src/modules/macho/tests/testdata/a1da3d22c5ec85143faf5f69fb18d79cb0e7394c7b5065f74b7ce8c52ace75f1.out index 1fc0de001..a08d56884 100644 --- a/lib/src/modules/macho/tests/testdata/a1da3d22c5ec85143faf5f69fb18d79cb0e7394c7b5065f74b7ce8c52ace75f1.out +++ b/lib/src/modules/macho/tests/testdata/a1da3d22c5ec85143faf5f69fb18d79cb0e7394c7b5065f74b7ce8c52ace75f1.out @@ -240,6 +240,11 @@ file: sdk: "12.6.0" exports: - "__mh_execute_header" + imports: + - "dyld_stub_binder" + - "_CallComponentDispatch" + - "_OpenDefaultComponent" + - "_exit" - magic: 0xcffaedfe cputype: 0x100000c cpusubtype: 0x0 diff --git a/lib/src/modules/macho/tests/testdata/chess.out b/lib/src/modules/macho/tests/testdata/chess.out index c5bb666dd..b58cab0a2 100644 --- a/lib/src/modules/macho/tests/testdata/chess.out +++ b/lib/src/modules/macho/tests/testdata/chess.out @@ -832,4 +832,313 @@ build_version: - tool: 3 version: "556.4" exports: - - "__mh_execute_header" \ No newline at end of file + - "__mh_execute_header" +imports: + - "_NSDefaultRunLoopMode" + - "_OBJC_CLASS_$_NSArray" + - "_OBJC_CLASS_$_NSData" + - "_OBJC_CLASS_$_NSDate" + - "_OBJC_CLASS_$_NSDictionary" + - "_OBJC_CLASS_$_NSInvocation" + - "_OBJC_CLASS_$_NSLocale" + - "_OBJC_CLASS_$_NSMutableArray" + - "_OBJC_CLASS_$_NSMutableDictionary" + - "_OBJC_CLASS_$_NSNull" + - "_OBJC_CLASS_$_NSRunLoop" + - "_OBJC_CLASS_$_NSSet" + - "_OBJC_CLASS_$_NSURL" + - "_OBJC_CLASS_$_NSUserDefaults" + - "___CFConstantStringClassReference" + - "_NSAccessibilityButtonRole" + - "_NSAccessibilityChildrenAttribute" + - "_NSAccessibilityContentsAttribute" + - "_NSAccessibilityDescriptionAttribute" + - "_NSAccessibilityEnabledAttribute" + - "_NSAccessibilityFocusedAttribute" + - "_NSAccessibilityGroupRole" + - "_NSAccessibilityParentAttribute" + - "_NSAccessibilityPositionAttribute" + - "_NSAccessibilityPressAction" + - "_NSAccessibilityRoleAttribute" + - "_NSAccessibilityRoleDescriptionAttribute" + - "_NSAccessibilitySelectedChildrenAttribute" + - "_NSAccessibilitySelectedChildrenChangedNotification" + - "_NSAccessibilitySelectedRowsChangedNotification" + - "_NSAccessibilitySizeAttribute" + - "_NSAccessibilityStaticTextRole" + - "_NSAccessibilityTitleAttribute" + - "_NSAccessibilityTopLevelUIElementAttribute" + - "_NSAccessibilityValueAttribute" + - "_NSAccessibilityWindowAttribute" + - "_NSApp" + - "_NSEventTrackingRunLoopMode" + - "_NSVoiceDemoText" + - "_NSVoiceLocaleIdentifier" + - "_NSVoiceName" + - "_NSWindowWillCloseNotification" + - "_OBJC_CLASS_$_NSAlert" + - "_OBJC_CLASS_$_NSAnimationContext" + - "_OBJC_CLASS_$_NSApplication" + - "_OBJC_CLASS_$_NSColor" + - "_OBJC_CLASS_$_NSCursor" + - "_OBJC_CLASS_$_NSDocument" + - "_OBJC_CLASS_$_NSDocumentController" + - "_OBJC_CLASS_$_NSEvent" + - "_OBJC_CLASS_$_NSFont" + - "_OBJC_CLASS_$_NSImageView" + - "_OBJC_CLASS_$_NSOpenGLPixelFormat" + - "_OBJC_CLASS_$_NSOpenGLView" + - "_OBJC_CLASS_$_NSScreen" + - "_OBJC_CLASS_$_NSSpeechSynthesizer" + - "_OBJC_CLASS_$_NSTableView" + - "_OBJC_CLASS_$_NSTextFieldCell" + - "_OBJC_CLASS_$_NSTrackingArea" + - "_OBJC_CLASS_$_NSUserDefaultsController" + - "_OBJC_CLASS_$_NSWindow" + - "_OBJC_CLASS_$_NSWindowController" + - "_OBJC_CLASS_$_NSWorkspace" + - "_OBJC_METACLASS_$_NSDocument" + - "_OBJC_METACLASS_$_NSImageView" + - "_OBJC_METACLASS_$_NSOpenGLView" + - "_OBJC_METACLASS_$_NSTableView" + - "_OBJC_METACLASS_$_NSTextFieldCell" + - "_OBJC_METACLASS_$_NSWindow" + - "_OBJC_METACLASS_$_NSWindowController" + - "_OBJC_CLASS_$_GKAchievement" + - "_OBJC_CLASS_$_GKDialogController" + - "_OBJC_CLASS_$_GKGameCenterViewController" + - "_OBJC_CLASS_$_GKLocalPlayer" + - "_OBJC_CLASS_$_GKMatchRequest" + - "_OBJC_CLASS_$_GKPlayer" + - "_OBJC_CLASS_$_GKTurnBasedMatch" + - "_OBJC_CLASS_$_GKTurnBasedMatchmakerViewController" + - "_NSCocoaErrorDomain" + - "_NSLocalizedDescriptionKey" + - "_OBJC_CLASS_$_NSAutoreleasePool" + - "_OBJC_CLASS_$_NSBundle" + - "_OBJC_CLASS_$_NSError" + - "_OBJC_CLASS_$_NSFileHandle" + - "_OBJC_CLASS_$_NSFileManager" + - "_OBJC_CLASS_$_NSMutableString" + - "_OBJC_CLASS_$_NSNotification" + - "_OBJC_CLASS_$_NSNotificationCenter" + - "_OBJC_CLASS_$_NSNotificationQueue" + - "_OBJC_CLASS_$_NSNumber" + - "_OBJC_CLASS_$_NSNumberFormatter" + - "_OBJC_CLASS_$_NSOperationQueue" + - "_OBJC_CLASS_$_NSPipe" + - "_OBJC_CLASS_$_NSPort" + - "_OBJC_CLASS_$_NSPortMessage" + - "_OBJC_CLASS_$_NSPropertyListSerialization" + - "_OBJC_CLASS_$_NSRegularExpression" + - "_OBJC_CLASS_$_NSString" + - "_OBJC_CLASS_$_NSTask" + - "_OBJC_CLASS_$_NSThread" + - "_OBJC_CLASS_$_NSValue" + - "_OBJC_CLASS_$_NSObject" + - "_OBJC_METACLASS_$_NSObject" + - "__objc_empty_cache" + - "_objc_msgSend" + - "_objc_release" + - "_objc_retain" + - "__ZdlPv" + - "__Znwm" + - "___gxx_personality_v0" + - "__DefaultRuneLocale" + - "__NSConcreteGlobalBlock" + - "__NSConcreteStackBlock" + - "___stack_chk_guard" + - "___stderrp" + - "___stdinp" + - "___stdoutp" + - "__dispatch_main_q" + - "dyld_stub_binder" + - "_AEGetParamPtr" + - "_AEInstallEventHandler" + - "_CFRelease" + - "_CGBitmapContextCreate" + - "_CGColorSpaceCreateDeviceRGB" + - "_CGColorSpaceRelease" + - "_CGContextClearRect" + - "_CGContextDrawImage" + - "_CGContextGetTextPosition" + - "_CGContextRelease" + - "_CGContextSelectFont" + - "_CGContextSetAlpha" + - "_CGContextSetShouldSubpixelQuantizeFonts" + - "_CGContextSetTextDrawingMode" + - "_CGContextShowTextAtPoint" + - "_CGImageGetHeight" + - "_CGImageGetWidth" + - "_CGImageRelease" + - "_CGImageSourceCreateImageAtIndex" + - "_CGImageSourceCreateWithURL" + - "_CGLDescribeRenderer" + - "_CGLQueryRendererInfo" + - "_GetCurrentProcess" + - "_NSAccessibilityActionDescription" + - "_NSAccessibilityRoleDescription" + - "_NSApplicationMain" + - "_NSBeep" + - "_NSFullUserName" + - "_NSLog" + - "_NSRectFill" + - "_SCNetworkReachabilityCreateWithAddress" + - "_SCNetworkReachabilityGetFlags" + - "_SRAddLanguageObject" + - "_SRAddText" + - "_SRCloseRecognitionSystem" + - "_SRCountItems" + - "_SREmptyLanguageObject" + - "_SRGetIndexedItem" + - "_SRGetProperty" + - "_SRNewLanguageModel" + - "_SRNewPath" + - "_SRNewRecognizer" + - "_SROpenRecognitionSystem" + - "_SRReleaseObject" + - "_SRSetLanguageModel" + - "_SRSetProperty" + - "_SRStartListening" + - "_SRStopListening" + - "__Block_copy" + - "__Block_object_assign" + - "__Block_object_dispose" + - "__Block_release" + - "__Unwind_Resume" + - "___bzero" + - "___cxa_guard_abort" + - "___cxa_guard_acquire" + - "___cxa_guard_release" + - "___error" + - "___maskrune" + - "___sincosf_stret" + - "___stack_chk_fail" + - "___tolower" + - "___toupper" + - "_abort" + - "_arc4random" + - "_atan2f" + - "_atoi" + - "_calloc" + - "_creat" + - "_dispatch_after" + - "_dispatch_apply" + - "_dispatch_async" + - "_dispatch_get_global_queue" + - "_dispatch_once" + - "_dispatch_queue_create" + - "_dispatch_time" + - "_exit" + - "_fclose" + - "_fileno" + - "_fmodf" + - "_fopen" + - "_fprintf" + - "_fputc" + - "_fputs" + - "_free" + - "_funopen" + - "_fwrite" + - "_getenv" + - "_getrlimit" + - "_gettimeofday" + - "_glBegin" + - "_glBindTexture" + - "_glBlendFunc" + - "_glCallList" + - "_glClear" + - "_glClearColor" + - "_glColor3fv" + - "_glColor4f" + - "_glColor4fv" + - "_glColorMask" + - "_glCullFace" + - "_glDeleteTextures" + - "_glDepthMask" + - "_glDisable" + - "_glEnable" + - "_glEnd" + - "_glEndList" + - "_glFlush" + - "_glGenTextures" + - "_glGetDoublev" + - "_glGetFloatv" + - "_glGetIntegerv" + - "_glGetString" + - "_glHint" + - "_glLightModeli" + - "_glLightf" + - "_glLightfv" + - "_glLighti" + - "_glLoadIdentity" + - "_glMaterialf" + - "_glMaterialfv" + - "_glMatrixMode" + - "_glNewList" + - "_glNormal3f" + - "_glPixelStorei" + - "_glPopAttrib" + - "_glPopMatrix" + - "_glPushAttrib" + - "_glPushMatrix" + - "_glReadPixels" + - "_glRotatef" + - "_glScalef" + - "_glShadeModel" + - "_glStencilFunc" + - "_glStencilOp" + - "_glTexCoord2f" + - "_glTexEnvi" + - "_glTexParameterf" + - "_glTexParameteri" + - "_glTranslatef" + - "_glVertex3d" + - "_glVertex3f" + - "_glVertex3fv" + - "_glViewport" + - "_gluBuild2DMipmaps" + - "_gluCylinder" + - "_gluDeleteQuadric" + - "_gluDisk" + - "_gluLookAt" + - "_gluNewQuadric" + - "_gluOrtho2D" + - "_gluPartialDisk" + - "_gluPerspective" + - "_gluProject" + - "_gluQuadricNormals" + - "_gluQuadricOrientation" + - "_gluQuadricTexture" + - "_gluUnProject" + - "_hypotf" + - "_isatty" + - "_ldexpf" + - "_lroundf" + - "_malloc" + - "_memchr" + - "_memcpy" + - "_memset" + - "_objc_alloc" + - "_objc_autorelease" + - "_objc_enumerationMutation" + - "_objc_msgSendSuper2" + - "_objc_msgSend_stret" + - "_objc_setProperty_nonatomic" + - "_pow" + - "_putenv" + - "_random" + - "_read" + - "_realloc" + - "_setrlimit" + - "_snprintf" + - "_srandom" + - "_strchr" + - "_strcspn" + - "_strlcpy" + - "_strlen" + - "_strspn" + - "_strstr" + - "_usleep" + - "__ZdlPv" + - "__Znwm" \ No newline at end of file diff --git a/lib/src/modules/macho/tests/testdata/tiny_universal.out b/lib/src/modules/macho/tests/testdata/tiny_universal.out index e79518c19..bf130daf1 100644 --- a/lib/src/modules/macho/tests/testdata/tiny_universal.out +++ b/lib/src/modules/macho/tests/testdata/tiny_universal.out @@ -196,6 +196,10 @@ file: - "_main" - "_factorial" - "__mh_execute_header" + imports: + - "dyld_stub_binder" + - "_printf" + - "_scanf" - magic: 0xcffaedfe cputype: 0x1000007 cpusubtype: 0x80000003 @@ -397,4 +401,8 @@ file: exports: - "_main" - "_factorial" - - "__mh_execute_header" \ No newline at end of file + - "__mh_execute_header" + imports: + - "dyld_stub_binder" + - "_printf" + - "_scanf" \ No newline at end of file diff --git a/lib/src/modules/protos/macho.proto b/lib/src/modules/protos/macho.proto index 47a15993c..c6d84791e 100644 --- a/lib/src/modules/protos/macho.proto +++ b/lib/src/modules/protos/macho.proto @@ -153,6 +153,7 @@ message File { optional BuildVersion build_version = 24; optional MinVersion min_version = 25; repeated string exports = 26; + repeated string imports = 27; } message Macho { @@ -183,15 +184,16 @@ message Macho { optional BuildVersion build_version = 24; optional MinVersion min_version = 25; repeated string exports = 26; + repeated string imports = 27; // Add fields for Mach-O fat binary header - optional uint32 fat_magic = 27 [(yaml.field).fmt = "x"]; - optional uint32 nfat_arch = 28; - repeated FatArch fat_arch = 29; + optional uint32 fat_magic = 28 [(yaml.field).fmt = "x"]; + optional uint32 nfat_arch = 29; + repeated FatArch fat_arch = 30; // Nested Mach-O files - repeated File file = 30; + repeated File file = 31; } enum Header { diff --git a/site/content/docs/modules/macho.md b/site/content/docs/modules/macho.md index d94c7fad0..372833117 100644 --- a/site/content/docs/modules/macho.md +++ b/site/content/docs/modules/macho.md @@ -151,6 +151,38 @@ rule has_rpath_example { } ``` +### has_import(import) + +Returns true if the Mach-O parsed imports contain `import` +- `import` is case-insensitive. + +#### Example + +```yara +import "macho" + +rule has_import_example { + condition: + macho.has_import("_NSEventTrackingRunLoopMode") +} +``` + +### has_export(export) + +Returns true if the Mach-O parsed exports contain `export` +- `export` is case-insensitive. + +#### Example + +```yara +import "macho" + +rule has_export_example { + condition: + macho.has_export("_main") +} +``` + ### dylib_hash() Returns an MD5 hash of the dylibs designated in the Mach-O binary. @@ -214,6 +246,27 @@ rule export_hash_example { } ``` +### import_hash() + +Returns an MD5 hash of the imports designated in the Mach-O binary. + +{{< callout title="Notice">}} + +The returned hash string is always in lowercase. + +{{< /callout >}} + +#### Example + +```yara +import "macho" + +rule import_hash_example { + condition: + macho.import_hash() == "35ea3b116d319851d93e26f7392e876e" +} +``` + ------ ### Module structure