寫在之前
之前工作中對Mach-O文件有一定的接觸, 原本早就想寫一篇文章分享一下舅桩,但是奈何只是不夠深入, 總怕分析的有問題誤導(dǎo)讀者。
最近又在閱讀深入解析Mac OS X 與iOS 操作系統(tǒng)担猛,借著這個機(jī)會記錄下自己的學(xué)習(xí)成果, 并結(jié)合之前的經(jīng)驗, 加上一些實例讓讀者更好的理解。
畢竟對于程序員來說 大部分人對抽象的概念的感覺就是 聽說過很多原理, 依然不知道大佬說的是什么
Mac OS 與 iOS 支持的文件類型
在Unix-Like
系列的操作系統(tǒng), 可以通過命令 chmod +x
給予文件可執(zhí)行權(quán)限, 但是這不代表這個文件具有可執(zhí)行權(quán)限, 實際上 Apple家的操作系統(tǒng)只支持三種文件格式丢氢。
- 以
#!
開頭的腳本文件 - 通用二進(jìn)制文件
- Mach-O格式文件
但是實際上 以#!
開頭的腳本文件其實是shell解釋器找到后面指定的腳本解釋器來執(zhí)行的, 而通用二進(jìn)制文件其實是多個架構(gòu)的Mach-O文件的打包體傅联。
通用二進(jìn)制文件其實有個更加形象化的名字fat binary
那么操作系統(tǒng)如何知道你打開的文件是何種類型的?
其實是通過這些文件頭的固定數(shù)字來區(qū)分的, 對于這些固定數(shù)字通常叫做 Magic Number
(魔數(shù)).
對于fat binary
的魔數(shù)是 0xcafebabe
(小端)0xbebafeca
大端
對于Mach-O
的魔數(shù)是 0xfeedface
(32位) 0xfeedfacf
(64位)
多說無益~~上代碼
我們以/usr/bin/perl為例 (這是一個fat binary)
$ file /usr/bin/perl
/usr/bin/perl: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [i386:Mach-O executable i386]
/usr/bin/perl (for architecture x86_64): Mach-O 64-bit executable x86_64
/usr/bin/perl (for architecture i386): Mach-O executable i386
$ otool -vh /usr/bin/perl
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL LIB64 EXECUTE 17 1800 NOUNDEFS DYLDLINK TWOLEVEL PIE
不過可能你覺得拿著系統(tǒng)的命令來看感覺不那么真實, 那么cat命令我們都用過吧疚察,來看下
在/usr/include/mach-o/fat.h
路徑下有關(guān)于fat binary
文件的頭文件定義
struct fat_header {
uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */
uint32_t nfat_arch; /* 包含的架構(gòu)數(shù) */
};
struct fat_arch {
cpu_type_t cputype; /* cpu類型 */
cpu_subtype_t cpusubtype; /* 機(jī)器標(biāo)示符 */
uint32_t offset; /* 當(dāng)前架構(gòu)在這個文件中的便宜量 */
uint32_t size; /* 當(dāng)前架構(gòu)在文件中的長度*/
uint32_t align; /* 對齊方式 */
};
不知道大家還記得不記得之前使用windows的時候有System32和64之分, 那是因為在windows操作系統(tǒng)中不同架構(gòu)的可執(zhí)行文件是分開存放的蒸走。
蘋果在某次WWDC大會聲稱自己優(yōu)雅的將多個架構(gòu)合并在了一個文件中。引來果粉一陣鼓掌貌嫡。
其實fat binary
文件的真正布局非常簡單比驻。
以/usr/bin/perl為例
Apple的實現(xiàn)只是將不同架構(gòu)的文件并排放在一起,然后在文件頭部添加不同架構(gòu)的描述信息, 然后再加載當(dāng)前架構(gòu)的Mach-O文件 丟棄掉其他架構(gòu)的部分即可岛抄。實在是簡單粗暴~~
Mach-O文件結(jié)構(gòu)
Unix標(biāo)準(zhǔn)了一個可移植的二進(jìn)制格式ELF
但是蘋果并沒有實現(xiàn)它而是維護(hù)了一套NeXTSTEP的遺物 Mach-Object
簡稱Mach-O
别惦。
但是這并不是說蘋果不遵守POSXI
規(guī)范,這個規(guī)范通常說的是源碼級別的跨平臺性夫椭,對于二進(jìn)制則不強(qiáng)制要求掸掸。
下面是一個官方提供的圖片。
Mach-0 Header
先來介紹Mach-O的Header(只介紹64位)信息蹭秋。
相關(guān)頭文件定義在/usr/include/mach-o/loader.h
里面扰付。如果需要使用只需要加載<mach-O/loader.h>
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; /* 文件類型 */
uint32_t ncmds; /* load commadns的個數(shù) */
uint32_t sizeofcmds; /* load commands的總大小 */
uint32_t flags; /* 動態(tài)連接器標(biāo)志*/
uint32_t reserved; /* 保留*/
};
/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* 小端 */
#define MH_CIGAM_64 0xcffaedfe /* 大端 */
注: Mach-O文件不僅僅是可執(zhí)行文件, 也包括目標(biāo)文件(.o) 動態(tài)庫, Bundle插件等。
標(biāo)志位
flag 標(biāo)記了一些dyld加載 執(zhí)行 中可配置的信息感凤。
關(guān)于Mach-O文件的魔數(shù)信息悯周,有興趣的讀者可以按照之前的方式親自動手嘗試一下
Mach-O Load commands
Mach-O文件中最重要的元信息就是 load Commands,加載命令緊跟在文件頭信息之后陪竿。
// [_mach_header_|___load_commands___||___load_commands___||____other____|]
struct load_command {
uint32_t cmd; /* load command的類型 */
uint32_t cmdsize; /* command 的長度 */
};
LC_SEGMENT
對于加載命令是LC_SEGMENT的命令指定了內(nèi)核如何設(shè)置新運(yùn)行的進(jìn)程的內(nèi)存空間
對應(yīng)的頭文件也在<mach-o/loader.h>
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; /* 當(dāng)前segment加載的虛擬內(nèi)存起始地址 */
uint64_t vmsize; /* 當(dāng)前segment加載的虛擬內(nèi)存地址占用的長度 */
uint64_t fileoff; /* segment在文件中的偏移 */
uint64_t filesize; /* segment在文件中的長度 */
vm_prot_t maxprot; /* 最大的保護(hù)級別 */
vm_prot_t initprot; /* 初始化的保護(hù)級別 */
uint32_t nsects; /* 包含sections的個數(shù) */
uint32_t flags; /* 標(biāo)志位 */
};
由于有了LC_SEGMENT命令禽翼。對于每一個Segment,將文件中偏移量為fileOff長度為filesize的文件內(nèi)容加載到虛擬地址為vmaddr的位置族跛,長度為vmsize, 頁面的權(quán)限通過initprot來初始化(比如設(shè)定讀/寫/執(zhí)行, 段的保護(hù)級別可以動態(tài)設(shè)置最大不超過maxprot
常見的Segment有以下幾個
- __TEXT 代碼段
- __PAGEZERO 空指針陷阱
- __DATA 數(shù)據(jù)段
- __LINKEDIT 包含需要被動態(tài)鏈接器使用的信息闰挡,包括符號表、字符串表礁哄、重定位項表等长酗。
- __OBJC(現(xiàn)已經(jīng)被合并到__DATA部分)包含會被Objective Runtime使用到的一些數(shù)據(jù)。
當(dāng)然讀者如果有興趣查看其他所有的loadcommands可以去loader.h頭文件定義去查看桐绒,也可以實際操練一下
如 使用otool 查看某些mach-O文件的所有l(wèi)oad_commands
otool -l /bin/ls
section
類型聲明如下
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, __DATA下面, 又有細(xì)分的各種Section夺脾,常見的如
名稱 | 作用 |
---|---|
TEXT.text | 只有可執(zhí)行的機(jī)器碼 |
TEXT.cstring | 硬編碼去重后的C字符串 |
TEXT.const | 初始化過的常量 |
DATA.data | 初始化過的可變的數(shù)據(jù) |
DATA.bss | 沒有初始化的靜態(tài)變量 |
DATA.common | 沒有初始化過的符號聲明 |
DATA.objc_clasname | oc類名稱 |
DATA.objc_classlist | 類列表 |
DATA.objc_protocollist | 協(xié)議列表 |
···
其他的就不一一列舉之拨,建議讀者親自動手試一試, 會發(fā)現(xiàn)很多有價值的東西
了解這些有什么用?
相信看了這些內(nèi)容, 你已經(jīng)大致知道Mach-O文件的物理布局, 那么我們知道了這個文件格式能用來做什么呢咧叭?
理解了這個可以用來做下面一些東西:
- 依賴解耦
- 元信息獲取
- 調(diào)試代碼
- CI工具插件檢測
- 逆向
相關(guān)一些示例放在下篇文章講解蚀乔。