本文所讀的源碼,能夠從這裏找到,這是 Mach-O 系列的第一篇html
咱們的程序想要跑起來,確定它的可執行文件格式要被操做系統所理解,好比 ELF
是 Linux
下可執行文件的格式,PE32/PE32+
是windows
的可執行文件的格式,那麼對於OS X
和iOS
來講 Mach-O
是其可執行文件的格式。windows
咱們平時瞭解到的可執行文件、庫文件、Dsym文件、動態庫、動態鏈接器都是這種格式的。Mach-O 的組成結構以下圖所示包括了Header
、Load commands
、Data
(包含Segement
的具體數據)bash
Mach-O
的頭部,使得能夠快速確認一些信息,好比當前文件用於32位仍是64位,對應的處理器是什麼、文件類型是什麼數據結構
能夠拿下面的代碼作一個例子架構
#include <stdio.h>
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
return 0;
}複製代碼
在終端執行如下命令,能夠生成一個可執行文件a.out
app
192:Test Joy$ gcc -g main.c複製代碼
咱們能夠使用MachOView
(是一個查看MachO
格式文件信息的開源工具)來查看.out
文件的具體格式如何ide
看到這裏確定有點懵比,不知道這是什麼東西,下面看一下 header
的數據結構工具
32位結構ui
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位架構this
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位架構的頭文件,沒有太大的區別,只是64位多了一個保留字段罷了
magic:
魔數,用於快速確認該文件用於64位仍是32位cputype:
CPU類型,好比 armcpusubtype:
對應的具體類型,好比arm6四、armv7filetype:
文件類型,好比可執行文件、庫文件、Dsym文件,demo中是2 MH_EXECUTE
,表明可執行文件* 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 :
加載命令條數sizeofcmds
:全部加載命令的大小reserved:
保留字段flags:
標誌位,剛纔demo
中顯示的都在這裏了,其他的有興趣能夠閱讀 mach o
源碼#define MH_NOUNDEFS 0x1 // 目前沒有未定義的符號,不存在連接依賴
#define MH_DYLDLINK 0x4 // 該文件是dyld的輸入文件,沒法被再次靜態連接
#define MH_PIE 0x200000 // 加載程序在隨機的地址空間,只在 MH_EXECUTE中使用
#define MH_TWOLEVEL 0x80 // 兩級名稱空間複製代碼
進程每一次啓動,地址空間都會簡單地隨機化。
對於大多數應用程序來講,地址空間隨機化是一個和他們徹底不相關的實現細節,可是對於黑客來講,它具備重大的意義。
若是採用傳統的方式,程序的每一次啓動的虛擬內存鏡像都是一致的,黑客很容易採起重寫內存的方式來破解程序。採用ASLR
能夠有效的避免黑客攻擊。
動態連接器,他是蘋果開源的一個項目,能夠在這裏下載,當內核執行LC_DYLINK
(後面會說到)時,鏈接器會啓動,查找進程所依賴的動態庫,並加載到內存中。
這是dyld
的一個獨有特性,說是符號空間中還包括所在庫的信息,這樣子就可讓兩個不一樣的庫導出相同的符號,與其對應的是平坦名稱空間
Load commands
緊跟在頭部以後,這些加載指令清晰地告訴加載器如何處理二進制數據,有些命令是由內核處理的,有些是由動態連接器處理的。在源碼中有明顯的註釋來講明這些是動態鏈接器處理的。
這裏列舉幾個看上去比較熟悉的....
// 將文件的32位或64位的段映射到進程地址空間
#define LC_SEGMENT 0x1
#define LC_SEGMENT_64 0x19
// 惟一的 UUID,標示二進制文件
#define LC_UUID 0x1b /* the uuid */
// 剛纔提到的,啓動動態加載鏈接器
#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
的結構以下
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};複製代碼
經過 MachOView
來繼續查看剛纔Demo中的Load commands
的一些細節,LC_SEGMENT_64
和LC_SEGMENT
是加載的主要命令,它負責指導內核來設置進程的內存空間
cmd:
就是Load commands
的類型,這裏LC_SEGMENT_64
表明將文件中64位的段映射到進程的地址空間。LC_SEGMENT_64
和LC_SEGMENT
的結構差異不大,下面只列舉一個,有興趣能夠閱讀源碼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 :
段的虛擬內存地址VM Size :
段的虛擬內存大小file offset:
段在文件中偏移量file size:
段在文件中的大小將該段對應的文件內容加載到內存中:從offset
處加載 file size
大小到虛擬內存 vmaddr
處,因爲這裏在內存地址空間中是_PAGEZERO
段(這個段不具備訪問權限,用來處理空指針)因此都是零
還有圖片中的其餘段,好比_TEXT
對應的就是代碼段,_DATA
對應的是可讀/可寫的數據,_LINKEDIT
是支持dyld
的,裏面包含一些符號表等數據
nsects:
標示了Segment
中有多少secetion
segment name:
段的名稱,當前是__PAGEZERO
這裏有個命名的問題,以下圖所示,__TEXT
表明的是Segment
,小寫的__text
表明 Section
Section
的數據結構
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
在內存的起始位置size:
該section
的大小offset:
該section
的文件偏移align :
字節大小對齊 reloff :
重定位入口的文件偏移nreloc:
須要重定位的入口數量flags:
包含section
的type
和attributes
發現不少底層知識都是以 Mach-O
爲基礎的,因此最近打算花時間結合Mach-O
作一些相對深刻的總結,好比符號解析、bitcode
、逆向工程等,加油吧