Mach-O 文件探索

之前在項(xiàng)目中使用 fishhook 來替換系統(tǒng)的 C 函數(shù)厂榛,其中涉及到很多和 iOS 系統(tǒng)相關(guān)的編譯、鏈接等方面的知識,由于內(nèi)容比較多,所以打算分幾篇文章來進(jìn)行講解瞳收,本文主要是分析 Mach—O 文件。

在 macOS 以及 iOS 系統(tǒng)中厢汹,可執(zhí)行文件的格式為 Mach-O螟深,理解 Mach-O 文件格式對于我們探究操作系統(tǒng)的運(yùn)作機(jī)制起著關(guān)鍵的作用。Mach-O 文件格式如下圖所示烫葬,它由 Header界弧、Load commands 以及 Data 三部分組成:

Header:記錄 Mach-O 文件的基本信息,包括文件類型搭综、支持的 CPU 類型以及加載命令的個(gè)數(shù)垢箕、大小等。

Load commands: 位于 Header 之后兑巾,向操作系統(tǒng)描述如何解析文件条获。

Data: 用于保存程序的 TEXT、DATA蒋歌、LINKEDIT 等 segment 數(shù)據(jù)帅掘。

以下是人民群眾喜聞樂見的一段代碼:

#include <stdio.h>

int main(int argc, const char * argv[]) {
    printf("Hello, World!\n");
    return 0;
}

使用 clang main.c -o MachOExplore 命令編譯上述代碼,我們得到名為 MachOExplore 的目標(biāo)文件堂油,也就是本文將要探究的 Mach-O 文件修档。otool 是用來查看 Mach-O 文件的常用工具,但是本文會使用另一種工具 MachOVie?w 來完成任務(wù)府框。

Header

MachOView 查看 Header 的內(nèi)容如下:

Header 中記錄了 Mach-O 文件的屬性信息吱窝,相關(guān)數(shù)據(jù)結(jié)構(gòu)定義在 loader.h ????中,分為32位和64位兩種:

/*
* 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) */

下表描述了各個(gè)字段的含義,mach_header_64 除了比 mach_header 多出一個(gè) reserved 字段外院峡,其他方面并無區(qū)別兴使。

字段 說明
magic 表明文件適用于64位操作系統(tǒng)還是32位操作系統(tǒng),因?yàn)榇笮《说拇嬖谒河瑁杂? MH_MAGIC_64 和 MH_CIGAM_64 兩種形式 。
cputype 描述文件所支持的 CPU 架構(gòu)蜈首,包括 ARM64实抡、X86_64、i386等欢策。
cpusubtype 描述文件對應(yīng)的具體 CPU 架構(gòu)吆寨,例如 ARM_V7S、ARM64_V8踩寇、X86_ARCH1 等啄清。
filetype 描述文件的類型,常見的類型有可執(zhí)行文件俺孙、可重定位文件辣卒、共享庫文件等。
ncmds 記錄加載命令的個(gè)數(shù)睛榄。
sizeofcmds 記錄所有加載命令的大小荣茫。
flags 描述文件在編譯、鏈接等過程中的信息场靴,示例中的 MH_NOUNDEFS 表示文件中不存在未定義的符號啡莉,MH_DYLDLINK 表示文件要交由 DYLD 進(jìn)一步處理,MH_TWOLEVEL 表示文件使用兩級命名空間旨剥,MH_PIE 表示啟用地址空間布局隨機(jī)化咧欣。

Load commands

Load commands 緊隨在 Header 后,它包含了一系列的加載命令轨帜,目的是向操作系統(tǒng)描述如何處理 Mach-O 文件魄咕。示例中包含的加載命令如下:

LC_SEGMENT_64 命令表示將相應(yīng)的 segment 映射到虛擬地址空間中,以 LC_SEGMENT_64(__PAGEZERO) 為例:

  • Command: 表示加載命令;

  • Command Size: 表示加載命令的大小;

  • Segment Name: 被加載的段的名字;

  • VM Address: 段所在的虛擬空間地址;

  • VM Size: 段所占用的虛擬空間的大小;

  • File Offset: 段在文件中的偏移量;

  • File Size: 段在文件中的大小;

  • Maximum VM protection: 表示與段相對應(yīng)的最大操作權(quán)限;

  • Initial VM protection: 表示段的初始操作權(quán)限;

  • Number of Sections: 段包含多少個(gè) Section;

  • Flags: 描述與段相關(guān)的加載信息蚌父,具體解釋請參考 loader.h 文件;

所以上述命令是將 __PAGEZERO 段映射到虛擬地址 0x0 處蚕礼,占用虛擬空間大小為 4GB,但是這4GB并不是真實(shí)的文件大小梢什,它僅表明將虛擬地址空間的前4GB映射為不可讀奠蹬、不可寫、不可執(zhí)行嗡午,與 NULL 指針相對應(yīng)囤躁。如果程序試圖訪問 __PAGEZERO 段,那么將會引起系統(tǒng)的崩潰。

