Skip to content

Commit

Permalink
feat: implement mach-o LC_LINKER_OPTION parsing for load commands (#256)
Browse files Browse the repository at this point in the history
This allows for better insight into how the binary is linked/compiled and could potentially be used for detection insight or better correlation when writing rules. It also allows for knowledge of language/development environment for the origins of the binary.
  • Loading branch information
latonis authored Dec 1, 2024
1 parent 8cb1ac5 commit 23e952a
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 5 deletions.
37 changes: 36 additions & 1 deletion lib/src/modules/macho/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const LC_VERSION_MIN_IPHONEOS: u32 = 0x00000025;
const LC_DYLD_ENVIRONMENT: u32 = 0x00000027;
const LC_MAIN: u32 = 0x28 | LC_REQ_DYLD;
const LC_SOURCE_VERSION: u32 = 0x0000002a;
const LC_LINKER_OPTION: u32 = 0x0000002d;
const LC_VERSION_MIN_TVOS: u32 = 0x0000002f;
const LC_VERSION_MIN_WATCHOS: u32 = 0x00000030;
const LC_BUILD_VERSION: u32 = 0x00000032;
Expand Down Expand Up @@ -284,6 +285,7 @@ impl<'a> MachO<'a> {
symtab: None,
dysymtab: None,
dynamic_linker: None,
linker_options: Vec::new(),
dyld_info: None,
source_version: None,
entry_point_offset: None,
Expand Down Expand Up @@ -413,6 +415,7 @@ pub struct MachOFile<'a> {
dysymtab: Option<Dysymtab>,
dyld_info: Option<DyldInfo>,
dynamic_linker: Option<&'a [u8]>,
linker_options: Vec<&'a [u8]>,
source_version: Option<String>,
rpaths: Vec<&'a [u8]>,
uuid: Option<&'a [u8]>,
Expand Down Expand Up @@ -517,7 +520,6 @@ impl<'a> MachOFile<'a> {
// minus 8.
command_size.saturating_sub(8),
)(remainder)?;

// Parse the command's data. Parsers for individual commands must
// consume all `command_data`.
match command {
Expand Down Expand Up @@ -588,6 +590,11 @@ impl<'a> MachOFile<'a> {
mv.device = command;
self.min_version = Some(mv);
}
LC_LINKER_OPTION => {
let (_, linker_options) =
self.linker_options_command()(command_data)?;
self.linker_options.extend(linker_options);
}
_ => {}
}

Expand Down Expand Up @@ -1129,6 +1136,26 @@ impl<'a> MachOFile<'a> {
}
}

/// Parser that parses a LC_LINKER_OPTION command.
fn linker_options_command(
&self,
) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], Vec<&'a [u8]>> + '_ {
move |input: &'a [u8]| {
let mut linker_options = Vec::new();
let (mut remainder, count) = u32(self.endianness)(input)?;
let mut option: &[u8];
for _ in 0..count {
(remainder, option) = map(
tuple((take_till(|b| b == b'\x00'), tag(b"\x00"))),
|(s, _)| s,
)(remainder)?;

linker_options.push(option);
}
Ok((&[], linker_options))
}
}

/// Parser that parses a LC_UUID command.
fn uuid_command(
&self,
Expand Down Expand Up @@ -1718,6 +1745,10 @@ impl From<MachO<'_>> for protos::macho::Macho {

result
.set_number_of_segments(m.segments.len().try_into().unwrap());

result
.linker_options
.extend(m.linker_options.iter().map(|lo| lo.to_vec()));
} else {
result.fat_magic = macho.fat_magic;
result.set_nfat_arch(macho.archs.len().try_into().unwrap());
Expand Down Expand Up @@ -1797,6 +1828,10 @@ impl From<&MachOFile<'_>> for protos::macho::File {
result.exports.extend(macho.exports.clone());
result.imports.extend(macho.imports.clone());

result
.linker_options
.extend(macho.linker_options.iter().map(|lo| lo.to_vec()));

result
.set_number_of_segments(result.segments.len().try_into().unwrap());

Expand Down
26 changes: 26 additions & 0 deletions lib/src/modules/macho/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ fn test_macho_module() {
"src/modules/macho/tests/testdata/8962a76d0aeaee3326cf840de11543c8beebeb768e712bd3b754b5cd3e151356.in.zip",
);

let linker_options_data = create_binary_from_zipped_ihex("src/modules/macho/tests/testdata/f3cde7740370819a974d1bc7fbeae1946382e3377e64f3162bcc3a5cb34828b7.in.zip");

rule_true!(
r#"
import "macho"
Expand Down Expand Up @@ -522,4 +524,28 @@ fn test_macho_module() {
"#,
&tiny_universal_macho_data
);

rule_true!(
r#"
import "macho"
rule macho_test {
condition:
for any obj in macho.linker_options:
(obj == "-lswiftCoreFoundation")
}
"#,
&linker_options_data
);

rule_false!(
r#"
import "macho"
rule macho_test {
condition:
for any obj in macho.linker_options:
(obj == "-lswiftNotExist")
}
"#,
&linker_options_data
);
}
Binary file not shown.
Loading

0 comments on commit 23e952a

Please sign in to comment.