iOS符號表恢復(fù)&逆向支付寶
推薦序
本文介紹了恢復(fù)符號表的技巧,并且利用該技巧實現(xiàn)了在 Xcode 中對目標程序下符號斷點調(diào)試,該技巧可以顯著地減少逆向分析時間。在文章的***,作者以支付寶為例,展示出通過在 UIAlertView 的 show 方法處下斷點,從而獲得支付寶的調(diào)用棧的過程。
本文涉及的代碼也開源在:https://github.com/tobefuturer/restore-symbol,歡迎 Star 和提 Issue。感謝作者授權(quán)發(fā)表。
作者介紹:楊君,中山大學(xué)計算機系研究生,iOS 開發(fā)者,擅長領(lǐng)域 iOS 安全和逆向工程,個人博客:http://blog.imjun.net 。
前言
符號表歷來是逆向工程中的 “必爭之地”,而 iOS 應(yīng)用在上線前都會裁去符號表,以避免被逆向分析。
本文會介紹一個自己寫的工具,用于恢復(fù) iOS 應(yīng)用的符號表。
直接看效果 , 支付寶恢復(fù)符號表后的樣子:
文章有點長,請耐心看到***,亮點在***。
為什么要恢復(fù)符號表
逆向工程中,調(diào)試器的動態(tài)分析是必不可少的,而 Xcode + lldb 確實是非常好的調(diào)試利器 , 比如我們在 Xcode 里可以很方便的查看調(diào)用堆棧,如上面那張圖可以很清晰的看到支付寶登錄的 RPC 調(diào)用過程。
實際上,如果我們不恢復(fù)符號表的話,你看到的調(diào)試頁面應(yīng)該是下面這個樣子:
同一個函數(shù)調(diào)用過程,Xcode 的顯示簡直天差地別。
原因是,Xcode 顯示調(diào)用堆棧中符號時,只會顯示符號表中有的符號。為了我們調(diào)試過程的順利,我們有必要把可執(zhí)行文件中的符號表恢復(fù)回來。
符號表是什么
我們要恢復(fù)符號表,首先要知道符號表是什么,他是怎么存在于 Mach-O 文件中的。
符號表儲存在 Mach-O 文件的 __LINKEDIT 段中,涉及其中的符號表(Symbol Table)和字符串表(String Table)。
這里我們用 MachOView 打開支付寶的可執(zhí)行文件,找到其中的 Symbol Table 項。
符號表的結(jié)構(gòu)是一個連續(xù)的列表,其中的每一項都是一個 struct nlist。
- // 位于系統(tǒng)庫 <macho-o/nlist.h> 頭文件中
- struct nlist {
- union {
- // 符號名在字符串表中的偏移量
- uint32_t n_strx;
- } n_un;
- uint8_t n_type;
- uint8_t n_sect;
- int16_t n_desc;
- // 符號在內(nèi)存中的地址,類似于函數(shù)指針
- uint32_t n_value;
- };
這里重點關(guān)注***項和***一項,***項是符號名在字符串表中的偏移量,用于表示函數(shù)名,***一項是符號在內(nèi)存中的地址,類似于函數(shù)指針(這里只說明大概的結(jié)構(gòu),詳細的信息請參考官方 Mach O 文件格式的文檔)。
也就是說如果我們知道了符號名和內(nèi)存地址的對應(yīng)關(guān)系,我們是可以根據(jù)這個結(jié)構(gòu)來逆向構(gòu)造出符號表數(shù)據(jù)的。
知道了如何構(gòu)造符號表,下一步就是收集符號名和內(nèi)存地址的對應(yīng)關(guān)系了。
獲取 OC 方法的符號表
因為 OC 語言的特性,編譯器會將類名、函數(shù)名等編譯進***的可執(zhí)行文件中,所以我們可以根據(jù) Mach-O 文件的結(jié)構(gòu)逆向還原出工程里的所有類,這也就是大名鼎鼎的逆向工具 class-dump 了。class-dump 出來的頭文件里是有函數(shù)地址的:
所以我們只要對 class-dump 的源碼稍作修改,即可獲取我們要的信息。
符號表恢復(fù)工具
整理完數(shù)據(jù)格式,又理清了數(shù)據(jù)來源,我們就可以寫工具了。
實現(xiàn)過程就不詳細說明了,工具開源在我的 Github 上了,鏈接:
https://github.com/tobefuturer/restore-symbol
我們來看看怎么用這個工具:
1. 下載源碼編譯
- git clone --recursive https://github.com/tobefuturer/restore-symbol.git
- cd restore-symbol && make
- ./restore-symbol
2. 恢復(fù) OC 的符號表,非常簡單
- ./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol
origin_AlipayWallet 為 Clutch 砸殼后,沒有符號表的 Mach-O 文件
-o 后面跟輸出文件位置
3. 把 Mach-O 文件重簽名打包,看效果
文件恢復(fù)符號表后,多出了 20M 的符號表信息
Xcode 里查看調(diào)用棧
可以看到,OC 函數(shù)這部分的符號已經(jīng)恢復(fù)了,函數(shù)調(diào)用棧里已經(jīng)能看出大致的調(diào)用過程了,但是支付寶里,采用了 block 的回調(diào)形式,所以還有很大一部分的符號沒能正確顯示。
下面我們就來看看怎么樣恢復(fù)這部分 block 的符號。
獲取 block 的符號信息
還是同樣的思路,要恢復(fù) block 的符號信息,我們必須知道 block 在文件中的儲存形式。
block 在內(nèi)存中的結(jié)構(gòu)
首先,我們先分析下運行時,block 在內(nèi)存中的存在形式。block 在內(nèi)存中是以一個結(jié)構(gòu)體的形式存在的,大致的結(jié)構(gòu)如下:
- struct __block_impl {
- /**
- block 在內(nèi)存中也是類 NSObject 的結(jié)構(gòu)體,
- 結(jié)構(gòu)體開始位置是一個 isa 指針
- */
- Class isa;
- /** 這兩個變量暫時不關(guān)心 */
- int flags; int reserved;
- /**
- 真正的函數(shù)指針??!
- */
- void (*invoke)(...);
- ...
- }
說明下 block 中的 isa 指針,根據(jù)實際情況會有三種不同的取值,來表示不同類型的 block:
- _NSConcreteStackBlock
棧上的 block,一般 block 創(chuàng)建時是在棧上分配了一個 block 結(jié)構(gòu)體的空間,然后對其中的 isa 等變量賦值。
2._NSConcreteMallocBlock
堆上的 block,當 block 被加入到 GCD 或者被對象持有時,將棧上的 block 復(fù)制到堆上,此時復(fù)制得到的 block 類型變?yōu)榱? _NSConcreteMallocBlock。
3._NSConcreteGlobalBlock
全局靜態(tài)的 block,當 block 不依賴于上下文環(huán)境,比如不持有 block 外的變量、只使用 block 內(nèi)部的變量的時候,block 的內(nèi)存分配可以在編譯期就完成,分配在全局的靜態(tài)常量區(qū)。
第 2 種 block 在運行時才會出現(xiàn),我們只關(guān)注 1、3 兩種,下面就分析這兩種 isa 指針和 block 符號地址之間的關(guān)聯(lián)。
block isa 指針和符號地址之間的關(guān)聯(lián)
分析這部分需要用到 IDA 這個反匯編軟件 , 這里結(jié)合兩個實際的小例子來說明:
1._NSConcreteStackBlock
假設(shè)我們的源代碼是這樣很簡單的一個 block:
- @implementation ViewController
- - (void)viewDidLoad {
- int t = 2;
- void (^ foo)() = ^(){
- NSLog(@"%d", t); //block 引用了外部的變量 t
- };
- foo();
- }
- @end
編譯完后,實際的匯編長這個樣子:
實際運行時,block 的構(gòu)造過程是這樣:
- 為 block 開辟??臻g
- 為 block 的 isa 指針賦值(一定會引用全局變量:_NSConcreteStackBlock)
- 獲取函數(shù)地址,賦值給函數(shù)指針
所以我們可以整理出這樣一個特征:
重點來了 !!!
凡是代碼里用到了棧上的 block,一定會獲取__NSConcreteStackBlock作為 isa 指針,同時會緊接著獲取一個函數(shù)地址,那個函數(shù)地址就是 block 的函數(shù)地址。
結(jié)合下面這個圖,仔細理解上面這句話
(這張圖和上面那張圖是同一個文件,不過裁掉了符號表)
利用這個特征,逆向分析時我們可以做如下推斷:
在一個 OC 方法里發(fā)現(xiàn)引用了__NSConcreteStackBlock這個變量,那么在這附近,一定會出現(xiàn)一個函數(shù)地址,這個函數(shù)地址就是這個 OC 方法里的一個 block。
比如上面圖中,我們發(fā)現(xiàn) viewDidLoad 里,引用了__NSConcreteStackBlock, 同時緊接著加載了 sub_100049D4 的函數(shù)地址,那我們就可以認定 sub_100049D4 是 viewDidLoad 里的一個 block, sub_100049D4 函數(shù)的符號名應(yīng)該是 viewDidLoad_block.
2. _NSConcreteGlobalBlock
全局的靜態(tài) block,是那種不引用 block 外變量的 block,他因為不引用外部變量,所以他可以在編譯期就進行內(nèi)存分配操作,也不用擔心 block 的復(fù)制等等操作,他存在于可執(zhí)行文件的常量區(qū)里。
不太理解的話,看個例子:
我們把源代碼改成這樣:
- @implementation ViewController
- - (void)viewDidLoad {
- void (^ foo)() = ^(){
- //block 不引用外部的變量
- NSLog(@"%d", 123);
- };
- foo();
- }
- @end
那么在編譯后會變成這樣:
那么借鑒上面的思路,在逆向分析的時候,我們可以這么推斷
- 在靜態(tài)常量區(qū)發(fā)現(xiàn)一個 _NSConcreteGlobalBlock 的引用
- 這個地方必然存在一個 block 的結(jié)構(gòu)體數(shù)據(jù)
- 在這個結(jié)構(gòu)體第 16 個字節(jié)的地方會出現(xiàn)一個值,這個值是一個 block 的函數(shù)地址
3. block 的嵌套結(jié)構(gòu)
實際在使用中,可能會出現(xiàn) block 內(nèi)嵌 block 的情況:
- - (void)viewDidLoad {
- dispatch_async(background_queue ,^{
- ...
- dispatch_async(main_queue, ^{
- ...
- });
- });
- }
所以這里 block 就出現(xiàn)了父子關(guān)系,如果我們將這些父子關(guān)系收集起來,就可以發(fā)現(xiàn),這些關(guān)系會構(gòu)成圖論里的森林結(jié)構(gòu),這里可以簡單用遞歸的深度優(yōu)先搜索來處理,詳細過程不再描述。
block 符號表提取腳本(IDA+python)
整理上面的思路,我們發(fā)現(xiàn)搜索過程依賴于 IDA 提供各種引用信息,而 IDA 是提供了編程接口的,可以利用這些接口來提取引用信息。
IDA 提供的是 Python 的 SDK,***完成的腳本也放在倉庫里 search_oc_block/ida_search_block.py (https://github.com/tobefuturer/restore-symbol/blob/master/search_oc_block/ida_search_block.py。
提取 block 符號表
這里簡單介紹下怎么使用上面這個腳本:
- 用 IDA 打開支付寶的 Mach-O 文件
- 等待分析完成! 可能要一個小時
- Alt + F7 或者 菜單欄 File -> Script file...
4.等待腳本運行完成,預(yù)計 30s 至 60s,運行過程中會有這樣的彈窗
5.彈窗消失即 block 符號表提取完成
6.在 IDA 打開文件的目錄下 , 會輸出一份名為block_symbol.json的 json 格式 block 符號表
恢復(fù)符號表 & 實際分析
用之前的符號表恢復(fù)工具,將 block 的符號表導(dǎo)入 Mach-O 文件
- ./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol -j block_symbol.json
-j 后面跟上之前得到的 json 符號表
***得到一份同時具有 OC 函數(shù)符號表和 block 符號表的可執(zhí)行文件
這里簡單介紹一個分析案例 , 你就能體會到這個工具的強大之處了。
1.在 Xcode 里對 -[UIAlertView show] 設(shè)置斷點
2.運行程序,并在支付寶的登錄頁面輸入手機號和 錯誤的密碼 ,點擊登錄
3.Xcode 會在 ‘密碼錯誤’ 的警告框彈出時停下,左側(cè)會顯示出這樣的調(diào)用棧
一張圖看完支付寶的登錄過程
項目開源地址:
https://github.com/tobefuturer/restore-symbol
歡迎大家在上面提各種 Issues,或者有問題也可以直接 Email(tobefuturer@gmail.com)。