接下來我們來看 LC_SEGMENT_64(__TEXT):

它將 __TEXT 段映射到虛擬地址空間 0x100000000 處狸演,也就是緊隨 __PAGEZERO 段言蛇,占用虛擬空間大小為 4096B,所對應(yīng)的權(quán)限是可讀宵距、可執(zhí)行腊尚、不可寫入。我們的 __TEXT 段包含以下5個(gè) Section:

  • __text: 包含程序的機(jī)器碼;

  • __stubs__stub_helper: 用來幫助 DYLD 綁定符號;

  • __cstring: 記錄了文件中的常量字符串信息(包含在雙引號中)满哪,我們可以依據(jù)此信息找到字符串的地址;

  • __unwind_info: 用于確定異常發(fā)生時(shí)棧所對應(yīng)的信息婿斥,包括棧指針、返回地址哨鸭、寄存器信息等民宿,它同樣包含相應(yīng)的處理函數(shù)來支持像 catch、final 等特性;

LC_SEGMENT_64(__DATA) 的作用是將 __DATA 段映射到緊隨 __TEXT 段的虛擬地址空間上像鸡,它包含兩個(gè) Section:

__nl_symbol_ptr Section 包含的符號指針需要在加載時(shí)綁定活鹰,而 __la_symbol_ptr Section 包含的符號指針則是在其第一次被程序使用時(shí)綁定。

LC_SEGMENT_64(__LINKEDIT) 則是將與動態(tài)鏈接相關(guān)的信息映射到虛擬地址空間只估,__LINKEDIT 段包括 rebase志群、bind、lazy bind 等信息蛔钙。

LC_DYLD_INFO_ONLY 記錄了有關(guān)鏈接的重要信息赖舟,它的數(shù)據(jù)結(jié)構(gòu)如下:

struct dyld_info_command {
    uint32_t   cmd;     /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
    uint32_t   cmdsize;     /* sizeof(struct dyld_info_command) */
    uint32_t   rebase_off;  /* file offset to rebase info  */
    uint32_t   rebase_size; /* size of rebase info   */
    uint32_t   bind_off;    /* file offset to binding info   */
    uint32_t   bind_size;   /* size of binding info  */
    uint32_t   weak_bind_off; /* file offset to weak binding info   */
    uint32_t   weak_bind_size;  /* size of weak binding info  */
    uint32_t   lazy_bind_off; /* file offset to lazy binding info */
    uint32_t   lazy_bind_size;  /* size of lazy binding infs */
    uint32_t   export_off;  /* file offset to lazy binding info */
    uint32_t   export_size; /* size of lazy binding infs */
};

根據(jù)它所記錄的偏移量,我們便可以找到在 Dynamic Loader Info 中的相關(guān)信息夸楣。它的 ONLY 后綴表明這是程序運(yùn)行所必須的宾抓,如果鏈接器不支持,那么加載過程就會終止豫喧。

LC_SYMTAB 記錄了程序的符號表以及字符串表的偏移量及大小石洗,符號表中記錄了程序用到的函數(shù)以及全局變量的信息,符號表?xiàng)l目的數(shù)據(jù)結(jié)構(gòu)定義在 nlist.h 中:

/*
 * Format of a symbol table entry of a Mach-O file for 32-bit architectures.
 */
struct nlist {
    union {
#ifndef __LP64__
        char *n_name;   /* for use when in-core */
#endif
        uint32_t n_strx;    /* index into the string table */
    } n_un;
    uint8_t n_type;     /* type flag, see below */
    uint8_t n_sect;     /* section number or NO_SECT */
    int16_t n_desc;     /* see <mach-o/stab.h> */
    uint32_t n_value;   /* value of this symbol (or stab offset) */
};
/*
 * This is the symbol table entry structure for 64-bit architectures.
 */
struct nlist_64 {
    union {
        uint32_t  n_strx; /* index into the string table */
    } n_un;
    uint8_t n_type;        /* type flag, see below */
    uint8_t n_sect;        /* section number or NO_SECT */
    uint16_t n_desc;       /* see <mach-o/stab.h> */
    uint64_t n_value;      /* value of this symbol (or stab offset) */
};

數(shù)據(jù)結(jié)構(gòu)中相關(guān)字段的含義都可以在 nlist.h 中找到紧显,這里值得一說的是 n_un 字段讲衫,它用來記錄符號的名字,但為什么是 uint32_t 類型呢孵班?又為什么在注釋中標(biāo)明是 string table 的 索引呢涉兽?

這是因?yàn)樵诔绦蛑校址拈L度是不固定的篙程,所以會將其放在 string table 中枷畏,然后存儲它在 string table 中的偏移。如果其他部分想要引用某個(gè)字符串虱饿,那么他首先需要找到 string table 的起始地址拥诡,然后根據(jù)偏移量找到相應(yīng)字符串的起始位置并向后讀取字符触趴,直到遇見 \0 才會停止讀取過程,最后返回讀到的字符串渴肉。

