在逆向C++模块时,经常遇到像各种各样的非直接跳转,而IDA F5反编译出来的代码可读性非常差,诸如*(*this + offset) + offset这样的操作,反汇编窗口对应着call qword ptr[rax + offset]这样的语句,对漏洞分析和逆向工程非常不友好,而HexRaysCodeXplorer这样的插件似乎只适合逆向Windows程序,在macOS下逆向一些C++项目非常不方便,于是便着手分析并尝试自己制作一个可用的IDA脚本。

0x01 分析

1. MetaClass

我们可以发现每个类均含有MetaClass::MetaClass方法,以IOBluetoothHCIUserClient为例,其汇编代码如下:

image-20181119233438877

反汇编之后的代码如下: image-20181119233519311

根据XNU源码中OSMetaClass的定义,我们可以发现如下信息:

OSMetaClass::OSMetaClass(
    const char        * inClassName, //类的名称
    const OSMetaClass * inSuperClass, //父类的metaClass指针
    unsigned int        inClassSize); //类对象的大小

从MetaClass构造函数中,我们可以获取的信息包括,类的名称,类的大小。同时我们还可以发现这样一个操作*this = offset_xxx,很明显这是一个虚函数表地址的赋值操作,这个操作同样也存在于其他类的构造函数中,I/O Kit中几乎无一例外。

2. 构造函数

I/O Kit类本身并不实现构造函数,而是由几个特殊的宏实现(OSMetaClass.h中定义),一个实例如下:

OSDeclareDefaultStructors(IOHIDResourceDeviceUserClient);
OSDefineMetaClassAndStructors(IOHIDResourceDeviceUserClient, IOUserClient);

而通过对构造函数反汇编,一般都有如下的结果

Imgur

同上,off_xxx处更新了虚函数表

3. 获得虚函数表

通过对mach-o文件的__DATA段的__const节(注意__const节在一个Binary中可能存在两个)扫描,可以得到函数的虚函数表(IDA也可以识别出,实际上存在两个字长(16字节)的偏移,实际的虚表地址为off_xxx而非vtable for xxx)

Imgur

只需要通过搜索vtable符号+ 2 * 字长即可得到真正的虚函数表的地址,或者根据构造函数进行分析,找到*this = off_xxx这一操作,逐字长扫描相关偏移,直到没有符号,即可获取完整的虚表。

一个很好的工具是: Github : mackextdump

0x02 Indirect Call的识别

C++中含有相当多的Indirect Call,而IDA无法直接识别出来,我们需要通过手动编写工具来恢复出来这些信息,一个Indirect Call示例如下:

call qword ptr [rxx + offset]

1. 向上回溯

这种方法适合虚函数表跳转的识别,简单的如下:

虚函数表可以在当前frame内找到

Imgur

复杂的函数的虚函数表在其他frame内赋值,一般为构造函数,分析前需要手动构造上下文

Imgur

根据寄存器的参数的传递顺序,我们可以发现

mov rbx, rdi ;; rbx = this
mov rax, [rbx] ;; rax = *this ; 取vtable,即rax = vtable
call qword ptr [rax+388h] ; 读取函数地址,即rcx = vtable[offset]
; 由于IDA基于静态分析,这种非直接跳转无法识别,导致F5反编译出来的代码非常难看

一个有效的算法是: 搜寻上述形式的Indirect Call,并向前回溯寄存器的赋值操作,最后根据函数的虚表,给当前Indirect Call的地址加上注释

2. 转化为结构体

也可以通过上述的虚表信息,我们可以将相关的虚表和类对象转化为结构体(Local Types, Shift + F1),从而使得IDA的反编译直接识别出来相关的函数调用,例如:

虚函数表定义:

image-20181119234755228

类对象的定义:

image-20181119234917080

接下来我们根据函数的demangled name修改C++函数的声明,并将this指针的类型转化为上述结构体,即可识别出上述的非直接跳转,一个不完善的结果如下(反汇编的代码具有可读性):

image-20181119235312118

其可读性,对比原版就可以看到伤害:

image-20181119235926760

存在的问题: 这种方法的优点是,生成的反汇编的代码可读性比较好,但是由于是只能在结构体内给相关成员添加注释,以实现跳转到函数定义的方式,有没有办法可以实现直接在反编译和反汇编代码中标记注释呢?

这种方法参考了IDA的第三方插件HexRaysCodeXplorer的做法,可能需要遍历IDA 的ctree来进一步获取信息,实现对结构体的xref以及在反汇编窗口注释(当前需要tab在反汇编和反编译窗口之间切换),还有点击结构体切换到实现

3. 改进方案

Bradon Azad使用了基于数据流分析的方案,使得可以根据对特定偏移的引用,来辅助创建相应的结构体,不过是针对iOS Kernel Cache的IDA Kernel Cache,正在尝试学习和往macOS上移植。

目前项目地址为: https://github.com/pwn-orz/ida_iokit,立个flag,等待继续更新。