水平有限,錯誤在所難免,求指點。
Mach-O格式全稱為Mach Object文件格式的縮寫,是mac上可執行文件的格式, 類似於windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)
偷一張蘋果官網上面的圖

從圖上我們可以大概的看出Mach-O可以分為3個部分
- Header
- segment
- section
按圖中的指示,header后面是segment,然后再跟着section,而一個segment是可以包含多個section的。mac系統上提供了一個很強大的工具 otool 來方便我們程序猿檢閱Mach-O格式, 以本機安裝的python2.7.6 為例(-f參數表示解析文件頭,也就是Header, 全文都以python2.7.6 為例)
yeweijundeMacBook-Pro:~ yeweijun$ python -V
Python 2.7.6
yeweijundeMacBook-Pro:~ yeweijun$ otool -f /usr/bin/python
Fat headers
fat_magic 0xcafebabe
nfat_arch 2
architecture 0
cputype 16777223
cpusubtype 3
capabilities 0x80
offset 4096
size 25904
align 2^12 (4096)
architecture 1
cputype 7
cpusubtype 3
capabilities 0x0
offset 32768
size 25648
align 2^12 (4096)
那么我們是否不依賴於工具,完全自己人肉解析呢,答案是可以的,只有我們根據apple提供的mach-o格式解析即可。
我們從第一部分 Header 結構體開始, Header的結構體在 /usr/include/mach-o/fat.h 文件當中。
#define FAT_MAGIC 0xcafebabe
#define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */
struct fat_header {
uint32_t magic; /* FAT_MAGIC */
uint32_t nfat_arch; /* number of structs that follow */
};
struct fat_arch {
cpu_type_t cputype; /* cpu specifier (int) */
cpu_subtype_t cpusubtype; /* machine specifier (int) */
uint32_t offset; /* file offset to this object file */
uint32_t size; /* size of this object file */
uint32_t align; /* alignment as a power of 2 */
};
用010 editor 以hex方式打開python,運行MachOtemplate模板,按fat_arch結構體的格式按偏移對應好。
magic是魔鬼數字,占4個字節,nfat_arch也是4個字節,fat_header后面緊跟的就是fat_arch結構體。 從010 editor hex截圖,從文件偏移0開始,直接套上結構體,可以知道
* magic 值是 0xcafebabe * nfat_arch 值為2, 表示后面跟着2個nfat_arch * cputype cpu類型 * cpu_subtype_t 機器標示 * offset 二進制偏移 * size 二進制大小 * 對於nfat_arch[0] cputype 為 1000007h (對應十進制16777223) cpusubtype 為 80000003h offset 為 1000h size 為 6530h align 為 Ch * 對於nfat_arch[1] cputype 為 7h cpusubtype 為 3h offset 為 8000h size 為 6430 align 為 Ch
對比可以看出,我們人肉分析的header的頭其實和otool -f參數 打印出來的結構是一致的。
實際上mach-o格式是個復合文件,一個Bin文件當中包含了多套的指令集。mac系統在啟動這個進程的時候會加載最合適的某一套指令集,cputype為對應指令集的標示,數值定義在/usr/include/mach/machine.h 中。
CPU框架信息相關函數
/* NXGetAllArchInfos() returns a pointer to an array of all known
* NXArchInfo structures. The last NXArchInfo is marked by a NULL name.
*/
extern const NXArchInfo *NXGetAllArchInfos(void);
/* NXGetLocalArchInfo() returns the NXArchInfo for the local host, or NULL
* if none is known.
*/
extern const NXArchInfo *NXGetLocalArchInfo(void);
nfat_arch結構體值定義
/*
* Capability bits used in the definition of cpu_type.
*/
#define CPU_ARCH_MASK 0xff000000 /* mask for architecture bits */
#define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */
/*
* Machine types known by all.
*/
#define CPU_TYPE_ANY ((cpu_type_t) -1)
#define CPU_TYPE_VAX ((cpu_type_t) 1)
/* skip ((cpu_type_t) 2) */
/* skip ((cpu_type_t) 3) */
/* skip ((cpu_type_t) 4) */
/* skip ((cpu_type_t) 5) */
#define CPU_TYPE_MC680x0 ((cpu_type_t) 6)
#define CPU_TYPE_X86 ((cpu_type_t) 7)
#define CPU_TYPE_I386 CPU_TYPE_X86 /* compatibility */
#define CPU_TYPE_X86_64 (CPU_TYPE_X86 | CPU_ARCH_ABI64)
/* skip CPU_TYPE_MIPS ((cpu_type_t) 8) */
/* skip ((cpu_type_t) 9) */
#define CPU_TYPE_MC98000 ((cpu_type_t) 10)
#define CPU_TYPE_HPPA ((cpu_type_t) 11)
#define CPU_TYPE_ARM ((cpu_type_t) 12)
#define CPU_TYPE_MC88000 ((cpu_type_t) 13)
#define CPU_TYPE_SPARC ((cpu_type_t) 14)
#define CPU_TYPE_I860 ((cpu_type_t) 15)
/* skip CPU_TYPE_ALPHA ((cpu_type_t) 16) */
/* skip ((cpu_type_t) 17) */
#define CPU_TYPE_POWERPC ((cpu_type_t) 18)
#define CPU_TYPE_POWERPC64 (CPU_TYPE_POWERPC | CPU_ARCH_ABI64)
nfat_arch[0]中cputype為7, 即CPU_TYPE_X86,nfat_arch[1]中cputype為1000007h, 即CPU_TYPE_X86_64。 也就是說python文件包含了x86和x64兩套指令集。 用hopper打開python,也可以看到其識別了2套指令集。
以nfat_arch[0]包含的x64為例。其offset為1000h。此處對應的結構體信息是 mach_header_64(文件位於/usr/include/mach-o/loader.h)
struct mach_header {
uint32_t magic; /* mach magic number identifier */ 魔鬼數字
cpu_type_t cputype; /* cpu specifier */ cpu類型和子類型
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */ 文件類型
uint32_t ncmds; /* number of load commands */ commands的個數
uint32_t sizeofcmds; /* the size of all the load commands */ commands大小
uint32_t flags; /* flags */
};
//如果是64位指令,對應的是這個,多了一個reserved
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
ncmds 為command的個數,值為12h(對應十進制18) , sizeofcmds為command的總大小。
command結構體緊跟着mach_header頭后面,跳過mach_header_64的大小,也是從1000h+20h, 繼續分析。
mach_header_64中 filetype 為文件的類型,可能的值如下, 2為MH_EXECUTE,說明是個執行體。
#define MH_OBJECT 0x1 /* relocatable object file */
#define MH_EXECUTE 0x2 /* demand paged executable file */
#define MH_FVMLIB 0x3 /* fixed VM shared library file */
#define MH_CORE 0x4 /* core file */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* dynamically bound shared library */
#define MH_DYLINKER 0x7 /* dynamic link editor */
#define MH_BUNDLE 0x8 /* dynamically bound bundle file */
#define MH_DYLIB_STUB 0x9 /* shared library stub for static */
/* linking only, no section contents */
#define MH_DSYM 0xa /* companion file with only debug */
command的結構體頭都是load_command開始,根據類型的不同再強制解析成不同的結構體,可能的類型的取值如下。
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
/*
* After MacOS X 10.1 when a new load command is added that is required to be
* understood by the dynamic linker for the image to execute properly the
* LC_REQ_DYLD bit will be or'ed into the load command constant. If the dynamic
* linker sees such a load command it it does not understand will issue a
* "unknown load command required for execution" error and refuse to use the
* image. Other load commands without this bit that are not understood will
* simply be ignored.
*/
#define LC_REQ_DYLD 0x80000000
//load_command中cmd可選值定義 省去部分。。。。
/* Constants for the cmd field of all load commands, the type */
#define LC_SEGMENT 0x1 /* segment of this file to be mapped */
#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */
#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */
#define LC_THREAD 0x4 /* thread */
#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be
mapped */
python的第一項是19h 也就是LC_SEGMENT_64。 那么強制轉換為segment_command_64結構體進行解析
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
* segname command名字 * vmaddr 虛擬地址 * vmsize 虛擬地址大小 * fileoff 這個段所在的文件偏移 * filesize 這個段大小 * maxprot 這個段鎖需要的內存屬性 * initprot 這個段初始化的內存屬性 * nsects 這個段上有多少個section
依次提取出來的python 的 segment名字如下
__PAGEZERO
__TEXT
__DATA
__LINKEDIT
以主要的__TEXT, __DATA為例,展開section分析
__TEXT段所包含的sections個數為7, 對應的結構體信息為section_64
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
};
套上結構體,得到對應的名字依次為
__text
__stubs
__stub_helper
__cstring
__const
__unwind_info
__eh_frame
__DATA段所包含的sections個數為6,
套上結構體,得到對應的名字依次為
__got
__nl_symbol_ptr
__la_symbol_ptr
__cfstring
__data
__bss
python 源碼是純C寫的,沒有使用oc語言,所有解析出來的信息里面沒有保存object-c class的信息,下篇找個使用oc的語言的程序進行解析,試着找出object-c class的內存布局。