IDA 不会为从 PDB 加载的子类创建 Vtbl 结构

逆向工程 艾达 虚函数 数据库
2021-07-10 07:17:15

我正在反编译 MFC 4.0 应用程序,现在MFCS42.PDB将 MFC 4.2 源代码加载到 IDA 7.0(没有 MFC 4.0 源代码),以使其创建表示许多 MFC 类和虚拟函数表的适当结构。

但是,IDA 似乎只*Vtbl为非常基类(如 )创建结构CObject,而不是为像 之类的子类创建结构,CCmdTarget如下所示:

struct CObjectVtbl // totally correct
{
    CRuntimeClass *(__thiscall *GetRuntimeClass)(CObject *this);
    void (__thiscall *~CObject)(CObject *this);
    void (__thiscall *Serialize)(CObject *this, CArchive *);
    void (__thiscall *AssertValid)(CObject *this);
    void (__thiscall *Dump)(CObject *this, CDumpContext *);
};
struct __cppobj CObject // totally correct
{
    CObjectVtbl *vfptr;
};

struct __cppobj CCmdTarget : CObject // wrong, makes it have only a CObject vftable
{
    int m_dwRef;
    IUnknown *m_pOuterUnknown;
    unsigned int m_xInnerUnknown;
    CCmdTarget::XDispatch m_xDispatch;
    int m_bResultExpected;
    CCmdTarget::XConnPtContainer m_xConnPtContainer;
    AFX_MODULE_STATE *m_pModuleState;
};

实际上,这会导致 缺少新的虚函数CCmdTarget,因为它仅CObjectVtbl通过继承自 来引用CObject,但CCmdTarget还有 7 个方法。

我之前手工制作了这些结构(你可以猜到这很乏味),它实际上应该看起来更像这样:

// CObject and CObjectVtbl same as above

struct CCmdTargetVtbl : CObjectVtbl // inherit to keep base methods
{
    BOOL (__thiscall *OnCmdMsg)(CCmdTarget *this, UINT nID, int nCode, void *pExtra, void *pHandlerInfo);
    void (__thiscall *OnFinalRelease)(CCmdTarget *this);
    AFX_MSGMAP *(__thiscall *GetMessageMap)(CCmdTarget *this);
    int field_20; // Don't know names yet
    int field_24;
    int field_28;
    int field_2C;
};
struct CCmdTargetMembers // member struct to reuse it in child classes
{
    int m_dwRef;
    IUnknown *m_pOuterUnknown;
    unsigned int m_xInnerUnknown;
    CCmdTarget::XDispatch m_xDispatch;
    int m_bResultExpected;
    CCmdTarget::XConnPtContainer m_xConnPtContainer;
    AFX_MODULE_STATE *m_pModuleState;
};
struct CCmdTarget
{
    CCmdTargetVtbl *vfptr;
    CCmdTargetMembers members;
};

只有这样,访问子类上的虚函数才有意义,因为它们的 vftables 是已知的。一个 hexrays 反编译示例表明,只有可用的基本 vftables 没有多大意义:

用子 vftables 反编译:

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    int nReturnCode; // esi MAPDST
    CWinApp *pWinApp; // edi
    CWinThreadVtbl *pThread; // ebx

    nReturnCode = -1;
    pWinApp = (CWinApp *)CBumperApp::instance;
    if ( AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nShowCmd) )
    {
        pThread = &pWinApp->vftable->CWinThread;
        if ( pWinApp->vftable->InitApplication(pWinApp) )
        {
            if ( pThread->InitInstance((CWinThread *)pWinApp) )
            {
                nReturnCode = pThread->Run((CWinThread *)pWinApp);
            }
[...]

没有子 vftables 的反编译:

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    int nReturnCode; // esi MAPDST
    CWinApp *pWinApp; // edi
    CObjectVtbl *pThread; // ebx

    nReturnCode = -1;
    pWinApp = CBumperApp::instance;
    if ( AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nShowCmd) )
    {
        pThread = pWinApp->vfptr;
        if ( pWinApp->vfptr[5].GetRuntimeClass(pWinApp) ) // nonsense
        {
            if ( (pThread[2].Serialize)(pWinApp) ) // nonsense
            {
                nReturnCode = (pThread[2].AssertValid)(pWinApp); // nonsense
            }
[...]

有什么方法可以让 IDA 从 PDB 加载子类的 vftables 并创建所有必需的结构?或者这在 IDA 7.0 中是不可能的?

据我所知,PDB 应该有这些信息。是否有工具可以查看 PDB 文件以查看它是否确实包含此信息?

1个回答

据我所知,你所说的一切都是100%正确的。我也遇到过同样的问题。事实上,当一位朋友给我发来这篇文章的链接时,我正在后台处理这个确切的问题。

这里实际上存在三个相关的问题。首先,在 IDA 7.2 之前,IDA 的类型系统本身没有虚函数表的概念。这意味着,虽然您可以创建一个包含一堆函数指针的结构,但 IDA 没有一种机制来更改派生对象中 VTable 指针的类型。“派生”只是意味着来自基类的所有内容都包含在偏移量 0 处。幸运的是,IDA 7.2 确实理解继承中 VTable 的概念。一点文档上六角光芒的网站。该页面的末尾总结了规则:

  • VFT 指针必须具有“__vftable”名称
  • VFT 类型必须遵循“CLASSNAME_vtbl”模式
  • 对于多重继承,使用“CLASSNAME_%04X_vtbl”作为 VFT 类型名称,其中 %04X 对应于 CLASSNAME 中 vft 指针的偏移量,如果偏移量不为零

第二个问题是 PDB 插件目前没有利用这些最近对类型系统的更改。即,它不会根据上述规则生成 VTable 类型/类 VTable 成员名称。

第三个问题是,坦率地说,PDB 文件格式是一场噩梦。您可以使用 Visual Studio 附带的“dia2dump”示例(在“DIA SDK”目录中)查看 PDB 的内容,但是,预先警告输出通常是错误的、误导性的或缺少重要信息。虚函数信息的处理尤其糟糕。我还没有完成我的调查,但到目前为止我学到的一件事是只有基类有SymTagVTable符号即,对于BA具有 VTable 的另一个类派生的类,只有A一个SymTagVTable符号,而不是B如果B定义了未在A. 目前,PDB 插件仅在类具有SymTagVTable符号时才创建 VTable ,因此是您所看到的行为。相反,您必须B使用该get_virtual方法遍历成员函数并检查它们是否是虚拟的——顺便说一下,这是 dia2dump 不做的事情。我仍在研究所有这些如何适用于多重继承。

简而言之,解决您的问题 - 以及我的问题 - 在于将 IDA 7.2 PDB 插件(SDK 附带)修改为:

  1. 为派生类恢复 VTable 信息,包括多继承场景
  2. 根据类名(以及多继承情况下的位移偏移)以合适的方式命名 VTable 结构类型
  3. 使用 in 中的新特性typeinf.hpp用属性标记 VTable 指针,用属性标记TAFLD_VFTABLEVTable 结构本身TAUDT_VFTABLE

同样,这也是我目前正在研究的内容。