天天看點

iOS 符号表恢複 & 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 & 實際分析 一張圖看完支付寶的登入過程

轉載來源: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 應用的符号表。

直接看效果 , 支付寶恢複符号表後的樣子:

iOS 符号表恢複 & 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 & 實際分析 一張圖看完支付寶的登入過程

文章有點長,請耐心看到最後,亮點在最後。

為什麼要恢複符号表

逆向工程中,調試器的動态分析是必不可少的,而 Xcode + lldb 确實是非常好的調試利器 , 比如我們在 Xcode 裡可以很友善的檢視調用堆棧,如上面那張圖可以很清晰的看到支付寶登入的 RPC 調用過程。

實際上,如果我們不恢複符号表的話,你看到的調試頁面應該是下面這個樣子:

iOS 符号表恢複 & 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 & 實際分析 一張圖看完支付寶的登入過程

同一個函數調用過程,Xcode 的顯示簡直天差地别。

原因是,Xcode 顯示調用堆棧中符号時,隻會顯示符号表中有的符号。為了我們調試過程的順利,我們有必要把可執行檔案中的符号表恢複回來。

符号表是什麼

我們要恢複符号表,首先要知道符号表是什麼,他是怎麼存在于 Mach-O 檔案中的。

符号表儲存在 Mach-O 檔案的 __LINKEDIT 段中,涉及其中的符号表(Symbol Table)和字元串表(String Table)。

這裡我們用 MachOView 打開支付寶的可執行檔案,找到其中的 Symbol Table 項。

iOS 符号表恢複 & 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 & 實際分析 一張圖看完支付寶的登入過程

符号表的結構是一個連續的清單,其中的每一項都是一個

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 出來的頭檔案裡是有函數位址的:

iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程

是以我們隻要對 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 的符号表資訊

iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程

Xcode 裡檢視調用棧

iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程

可以看到,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:

  1. _NSConcreteStackBlock

    棧上的 block,一般 block 建立時是在棧上配置設定了一個 block 結構體的空間,然後對其中的 isa 等變量指派。

  2. _NSConcreteMallocBlock

    堆上的 block,當 block 被加入到 GCD 或者被對象持有時,将棧上的 block 複制到堆上,此時複制得到的 block 類型變為了 _NSConcreteMallocBlock。

  3. _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
           

編譯完後,實際的彙編長這個樣子:

iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程

實際運作時,block 的構造過程是這樣:

  1. 為 block 開辟棧空間
  2. 為 block 的 isa 指針指派(一定會引用全局變量:

    _NSConcreteStackBlock

  3. 擷取函數位址,指派給函數指針

是以我們可以整理出這樣一個特征:

重點來了 !!!

凡是代碼裡用到了棧上的 block,一定會擷取

__NSConcreteStackBlock

作為 isa 指針,同時會緊接着擷取一個函數位址,那個函數位址就是 block 的函數位址。

結合下面這個圖,仔細了解上面這句話

(這張圖和上面那張圖是同一個檔案,不過裁掉了符号表)

iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程

利用這個特征,逆向分析時我們可以做如下推斷:

在一個 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
           

那麼在編譯後會變成這樣:

iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程

那麼借鑒上面的思路,在逆向分析的時候,我們可以這麼推斷

  1. 在靜态常量區發現一個 _NSConcreteGlobalBlock 的引用
  2. 這個地方必然存在一個 block 的結構體資料
  3. 在這個結構體第 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 符号表

這裡簡單介紹下怎麼使用上面這個腳本:

  1. 用 IDA 打開支付寶的 Mach-O 檔案
  2. 等待分析完成! 可能要一個小時
  3. Alt + F7 或者 菜單欄

    File -> Script file...

    iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程
  4. 等待腳本運作完成,預計 30s 至 60s,運作過程中會有這樣的彈窗
    iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程
  5. 彈窗消失即 block 符号表提取完成
  6. 在 IDA 打開檔案的目錄下 , 會輸出一份名為

    block_symbol.json

    的 json 格式 block 符号表
    iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程
    iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程

恢複符号表 & 實際分析

用之前的符号表恢複工具,将 block 的符号表導入 Mach-O 檔案

./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol -j block_symbol.json
           

-j 後面跟上之前得到的 json 符号表

最後得到一份同時具有 OC 函數符号表和 block 符号表的可執行檔案

這裡簡單介紹一個分析案例 , 你就能體會到這個工具的強大之處了。

  1. 在 Xcode 裡對

    -[UIAlertView show]

    設定斷點
    iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程
  2. 運作程式,并在支付寶的登入頁面輸入手機号和 錯誤的密碼 ,點選登入
  3. Xcode 會在 ‘密碼錯誤’ 的警告框彈出時停下,左側會顯示出這樣的調用棧

一張圖看完支付寶的登入過程

iOS 符号表恢複 &amp; 逆向支付寶 推薦序 前言 為什麼要恢複符号表 符号表是什麼 擷取 OC 方法的符号表 符号表恢複工具 擷取 block 的符号資訊 恢複符号表 &amp; 實際分析 一張圖看完支付寶的登入過程

項目開源位址:

https://github.com/tobefuturer/restore-symbol

歡迎大家在上面提各種 Issues,或者有問題也可以直接 Email([email protected])。

全文完。文本的所有打賞歸作者楊君所有。