iOS系統分析(二)Mach-O二進制文件解析

更多技術乾貨請戳:聽雲博客git

0x01  Mach-O格式簡單介紹github

Mach-O文件格式是 OS X 與 iOS 系統上的可執行文件格式,相似於windows的 PE 文件 與 Linux(其餘 Unix like)的 ELF 文件,若是不完全搞清楚Mach-O的格式與相關內容,那麼深刻研究 xnu 內核就無從談起。web

Mach-O文件的格式以下圖所示:windows

1.png

有以下幾個部分組成:數據結構

1. Header:保存了Mach-O的一些基本信息,包括了平臺、文件類型、LoadCommands的個數等等。架構

2. LoadCommands:這一段緊跟Header,加載Mach-O文件時會使用這裏的數據來肯定內存的分佈。dom

3. Data:每個segment的具體數據都保存在這裏,這裏包含了具體的代碼、數據等等。函數

0x02 FAT二進制數據 ,數據結構定義在 \<mach-o/fat.h\>工具

06.png

08.png

1. 第一段爲magic 魔數,這裏注意大小端,讀出來以後須要看下是0xCAFEBABE仍是 0xBEBAFECA(不然即爲thin),須要根據這個來轉後續讀取的字節的字節序。  能夠看出來 前4byte 爲 0xBEBAFECA ,說明爲fat。ui

2. 第二段爲arch count,也就是該App或dSYM中包含哪些CPU架構,好比armv七、arm64等,這個例子中爲2(後4byte  0x 00 00 00 02),表示包含了兩種cpu架構。  

  `sizeof(struct fat-header) = 8byte`

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數據。

   `sizeof(struct fat-arch) = 20byte`

4. 根據fat頭中讀取的offset數據,咱們能夠跳到文件對應的arch數據的位置,固然若是隻有一種架構的話就不須要計算偏移量了。 下圖給出解析的函數

05.png

0x03 Mach Header二進制數據

經過magic咱們能夠區分出是32-bit仍是64-bit,64-bit多了4個字節的保留字段,這裏一樣須要注意字節序的問題,也就是判斷magic,來肯定是否須要轉換字節序。  

`sizeof(struct mach-header-64) = 32byte`  ; `sizeof(struct mach-header) = 28byte`

07.png

根據mach-header與mach-header_64的定義,很明顯能夠看出,Headers的主要做用就是幫助系統迅速的定位Mach-O文件的運行環境,文件類型。

2.png

FileType 

由於Mach-O文件不只僅用來實現可執行文件,同時還用來實現了其餘內容

1. 內核擴展

2. 庫文件

3. CoreDump

4.  其它

3.png

下面是一些精彩用到的文件類型

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的加載參數。

4.png

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   堆內存沒法執行代碼

5.png

0x04 LoadCommands

Load Commands 直接就跟在Header後面,全部command佔用內存的總和在Mach-O Header裏面已經給出了。在加載過Header以後就是經過解析LoadCommand來加載接下來的數據了。定義以下:

6.png

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)

09.png

Segment數據

加載數據時,主要加載的就是LC-SEGMET活着LC-SEGMENT_64。其餘的Segment的用途在這裏不作深究。

LCSEGMENT以及LC-SEGMENT-64 定義以下圖。

 

7.png

10.png

能夠看出,這裏大部分的數據是用來幫助內核將Segment映射到虛擬內存的。

nsects 字段,標示了Segment中有多少secetion ,section是具體有用的數據存放的地方。

TEXT的vmaddr也就是程序的加載地址; —DWARF中代表了DWARF數據塊的信息,表示dSYM是DWARF格式的數據結構。 

` sizeof(struct segment-command) = 56byte   ;   sizeof(struct segment-command-64) = 72byte`

Section數據

8.png

從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版本的差別,每一個數據塊之間都是緊密聯繫的,一個字節的讀取誤差就會形成後續數據的讀取錯誤,正所謂差之毫釐,失之千里。

 

原文連接:http://blog.tingyun.com/web/article/detail/1341

相關文章
相關標籤/搜索