12.1 识别类和类之间的关系(11)
以代码清单12-5的Debug版为例,使用IDA对其进行分析,先找到某个子类的构造函数。由于子类的构造函数必然会先调用父类的构造函数,因此我们利用交叉参考功能即可查询出所有引用这个父类构造函数的指令的位置,这当然包括这个父类的所有直接子类构造函数的位置,借此即可判定父类派生的所有直接子类,如图12-6所示。
|
(点击查看大图)图12-6 父类派生关系图 |
接下来分析sub_4011E0函数的功能,反汇编代码如下所示:
- ; 注意这里的引用提示:是在sub_4011C0函数中调用本函数,稍后会带领读者去这个地址"探险"
- .text:004011E0 sub_4011E0 proc near ; CODE XREF: sub_4011C0+3↑p
- .text:004011E0
- .text:004011E0 var_10= dword ptr -10h
- .text:004011E0 var_C= dword ptr -0Ch
- .text:004011E0 var_4= dword ptr -4
- .text:004011E0
- .text:004011E0 push 0FFFFFFFFh
- .text:004011E2 push offset unknown_libname_36 ; Microsoft VisualC 2-9/net runtime
- .text:004011E7 mov eax, large fs:0
- .text:004011ED push eax
- .text:004011EE mov large fs:0, esp
- .text:004011F5 push ecx
- .text:004011F6 push esi ; 以上注册异常处理,保留寄存器环境
- .text:004011F7 mov esi, ecx
- .text:004011F9 mov [esp+14h+var_10], esi
- ; 在虚表指针处写入子类虚表地址
- .text:004011FD mov dword ptr [esi], offset vTable_40C0D0
- .text:00401203 mov [esp+14h+var_4], 0 ; 计数器置为0
- .text:0040120B call ds:pfnGetCChinese
- .text:00401211 push eax ; 获取字符串,并向printf传递参数
- .text:00401212 push offset Format ; "%s::ShowSpeak()\r\n"
- .text:00401217 call _printf
- .text:0040121C add esp, 8 ; 执行printf,并平衡参数
- .text:0040121F mov ecx, esi ; 传递this指针
- .text:00401221 mov [esp+14h+var_4], 0FFFFFFFFh ; 将计数器置为-1
- ; 在虚表指针处写入父类虚表地址
- .text:00401229 mov dword ptr [esi], offset vTable_40C0DC
- .text:0040122F call ds:pfnGetCPerson
- .text:00401235 push eax ; 获取字符串,并向printf传递参数
- .text:00401236 push offset Format ; "%s::ShowSpeak()\r\n"
- .text:0040123B call _printf
- ; 流水线优化,因为mov large fs:0, ecx和当前指令依赖同一个寄存器ecx,会造成指令相关性,所以
- ; 提前到add esp, 8之上,以提高流水线的并行能力
- .text:00401240 mov ecx, [esp+1Ch+var_C]
- .text:00401244 add esp, 8 ; 执行printf,并平衡参数
- .text:00401247 mov large fs:0, ecx ; 恢复环境并还原SEH
- .text:0040124E pop esi
- .text:0040124F add esp, 10h
- .text:00401252 retn
- .text:00401252 sub_4011E0 endp
以上代码中存在虚表的写入操作,其写入顺序和前面分析的构造函数相反,先写入子类自身的虚表,然后写入父类的虚表,满足了析构函数的充分条件。我们将虚构函数命名为Destructor_4011E0,IDA会提示符号名称过长,不必理会,单击“确定”按钮即可。
Destructor_4011E0被sub_4011C0调用,因此接下来分析sub_4011C0,这个函数有一个参数,IDA给出的名称为arg_0。
- ; 查看引用参考可得知,这个函数是在虚表vTable_40C0D0中定义的第一个虚函数
- .text:004011C0 sub_4011C0 proc near ; DATA XREF: .rdata:vTable_40C0D0o
- .text:004011C0
- .text:004011C0 arg_0= byte ptr 4
- .text:004011C0
- .text:004011C0 push esi
- .text:004011C1 mov esi, ecx ; esi保留了this指针
- .text:004011C3 call Destructor_4011E0 ; 先调用析构函数
- .text:004011C8 test [esp+4+arg_0], 1
- ; 如果参数为1,则以对象首地址为目标释放内存,否则本函数仅仅执行对象的析构函数
- .text:004011CD jz short loc_4011D8
- .text:004011CF push esi ; 传入对象的首地址
- .text:004011D0 call 3@YAXPAX@Z ; operator delete(void *)
- .text:004011D5 add esp, 4 ; 调用delete,并平衡参数
- .text:004011D8
- .text:004011D8 loc_4011D8: ; CODE XREF: sub_4011C0+Dj
- .text:004011D8 mov eax, esi
- .text:004011DA pop esi
- .text:004011DB retn 4
- .text:004011DB sub_4011C0 endp