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。标记此 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)
它在结构的末尾添加一个虚拟字段,然后取消定义它。