Skip to content

Commit

Permalink
feat: implement dyld_info load command parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
latonis committed Jan 14, 2024
1 parent 36f4b4a commit 62b22d1
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 15 deletions.
167 changes: 166 additions & 1 deletion yara-x/src/modules/macho/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ const LC_LOAD_WEAK_DYLIB: u32 = 0x18 | LC_REQ_DYLD;
const LC_SEGMENT_64: u32 = 0x00000019;
const LC_RPATH: u32 = 0x1c | LC_REQ_DYLD;
const LC_REEXPORT_DYLIB: u32 = 0x1f | LC_REQ_DYLD;
const LC_DYLD_INFO: u32 = 0x00000022;
const LC_DYLD_INFO_ONLY: u32 = 0x22 | LC_REQ_DYLD;
const LC_DYLD_ENVIRONMENT: u32 = 0x00000027;
const LC_MAIN: u32 = 0x28 | LC_REQ_DYLD;
const LC_SOURCE_VERSION: u32 = 0x0000002a;
Expand Down Expand Up @@ -169,6 +171,25 @@ struct LoadCommand {
cmdsize: u32,
}

/// `DyldInfoCommand`: Represents the dyld info load command in the Mach-O file.
/// Fields: cmd, cmdsize, rebase_off, rebase_size, bind_off, bind_size, weak_bind_off
/// weak_bind_size, lazy_bind_off, lazy_bind_size, export_off, export_size
#[derive(Debug, Default, Clone, Copy)]
struct DyldInfoCommand {
cmd: u32,
cmdsize: u32,
rebase_off: u32,
rebase_size: u32,
bind_off: u32,
bind_size: u32,
weak_bind_off: u32,
weak_bind_size: u32,
lazy_bind_off: u32,
lazy_bind_size: u32,
export_off: u32,
export_size: u32,
}

/// `SymtabCommand`: Represents a symbol table load command in the Mach-O file.
/// Fields: cmd, cmdsize, symoff, nsyms, stroff, strsize
#[derive(Debug, Default, Clone, Copy)]
Expand Down Expand Up @@ -797,6 +818,35 @@ fn swap_dylib_command(command: &mut DylibCommand) {
command.cmdsize = BigEndian::read_u32(&command.cmdsize.to_le_bytes());
}

/// Swaps the endianness of fields within a Mach-O dyld info command from
/// BigEndian to LittleEndian in-place.
///
/// # Arguments
///
/// * `command`: A mutable reference to the Mach-O dyld info command.
fn swap_dyld_info_command(command: &mut DyldInfoCommand) {
command.cmd = BigEndian::read_u32(&command.cmd.to_le_bytes());
command.cmdsize = BigEndian::read_u32(&command.cmdsize.to_le_bytes());
command.rebase_off =
BigEndian::read_u32(&command.rebase_off.to_le_bytes());
command.rebase_size =
BigEndian::read_u32(&command.rebase_size.to_le_bytes());
command.bind_off = BigEndian::read_u32(&command.bind_off.to_le_bytes());
command.bind_size = BigEndian::read_u32(&command.bind_size.to_le_bytes());
command.weak_bind_off =
BigEndian::read_u32(&command.weak_bind_off.to_le_bytes());
command.weak_bind_size =
BigEndian::read_u32(&command.weak_bind_size.to_le_bytes());
command.lazy_bind_off =
BigEndian::read_u32(&command.lazy_bind_off.to_le_bytes());
command.lazy_bind_size =
BigEndian::read_u32(&command.lazy_bind_size.to_le_bytes());
command.export_off =
BigEndian::read_u32(&command.export_off.to_le_bytes());
command.export_size =
BigEndian::read_u32(&command.export_size.to_le_bytes());
}

/// Swaps the endianness of fields within a Mach-O dylinker command from
/// BigEndian to LittleEndian in-place.
///
Expand Down Expand Up @@ -1203,6 +1253,54 @@ fn parse_dylib_command(
Ok((input, dy))
}

/// Parse a Mach-O DyldInfoCommand, transforming raw bytes into a
/// structured format.
///
/// # Arguments
///
/// * `input`: A slice of bytes containing the raw DyldInfoCommand data.
///
/// # Returns
///
/// A `nom` IResult containing the remaining unparsed input and the parsed
/// DyldInfoCommand structure, or a `nom` error if the parsing fails.
///
/// # Errors
///
/// Returns a `nom` error if the input data is insufficient or malformed.
fn parse_dyld_info_command(input: &[u8]) -> IResult<&[u8], DyldInfoCommand> {
let (input, cmd) = le_u32(input)?;
let (input, cmdsize) = le_u32(input)?;
let (input, rebase_off) = le_u32(input)?;
let (input, rebase_size) = le_u32(input)?;
let (input, bind_off) = le_u32(input)?;
let (input, bind_size) = le_u32(input)?;
let (input, weak_bind_off) = le_u32(input)?;
let (input, weak_bind_size) = le_u32(input)?;
let (input, lazy_bind_off) = le_u32(input)?;
let (input, lazy_bind_size) = le_u32(input)?;
let (input, export_off) = le_u32(input)?;
let (input, export_size) = le_u32(input)?;

Ok((
input,
DyldInfoCommand {
cmd,
cmdsize,
rebase_off,
rebase_size,
bind_off,
bind_size,
weak_bind_off,
weak_bind_size,
lazy_bind_off,
lazy_bind_size,
export_off,
export_size,
},
))
}

