Mach-O 文件探索

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

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

Header:記錄 Mach-O 文件的基本信息,包括文件類型、支持的 CPU 類型以及加載命令的個數、大小等。

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

Data: 用于保存程序的 TEXT、DATA、LINKEDIT 等 segment 數據。

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

#include <stdio.h>

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

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

Header

MachOView 查看 Header 的內容如下:

Header 中記錄了 Mach-O 文件的屬性信息,相關數據結構定義在 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) */

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

字段 說明
magic 表明文件適用于64位操作系統(tǒng)還是32位操作系統(tǒng),因為大小端的存在,所以有 MH_MAGIC_64 和 MH_CIGAM_64 兩種形式 。
cputype 描述文件所支持的 CPU 架構,包括 ARM64、X86_64、i386等。
cpusubtype 描述文件對應的具體 CPU 架構,例如 ARM_V7S、ARM64_V8、X86_ARCH1 等。
filetype 描述文件的類型,常見的類型有可執(zhí)行文件、可重定位文件、共享庫文件等。
ncmds 記錄加載命令的個數。
sizeofcmds 記錄所有加載命令的大小。
flags 描述文件在編譯、鏈接等過程中的信息,示例中的 MH_NOUNDEFS 表示文件中不存在未定義的符號,MH_DYLDLINK 表示文件要交由 DYLD 進一步處理,MH_TWOLEVEL 表示文件使用兩級命名空間,MH_PIE 表示啟用地址空間布局隨機化。

Load commands

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

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

  • Command: 表示加載命令;

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

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

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

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

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

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

  • Maximum VM protection: 表示與段相對應的最大操作權限;

  • Initial VM protection: 表示段的初始操作權限;

  • Number of Sections: 段包含多少個 Section;

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

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

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

它將 __TEXT 段映射到虛擬地址空間 0x100000000 處,也就是緊隨 __PAGEZERO 段,占用虛擬空間大小為 4096B,所對應的權限是可讀、可執(zhí)行、不可寫入。我們的 __TEXT 段包含以下5個 Section:

  • __text: 包含程序的機器碼;

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

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

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

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

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

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

LC_DYLD_INFO_ONLY 記錄了有關鏈接的重要信息,它的數據結構如下:

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 */
};

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

LC_SYMTAB 記錄了程序的符號表以及字符串表的偏移量及大小,符號表中記錄了程序用到的函數以及全局變量的信息,符號表條目的數據結構定義在 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) */
};

數據結構中相關字段的含義都可以在 nlist.h 中找到,這里值得一說的是 n_un 字段,它用來記錄符號的名字,但為什么是 uint32_t 類型呢?又為什么在注釋中標明是 string table 的 索引呢?

這是因為在程序中,字符串的長度是不固定的,所以會將其放在 string table 中,然后存儲它在 string table 中的偏移。如果其他部分想要引用某個字符串,那么他首先需要找到 string table 的起始地址,然后根據偏移量找到相應字符串的起始位置并向后讀取字符,直到遇見 \0 才會停止讀取過程,最后返回讀到的字符串。

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

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

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

Data

Data 包括文件所需的 segment 數據,除了 MachOView 工具,你也可以通過 size 工具來查看,運行 size -x -l -m MachOExplore 命令后可得到以下內容:

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

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

同時 otool 也為一些常見的命令設置了縮寫,例如 -t 就是 -s __TEXT __text 簡稱,而 -d 就是 -s __DATA __data 的簡稱,相關信息都可以通過 man otool 命令來查看。這些數據都可以在 MachOView 中看得很清楚,但是這些命令行工具記一下也無妨。

總結

以上便是 Mach-O 文件的探究(其實總結部分就是強行湊的(??????)??)。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容

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