作者:PtGiraffe@銀河安全實驗室
公眾號:https://mp.weixin.qq.com/s/p81UG0BKGhAeHjBuEhrx-g
最近,在使用IDA Pro研究iOS應用的過程中,我發現,雖然IDA Pro和神奇的Decompiler插件能夠以超高的還原度生成大部分的源代碼,但如果想要針對某一個方法跟蹤交叉引用(Cross Reference)的話,會發現其中缺失了許多實際存在的交叉引用,這對于靜態分析工作的完整性造成了極大的挑戰。
DUO Labs在https://duo.com/blog/reversing-objective-c-binaries-with-the-reobjc-module-for-ida-pro發布了一篇文章,提到了IDA Pro中的這個問題,解釋了其中的原理,并開發了一款工具,幫助逆向研究者更全面地獲取交叉引用的信息。
Objective-C與IDA Pro交叉引用
Objective-C是C語言的變體。用它開發的程序與Objective-C Runtime共享庫鏈接。這個庫實現了整個對象模型來支持Objective-C。
Runtime的目標之一是盡可能動態。這樣的設計會對對象的函數調用產生影響。在Objective-C中,函數方法的調用被稱為消息傳遞。對象一旦接收到這些消息,會調用其中的一個對象的方法。Runtime在運行時動態解析方法調用。Objective-C源代碼中的方法調用由編譯器轉換為Runtime函數“objc_msgSend()”的調用。
在這里,我們將仔細研究一下IDA Pro模塊REobjc,該模塊將調用objc_msgSend()的正確交叉引用添加到被調用的實際函數中。
如果從一個方法到另一個方法的調用被編譯為對objc_msgSend()的調用,這樣運行時調用的實際函數將不會反映在IDA Pro的交叉引用中。objc_msgSend()的函數簽名定義如下:
id objc_msgSend(id self, SEL op, ...)
對于任何的Objective-C方法調用,前兩個參數是對象的self指針以及selector,selector參數是調用方法對應的字符串表示。帶參數的Objective-C方法將在selector之后按順序傳遞參數。
編譯Objective-C程序
下面的代碼示例為一個簡單的Objective-C源代碼。此init方法包括4個Objective-C方法調用。

從概念上講,編譯器讀取上面的Objective-C方法,并將它們編譯成類似于以下內容的C代碼。這個C代碼示例實際上來自于IDA Pro的反編譯器輸出,它很好地說明了編譯器如何將Objective-C調用轉換為C。

如圖所示,[super init]被轉換為對objc_msgSendSuper2()的調用。這是初始化子類時的常見做法。[NSString string]被轉換為objc_msgSend()調用,該調用被發送到NSString的對象。對于有參數的類方法的調用,[NSMutableData dataWithLength:_length]也被轉換為對objc_msgSend()的調用。
示例中的最后一個Objective-C調用[[BTGattLookup alloc] init]展示了一個常見的allocate-initialize模式。首先,BTGattLookup類收到alloc消息,構造出了該類的實例。然后使用第二個objc_msgSend()調用init。
如果生成對應英特爾X64架構的二進制文件,調用將依照英特爾X64 ABI。函數參數按照RDI,RSI,RDX,RCX,R8,R9的順序在寄存器中傳遞。這意味著RDI寄存器保存自指針,RSI寄存器保存選擇器指針。如有參數,將從RDX寄存器開始保存。

如果要在IDA中添加從一個Objective-C函數到另一個Objective-C函數正確的交叉引用的話,就必須知道RDI和RSI寄存器中的值。對于大多數objc_msgSend()調用,這兩個寄存器中的值通常很容易獲取。
不過,除了上述方法調用方式之外,編譯器可能也會使用其他方式。在X64上,編譯器通常使用CALL和JMP指令生成函數調用。其他的可能性有使用條件跳轉指令或直接分配方法給指令指針,但是概率很小。
編譯器還可以將函數調用編碼為間接調用或直接調用。在間接調用的情況下,指令參數是寄存器。在直接調用的情況下,指令參數是對內存位置的引用。不管哪種情況,都必須確定CALL或JMP是否引用了objc_msgSend()。
此外,為了正確生成交叉引用,必須跟蹤函數調用的返回值。在X64中,函數調用的返回值存儲在RAX寄存器中。如果在源碼中首先alloc一個對象,然后對生成的對象實例執行方法調用,則需要跟蹤存儲在RAX中的對象指針的類型,以正確理解調用objc_msgSend()時傳遞的對象。
REobjc Idapython模塊
REobjc idapython模塊的主要目的是在Objective-C方法之間創建交叉引用。要使用REobjc,打開IDA Pro命令窗口并執行以下Python腳本:
idaapi.require(“reobjc”)
r = reobjc.REobjc(autorun = True)
REOBjc深入
要定位到我們關注的Objective-C Runtime調用,首先需要理解編譯器可以通過多種方式在二進制中對調用進行編碼。無論是直接的還是間接的調用,都有多種方式對調用指令進行編碼。 所有Objective-C程序都鏈接了Objective-C Runtime,因此,所有調用最終都會進入導入的libobjc.dylib庫。
通常,程序將包含一個插樁(stub)函數,它只執行無條件跳轉到objc_msgSend()函數。這允許程序在任意地址加載并且正確調用庫。
在REobjc模塊中,將通過識別objc_msgSend()的所有調用形式來識別方法調用。調用的目標可能是_objc_msgSend,可能是__imp__objc_msgSend形式的導入指針。模塊將查找當前數據庫中所有相關的內容。
模塊使用API idautils.Names()檢索IDA數據庫中所有的名稱,然后通過正則表達式匹配目標函數,將匹配存儲在數組中。分析時,每個候選調用或跳轉指令都與Objective-C Runtime函數列表進行比較,記錄使用任意形式的objc_msgSend()調用,并添加到交叉引用列表中。
隨后模塊迭代二進制文件中的所有函數,并且對于每個函數,迭代其中所有的指令。當識別出CALL或JMP指令時,模塊確定指令的對象。如果對象是寄存器,則從CALL或JMP指令向前尋找寄存器的值。直接調用相比較為簡單,可以立即獲得CALL或JMP的對象。無論哪種情況,如果目標是objc_msgSend(),則CALL或JMP很有可能添加交叉引用。
當識別出objc_msgSend()的調用時,必須標識前兩個函數參數。如上文所述,第一個參數指向接收Objective-C消息對象的指針。第二個參數是指向傳遞給對象的selector或消息的指針。由resolve_register_backwalk_ea()方法解析寄存器的值。
如上文所述,這里有兩個寄存器需要特別關注。 RAX寄存器將保存先前CALL指令的返回值,因此REobjc.resolve_register_backwalk_ea()將通過CALL來跟蹤RAX。此外,由于RDI寄存器用作Objective-C中的self指針,因此有些情況下RDI未在函數內顯式設置。這是因為這些目標函數是在自己的self指針上調用方法。因此,當往回尋找時,如果RDI目標寄存器并且未顯式設置,則代碼將確定該方法來自當前類。
一旦為RDI和RSI寄存器解析了self和selector指針,模塊將對可能方法嘗試創建交叉引用。這是通過利用IDA Pro中現有的Objective-C支持來完成的。模塊函數REobjc.objc_msgsend_xref將處理交叉引用的創建。該函數獲取CALL的位置,以及設置RDI和RSI的位置,并嘗試添加適當的交叉引用。
目前模塊只能添加在當前二進制文件中方法的交叉引用,之后會嘗試添加導入的庫中方法的交叉引用。
REobjc:實踐
接下來,我將通過搭建實際的iOS測試工程來實踐不同方法調用方式的效果以及REobjc模塊對完善交叉引用記錄的效果。
首先,我們看一下最基礎的一個調用。每一個ViweController都會默認調用父類中的viewDidLoad方法。

把工程編譯后,在IDA Pro中看到的匯編代碼如下:

使用Decompiler插件生成偽代碼后:

可以發現,IDA Pro很好地還原了方法調用的邏輯。查看交叉引用信息,發現IDA也識別出了該調用。只不過,識別出來的被調用函數為msgSendSuper2,并不是我們想要的viewDidLoad。

我們運行REobjc后發現,交叉引用中添加了一條記錄,表示ViewController的viewDidLoad方法被調用。DUO Labs作者在文章中也提到,目前的版本在正確識別父類方法調用上會有問題,可能會將子類的方法調用交叉引用添加到父類中,這就導致了我們在子類的交叉引用中看到了這條指向自己的記錄。

接下來我們自己編寫一個方法,在Lion類中新建一個實例方法lionFirstMethod如下:

可以看到方法中僅調用了NSLog用來打印調試語句。我們在工程其他方法中有兩處引用了該方法,可以在搜索中看到:

接下來我們在IDA Pro中查看交叉引用信息,發現IDA Pro一個也沒有識別出來(因為都識別成了msgSend)。

其實,這個結果是讓我有點吃驚的,畢竟代碼中寫的還是非常直白的。


從生成的偽代碼中也可以清晰地看出方法調用


隨后我們嘗試使用REobjc模塊進行補完,發現交叉引用中正確地添加上了兩條引用記錄。

接下來,我們再做一個嘗試。我們在Lion類中添加一個類方法,并且使用反射的方式引用類和該類方法。


不出意外,我們發現,IDA Pro并沒有識別出該調用(僅識別出了msgSend,沒有識別出performSelector,更不用說lionClassMethod了)。

當我們嘗試使用REobjc進行補完,發現也沒有識別出來最終的lionClassMethod。

總體來說,REobjc對一些基礎的方法調用進行了補充,可以說完善了IDA Pro在Objective-C方面的一些缺陷,但是在更加細節的方面需要進行加強。另外,REobjc還沒有針對arm架構的版本,我們使用模擬器進行編譯的可執行文件可以使用REobjc,但是對于實際需要進行逆向分析的目標應用,通常無法獲取非arm版本,所以后續我們也會嘗試針對arm平臺進行補充完善。
參考:https://duo.com/blog/reversing-objective-c-binaries-with-the-reobjc-module-for-ida-pro
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/887/
暫無評論