iOS系統分析(二)Mach-O二進位制檔案解析
原文出自【聽雲技術部落格】:http://blog.tingyun.com/web/article/detail/1341
0x01 Mach-O格式簡單介紹
Mach-O檔案格式是 OS X 與 iOS 系統上的可執行檔案格式,類似於windows的 PE 檔案 與 Linux(其他 Unix like)的 ELF 檔案,如果不徹底搞清楚Mach-O的格式與相關內容,那麼深入研究 xnu 核心就無從談起。
Mach-O檔案的格式如下圖所示:
有如下幾個部分組成:
Header:儲存了Mach-O的一些基本資訊,包括了平臺、檔案型別、LoadCommands的個數等等。 LoadCommands:這一段緊跟Header,載入Mach-O檔案時會使用這裡的資料來確定記憶體的分佈。 Data:每一個segment的具體資料都儲存在這裡,這裡包含了具體的程式碼、資料等等。
0x02 FAT二進位制資料 ,資料結構定義在\
1.第一段為magic 魔數,這裡注意大小端,讀出來之後需要看下是0xCAFEBABE還是 0xBEBAFECA(否則即為thin),需要根據這個來轉後續讀取的位元組的位元組序。 可以看出來 前4byte 為 0xBEBAFECA ,說明為fat。
2.第二段為arch count,也就是該App或dSYM中包含哪些CPU架構,比如armv7、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資料的位置,當然如果只有一種架構的話就不需要計算偏移量了。 下圖給出解析的函式
0x03 Mach Header二進位制資料
通過magic我們可以區分出是32-bit還是64-bit,64-bit多了4個位元組的保留欄位,這裡同樣需要注意位元組序的問題,也就是判斷magic,來確定是否需要轉換位元組序。
`sizeof(struct mach-header-64) = 32byte` ; `sizeof(struct mach-header) = 28byte`
根據mach-header與mach-header_64的定義,很明顯可以看出,Headers的主要作用就是幫助系統迅速的定位Mach-O檔案的執行環境,檔案型別。
FileType
因為Mach-O檔案不僅僅用來實現可執行檔案,同時還用來實現了其他內容
1.核心擴充套件
2.庫檔案
3.CoreDump
4.其它
下面是一些精彩用到的檔案型別
- MH-OBJECT 編譯過程中產生的 obj檔案 (gcc -c xxx.c 生成xxx.o檔案)
- MH-EXECUTABLE 可執行二進位制檔案 (/usr/bin/ls)
- MH-CORE CoreDump (崩潰時的Dump檔案)
- MH-DYLIB 動態庫(/usr/lib/裡面的那些共享庫檔案)
- MH-DYLINKER 聯結器linker(/usr/lib/dyld檔案)
- MH-KEXT-BUNDLE 核心擴充套件檔案 (自己開發的簡單核心模組)
flags
Mach-O headers還包含了一些很重要的dyld的載入引數。
- MH-NOUNDEFS 目標沒有未定義的符號,不存在連結依賴
- MH-DYLDLINK 該目標檔案是dyld的輸入檔案,無法被再次的靜態連結
- MH-PIE 允許隨機的地址空間(開啟ASLR ->Address Space Layout Randomization)
- MH-ALLOW-STACK-EXECUTION 棧記憶體可執行程式碼,一般是預設關閉的。
- MH-NO-HEAP-EXECUTION 堆記憶體無法執行程式碼
0x04 LoadCommands
Load Commands 直接就跟在Header後面,所有command佔用記憶體的總和在Mach-O Header裡面已經給出了。在載入過Header之後就是通過解析LoadCommand來載入接下來的資料了。定義如下:
cmd欄位
根據cmd欄位的型別不同,使用了不同的函式來載入。簡單的列出一張表看一看在核心程式碼中不同的command型別都有哪些作用。
- LC-SEGMENT;LC-SEGMENT-64 在核心中由load-segment 函式處理(將segment中的資料載入並對映到程式的記憶體空間去)
- LC-LOAD-DYLINKER 在核心中由load-dylinker 函式處理(呼叫/usr/lib/dyld程式)
- LC-UUID 在核心中由load-uuid 函式處理 (載入128-bit的唯一ID)
- LC-THREAD 在核心中由load-thread 函式處理 (開啟一個MACH執行緒,但是不分配棧空間)
- LC-UNIXTHREAD 在核心中由load-unixthread 函式處理 (開啟一個UNIX posix執行緒)
- LC-CODE-SIGNATURE 在核心中由load-code-signature 函式處理 (進行數字簽名)
- 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
Segment資料
載入資料時,主要載入的就是LC-SEGMET活著LC-SEGMENT_64。其他的Segment的用途在這裡不做深究。
LCSEGMENT以及LC-SEGMENT-64 定義如下圖。
可以看出,這裡大部分的資料是用來幫助核心將Segment對映到虛擬記憶體的。
nsects 欄位,標示了Segment中有多少secetion ,section是具體有用的資料存放的地方。
TEXT的vmaddr也就是程式的載入地址; —DWARF中表明瞭DWARF資料塊的資訊,表示dSYM是DWARF格式的資料結構。
` sizeof(struct segment-command) = 56byte ; sizeof(struct segment-command-64) = 72byte`
Section資料
從Section資料中,我們可以找到—debug-info、—debug-pubnames, —debug-line等除錯資訊,通過這些除錯資訊我們可以找到程式中符號的起始地址、變數型別等資訊。如果我們要符號化的話,就可以通過解析這些資料得到我們想要的資訊。
Symbol 資料
通過SymTab中的資料可以得到Symbol在檔案中的位置和個數,Symbol塊資料中包含了符號的起始地址、字串的偏移量等資料,這部分資料結構可以參考 和 。在這部分資料全部讀取後,就可以讀取所有的符號資料了,也就是接下來的資料。
Symbol String 資料
通過SymTab和Symbo中的資料可以得到每個符號字串在檔案中的偏移量和大小,每個符號資料是以0結尾的字串。 我們通過以上兩部分資料的組合就可以得到每個symbo在程式中的載入地址了。這些資料對於以後做符號工作都非常的有幫助。 到此,關於dSYM檔案中頭部資料讀取就完成了。頭部資料都有相應的資料結構定義,讀取時相對會比較容易些,解析資料時要注意位元組序的問題,32-bit和64-bit資料結構的差異、位元組長度的差異,DWARF版本的差異,每個資料塊之間都是緊密聯絡的,一個位元組的讀取偏差就會造成後續資料的讀取錯誤,正所謂差之毫釐,失之千里。
相關文章
- Java二進位制Class檔案格式解析Java
- 二進位制檔案視覺化(二)視覺化
- 二進位制檔案複製
- php寫二進位制檔案PHP
- 二進位制檔案拷貝
- 檔案操作(二進位制拷貝)
- Git處理二進位制檔案Git
- MySQL二進位制檔案(binlog)MySql
- office檔案格式複合文件二進位制結構解析
- Python讀寫二進位制檔案Python
- c++ 二進位制儲存檔案C++
- C#的二進位制檔案操作C#
- 使用UltraEdit 拷貝二進位制檔案
- 二進位制與二進位制運算
- 文字檔案與二進位制檔案的區別
- MySQL 匯出匯入二進位制檔案MySql
- UltraEdit--二進位制檔案編輯功能
- 用shell處理二進位制檔案(轉)
- UE複製貼上二進位制檔案
- 介面返回二進位制檔案的下載。
- 二進位制檔案記憶體對映記憶體
- golang: 給二進位制檔案增加版本資訊Golang
- iOS 二進位制流轉化-專案筆記iOS筆記
- mach-o 檔案分析(解析類)Mac
- (二進位制)
- 二進位制
- Carthage和iOS元件二進位制化iOS元件
- 通過Nvidia簽名的二進位制檔案執行系統命令
- 二進位制檔案安裝安裝etcd
- 6.3建立自己執行的二進位制檔案
- C++ 讀取二進位制檔案到char*C++
- C/C++ 二進位制讀寫 png 檔案C++
- Oracle建立二進位制檔案索引的方法(轉)Oracle索引
- 什麼是二進位制?二進位制如何轉換?
- 二進位制漏洞分析技能腦圖
- INODE結構二進位制頁分析
- Swift之struct二進位制大小分析SwiftStruct
- 利用vstruct解析二進位制資料Struct