什么是Mach-O文件?
Mach-O是Mach object的縮寫,是Mac\iOS上用來存儲程序、庫的標準格式
Mach-O文件類型
- 可以點擊下載xnu源碼,在源碼中的<font color=red>EXTERNAL_HEADERS/mach-o/loader.h
</font>文件中,我們可以看到Mach-O格式的所有文件類型
xun是蘋果MacOS\iOS等操作系統(tǒng)的內(nèi)核
- 常見的Mach-O文件類型
| Mach-O類型 | 示例文件 |
|---|---|
| MH_OBJECT | 目標文件(.o) 靜態(tài)庫文件(.a)注: |
| MH_EXECUTE | 可執(zhí)行文件,存放App的所有源碼信息,在.app/xx |
| MH_DYLIB | 動態(tài)庫文件.dylib 或者 .framework/xx |
| MH_DYLINKER | 動態(tài)鏈接編輯器,也就是之前所說的/usr/lib/dyld工具 |
| MH_DSYM | 此文件中存儲這二進制文件符號信息,在開發(fā)中,我們經(jīng)常使用此文件來分析App的崩潰信息 |
Mach-O的基本結(jié)構(gòu)
可以點擊官網(wǎng)查看Mach-O的介紹。
Mach-O組成
Mach-O由3個部分組成
- Header,包含文件類型、目標架構(gòu)類型等等
- Load commands,是描述文件在虛擬內(nèi)存中的邏輯結(jié)構(gòu)和布局,相當于一份目錄索引
- Raw segment data,在Load commands中所定義的Segment,在這里都能找到原始數(shù)據(jù)。
Raw segment data存放了所有的原始數(shù)據(jù),而Load commands相當于Raw segment data的索引目錄
窺探Mach-O的結(jié)構(gòu)
常用工具
- 命令行工具,通過file命令查看Mach-O文件的基本信息
file 文件路徑
- otool,查看Mach-O特定部分和段的內(nèi)容
#查看Mach-O文件的header信息
otool -h 文件路徑
#查看Mach-O文件的load commands信息
otool -l 文件路徑
更多使用方法,終端輸入otool -help查看
- lipo,用來處理多架構(gòu)Mach-O文件,常用命令如下
#查看架構(gòu)信息
lipo -info 文件路徑
#導出某種類型的架構(gòu)
lipo 文件路徑 -thin 架構(gòu)類型 -output 輸出文件路徑
#合并多種架構(gòu)類型
lipo 文件路徑1 文件路徑2 -output 輸出文件路徑
- GUI工具,MachOView的使用
- 點擊查看MachOView官網(wǎng)
- 直接點擊下載MachOView.dmg,提取碼: 62jk
Universal Binary(通用二進制文件)
通用二進制文件就是同時適用于多種架構(gòu)的二進制文件,它包含了多種不同架構(gòu)的獨立的二進制文件,它有以下特點
- 因為需要存儲多種架構(gòu)的代碼,所以通用二進制文件要比單架構(gòu)二進制文件要大
- 因為兩種種架構(gòu)之間可以共用一些資源,所以兩種架構(gòu)的通用二進制文件大小不會達到單一架構(gòu)版本的兩倍。
- 運行過程中只會調(diào)用其中的部分代碼,所以運行起來不會占用額外的內(nèi)存
- 通用二進制文件通常也被稱為“胖二進制文件(Fat binary)”
dyld和Mach-O
dyld是iOS中用來加載可執(zhí)行文件、動態(tài)庫的工具,其實它本身也是一個Mach-O文件。
什么是dyld?
- dyld 動態(tài)加載器(又叫做動態(tài)鏈接編輯器)
- dyld的源碼可以點擊此處下載
dyld的作用。
dyld可以用來加載以下三種類型的Mach-O文件
- MH_EXECUTE
- MH_DYLIB
- MH_BUNDLE
通過查看dyld的源碼可以看到加載文件時的類型校驗
從編碼到App安裝到手機
想要了解Mach-O文件,首先要了解從編寫代碼,開發(fā)App到App打包并安裝到手機上的整個過程。
- 首先我們編寫完成代碼之后,會通過LLVM編譯器預(yù)處理我們的代碼,比如將宏放在指定的位置
- 預(yù)處理結(jié)束之后,LLVM會對代碼進行詞法分析和語法分析,生成AST。AST是抽象語法樹,主要用來進行快速遍歷,實現(xiàn)靜態(tài)代碼檢查的功能。
- AST會生成IR,IR是一種更加接近機器碼的語言,通過IR可以生成不同平臺的機器碼。對于iOS平臺,IR生成的可執(zhí)行文件就是Mach-O.
- 然后通過鏈接器將符號和地址綁定在一起,并且將項目中的多個Mach-O文件合并成一個Mach-O文件。
- 最后通過簽名等操作生成.app文件,然后對.app文件進行壓縮就生成了我們可以安裝的ipa包。
- 當然,ipa包的安裝途徑有兩種:
- 通過開發(fā)者賬號上傳到App Store,然后在App Store上下載安裝。
- 通過PP助手、iFunBox、Xcode等工具來安裝
逆向App,我們需要做哪些工作?
初步了解了什么是Mach-O文件,以及App從開發(fā)到安裝的過程,我們就可以來學習如何逆向一款A(yù)pp
- 界面分析
通過之前的學習,我們已經(jīng)可以使用Cycript和Reveal對App的界面進行分析 - 代碼分析
iOS開發(fā)中,所有的代碼最后都會經(jīng)過編譯生成Mach-o文件,所以我們需要對Mach-O文件進行靜態(tài)分析
靜態(tài)分析的工具有MachOView、class-dump、Hopper Disassembler、ida等等,后面會一一學習
- 動態(tài)調(diào)試
除了靜態(tài)分析,我們還需要運行目標App,對App進行動態(tài)調(diào)試
動態(tài)調(diào)試的工具有debugserver、LLDB等等
- 代碼編寫、注入
進行完界面分析、代碼分析和動態(tài)調(diào)試之后,我們可以在特定位置注入我們自己寫的代碼,必要時可以重新簽名并且打包ipa
調(diào)試工具
calss-dump
class-dump的作用就是把Mach-O文件的class信息給導出來,生成對應(yīng)的.h頭文件
- 可以點擊官網(wǎng)下載class-dump工具包
- 下載完成之后將其中的class-dump可執(zhí)行文件復制到Mac上的<font color=red>/usr/local/bin</font>目錄中,這樣在終端就能識別class-dump命令了
在Mac中,終端執(zhí)行的所有指令都會去<font color=red>/usr/bin</font>目錄和<font color=red>/usr/local/bin</font>目錄下尋找
- class-dump的常用命令如下
# -H表示需要生成頭文件 -o用于指定頭文件的存放目錄
class-dump -H Mach-O文件路徑 -o 頭文件存放目錄
Hopper Disassmbler
Hopper Disassmbler可以將Mach-O文件的機器語言反編譯成匯編代碼、OC偽代碼或者是Swift偽代碼。最常使用的快捷鍵有
#找出哪里引用了這個方法
Shift + Option + X
下載地址:https://pan.baidu.com/s/1yP_VcBlQ2G-rWsRue3uSBg
靜態(tài)庫和動態(tài)庫
在iOS開發(fā)中,有很多功能都是現(xiàn)成可用的,不關(guān)你的App在用,其它的App也在用,比如UIKit框架、GUI框架、I/O、網(wǎng)絡(luò)等等。這些庫都是通過鏈接器鏈接到Mach-O文件中的。
靜態(tài)庫
靜態(tài)庫是編譯時鏈接的庫,需要連接進入Mach-O文件中,如果需要更新就必須重新編譯一次,無法做到動態(tài)加載和更新
動態(tài)庫
動態(tài)庫是運行時鏈接的庫。
Mach-O是文件編譯之后的產(chǎn)物,所以動態(tài)庫并沒有參與Mach-O文件的編譯和鏈接。所以Mach-O文件中沒有包含動態(tài)庫的符號定義,也就是說這些符號會直接顯示未定義,但是他們的名字和對應(yīng)庫的路徑會被記錄下來。在運行時通過dlopen和dlsym導入動態(tài)庫時,會根據(jù)記錄的路徑找到對應(yīng)的庫,再通過記錄的名字符號找到綁定的地址。
動態(tài)庫共享緩存(dyld shared cache)
從iOS3.1開始,為了提高性能,絕大部分的系統(tǒng)動態(tài)庫文件都打包存放到了一個緩存文件中(dyld shared cache),緩存路徑是<font color=red>/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX</font>
dyld_shared_cache_armX里面的X代表ARM處理器指令集的架構(gòu)
ARM指令集
ARM指令集(CPU指令的集合)有以下幾種
| ARM指令集 | 支持的設(shè)備 |
|---|---|
| armv6 | iPhone、iPhone3G iPod Touch、iPod Touch2 |
| armv7 | iPhone3GS、iPhone4、iPhone4S iPad、iPad2、iPad3(The New iPad)、iPad mini iPod、Touch3G、iPod Touch4、iPod Touch5 |
| armv7s | iPhone5、iPhone5C、iPad4 |
| arm64 | iPhone5S、iPhone6、iPhone6 Plus、iPhone6S、iPhone6S Plus iPhoneSE、iPhone7、iPhone7Plus、iPhone8、iPhone8 Plus、iPhoneX iPad5、iPad Air、iPad Air2、iPad Pro、iPad Pro2 iPad mini with Retina display、iPad mini3、iPad mini4 iPod Touch6 |
以上所有的指令集都是向下兼容的
為什么要使用動態(tài)庫共享緩存呢?最大的好處就是節(jié)省內(nèi)存。
從動態(tài)庫共享緩存抽取動態(tài)庫
由于動態(tài)庫共享緩存太大,如果想獲取其中某個動態(tài)庫,例如UIKit,就需要從動態(tài)庫共享緩存中抽取對應(yīng)的動態(tài)庫
-
使用dyld源碼中提供的方式來進行抽取,工具在源碼中的<font color=red>launch-cache/dsc_extractor.cpp</font>文件中
- 首先需要去掉源碼中的<font color=red>#if 0</font>判斷
- 然后使用如下命令編譯<font color=red>dsc_extractor.cpp</font>文件
clang++ -o dsc_extractor dsc_extractor.cpp此處是將dsc_extractor.cpp編譯生成可執(zhí)行文件dsc_extractor
- 進入執(zhí)行文件dsc_extractor所在目錄。通過以下的命令來抽取動態(tài)庫
./dsc_extractor 動態(tài)庫共享緩存文件的路徑 用于存放抽取結(jié)果的目錄
>建議抽取armv7s架構(gòu)的動態(tài)庫,arm64抽取時會報以上錯誤,原因是dsc_extractor.bundle不能在Xcode10之后使用
- 抽取完成之后,使用Hopper Disassmbler打開想要逆向的動態(tài)庫,就可以看到動態(tài)庫中的源碼信息。
