Directly following the Mach-O header are the binary’s load commands, which tell the dynamic loader (dyld) how to load and link the binary in memory.

load commands can specify required dynamic libraries, the binary’s in-memory layout, and the initial execution state of the program’s main thread

You can view a Mach-O binary’s load commands with otool using the -l

Load commands all begin with a load_command structure, de!ned in mach-o/loader.h

struct load_command { 
	uint32_t cmd; /* type of load command */ 
	uint32_t cmdsize; /* total size of command in bytes */ 
};

Immediately after this load_command structure is the corresponding load command’s data, which is speci!c to the type of load command

Some Important Load Commands:

  • LC_SEGMENT_64 :For a given range of bytes in a Mach-O binary, a segment provides required information for the loader, such as the memory protections those bytes should have when mapped into virtual memory. LC_SEGMENT_64 load commands contain all the relevant information for the dynamic loader to map the segment into memory and set its memory permissions. You’ll likely encounter, amongst others, the following three segments while analyzing Mach-O binaries:

    • __TEXT: Contains executable code and data that is read-only
    • ____DATA: Contains data that is writable
    • __LINKEDIT : Contains information for the dynamic loader, for both linking and binding symbols
    • If the binary was written in Objective-C, it may have an __OBJC segment that contains information used by the Objective-C runtime, though this information might also be found in the __DATA segment within various __objc_* sections
  • LC_MAIN : The LC_MAIN load command is a structure of type entry_point_command:

    struct entry_point_command { 
    	uint32_t cmd; /* LC_MAIN only used in MH_EXECUTE filetypes */ 
    	uint32_t cmdsize; /* 24 */ uint64_t entryoff; /* 
    	file (__TEXT) offset of main() */
    	uint64_t stacksize; /* if not zero, initial stack size */ 
    };
    

    For the purposes of malware analysis, the most important member in the entry_point_command structure is entryoff, which contains the offset of the binary’s entry point. At load time, the dynamic loader simply adds this value to the in-memory base of the binary, and then jumps to this instruction to begin execution of the binary’s code The LC_MAIN load command replaces the deprecated LC_UNIXTHREAD load command, which you might still come across if you’re analyzing older Mach-O binaries. The LC_UNIXTHREAD load command contains the entire context, or register values, of the initial thread. Lastly, a Mach-O binary can contain one or more constructors that will be executed before the address speci!ed in LC_MAIN. The offsets of any constructors are held in the __mod_init_func section of the __DATA_CONST segment

  • LC_LOAD_DYLIB: The LC_LOAD_DYLIB load command describes a dynamic library dependency, and it instructs the dynamic loader to load and link a certain library. You’ll !nd an LC_LOAD_DYLIB load command for each library the Mach-O binary requires. This load command is a structure of type dylib_command, which itself contains a dylib structure that describes the dynamic library

    struct dylib_command { 
    	uint32_t cmd; /* LC_LOAD_{,WEAK_}DYLIB */ 
    	uint32_t cmdsize; /* includes pathname string */ 
    	struct dylib dylib; /* the library identification */ 
    }; 
    struct dylib { union lc_str name; /* library's path name */ 
    	uint32_t timestamp; /* library's build time stamp */ 
    	uint32_t current_version; /* library's current version number */ 
    	uint32_t compatibility_version; /* library's compatibility vers                                                number */ 
    };
    

    You can parse a Mach-O binary’s LC_LOAD_DYLIB load command in order to view the binary’s dependencies. To do so, use the otool utility with the -L ag or MachOView