天天看點

iOS系統分析(二)Mach-O二進制檔案解析

0x01  mach-o格式簡單介紹

mach-o檔案格式是 os x 與 ios 系統上的可執行檔案格式,類似于windows的 pe 檔案 與 linux(其他 unix like)的 elf 檔案,如果不徹底搞清楚mach-o的格式與相關内容,那麼深入研究 xnu 核心就無從談起。

mach-o檔案的格式如下圖所示:

iOS系統分析(二)Mach-O二進制檔案解析

有如下幾個部分組成:

1. header:儲存了mach-o的一些基本資訊,包括了平台、檔案類型、loadcommands的個數等等。

2. loadcommands:這一段緊跟header,加載mach-o檔案時會使用這裡的資料來确定記憶體的分布。

3. data:每一個segment的具體資料都儲存在這裡,這裡包含了具體的代碼、資料等等。

0x02 fat二進制資料 ,資料結構定義在 \<mach-o/fat.h\>

iOS系統分析(二)Mach-O二進制檔案解析
iOS系統分析(二)Mach-O二進制檔案解析

1. 第一段為magic 魔數,這裡注意大小端,讀出來之後需要看下是0xcafebabe還是 0xbebafeca(否則即為thin),需要根據這個來轉後續讀取的位元組的位元組序。  可以看出來 前4byte 為 0xbebafeca ,說明為fat。

2. 第二段為arch count,也就是該app或dsym中包含哪些cpu架構,比如armv7、arm64等,這個例子中為2(後4byte  0x 00 00 00 02),表示包含了兩種cpu架構。  

3. 後續段中包含cputype(0x  0c 00  00 01)、cpusubtype (0x 00 00 00 00)、offset (0x 00 10 00  00)、size(0x 00  f0 27 00)等資料,根據fat中的結構定義,依次讀取,這裡需要說明的是,如果隻包含一種cpu架構的話,是沒有這段fat頭定義的,可以跳過這部分,直接讀取arch資料。

4. 根據fat頭中讀取的offset資料,我們可以跳到檔案對應的arch資料的位置,當然如果隻有一種架構的話就不需要計算偏移量了。 下圖給出解析的函數

iOS系統分析(二)Mach-O二進制檔案解析

0x03 mach header二進制資料

通過magic我們可以區分出是32-bit還是64-bit,64-bit多了4個位元組的保留字段,這裡同樣需要注意位元組序的問題,也就是判斷magic,來确定是否需要轉換位元組序。  

iOS系統分析(二)Mach-O二進制檔案解析

根據mach-header與mach-header_64的定義,很明顯可以看出,headers的主要作用就是幫助系統迅速的定位mach-o檔案的運作環境,檔案類型。

iOS系統分析(二)Mach-O二進制檔案解析

filetype 

因為mach-o檔案不僅僅用來實作可執行檔案,同時還用來實作了其他内容

1. 核心擴充

2. 庫檔案

3. coredump

4.  其它

iOS系統分析(二)Mach-O二進制檔案解析

下面是一些精彩用到的檔案類型

1. mh-object    編譯過程中産生的  obj檔案 (gcc -c xxx.c 生成xxx.o檔案)

2. mh-executable  可執行二進制檔案 (/usr/bin/ls)

3. mh-core      coredump (崩潰時的dump檔案)

4. mh-dylib  動态庫(/usr/lib/裡面的那些共享庫檔案)

5. mh-dylinker  連接配接器linker(/usr/lib/dyld檔案)

6. mh-kext-bundle   核心擴充檔案 (自己開發的簡單核心子產品)

flags

mach-o headers還包含了一些很重要的dyld的加載參數。

iOS系統分析(二)Mach-O二進制檔案解析

1. mh-noundefs   目标沒有未定義的符号,不存在連結依賴

2. mh-dyldlink     該目标檔案是dyld的輸入檔案,無法被再次的靜态連結

3. mh-pie      允許随機的位址空間(開啟aslr  -\>address space layout randomization)

4. mh-allow-stack-execution   棧記憶體可執行代碼,一般是預設關閉的。

5. mh-no-heap-execution   堆記憶體無法執行代碼

iOS系統分析(二)Mach-O二進制檔案解析

0x04 loadcommands

load commands 直接就跟在header後面,所有command占用記憶體的總和在mach-o header裡面已經給出了。在加載過header之後就是通過解析loadcommand來加載接下來的資料了。定義如下:

