轉載來源:https://mp.weixin.qq.com/s?__biz=MjM5NTIyNTUyMQ==&mid=2709545175&idx=1&sn=1c080685fabf2f24269c6e544e9213d7&scene=0&key=cf237d7ae24775e8a291c430bd754bd72b2faee6c9edbe6ff3d32afdfd41e008ef2567182b323d8febc85346baef67cb&ascene=0&uin=NDU1NzA2MTk1&devicetype=iMac+MacBookPro12%2C1+OSX+OSX+10.11.5+build
雖然看不懂,但是感覺還是很有分量的,以後需要的時候再學習學習。
iOS 符号表恢複 & 逆向支付寶
原創 2016-08-29 楊君 iOS開發
推薦序
本文介紹了恢複符号表的技巧,并且利用該技巧實作了在 Xcode 中對目标程式下符号斷點調試,該技巧可以顯著地減少逆向分析時間。在文章的最後,作者以支付寶為例,展示出通過在 UIAlertView 的 show 方法處下斷點,進而獲得支付寶的調用棧的過程。
本文涉及的代碼也開源在:https://github.com/tobefuturer/restore-symbol,歡迎 Star 和提 Issue。感謝作者授權發表。
作者介紹:楊君,中山大學計算機系研究所學生,iOS 開發者,擅長領域 iOS 安全和逆向工程,個人部落格:http://blog.imjun.net 。
前言
符号表曆來是逆向工程中的 “必争之地”,而 iOS 應用在上線前都會裁去符号表,以避免被逆向分析。
本文會介紹一個自己寫的工具,用于恢複 iOS 應用的符号表。
直接看效果 , 支付寶恢複符号表後的樣子:
文章有點長,請耐心看到最後,亮點在最後。
為什麼要恢複符号表
逆向工程中,調試器的動态分析是必不可少的,而 Xcode + lldb 确實是非常好的調試利器 , 比如我們在 Xcode 裡可以很友善的檢視調用堆棧,如上面那張圖可以很清晰的看到支付寶登入的 RPC 調用過程。
實際上,如果我們不恢複符号表的話,你看到的調試頁面應該是下面這個樣子:
同一個函數調用過程,Xcode 的顯示簡直天差地别。
原因是,Xcode 顯示調用堆棧中符号時,隻會顯示符号表中有的符号。為了我們調試過程的順利,我們有必要把可執行檔案中的符号表恢複回來。
符号表是什麼
我們要恢複符号表,首先要知道符号表是什麼,他是怎麼存在于 Mach-O 檔案中的。
符号表儲存在 Mach-O 檔案的 __LINKEDIT 段中,涉及其中的符号表(Symbol Table)和字元串表(String Table)。
這裡我們用 MachOView 打開支付寶的可執行檔案,找到其中的 Symbol Table 項。
符号表的結構是一個連續的清單,其中的每一項都是一個
struct nlist
。
// 位于系統庫 <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;
// 符号在記憶體中的位址,類似于函數指針
uint32_t n_value;
};
這裡重點關注第一項和最後一項,第一項是符号名在字元串表中的偏移量,用于表示函數名,最後一項是符号在記憶體中的位址,類似于函數指針(這裡隻說明大概的結構,詳細的資訊請參考官方 Mach O 檔案格式的文檔)。
也就是說如果我們知道了符号名和記憶體位址的對應關系,我們是可以根據這個結構來逆向構造出符号表資料的。
知道了如何構造符号表,下一步就是收集符号名和記憶體位址的對應關系了。
擷取 OC 方法的符号表
因為 OC 語言的特性,編譯器會将類名、函數名等編譯進最後的可執行檔案中,是以我們可以根據 Mach-O 檔案的結構逆向還原出工程裡的所有類,這也就是大名鼎鼎的逆向工具 class-dump 了。class-dump 出來的頭檔案裡是有函數位址的:
是以我們隻要對 class-dump 的源碼稍作修改,即可擷取我們要的資訊。
符号表恢複工具
整理完資料格式,又理清了資料來源,我們就可以寫工具了。
實作過程就不詳細說明了,工具開源在我的 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. 恢複 OC 的符号表,非常簡單
./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol
origin_AlipayWallet 為 Clutch 砸殼後,沒有符号表的 Mach-O 檔案
-o 後面跟輸出檔案位置
3. 把 Mach-O 檔案重簽名打包,看效果
檔案恢複符号表後,多出了 20M 的符号表資訊
Xcode 裡檢視調用棧
可以看到,OC 函數這部分的符号已經恢複了,函數調用棧裡已經能看出大緻的調用過程了,但是支付寶裡,采用了 block 的回調形式,是以還有很大一部分的符号沒能正确顯示。
下面我們就來看看怎麼樣恢複這部分 block 的符号。
擷取 block 的符号資訊
還是同樣的思路,要恢複 block 的符号資訊,我們必須知道 block 在檔案中的儲存形式。
block 在記憶體中的結構
首先,我們先分析下運作時,block 在記憶體中的存在形式。block 在記憶體中是以一個結構體的形式存在的,大緻的結構如下:
struct __block_impl {
/**
block 在記憶體中也是類 NSObject 的結構體,
結構體開始位置是一個 isa 指針
*/
Class isa;
/** 這兩個變量暫時不關心 */
int flags; int reserved;
/**
真正的函數指針!!
*/
void (*invoke)(...);
...
}
說明下 block 中的 isa 指針,根據實際情況會有三種不同的取值,來表示不同類型的 block:
-
_NSConcreteStackBlock
棧上的 block,一般 block 建立時是在棧上配置設定了一個 block 結構體的空間,然後對其中的 isa 等變量指派。
-
_NSConcreteMallocBlock
堆上的 block,當 block 被加入到 GCD 或者被對象持有時,将棧上的 block 複制到堆上,此時複制得到的 block 類型變為了 _NSConcreteMallocBlock。
-
_NSConcreteGlobalBlock
全局靜态的 block,當 block 不依賴于上下文環境,比如不持有 block 外的變量、隻使用 block 内部的變量的時候,block 的記憶體配置設定可以在編譯期就完成,配置設定在全局的靜态常量區。
第 2 種 block 在運作時才會出現,我們隻關注 1、3 兩種,下面就分析這兩種 isa 指針和 block 符号位址之間的關聯。
block isa 指針和符号位址之間的關聯
分析這部分需要用到 IDA 這個反彙編軟體 , 這裡結合兩個實際的小例子來說明:
1._NSConcreteStackBlock
假設我們的源代碼是這樣很簡單的一個 block:
@implementation ViewController
- (void)viewDidLoad {
int t = 2;
void (^ foo)() = ^(){
NSLog(@"%d", t); //block 引用了外部的變量 t
};
foo();
}
@end
編譯完後,實際的彙編長這個樣子:
實際運作時,block 的構造過程是這樣:
- 為 block 開辟棧空間
- 為 block 的 isa 指針指派(一定會引用全局變量:
)_NSConcreteStackBlock
- 擷取函數位址,指派給函數指針
是以我們可以整理出這樣一個特征:
重點來了 !!!
凡是代碼裡用到了棧上的 block,一定會擷取
__NSConcreteStackBlock
作為 isa 指針,同時會緊接着擷取一個函數位址,那個函數位址就是 block 的函數位址。
結合下面這個圖,仔細了解上面這句話
(這張圖和上面那張圖是同一個檔案,不過裁掉了符号表)
利用這個特征,逆向分析時我們可以做如下推斷:
在一個 OC 方法裡發現引用了
__NSConcreteStackBlock
這個變量,那麼在這附近,一定會出現一個函數位址,這個函數位址就是這個 OC 方法裡的一個 block。
比如上面圖中,我們發現 viewDidLoad 裡,引用了
__NSConcreteStackBlock
, 同時緊接着加載了 sub_100049D4 的函數位址,那我們就可以認定 sub_100049D4 是 viewDidLoad 裡的一個 block, sub_100049D4 函數的符号名應該是 viewDidLoad_block.
2. _NSConcreteGlobalBlock
全局的靜态 block,是那種不引用 block 外變量的 block,他因為不引用外部變量,是以他可以在編譯期就進行記憶體配置設定操作,也不用擔心 block 的複制等等操作,他存在于可執行檔案的常量區裡。
不太了解的話,看個例子:
我們把源代碼改成這樣:
@implementation ViewController
- (void)viewDidLoad {
void (^ foo)() = ^(){
//block 不引用外部的變量
NSLog(@"%d", 123);
};
foo();
}
@end
那麼在編譯後會變成這樣:
那麼借鑒上面的思路,在逆向分析的時候,我們可以這麼推斷
- 在靜态常量區發現一個 _NSConcreteGlobalBlock 的引用
- 這個地方必然存在一個 block 的結構體資料
- 在這個結構體第 16 個位元組的地方會出現一個值,這個值是一個 block 的函數位址
3. block 的嵌套結構
實際在使用中,可能會出現 block 内嵌 block 的情況:
- (void)viewDidLoad {
dispatch_async(background_queue ,^{
...
dispatch_async(main_queue, ^{
...
});
});
}
是以這裡 block 就出現了父子關系,如果我們将這些父子關系收集起來,就可以發現,這些關系會構成圖論裡的森林結構,這裡可以簡單用遞歸的深度優先搜尋來處理,詳細過程不再描述。
block 符号表提取腳本(IDA+python)
整理上面的思路,我們發現搜尋過程依賴于 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...
- 等待腳本運作完成,預計 30s 至 60s,運作過程中會有這樣的彈窗
- 彈窗消失即 block 符号表提取完成
- 在 IDA 打開檔案的目錄下 , 會輸出一份名為
的 json 格式 block 符号表block_symbol.json
恢複符号表 & 實際分析
用之前的符号表恢複工具,将 block 的符号表導入 Mach-O 檔案
./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol -j block_symbol.json
-j 後面跟上之前得到的 json 符号表
最後得到一份同時具有 OC 函數符号表和 block 符号表的可執行檔案
這裡簡單介紹一個分析案例 , 你就能體會到這個工具的強大之處了。
- 在 Xcode 裡對
設定斷點-[UIAlertView show]
- 運作程式,并在支付寶的登入頁面輸入手機号和 錯誤的密碼 ,點選登入
- Xcode 會在 ‘密碼錯誤’ 的警告框彈出時停下,左側會顯示出這樣的調用棧
一張圖看完支付寶的登入過程
項目開源位址:
https://github.com/tobefuturer/restore-symbol
歡迎大家在上面提各種 Issues,或者有問題也可以直接 Email([email protected])。
全文完。文本的所有打賞歸作者楊君所有。