虚函数表中存储了什么?(Android NDK, GNU C++)

逆向工程 C++ 安卓 手臂 虚函数
2021-06-11 22:23:18

我看到虚拟方法表(VMT,又名 VFT)包含一些奇数,例如:

obj:0x123c1a8 [704] vmt:0x611f77f8
data: 0123c1a8 f8 77 1f 61 00 96 23 01 00 00 00 00 c4 e0 b4 5c 00 00 00 00 00 00 00 00 00 00 00 00 30 f2 76 5e 78 a2 23 01 78 e0 b4 5c 00 00 00 00 14 b1 75 5e 00 00 00 00 f8 7c 1f 61 f0 a2 23 01 00 00 00 00 `w`a``#````````\````````````0`v^x`#`x``\``````u^`````|`a``#`````
vmt:  611f77f8 09 63 98 60 25 63 98 60 95 67 98 60 e5 67 98 60 9d 68 98 60 e1 2f 9f 5c 51 07 9f 5c 45 07 9f 5c 49 07 9f 5c 4d 07 9f 5c 99 a3 69 5e a1 a3 69 5e 31 dd 68 5e a1 dd 68 5e d1 dd 68 5e c1 dd 68 5e `c``%c```g```g```h```/`\Q``\E``\I``\M``\``i^``i^1`h^``h^``h^``h^

(你会在虚函数表的开头看到这些奇怪的 0x60986309 和 60986325。)

虚函数表中存储了什么?

编译器是 GCC,更准确地说,是来自 Android NDK 的用于 ARM 的 G++(GNU C++)。

编辑:我看到 VMT 包含不是函数指针的条目。

这是 Qt 类的反汇编代码QSystemLocale,您会看到成对的行,这些是 IDA 输出行,后跟由以下处理的相同行c++filt

 _ZTV13QSystemLocale      DCD 0, _ZTI13QSystemLocale, _ZN13QSystemLocaleD2Ev+1, _ZN13QSystemLocaleD0Ev+1
 vtable for QSystemLocale DCD 0, typeinfo for QSystemLocale, QSystemLocale::~QSystemLocale()+1, QSystemLocale::~QSystemLocale()+1

             DCD _ZNK13QSystemLocale5queryENS_9QueryTypeE8QVariant+1
             DCD QSystemLocale::query(QSystemLocale::QueryType, QVariant) const+1

             DCD _ZNK13QSystemLocale14fallbackLocaleEv+1
             DCD QSystemLocale::fallbackLocale() const+1

在这里我们看到:

  1. VMT一开始就有一个可疑的0。建议有时它可能不是 0 是合理的,因为它发生在上面的转储中,但它是什么?见这里

  2. 有类型信息。它允许做什么?见这里

  3. 两个析构函数,_ZN13QSystemLocaleD2Ev_ZN13QSystemLocaleD0Ev看看为什么

    最后:

  4. 我可以确定 VMT 没有在运行时初始化/修改吗?如果它被写入,写入什么?

typeinfo是:

             EXPORT _ZTI13QSystemLocale
             EXPORT typeinfo for QSystemLocale

 _ZTI13QSystemLocale DCD _ZTVN10__cxxabiv117__class_type_infoE+8, _ZTS13QSystemLocale
 typeinfo for QSystemLocale DCD vtable for __cxxabiv1::__class_type_info+8, typeinfo name for QSystemLocale
2个回答

为什么你认为这些值是“奇怪的”?奇怪的是“奇怪”还是“不能被 2 整除”?

vmt 中的值是指向类对象的函数(或方法,即 vmt 与 vft)的指针。这些看起来还不错,因为 vmt 的地址0x611f77f8略高于方法60986309, 60986325由于编译器首先编译所有类方法,然后创建 vmt,在比方法稍高的内存地址处的 vmt 似乎很正常。

现在,ARM 上的指令是 4 字节对齐的,在 THUMB 模式下,它们是 2 字节对齐的,但函数指针指向的地址不是。这是因为,在执行间接分支时,ARM 处理器使用最低位在 THUMB 和 ARM 模式之间切换。所以,地址0x60986309实际上意味着“将处理器切换到 THUMB 模式,然后开始执行0x60986308。当调用其中一个 vmt 函数时,编译器生成一条指令将 vmt 引用放入寄存器,然后BLX在该寄存器上执行 a ,所以最低位将具有切换指令类型的含义。

这个ARM参考:

Rm is a register containing an address to branch to.
....
The BX and BLX instructions can change the processor state from ARM to Thumb, or from Thumb to ARM.
BLX label always changes the state.
BX Rm and BLX Rm derive the target state from bit[0] of Rm:
    if bit[0] of Rm is 0, the processor changes to, or remains in, ARM state
    if bit[0] of Rm is 1, the processor changes to, or remains in, Thumb state.

至于VFT开头的0,必须是http://mentorembedded.github.io/cxx-abi/abi.html#vtable-components中描述的到top偏移量当一个类派生自多个基类时使用该值。

这个 0 是vmt[-2]你在运行时获得 vmt 地址的时候,但在 IDA 反汇编中它是 vtable 的第一个元素。

; DerivedStuff::DerivedStuff(int, int)
            EXPORT _ZN12DerivedStuffC2Eii
_ZN12DerivedStuffC2Eii
            PUSH            {R4,LR}
            MOV             R4, R0
            BL              _ZN9BaseStuffC2Eii ; BaseStuff::BaseStuff(int,int)
            LDR             R3, =(_ZTV12DerivedStuff_ptr - 0x1AF4)
            MOV             R0, R4
            ADD             R3, PC ; _ZTV12DerivedStuff_ptr
            LDR             R3, [R3] ; `vtable for'DerivedStuff
            ADDS            R3, #8 ; <===== this is where 8 is added to the vmt address!!!
            STR             R3, [R4]
            POP             {R4,PC}
; End of function DerivedStuff::DerivedStuff(int,int)