這也是 LC_SYMTAB 額外記錄 string table 地址的原因冗懦,string table 通常用于記錄 section 名、符號名等信息仇祭。

其他的加載命令如下表所示:

加載命令 描述
LC_DYSYMTAB 包括動態(tài)鏈接過程中所需要的信息
LC_LOAD_DYLINKER 指定動態(tài)鏈接器的地址
LC_LOAD_DYLIB 記錄了程序所需要的動態(tài)庫的相關(guān)信息
LC_UUID 靜態(tài)鏈接器為其生成的文件所提供的唯一標(biāo)識符
LC_MAIN 指定 main 函數(shù)的地址
LC_FUNCTION_STARTS 記錄文件中每個(gè)函數(shù)的起始地址
LC_DATA_IN_CODE 記錄那些寫在程序二進(jìn)制執(zhí)行指令中的數(shù)據(jù)
LC_CODE_SIGNATURE 代碼簽名

Data

Data 包括文件所需的 segment 數(shù)據(jù)披蕉,除了 MachOView 工具,你也可以通過 size 工具來查看乌奇,運(yùn)行 size -x -l -m MachOExplore 命令后可得到以下內(nèi)容:

我們還可以通過輸入 otool -s __DATA __la_symbol_ptr MachOExplore 命令來查看 __la_symbol_ptr Section 的數(shù)據(jù):

也可以使用 otool -V -s __TEXT __text MachOExplore 命令來查看 __text Section 的反匯編代碼:

同時(shí) otool 也為一些常見的命令設(shè)置了縮寫没讲,例如 -t 就是 -s __TEXT __text 簡稱,而 -d 就是 -s __DATA __data 的簡稱华弓,相關(guān)信息都可以通過 man otool 命令來查看食零。這些數(shù)據(jù)都可以在 MachOView 中看得很清楚困乒,但是這些命令行工具記一下也無妨寂屏。

總結(jié)

以上便是 Mach-O 文件的探究(其實(shí)總結(jié)部分就是強(qiáng)行湊的(??????)??)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末娜搂,一起剝皮案震驚了整個(gè)濱河市迁霎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌百宇,老刑警劉巖考廉,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異携御,居然都是意外死亡昌粤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門啄刹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涮坐,“玉大人,你說我怎么就攤上這事誓军「ざ铮” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵昵时,是天一觀的道長捷雕。 經(jīng)常有香客問我,道長壹甥,這世上最難降的妖魔是什么救巷? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮句柠,結(jié)果婚禮上征绸,老公的妹妹穿的比我還像新娘久橙。我一直安慰自己,他們只是感情好管怠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布淆衷。 她就那樣靜靜地躺著,像睡著了一般渤弛。 火紅的嫁衣襯著肌膚如雪祝拯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天她肯,我揣著相機(jī)與錄音佳头,去河邊找鬼。 笑死晴氨,一個(gè)胖子當(dāng)著我的面吹牛康嘉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播籽前,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼亭珍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枝哄?” 一聲冷哼從身側(cè)響起肄梨,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挠锥,沒想到半個(gè)月后众羡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蓖租,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年粱侣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蓖宦。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡齐婴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出球昨,到底是詐尸還是另有隱情尔店,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布主慰,位于F島的核電站嚣州,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏共螺。R本人自食惡果不足惜该肴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藐不。 院中可真熱鬧匀哄,春花似錦秦效、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至法梯,卻和暖如春苔货,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背立哑。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工夜惭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铛绰。 一個(gè)月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓诈茧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捂掰。 傳聞我的和親對象是個(gè)殘疾皇子敢会,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 上一篇博客介紹了mach_header相關(guān)內(nèi)容,Mach-O文件介紹之mach_header尘颓。這篇博客主要介紹Ma...
    Tomychen閱讀 2,372評論 0 7
  • Mach-O 概述 和 部分命令介紹 我們知道Windows下的文件都是PE文件走触,同樣在OS X和iOS中可執(zhí)行文...
    青花瓷的平方閱讀 14,917評論 2 52
  • 熟悉Linux和windows開發(fā)的同學(xué)都知道晦譬,ELF是Linux下可執(zhí)行文件的格式疤苹,PE32/PE32+是win...
    Klaus_J閱讀 3,958評論 1 10
  • 13. Hook原理介紹 13.1 Objective-C消息傳遞(Messaging) 對于C/C++這類靜態(tài)語...
    Flonger閱讀 1,413評論 0 3
  • 13.1 Objective-C消息傳遞(Messaging) 對于C/C++這類靜態(tài)語言,調(diào)用一個(gè)方法其實(shí)就是跳...
    泰克2008閱讀 2,024評論 1 6