本文所讀的源碼,可以從這里找到,這是 Mach-O 系列的第一篇
我們的程序想要跑起來,肯定它的可執(zhí)行文件格式要被操作系統(tǒng)所理解,比如 ELF
是 Linux
下可執(zhí)行文件的格式,PE32/PE32+
是windows
的可執(zhí)行文件的格式,那么對于OS X
和iOS
來說 Mach-O
是其可執(zhí)行文件的格式。
我們平時(shí)了解到的可執(zhí)行文件、庫文件、Dsym文件、動(dòng)態(tài)庫、動(dòng)態(tài)連接器都是這種格式的。Mach-O 的組成結(jié)構(gòu)如下圖所示包括了Header
、Load commands
、Data
(包含Segement
的具體數(shù)據(jù))
Header 的結(jié)構(gòu)
Mach-O
的頭部,使得可以快速確認(rèn)一些信息,比如當(dāng)前文件用于32位還是64位,對應(yīng)的處理器是什么、文件類型是什么
可以拿下面的代碼做一個(gè)例子
#include <stdio.h>
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
return 0;
}
在終端執(zhí)行以下命令,可以生成一個(gè)可執(zhí)行文件a.out
192:Test Joy$ gcc -g main.c
我們可以使用MachOView
(是一個(gè)查看MachO
格式文件信息的開源工具)來查看
.out
文件的具體格式如何
看到這里肯定有點(diǎn)懵比,不知道這是什么東西,下面看一下 header
的數(shù)據(jù)結(jié)構(gòu)
32位結(jié)構(gòu)
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 */
};
64位架構(gòu)
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 */
};
32位和64位架構(gòu)的頭文件,沒有太大的區(qū)別,只是64位多了一個(gè)保留字段罷了
-
magic:
魔數(shù),用于快速確認(rèn)該文件用于64位還是32位 -
cputype:
CPU類型,比如 arm -
cpusubtype:
對應(yīng)的具體類型,比如arm64、armv7 -
filetype:
文件類型,比如可執(zhí)行文件、庫文件、Dsym文件,demo中是2MH_EXECUTE
,代表可執(zhí)行文件
* Constants for the filetype field of the mach_header
*/
#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 */
#define MH_DSYM 0xa /* companion file with only debug */
#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */
-
ncmds :
加載命令條數(shù) -
sizeofcmds
:所有加載命令的大小 -
reserved:
保留字段 -
flags:
標(biāo)志位,剛才demo
中顯示的都在這里了,其余的有興趣可以閱讀mach o
源碼
#define MH_NOUNDEFS 0x1 // 目前沒有未定義的符號,不存在鏈接依賴
#define MH_DYLDLINK 0x4 // 該文件是dyld的輸入文件,無法被再次靜態(tài)鏈接
#define MH_PIE 0x200000 // 加載程序在隨機(jī)的地址空間,只在 MH_EXECUTE中使用
#define MH_TWOLEVEL 0x80 // 兩級名稱空間
隨機(jī)地址空間
進(jìn)程每一次啟動(dòng),地址空間都會(huì)簡單地隨機(jī)化。
對于大多數(shù)應(yīng)用程序來說,地址空間隨機(jī)化是一個(gè)和他們完全不相關(guān)的實(shí)現(xiàn)細(xì)節(jié),但是對于黑客來說,它具有重大的意義。
如果采用傳統(tǒng)的方式,程序的每一次啟動(dòng)的虛擬內(nèi)存鏡像都是一致的,黑客很容易采取重寫內(nèi)存的方式來破解程序。采用ASLR
可以有效的避免黑客攻擊。
dyld
動(dòng)態(tài)鏈接器,他是蘋果開源的一個(gè)項(xiàng)目,可以在這里下載,當(dāng)內(nèi)核執(zhí)行LC_DYLINK
(后面會(huì)說到)時(shí),連接器會(huì)啟動(dòng),查找進(jìn)程所依賴的動(dòng)態(tài)庫,并加載到內(nèi)存中。
二級名稱空間
這是dyld
的一個(gè)獨(dú)有特性,說是符號空間中還包括所在庫的信息,這樣子就可以讓兩個(gè)不同的庫導(dǎo)出相同的符號,與其對應(yīng)的是平坦名稱空間
Load commands 結(jié)構(gòu)
Load commands
緊跟在頭部之后,這些加載指令清晰地告訴加載器如何處理二進(jìn)制數(shù)據(jù),有些命令是由內(nèi)核處理的,有些是由動(dòng)態(tài)鏈接器處理的。在源碼中有明顯的注釋來說明這些是動(dòng)態(tài)連接器處理的。
這里列舉幾個(gè)看上去比較熟悉的....
// 將文件的32位或64位的段映射到進(jìn)程地址空間
#define LC_SEGMENT 0x1
#define LC_SEGMENT_64 0x19
// 唯一的 UUID,標(biāo)示二進(jìn)制文件
#define LC_UUID 0x1b /* the uuid */
// 剛才提到的,啟動(dòng)動(dòng)態(tài)加載連接器
#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */
// 代碼簽名和加密
#define LC_CODE_SIGNATURE 0x1d /* local of code signature */
#define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */
load command
的結(jié)構(gòu)如下
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
通過 MachOView
來繼續(xù)查看剛才Demo中的Load commands
的一些細(xì)節(jié),LC_SEGMENT_64
和LC_SEGMENT
是加載的主要命令,它負(fù)責(zé)指導(dǎo)內(nèi)核來設(shè)置進(jìn)程的內(nèi)存空間
-
cmd:
就是Load commands
的類型,這里LC_SEGMENT_64
代表將文件中64位的段映射到進(jìn)程的地址空間。LC_SEGMENT_64
和LC_SEGMENT
的結(jié)構(gòu)差別不大,下面只列舉一個(gè),有興趣可以閱讀源碼
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 */
};
-
cmdsize:
代表load command
的大小 -
VM Address :
段的虛擬內(nèi)存地址 -
VM Size :
段的虛擬內(nèi)存大小 -
file offset:
段在文件中偏移量 -
file size:
段在文件中的大小
將該段對應(yīng)的文件內(nèi)容加載到內(nèi)存中:從offset
處加載 file size
大小到虛擬內(nèi)存 vmaddr
處,由于這里在內(nèi)存地址空間中是_PAGEZERO
段(這個(gè)段不具有訪問權(quán)限,用來處理空指針)所以都是零
還有圖片中的其他段,比如_TEXT
對應(yīng)的就是代碼段,_DATA
對應(yīng)的是可讀/可寫的數(shù)據(jù),_LINKEDIT
是支持dyld
的,里面包含一些符號表等數(shù)據(jù)
-
nsects:
標(biāo)示了Segment
中有多少secetion
-
segment name:
段的名稱,當(dāng)前是__PAGEZERO
Segment & Section
這里有個(gè)命名的問題,如下圖所示,__TEXT
代表的是Segment
,小寫的__text
代表 Section
Section
的數(shù)據(jù)結(jié)構(gòu)
struct section { /* for 32-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint32_t addr; /* memory address of this section */
uint32_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) */
};
-
sectname:
比如_text
、stubs
-
segname :
該section
所屬的segment
,比如__TEXT
-
addr :
該section
在內(nèi)存的起始位置 -
size:
該section
的大小 -
offset:
該section
的文件偏移 -
align :
字節(jié)大小對齊 -
reloff :
重定位入口的文件偏移 -
nreloc:
需要重定位的入口數(shù)量 -
flags:
包含section
的type
和attributes
發(fā)現(xiàn)很多底層知識都是以 Mach-O
為基礎(chǔ)的,所以最近打算花時(shí)間結(jié)合Mach-O
做一些相對深入的總結(jié),比如符號解析、bitcode
、逆向工程等,加油吧
參考鏈接
- 深入理解 MAC OS X & iOS 操作系統(tǒng)
- mach-o/loader.h
- Mach-O文件格式和程序從加載到執(zhí)行過程
- OS X ABI Mach-O File Format Reference