IDA Pro 的 ida_medigate C++ 插件
[目錄]
對已編譯的 C++ 程式碼進行逆向工程並不有趣。已編譯的 C++ 程式碼的靜態逆向工程令人沮喪。造成如此困難的主要原因是虛函數。與編譯的 C 程式碼相比,沒有明確的程式碼流程。很多時候,人們會花費大量時間試圖理解下一個被呼叫的虛擬函數是什麼,而不是像在編譯的 C 程式碼中那樣僅僅查看該函數。
當一個人研究一個虛擬功能時,他或她需要花大量的時間來找到它的外部參照,這是沒有意義的。
在經歷了太多的 C++ RE 專案之後,我放棄了並決定需要一個靈活的(Python)和穩定的(我可以輕鬆維護)工具來進行此類研究。這個外掛程式的大部分內容是在 2018 年 1 月編寫的,最近我決定清理灰塵並添加對新 IDA (7.2) 類別的支援。
該插件並不總是“開箱即用”,而是作為逆向者的另一個工具。
該插件由兩部分組成:
第一部分不依賴第二部分,因此可以透過根據插件的 API 手動定義這些類,使用該插件對不包含 RTTI 的二進位檔案進行逆向工程。
該插件的獨特之處在於它使用研究人員已經熟悉的相同環境,並且不添加任何新的選單或對象,並且基於已知的 IDA 構建塊(結構、聯合、結構成員的類型等) ) -這使得插件能夠支援IDA 支援的每種架構的C++ 抽象化。
注意: RTTI 解析器解析 x86/x64 g++ RTTI,但其結構可以輕鬆添加對更多架構和編譯器的支援。
plugins/
目錄下)將medigate_cpp_plugin
複製到plugins
目錄並將原始碼路徑新增至您的idapythonrc.py
檔案中
將plugins/ida-referee/referee.py
複製到同一目錄。
假設二進位原始原始碼如下( examples/a.cpp
):
using namespace std ;
class A {
public:
int x_a;
virtual int f_a ()=0;
};
class B : public A {
public:
int x_b;
int f_a (){x_a = 0 ;}
virtual int f_b (){ this -> f_a ();}
};
class Z {
public:
virtual int f_z1 (){cout << " f_z1 " ;}
virtual int f_z2 (){cout << " f_z2 " ;}
};
class C : public B , public Z {
public:
int f_a (){x_a = 5 ;}
int x_c;
int f_c (){x_c = 0 ;}
virtual int f_z1 (){cout << " f_z3 " ;}
};
int main ()
{
C *c = new C ();
c-> f_a ();
c-> f_b ();
c-> f_z1 ();
c-> f_z2 ();
return 0 ;
}
二進位已被剝離,但包含 RTTI。
當我們載入二進位檔案時, main
函數(32 位元版本中的sub_84D
)如下所示:
使用以下命令啟動 g++ RTTI 解析器並執行它:
from ida_medigate.rtti_parser import GccRTTIParser
GccRTTIParser.init_parser()
GccRTTIParser.build_all()
現在刷新 struct C (參見備註部分),將v0
轉換為C *
,再次反編譯:
對於沒有 RTTI 的情況,我們的基礎設施仍然可以手動定義 C++ 類別。對於同一個範例(examples/a32_stripped),您可以手動建立結構 B,然後選擇它的虛擬表並輸入
from ida_medigate import cpp_utils
cpp_utils.make_vtable("B")
make_vtable
也可以取得vtable_ea
和vtable_ea_stop
而不是選取的區域。
然後建立struct C,並套用繼承:
cpp_utils.add_baseclass("C", "B")
現在,您可以透過選擇 C 類 vtable 並鍵入以下內容來重建它:
cpp_utils.make_vtable("C")
加上結構 Z,也重建其 vtable,現在是最酷的部分:
cpp_utils.add_baseclass("C", "Z", 0x0c, to_update=True)
在偏移量 0x0c 處應用 Z 的 C 繼承並刷新結構(參見備註)。
最後剩下的一件事就是更新 C 的第二個 vtable,也就是實作 Z 介面的 vtable。
cpp_utils.make_vtable("C", offset_in_class=0x0c)
ida_medigate 知道這個 vtable 是 Z 類別的 vtable,結果會是:
最終結果與 RTTI 情況相同:
當建立新的 vtable 結構時(透過 RTTI 解析器或使用者手動建立),每個尚未變更的函數都會被重新命名、反編譯,並將其第一個參數設為this
。
雙擊函數對應的結構體成員將導航到該函數,以便使用者可以輕鬆閱讀C++程式碼流程!
函數或其在 vtable 中對應的函數指標成員的每次名稱或類型變更都會在它們之間進行掛鉤和同步。這意味著,例如,使用者可以透過反編譯器視窗更改vtable成員類型,而這個新類型(原型)也將應用於目標函數。
在上圖中的第 15 行中,呼叫了 B::sub_9A8(原始碼中的 B::f_b)。此函數參數是B *
:
但是,該函數也可能由C
實例呼叫(向上轉換)。我們希望看到它的實例將調用的虛擬函數。假設有許多潛在的派生類,因此將this
轉換為C *
並不總是可能的。因此,我們為每個具有不同虛擬表的子類別的基底類別實作一個聯合。可以透過點選 alt+y (選擇不同聯合成員的捷徑)來選擇顯示B
導數的不同派生虛擬表:
所以最終我們可以只「強制轉換」對不同虛函數的特定呼叫:
失意的 C++ 逆向工程師的聖杯。我們維護從虛擬函數到代表它們的 vtable 結構成員的外部引用!
將此與ida-referee
結合使我們能夠追蹤虛擬函數呼叫的所有外部參考!
限制:我們只能追蹤已經反編譯的虛擬呼叫。幸運的是,自動分析知道在函數之間填充參數類型,因此透過轉換更多參數-> 反編譯所有相關函數-> 再次讀取程式碼並轉換更多參數(...)的迭代過程,這種能力變得真正強大的!
我們在 IDAPython 中將結構成員標記為子類別的方式不會立即同步到 IDB。我們所做的駭客是編輯結構,以便觸發同步。您也可以使用
utils.refresh_struct(struct_ptr)
它在結構的末尾添加一個虛擬字段,然後取消定義它。