hopper的逆向代碼功能並不如想象中那么好,尤其是在逆向c++代碼時。對於從ObjC進入iOS開發又不太清楚運行時的人員來說,hopper可以將反匯編碼輸出成[obj selector:what]這樣的ObjC式的函數調用,一定會很驚嘆。其實ObjC式函數調用的關鍵就是樞紐函數的msg_send(c style)以及樞紐機制(ObjC對象消息機制)中的分派機制(消息分派)的消息@selector。msg_send是c風格的函數,只要參照其傳參設定(前面文章以經介紹過,《gcc在x64體系中如何傳遞參數,linux,mac,iOS適用》)。對象消息@selector是一個SEL類型,其定義是const char* const, 而且在映像的數據段必須有對應的字符串。這樣一來,只要有這個SEL消息的字符串,就可以寫一個類似格式轉換輸出的腳本。的確,用hopper來逆向ObjC的函數可以大大簡化反匯編碼的閱讀(調用了哪些函數這一點上),例如一個ObjC式的函數調用,都那么重復那些步驟而且是許多步,一屏也看不了幾個調用。
但c++的情況就不一樣。下面我選了一個函數,因為hopper的簡單分析失效了。
我選用的是cocoa的iOS模擬器x64版本里的QuartzCore.framework的一個函數,CA::Layer::set_bit。下面是hopper的逆向偽代碼:

就這樣一瞥,效果很不錯,但是卻是錯誤的。
首先是傳參的解析,這個一錯,就是開始錯了,后面就大錯。這比直接去分析反匯編碼還冤枉。
其次是沒有辦法分辨靜態成員函數還是成員函數。
第三就是不清楚成員函數指針的調用。
下面是我對hopper加的批注:
先是逆向偽代碼:

再是反匯編代碼:
<20161002 補充>
hopper將rdi至r8五個寄存器往函數原型的五個參數上硬套。
實際上CA::Layer::set_bit是一個成員函數,rdi是對象指針,rsi才是函數原型的第一個參數,一共使用了4個寄存數作為傳參,最后一個參數是一個函數指針,沒有使用r9,而是使用了堆棧兩個cpu字長的長度作為最后一個參數。
</20161002>

現在開始逐一分析。
首先hopper無視了this指針這個參數。為什么ObjC的函數它又可以解析出this,嚴格來說self不是this指針,它是llvm編譯器約定在c風格的函數msg_send定義的第一個參數,以及msg_send_stret定義的第二個參數,也就是說ObjC的函數調用實質是一個c風格的函數調用。而c++的成員函數調用又有另外的約定,所以hopper失效了。
hopper以c風格函數的傳參約定套用在成員函數CA::Layer::set_bit定義的參數序列,這是錯誤的開端。可以參看反匯編碼,寄存器%rsi是傳送到了-0x40(%rbp)的內存單元中,而這個%rsi才是c++成員函數的第一個參數,而對應於hopper逆向代碼的arg0。然而hopper錯誤套用約定,hopper看到的參數向左shift了一個身位,所以它將%rsi看成了成員函數的第二個參數,var_40=arg1。
所以hopper正確的逆向應該從這樣開始:
int CA::Layer::set_bit(uint32_t arg1, uint32_t arg2, uint32_t arg3, bool arg4, void(*arg5)(*)) { assert(this == arg0); var_40 = arg1;
這樣它逆向輸出的代碼才有可能不誤導人。
你看出了不同了嗎,我再補一下hopper的錯誤生成作對比
int CA::Layer::set_bit(uint32_t arg0, uint32_t arg1, uint32_t arg2, bool arg3, void(*arg4)(*)) { var_40 = arg1;
這在實際開發和調試中,都足以冤枉大量工作時間。
通過調整,hopper失效的問題就是解決了嗎?
沒有,hopper逆向還是失效了。錯誤就在對成員函數指針的解析。逆向的樣本CA::Layer::set_bit最后一個參數是一個成員函數指針,用於回調用的。但在hopper的逆向代碼中,這個參數並沒有被使用過,因為hopper失靈了。
或許在msvc平台下,hopper將成員函數指針看作是void*可能會擦邊正確,但是在*nix和bsd體系的平台上就不一定。因為*nix和bsd是由GNU_C標准的編譯器編譯的,也就是GNU_C中成員函數指針的規則並非一個單純的函數入口地址,前面的文章我也有介紹過。我們來回看反匯編碼,這個樣本函數並沒有使用r9來傳遞arg5,然而卻使用了兩個堆棧單元來傳參,這樣一來參數個數就多出來了。如果這時你還不清楚GNU_C編譯器下的成員函數指針是什么一回事,就比較難以解釋這個函數了。詳細請參看我前面的文章,《反匯編帶看清成員函數指針的本尊(gcc@x64平台)》。這里的兩個堆棧單元其實就是最后一個參數(成員函數指針),而且不是一個單純的地址指針,而是一個sizeof(void*)*2大小的對象。沒錯成員函數指針是一個對象。在使用var_50和var_58的地方,其實就是在回調這個成員函數指針的引用。
但是hopper的逆向還是錯了(rcx)(rdi),hopper將成員函數指針當作普通函數指針來解釋了。清楚的人都明白,rdi就不是成員函數定義輸入參數,修正后應該是rdi->(rcx)(var_38)。
還有就是hopper並沒有正確分析出這個樣本函數的返回類型,用了一個int來充當。其實從返回部分的反匯編碼可以看到,並沒有對rax作是任何操作,也就是說返回void。
第二個問題和上面的問題一樣,但反應在逆向代碼體中對其它成員函數的調用。CA::Transaction::lock()這個並非靜態成員函數,必須要有一個調用的對象,然而是hopper沒有明確生成關於調用對象的指向。
r12 = CA::Transaction::ensure_compat();
CA::Transaction::lock();
應該為:
rdi = r12 = CA::Transaction::ensure_compat();
((CA::Transaction*)r12)->lock();
第三個問題,不清楚成員函數指針的調用,這一點在第一個問題談及成員函數指針參數時講了。
最后我貼上我逆向出來的代碼:




