附录 I ms_rtti4.idc

       这是我写的解析RTTIvtfable的脚本。你可以从Microsoft VC++ Reversing Helpers打包下载我的两篇文章和脚本。这个脚本的特点包括:

  • 解析RTTI结构,用对应的类名重命名vftables
  • 对于某些简单情形,识别和重命名构造函数和析构函数
  • 输出所有的虚函数表,引用的函数,及类的层次到一个文件里

 

使用说明:

在第一次分析结束后,加载ms_rtti4.idc。它会问你是否想要扫描exe文件来获得vtable。注意这可能是一个漫长的过程。即使你跳过了扫描,你还是可以手工分析vtables。若你选择了扫描,脚本将会试着识别所有的vtablesRTTI,重命名它们,识别和重命名构造函数、析构函数。有时候它会失败,特别是存在虚继承时。扫描结束后,它会打开记录了结果的文本文件。

在加载脚本后,你可以用下面的快捷键手动分析一些MSVC结构:

  • Alt-F8 分析vtable。光标应该位于vtable的开始处。若有RTTI,脚本会使用类名。若没有RTTI,你可以输入一个类名,然后脚本将重命名vtable。若有可识别的虚析构函数,脚本也会重命名它。
  • Alt-F7 分析FuncInfoFuncInfo是存在于有对象分配在栈中或使用了异常处理的函数中的结构体。它的地址被传给函数异常处理程序的_CxxFrameHandler

 

    mov eax, offset FuncInfo1

jmp _CxxFrameHandler

 

大多数情况下,它可以被IDA自动识别和分析。但我的脚本提供了更丰富的信息。你也可以用我第一篇文章中的_ehseh.idc分析所有的FuncInfo

 

把光标放在FuncInfo的开始处,用快捷键。

  • Alt-F9 分析ThrowInfoThrowInfo_CxxThrowException用来实现_throw_操作符的一个辅助结构。它的地址是_CxxThrowException的第二个参数。

 

    lea     ecx, [ebp+e]

    call    E::E()

    push    offset ThrowInfo_E

    lea     eax, [ebp+e]

    push    eax

call    _CxxThrowException

 

把光标放在ThrowInfo的开始处,使用该快捷键。脚本会分析该结构体,重复添加thrown类的名字到注释中。它还可以识别和重命名异常的析构函数和拷贝构造函数。

附录II:恢复一个类的实践

我们的题目是:MSN Messenger 7.5msnmsgr.exe版本号是7.5.324,大小7094272字节)。它使用了大量的C++,含有很多RTTI信息。让我们考虑两个vftable,地址分别在.0040EFD8.0040EFE0。它们完整的RTTI结构层次如下图:

 VC++.RTTI.多重继承.2_RTTI

RTTI hierarchy for MSN Messenger 7.5

 

所以,这两个vftables都属于一个类-CContentMenuItem。通过查看它的基类描述符,我们看到:

  • CContentMenuItem包括三个基类-CDownloader, CNativeEventSinkCNativeEventSource
  • CDownloader包含一个基类-CNativeEventSink
  • 因此CContentMenuItem直接从CDownloader, CNativeEventSinkCNativeEventSource继承,而CDownloaderCNativeEventSink继承。
  • CDownloader位于完整对象的起始处,CNativeEventSource是在0x24偏移处。

 VC++.RTTI.多重继承.2_RTTI_02

 

所以我们可以得出结论,第一个vftable列出了CNativeEventSource的方法,第二个列出了CDownloaderCNatvieEventSink的方法(若干二者均没有虚方法,CContentMenuItem将复用CNativeEventSourcevftable)。现在我们看看有什么指向了这两个表。它们都被两个函数引用,在.052B5E0.052B547。(这更说明了它们都属于同一个类)。进一步,如果我们看看在函数.052B547的开始处,_state_变量初始化为6,意味着那个函数是析构函数。因为一个类只有一个析构函数,我们可以断定.052B5E0就是它的构造函数。让我们看得更近些:

 

CContentMenuItem::CContentMenuItem   proc near

this = esi

    push    this

    push    edi

    mov     this, ecx

    call    sub_4CA77A

    lea     edi, [this+24h]

    mov     ecx, edi

    call    sub_4CBFDB

    or      dword ptr [this+48h], 0FFFFFFFFh

    lea     ecx, [this+4Ch]

    mov     dword ptr [this], offset const CContentMenuItem::'vftable'{for 'CContentMenuItem'}

    mov     dword ptr [edi], offset const CContentMenuItem::'vftable'{for 'CNativeEventSource'}

    call    sub_4D8000

    lea     ecx, [this+50h]

    call    sub_4D8000

    lea     ecx, [this+54h]

    call    sub_4D8000

    lea     ecx, [this+58h]

    call    sub_4D8000

    lea     ecx, [this+5Ch]

    call    sub_4D8000

    xor     eax, eax

    mov     [this+64h], eax

    mov     [this+68h], eax

    mov     [this+6Ch], eax

    pop     edi

    mov     dword ptr [this+60h], offset const CEventSinkList::'vftable'

    mov     eax, this

    pop     this

    retn

sub_52B5E0      endp

 

编译器在prolog后的第一件事情就是从exc拷贝_this_指针到esi,因此随后的地址引用都是基于esi。在初始化vfptr前,它调了两个其它函数,一定是基类的构造函数 我们的例子中就是CDownloaderCNativeEventSource。进到这两个函数中,我们可以确认第一个用CDownloader::’vftable’初始化它的vfptr,第二个用CNativeEventSource::’vftable’。我们还可以进一步看看CDownloader的构造函数-它调用了基类CNativeEventSink的构造函数。

同样,从edi中取得传给第二个函数的_this_指针,它指向this+24h。根据我们的类结构图,这个地址是CNativeEventSource子对象的位置。这从另一个方向确认了调用的第二个函数是CNativeEventSource的构造函数。

调用完基类构造函数后,基类对象的vfptr都被CContentMenuItem的实现重写了,意味着CContentMenuItem覆盖了基类的某些虚方法(或添加了它自己的)。(如果有需要,我们可以比较这些表,查看哪些指针被改变或者添加-被添加的就是CContentMenuItem新实现的。

下面我们看看几个在地址.04D8000的函数调用,这时ecxthis+4Ch被设置到this+5Ch 很显然,初始化了一些成员变量。我们如何得知那是一个编译器生成的构造函数调用还是以程序员写的初始化函数呢?这里有几个提示:

  • 函数使用_thiscall_调用习惯,而且是第一次访问这些域。
  • 这些域的初始化是按照地址增长的方向进行的。

为了保证我们可以查看析构函数中的unwind funclet 那里我们可以看得为这些成员变量,编译器生成的析构函数调用。

这个新类不包括虚函数,也就没有RTTI,所以我们不知道它的真实名字。就叫它RefCountedPtr吧。我们已经确定,4D8000是它的构造函数。析构函数我们可以从CContentMenuItem析构函数的unwind funclet找到,它在63CCB4

回到CContentMenuItem的构造函数,我们看得3个域初始化为0,还有一个vftable指针。这看起来像是一个成员变量内联展开的构造函数(不是基类的,因为若是基类,就应该在继承树中存在)。从用到的vftableRTTI,我们看得它是CEventSinkList模板的一个实例。

现在,我们来写一个可能的类声明:

 

class CContentMenuItem: public CDownloader, public CNativeEventSource

{

/* 00 CDownloader */

/* 24 CNativeEventSource */

/* 48 */ DWORD m_unknown48;

/* 4C */ RefCountedPtr m_ptr4C;

/* 50 */ RefCountedPtr m_ptr50;

/* 54 */ RefCountedPtr m_ptr54;

/* 58 */ RefCountedPtr m_ptr58;

/* 5C */ RefCountedPtr m_ptr5C;

/* 60 */ CEventSinkList m_EventSinkList;

/* size = 70? */

};

 

我们不确定在偏移48处的变量是否不是CNativeEventSource的一部分,因为在CNativeEventSource的构造函数中没有访问过,它很可能是CContentMenuItem的一部分。包含被重命名的方法的构造函数和类结构如下:

 

public: __thiscall CContentMenuItem::CContentMenuItem(void) proc near

    push    this

    push    edi

    mov     this, ecx

    call    CDownloader::CDownloader(void)

    lea     edi, [this+CContentMenuItem._CNativeEventSource]

    mov     ecx, edi

    call    CNativeEventSource::CNativeEventSource(void)

    or      [this+CContentMenuItem.m_unknown48], -1

    lea     ecx, [this+CContentMenuItem.m_ptr4C]

    mov     [this+CContentMenuItem._CDownloader._vfptr], offset const CContentMenuItem::'vftable'{for 'CContentMenuItem'}

    mov     [edi+CNativeEventSource._vfptr], offset const CContentMenuItem::'vftable'{for 'CNativeEventSource'}

    call    RefCountedPtr::RefCountedPtr(void)

    lea     ecx, [this+CContentMenuItem.m_ptr50]

    call    RefCountedPtr::RefCountedPtr(void)

    lea     ecx, [this+CContentMenuItem.m_ptr54]

    call    RefCountedPtr::RefCountedPtr(void)

    lea     ecx, [this+CContentMenuItem.m_ptr58]

    call    RefCountedPtr::RefCountedPtr(void)

    lea     ecx, [this+CContentMenuItem.m_ptr5C]

    call    RefCountedPtr::RefCountedPtr(void)

    xor     eax, eax

    mov     [this+CContentMenuItem.m_EventSinkList.field_4], eax

    mov     [this+CContentMenuItem.m_EventSinkList.field_8], eax

    mov     [this+CContentMenuItem.m_EventSinkList.field_C], eax

    pop     edi

    mov     [this+CContentMenuItem.m_EventSinkList._vfptr], offset const CEventSinkList::'vftable'

    mov     eax, this

    pop     this

    retn

public: __thiscall CContentMenuItem::CContentMenuItem(void) endp

链接和参考资料

[1] http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnarvc/html/jangrayhood.asp
with illustrations (but in Japanese): http://www.microsoft.com/japan/msdn/vs_previous/visualc/techmat/feature/jangrayhood/
C++: Under the Hood (PDF)

[2] http://www.lrdev.com/lr/c/virtual.html

[3] Microsoft patents which describe various parts of their C++ implementation. Very insightful.

  • 5410705: Method for generating an object data structure layout for a class in a compiler for an object-oriented programming language
  • 5617569: Method for implementing pointers to members in a compiler for an object-oriented programming language
  • 5754862: http://freepatentsonline.com/5854931.html Method and system for accessing virtual base classes
  • 5297284: Method and system for implementing virtual functions and virtual base classes and setting a this pointer for an object-oriented programming language
  • 5371891: Method for object construction in a compiler for an object-oriented programming language
  • 5603030: Method and system for destruction of objects using multiple destructor functions in an object-oriented computer system
  • 6138269: Determining the actual class of an object at run time

[4] Built-in types for compiler's RTTI and exception support.
http://members.ozemail.com.au/~geoffch@ozemail.com.au/samples/programming/msvc/language/predefined/index.html

[5] #pragma init_seg
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/_predir_init_seg.asp

 


    ©2000-2007 PEdiy.com All rights reserved.
By PEDIY