iOS系統分析(二)Mach-O二進制檔案解析

cmd字段

根據cmd字段的類型不同,使用了不同的函數來加載。簡單的列出一張表看一看在核心代碼中不同的command類型都有哪些作用。

1. lc-segment;lc-segment-64   在核心中由load-segment 函數處理(将segment中的資料加載并映射到程序的記憶體空間去)

2. lc-load-dylinker    在核心中由load-dylinker 函數處理(調用/usr/lib/dyld程式)

3. lc-uuid 在核心中由load-uuid 函數處理 (加載128-bit的唯一id)

4. lc-thread  在核心中由load-thread 函數處理 (開啟一個mach線程,但是不配置設定棧空間)

5. lc-unixthread 在核心中由load-unixthread 函數處理 (開啟一個unix posix線程)

6. lc-code-signature 在核心中由load-code-signature 函數處理 (進行數字簽名)

7. lc-encryption-info 在核心中由 set-code-unprotect 函數處理 (加密二進制檔案)

uuid 二進制資料    128byte

uuid是16個位元組(128bit)的一段資料,是檔案的唯一辨別,前面提到的符号化時,這個uuid必須要和app二進制檔案中的uuid一緻,才能被正确的符号化。dwarfdump檢視的uuid就是這段資料。讀取這部分資料時通過command結構讀取的,也就是第一段(0x0000001b)表示接下來的資料類型,第二段(0x00000018)資料的大小(包含command資料)。 

symtab 二進制資料

1. 符号表資料塊結構,前二段依然是command資料。後邊4段分别為符号在檔案中的偏移量(0x001df5e0)、符号個數(0x001df5e0)、字元串在檔案中的偏移量(0x0020c3a0)、字元串表大小(0x000729a8)。 

2. 接下來就是讀取segment和section資料塊了,和上面讀取資料塊結構一樣是根據command結構讀取,下圖展示的segment資料和section資料,它們在二進制檔案中它們是連續的,也就是每一條segment資料後面會緊跟着多條對應的section資料,section的資料總數是通過segment結構中的nsects決定的。 

3. 這裡我寫了一個簡單地mach-o解析工具 [https://github.com/liutianshx2012/tmacho](https://github.com/liutianshx2012/tmacho)

iOS系統分析(二)Mach-O二進制檔案解析

segment資料

加載資料時,主要加載的就是lc-segmet活着lc-segment_64。其他的segment的用途在這裡不做深究。

lcsegment以及lc-segment-64 定義如下圖。

iOS系統分析(二)Mach-O二進制檔案解析
iOS系統分析(二)Mach-O二進制檔案解析

可以看出,這裡大部分的資料是用來幫助核心将segment映射到虛拟記憶體的。

nsects 字段,标示了segment中有多少secetion ,section是具體有用的資料存放的地方。

text的vmaddr也就是程式的加載位址; —dwarf中表明了dwarf資料塊的資訊,表示dsym是dwarf格式的資料結構。 

section資料

iOS系統分析(二)Mach-O二進制檔案解析

從section資料中,我們可以找到—debug-info、—debug-pubnames, —debug-line等調試資訊,通過這些調試資訊我們可以找到程式中符号的起始位址、變量類型等資訊。如果我們要符号化的話,就可以通過解析這些資料得到我們想要的資訊。

symbol 資料

通過symtab中的資料可以得到symbol在檔案中的位置和個數,symbol塊資料中包含了符号的起始位址、字元串的偏移量等資料,這部分資料結構可以參考\<nlist.h\> 和 \<stabl.h\>。在這部分資料全部讀取後,就可以讀取所有的符号資料了,也就是接下來的資料。 

symbol string 資料

1. 通過symtab和symbo中的資料可以得到每個符号字元串在檔案中的偏移量和大小,每個符号資料是以0結尾的字元串。 

2. 我們通過以上兩部分資料的組合就可以得到每個symbo在程式中的加載位址了。這些資料對于以後做符号工作都非常的有幫助。

3. 到此,關于dsym檔案中頭部資料讀取就完成了。頭部資料都有相應的資料結構定義,讀取時相對會比較容易些,解析資料時要注意位元組序的問題,32-bit和64-bit資料結構的差異、位元組長度的差異,dwarf版本的差異,每個資料塊之間都是緊密聯系的,一個位元組的讀取偏差就會造成後續資料的讀取錯誤,正所謂差之毫厘,失之千裡。

繼續閱讀