一、前言
本文簡(jiǎn)要解析Mach-O文件格式、結(jié)構(gòu),主要是自己認(rèn)識(shí)Mach-O文件,學(xué)習(xí)的一個(gè)過程,一些地方可能介紹得不到位,要了解更多有關(guān)信息,可以考慮閱讀蘋果提供的官方文檔介紹。
二、什么是Mach-O文件
維基百科簡(jiǎn)要說明:
Mach-O為Mach Object文件格式的縮寫,它是一種用于可執(zhí)行文件,目標(biāo)代碼,動(dòng)態(tài)庫(kù),內(nèi)核轉(zhuǎn)儲(chǔ)的文件格式。作為a.out格式的替代,Mach-O提供了更強(qiáng)的擴(kuò)展性,并提升了符號(hào)表中信息的訪問速度。
Mach-O曾經(jīng)為大部分基于Mach核心的操作系統(tǒng)所使用。NeXTSTEP,Darwin和Mac OS X等系統(tǒng)使用這種格式作為其原生可執(zhí)行文件,庫(kù)和目標(biāo)代碼的格式。而同樣使用GNU Mach作為其微內(nèi)核的GNU Hurd系統(tǒng)則使用ELF而非Mach-O作為其標(biāo)準(zhǔn)的二進(jìn)制文件格式。
三、Mach-O格式
Mach-O是一個(gè)以數(shù)據(jù)塊分組的二進(jìn)制字節(jié)流,這些數(shù)據(jù)塊包含元信息,比如字節(jié)順序、CPU類型、數(shù)據(jù)塊大小等等。
典型的Mach-O文件包含三個(gè)區(qū)域:
1.Header:保存Mach-O的一些基本信息,包括平臺(tái)、文件類型、指令數(shù)、指令總大小,dyld標(biāo)記Flags等等。
2.Load Commands:緊跟Header,加載Mach-O文件時(shí)會(huì)使用這部分?jǐn)?shù)據(jù)確定內(nèi)存分布,對(duì)系統(tǒng)內(nèi)核加載器和動(dòng)態(tài)連接器起指導(dǎo)作用。
3.Data:每個(gè)segment的具體數(shù)據(jù)保存在這里,包含具體的代碼、數(shù)據(jù)等等。
用一張圖表示Mach-O
(一)、Header
數(shù)據(jù)結(jié)構(gòu)
/*
* The 32-bit mach header appears at the very beginning of the object file for
* 32-bit architectures.
*/
struct mach_header {
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 */
};
/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC 0xfeedface /* the mach magic number */
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
/*
* The 64-bit mach header appears at the very beginning of object files for
* 64-bit architectures.
*/
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 */
};
/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
根據(jù)定義與注釋,得到以下解釋
名稱 | 含義 |
---|---|
magic | Mach-O魔數(shù),FAT:0xcafebabeARMv7:0xfeedface,ARM64:0xfeedfacf |
cputype、cpusubtype | CPU架構(gòu)及子版本 |
filetype | MH_EXECUTABLE(可執(zhí)行二進(jìn)制文件)、MH_OBJECT(目標(biāo)文件)、MH_DYLIB(動(dòng)態(tài)庫(kù)),有11種宏定義類型,具體可查看源碼 |
ncmds | 加載命令的數(shù)量 |
sizeofcmds | 所有加載命令的大小 |
flags | dyld加載需要的一些標(biāo)記,有28種宏定義,具體看源碼,其中MH_PIE表示啟用ASLR地址空間布局隨機(jī)化 |
reserved | 64位保留字段 |
使用MachOView查看某可執(zhí)行文件:
(二)、Load Commands
數(shù)據(jù)結(jié)構(gòu):
/*
* The load commands directly follow the mach_header. The total size of all
* of the commands is given by the sizeofcmds field in the mach_header. All
* load commands must have as their first two fields cmd and cmdsize. The cmd
* field is filled in with a constant for that command type. Each command type
* has a structure specifically for it. The cmdsize field is the size in bytes
* of the particular load command structure plus anything that follows it that
* is a part of the load command (i.e. section structures, strings, etc.). To
* advance to the next load command the cmdsize can be added to the offset or
* pointer of the current load command. The cmdsize for 32-bit architectures
* MUST be a multiple of 4 bytes and for 64-bit architectures MUST be a multiple
* of 8 bytes (these are forever the maximum alignment of any load commands).
* The padded bytes must be zero. All tables in the object file must also
* follow these rules so the file can be memory mapped. Otherwise the pointers
* to these tables will not work well or at all on some machines. With all
* padding zeroed like objects will compare byte for byte.
*/
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
注釋的大概意思:
load_commands緊跟mach_header,load_commands展開后的數(shù)目與總大小已經(jīng)在mach_header有記錄,所有加載指令都是以cmd、cmdsize起頭。cmd字段用該命令類型的常量表示,有專門的結(jié)構(gòu);cmdsize字段以字節(jié)為單位,主要記錄偏移量讓load command指針進(jìn)入下一條加載指令,32位架構(gòu)的cmdsize是以4字節(jié)的倍數(shù),64位結(jié)構(gòu)的cmdsize是以8字節(jié)的倍數(shù)(加載指令永遠(yuǎn)是這樣對(duì)齊),不夠用0填充字節(jié)。文件中的所有表都遵循這樣的規(guī)則,這樣就可以被映射到內(nèi)存,否則的話指針不能很好地指向。
使用MachOView查看Load Commands區(qū):
Load Commands下常見的加載指令:
指令 | 含義 |
---|---|
LC_SEGMENT_64 | 定義一段(Segment),加載后被映射到進(jìn)程的內(nèi)存空間中,包括里面的節(jié)(Section) |
LC_DYLD_INFO_ONLY | 記錄有關(guān)鏈接的信息,包括在__LINKEDIT中動(dòng)態(tài)鏈接的相關(guān)信息的具體偏移與大小(重定位,綁定,弱綁定,懶加載綁定,導(dǎo)出信息等),ONLY表示該指令是程序運(yùn)行所必需的。 |
LC_SYMTAB | 定義符號(hào)表和字符串表,鏈接文件時(shí)被dyld使用,也用于調(diào)試器映射符號(hào)到源文件。符號(hào)表定義的本地符號(hào)僅用于調(diào)試,而已定義和未定義的external符號(hào)被鏈接器使用 |
LC_DYSYMTAB | 將符號(hào)表中給出符號(hào)的額外信息提供給dyld |
LC_LOAD_DYLINKER | dyld的默認(rèn)路徑 |
LC_UUID | Mach-O唯一ID |
LC_VERSION_MIN_IPHONES | 系統(tǒng)要求的最低版本 |
LC_SOURCE_VERSION | 構(gòu)建二進(jìn)制文件的源代碼版本號(hào) |
LC_MAIN | 應(yīng)用程序入口,dyld的_main函數(shù)獲取該地址,然后跳轉(zhuǎn) |
LC_ENCRYPTION_INFO_64 | 文件加密標(biāo)志,加密內(nèi)容偏移和大小 |
LC_LOAD_DYLIB | 依賴的動(dòng)態(tài)庫(kù),含動(dòng)態(tài)庫(kù)名,版本號(hào)等信息 |
LC_RPATH | @rpath搜索路徑 |
LC_DATA_IN_CODE | 定義在代碼段內(nèi)的非指令的表 |
LC_CODE_SIGNATURE | 代碼簽名信息 |
LC_SEGMENT_64段數(shù)據(jù)結(jié)構(gòu)(說明附在注釋部分):
/*
* The 64-bit segment load command indicates that a part of this file is to be
* mapped into a 64-bit task's address space. If the 64-bit segment has
* sections then section_64 structures directly follow the 64-bit segment
* command and their size is reflected in cmdsize.
*/
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* Load Command類型 */
uint32_t cmdsize; /*包含的所有section結(jié)構(gòu)體的大小 */
char segname[16]; /* 段名 */
uint64_t vmaddr; /* 映射到虛擬地址的偏移 */
uint64_t vmsize; /* 映射到虛擬地址的大小 */
uint64_t fileoff; /* 相對(duì)于當(dāng)前架構(gòu)文件的偏移 */
uint64_t filesize; /* 文件大小 */
vm_prot_t maxprot; /* 段頁(yè)面的最高內(nèi)存保護(hù) */
vm_prot_t initprot; /* 初始內(nèi)存保護(hù) */
uint32_t nsects; /* 包含的section數(shù) */
uint32_t flags; /* 段頁(yè)面標(biāo)志 */
};
該數(shù)據(jù)結(jié)構(gòu)的段主要有以下4種:
段 | 含義 |
---|---|
_PAGEZERO | 空指針陷阱段,映射到虛擬內(nèi)存空間第一頁(yè),捕捉對(duì)NULL指針的引用 |
_TEXT | 代碼段、只讀數(shù)據(jù)段 |
_DATA | 讀取和寫入數(shù)據(jù)段 |
_LINKEDIT | dyld需要使用的信息,包括重定位、綁定、懶加載信息等 |
(三)、Data
Load Commands區(qū)域下來接著就是DATA區(qū)域,展開Load Commands下的LC_SEGMENT_64可以看到多個(gè)Section64,各個(gè)Section的具體信息可以在Load Commands緊接著的部分查看,它們是一一對(duì)應(yīng)的:
section的數(shù)據(jù)結(jié)構(gòu)如下:
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* 節(jié)名 */
char segname[16]; /* 所屬段名 */
uint64_t addr; /* 映射到虛擬地址的偏移 */
uint64_t size; /* 節(jié)的大小 */
uint32_t offset; /* 節(jié)在當(dāng)前架構(gòu)文件中的偏移 */
uint32_t align; /* 節(jié)的字節(jié)對(duì)齊大小n,2^n */
uint32_t reloff; /* 重定位入口的文件偏移 */
uint32_t nreloc; /* 重定位入口個(gè)數(shù) */
uint32_t flags; /* 節(jié)的類型和屬性*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* 保留位,以上兩同理 */
};
section節(jié)已經(jīng)是最小的分類,大部分內(nèi)容集中在__TEXT,__DATA這兩段中,部分內(nèi)容如下:
__TEXT節(jié) | 含義 |
---|---|
__text | 程序可執(zhí)行代碼區(qū)域 |
__stubs | 間接符號(hào)存根,用于跳轉(zhuǎn)到懶加載指針表 |
__stubs_helper | 懶加載符號(hào)加載輔助函數(shù) |
__cstring | 只讀的C字符串,包含OC的部分字符串和屬性名 |
...... | ...... |
__DATA | 含義 |
---|---|
__nl_symbol_ptr | 非懶加載指針表,dyld加載時(shí)立即綁定值 |
__la_symbol_ptr | 懶加載指針表,第1次調(diào)用才綁定值 |
__got | 非懶加載全局指針表 |
__mod_init_func | constructor函數(shù) |
__cfstring | OC字符串 |
...... | ...... |
四、小結(jié)
了解Mach-O可以幫助我們理解dyld的加載Mach-O的過程以及與Mach-O相關(guān)的讀取或操作,如fishhook、文件內(nèi)偏移地址等。
五、參考
Dynamic Linking of Imported Functions in Mach-O
mach-o格式分析
《iOS應(yīng)用逆向與安全》