/// Parse a Mach-O DylinkerCommand, transforming raw bytes into a structured
/// format.
///
Expand Down Expand Up @@ -1802,7 +1900,7 @@ fn parse_m88k_thread_state(input: &[u8]) -> IResult<&[u8], M88KThreadState> {
///
/// # Errors
///
/// Returns a `nom` error if the input data is insufficient or malformed.
/// Returns a `nom` error if the input data is insuffidyl.exports_offcient or malformed.
fn parse_x86_thread_state64(input: &[u8]) -> IResult<&[u8], X86ThreadState64> {
let (input, rax) = le_u64(input)?;
let (input, rbx) = le_u64(input)?;
Expand Down Expand Up @@ -1978,6 +2076,69 @@ fn handle_dylib_command(
Ok(())
}

/// Handles the LC_DYLD_INFO_ONLY and LC_DYLD_INFO commands for Mach-O files,
/// parsing the data and populating a protobuf representation of the dyld info command.
///
/// # Arguments
///
/// * `command_data`: The raw byte data of the dyld info command.
/// * `size`: The size of the dyld info command data.
/// * `macho_file`: Mutable reference to the protobuf representation of the
/// Mach-O file.
///
/// # Returns
///
/// Returns a `Result<(), MachoError>` indicating the success or failure of the
/// operation.
///
/// # Errors
///
/// * `MachoError::FileSectionTooSmall`: Returned when the segment size is
/// smaller than the expected DyldInfoCommand struct size.
/// * `MachoError::ParsingError`: Returned when there is an error parsing the
/// dyld info command data.
/// * `MachoError::MissingHeaderValue`: Returned when the "magic" header value
/// is missing, needed for determining if bytes should be swapped.
fn handle_dyld_info_command(
command_data: &[u8],
size: usize,
macho_file: &mut File,
) -> Result<(), MachoError> {
if size < std::mem::size_of::<DyldInfoCommand>() {
return Err(MachoError::FileSectionTooSmall(
"DyldInfoCommand".to_string(),
));
}

let (_, mut dyl) = parse_dyld_info_command(command_data)
.map_err(|e| MachoError::ParsingError(format!("{:?}", e)))?;

if should_swap_bytes(
macho_file
.magic
.ok_or(MachoError::MissingHeaderValue("magic".to_string()))?,
) {
swap_dyld_info_command(&mut dyl);
};

macho_file.dyld_info = MessageField::some(DyldInfo {
cmd: Some(dyl.cmd),
cmdsize: Some(dyl.cmdsize),
rebase_off: Some(dyl.rebase_off),
rebase_size: Some(dyl.rebase_size),
bind_off: Some(dyl.bind_off),
bind_size: Some(dyl.bind_size),
weak_bind_off: Some(dyl.weak_bind_off),
weak_bind_size: Some(dyl.weak_bind_size),
lazy_bind_off: Some(dyl.lazy_bind_off),
lazy_bind_size: Some(dyl.lazy_bind_size),
export_off: Some(dyl.export_off),
export_size: Some(dyl.export_size),
..Default::default()
});

Ok(())
}
/// Handles the LC_ID_DYLINKER, LC_LOAD_DYLINKER and LC_DYLD_ENVIRONMENT
/// commands for Mach-O files, parsing the data and populating a protobuf
/// representation of the rpath command.
Expand Down Expand Up @@ -2805,6 +2966,9 @@ fn handle_command(
LC_ID_DYLINKER | LC_LOAD_DYLINKER | LC_DYLD_ENVIRONMENT => {
handle_dylinker_command(command_data, cmdsize, macho_file)?;
}
LC_DYLD_INFO | LC_DYLD_INFO_ONLY => {
handle_dyld_info_command(command_data, cmdsize, macho_file)?;
}
LC_SOURCE_VERSION => {
handle_source_version_command(
command_data,
Expand Down Expand Up @@ -3451,6 +3615,7 @@ fn main(data: &[u8]) -> Macho {
macho_proto.symtab = file_data.symtab;
macho_proto.source_version = file_data.source_version;
macho_proto.dynamic_linker = file_data.dynamic_linker;
macho_proto.dyld_info = file_data.dyld_info;
macho_proto.dysymtab = file_data.dysymtab;
macho_proto.entry_point = file_data.entry_point;
macho_proto.stack_size = file_data.stack_size;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,19 @@ dylibs:
timestamp: 2 # 1970-01-01 00:00:02 UTC
compatibility_version: "1.0.0"
current_version: "1252.0.0"
dyld_info:
cmd: 2147483682
cmdsize: 48
rebase_off: 28672
rebase_size: 8
bind_off: 28680
bind_size: 624
weak_bind_off: 29304
weak_bind_size: 48
lazy_bind_off: 29352
lazy_bind_size: 2464
export_off: 31816
export_size: 2448
source_version: "0.0.0.0.0"
dysymtab:
cmd: 11
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ dylibs:
timestamp: 2 # 1970-01-01 00:00:02 UTC
compatibility_version: "1.0.0"
current_version: "1213.0.0"
dyld_info:
cmd: 2147483682
cmdsize: 48
rebase_off: 0
rebase_size: 0
bind_off: 0
bind_size: 0
weak_bind_off: 0
weak_bind_size: 0
lazy_bind_off: 0
lazy_bind_size: 0
export_off: 4096
export_size: 24
source_version: "0.0.0.0.0"
dysymtab:
cmd: 11
Expand Down
13 changes: 13 additions & 0 deletions yara-x/src/modules/macho/tests/testdata/macho_x86_file.out
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,19 @@ rpaths:
cmdsize: 38
path: "@loader_path/../Frameworks"
dynamic_linker: "/usr/lib/dyld"
dyld_info:
cmd: 2147483682
cmdsize: 48
rebase_off: 8192
rebase_size: 16
bind_off: 8208
bind_size: 24
weak_bind_off: 0
weak_bind_size: 0
lazy_bind_off: 8232
lazy_bind_size: 28
export_off: 8260
export_size: 44
entry_point: 3728
stack_size: 0
source_version: "0.0.0.0.0"
Expand Down
26 changes: 26 additions & 0 deletions yara-x/src/modules/macho/tests/testdata/tiny_universal.out
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,19 @@ file:
compatibility_version: "1.0.0"
current_version: "1213.0.0"
dynamic_linker: "/usr/lib/dyld"
dyld_info:
cmd: 2147483682
cmdsize: 48
rebase_off: 8192
rebase_size: 16
bind_off: 8208
bind_size: 24
weak_bind_off: 0
weak_bind_size: 0
lazy_bind_off: 8232
lazy_bind_size: 28
export_off: 8260
export_size: 60
entry_point: 3808
stack_size: 0
source_version: "0.0.0.0.0"
Expand Down Expand Up @@ -344,6 +357,19 @@ file:
compatibility_version: "1.0.0"
current_version: "1213.0.0"
dynamic_linker: "/usr/lib/dyld"
dyld_info:
cmd: 2147483682
cmdsize: 48
rebase_off: 8192
rebase_size: 8
bind_off: 8200
bind_size: 24
weak_bind_off: 0
weak_bind_size: 0
lazy_bind_off: 8224
lazy_bind_size: 32
export_off: 8256
export_size: 64
entry_point: 3808
stack_size: 0
source_version: "0.0.0.0.0"
Expand Down
45 changes: 31 additions & 14 deletions yara-x/src/modules/protos/macho.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ message Dylib {
optional string current_version = 4;
}

message DyldInfo {
optional uint32 cmd = 1;
optional uint32 cmdsize = 2;
optional uint32 rebase_off = 3;
optional uint32 rebase_size = 4;
optional uint32 bind_off = 5;
optional uint32 bind_size = 6;
optional uint32 weak_bind_off = 7;
optional uint32 weak_bind_size = 8;
optional uint32 lazy_bind_off = 9;
optional uint32 lazy_bind_size = 10;
optional uint32 export_off = 11;
optional uint32 export_size = 12;
}

message RPath {
optional uint32 cmd = 1;
optional uint32 cmdsize = 2;
Expand Down Expand Up @@ -109,11 +124,12 @@ message File {
repeated Dylib dylibs = 11;
repeated RPath rpaths = 12;
optional string dynamic_linker = 13;
optional uint64 entry_point = 14;
optional uint64 stack_size = 15;
optional string source_version = 16;
optional Dysymtab dysymtab = 17;
optional Symtab symtab = 18;
optional DyldInfo dyld_info = 14;
optional uint64 entry_point = 15;
optional uint64 stack_size = 16;
optional string source_version = 17;
optional Dysymtab dysymtab = 18;
optional Symtab symtab = 19;
}

message Macho {
Expand All @@ -131,20 +147,21 @@ message Macho {
repeated Dylib dylibs = 11;
repeated RPath rpaths = 12;
optional string dynamic_linker = 13;
optional uint64 entry_point = 14;
optional uint64 stack_size = 15;
optional string source_version = 16;
optional Dysymtab dysymtab = 17;
optional Symtab symtab = 18;
optional DyldInfo dyld_info = 14;
optional uint64 entry_point = 15;
optional uint64 stack_size = 16;
optional string source_version = 17;
optional Dysymtab dysymtab = 18;
optional Symtab symtab = 19;


// Add fields for Mach-O fat binary header
optional uint32 fat_magic = 19 [(yaml.field).fmt = "x"];
optional uint32 nfat_arch = 20;
repeated FatArch fat_arch = 21;
optional uint32 fat_magic = 20 [(yaml.field).fmt = "x"];
optional uint32 nfat_arch = 21;
repeated FatArch fat_arch = 22;

// Nested Mach-O files
repeated File file = 22;
repeated File file = 23;
}

enum HEADER {
Expand Down

0 comments on commit 62b22d1

Please sign in to comment.