首页
社区
课程
招聘
[旧帖] [翻译]不知道有没有人翻译过:Reversing Microsoft Visual C++ Part II: Classes, Methods and RTTI 0.00雪花
发表于: 2008-6-9 15:54 6394

[旧帖] [翻译]不知道有没有人翻译过:Reversing Microsoft Visual C++ Part II: Classes, Methods and RTTI 0.00雪花

2008-6-9 15:54
6394
小弟最近在家过宅男+啃老族的生活,无事+无收入,为了个小项目,需要逆向一段用到了COM的用户级驱动。初涉逆向这一深潭,便遇到C++/COM这样复杂的逆向问题 ,遍寻看雪未能找到好的关于C++的逆向入门文章,幸得Reversing enginerring with IDA pro,提及了OpenRCE.org的这的两篇文章,觉其甚好,欲译之。
原文地址:http://www.openrce.org/articles/full_view/23

若此篇已有人译之,也请好心人告诉于我,谢谢

逆向Microsoft Visual C++第二部分:类、方法及RTTI

摘要:省略 (第一部分:异常的处理http://www.openrce.org/articles/full_view/21)

    正文:类的基本组织


在叙述后面的东西之前,先让我们看看下面的代码:
class A
    {
      int a1;
    public:
      virtual int A_virt1();
      virtual int A_virt2();
      static void A_static1();
      void A_simple1();
    };

    class B
    {
      int b1;
      int b2;
    public:
      virtual int B_virt1();
      virtual int B_virt2();
    };

    class C: public A, public B
    {
      int c1;
    public:
      virtual int A_virt2();
      virtual int B_virt2();
    };


在大多数情况下MSVC以如下的方式组织类:
1.虚函数表指针(_vtable_ 或者 _vftable_)。只有当类中包含virtual函数或者基类中没有合适的函数表可以重用才会增加此指针
2.基类
3.类成员

虚函数表以虚方法首先出现的顺序组织函数地址。重载的函数地址替换了基类中被重载函数的地址。

由此,我们那三个函数将被组织成下面的样子:
 class A size(8):
        +---
     0  | {vfptr}
     4  | a1
        +---

    A's vftable:
     0  | &A::A_virt1
     4  | &A::A_virt2

    class B size(12):
        +---
     0  | {vfptr}
     4  | b1
     8  | b2
        +---

    B's vftable:
     0  | &B::B_virt1
     4  | &B::B_virt2

    class C size(24):
        +---
        | +--- (base class A)
     0  | | {vfptr}
     4  | | a1
        | +---
        | +--- (base class B)
     8  | | {vfptr}
    12  | | b1
    16  | | b2
        | +---
    20  | c1
        +---

    C's vftable for A:
     0  | &A::A_virt1
     4  | &C::A_virt2

    C's vftable for B:
     0  | &B::B_virt1
     4  | &C::B_virt2


上面的图是由VC8的一个未公开(undocumented)的开关(参数)生成的。
开关这样用:
要想看单个类组织用:-d1reportSingleClassLayout (译注:这个我没试出来 )
要想看所有类(包括内部的CRT中的类)的组织:-d1reportAllClassLayout(译注:这个是可以的)。类的组织被输出到stdout上。

如你所见,C类有两个虚函数表,因为它所继承的两个类都有虚函数,在C类的A的虚函数表中,C::A_virt2的地址替换了A::A_virt2的地址;同样在另一表中,C::B_virt2替换了B::B_virt2

    调用规约(译注:convention怎么翻译好? )和类方法

默认情况下,所有的类方法都使用_thiscall_规约。类的实例地址(_this_指针)通过ecx寄存器作为隐含参数被传递(译注:之前我RE的时候大量发现用ecx传递某个指针,不知其为何物,疑VC使用ECX传递指针以优化,幸得高人指点:ecx经常被作为this指针传递)。在函数体中,它经常又被隐藏于寄存器(如esi,edi)中,和/或堆栈变量中。其他的任何类成员的寻址都通过那个寄存器和/或堆栈变量完成。然而,当实现一个COM类的时候,使用_stdcall_规约。下面是各种类方法 类型的总览。

1)静态方法:
静态方法不需要类实例,因此可以像通常的函数一样调用它们。没有_this_指针被传递给它们。因此不可能可靠地将静态函数与普通函数相区别,如:
A::A_static1();
call    A::A_static1


2)简单方法:
简单方法需要类实例,因此_this_指针被作为隐含的第一个参数被传递,通常使用_thiscall_规约,也就是说,通过_ecx_寄存器。当基类的对象不是在继承类的开始位置,那么在调用函数之前,需要首先调整_this_指针,以使其指向真正的基的子对象的开始位置,如:
    ;pC->A_simple1(1);
    ;esi = pC
    push    1
    mov ecx, esi
    call    A::A_simple1

    ;pC->B_simple1(2,3);
    ;esi = pC
    lea edi, [esi+8] ;adjust this
    push    3
    push    2
    mov ecx, edi
    call    B::B_simple1


如你所见,在调用B的方法之前,_this_指针首先被调整以指向B的子对象。

3)虚方法
为了调用虚方法,编译器首先需要从_vftable_得到函数地址,然后像调用简单方法一样使用那个地址调用虚方法(也就是说,将_this_指针作为隐含的参数传递),例如:
;pC->A_virt2()
    ;esi = pC
    mov eax, [esi]  ;得到虚函数表指针
    mov ecx, esi
    call [eax+4]  ;调用第二个虚方法
    
    ;pC->B_virt1()
    ;edi = pC
    lea edi, [esi+8] ;调整这个指针
    mov eax, [edi]   ;获取虚函数表
    mov ecx, edi
    call [eax]       ;调用第一个虚方法


4)构造与析构函数
构造函数与析构函数与简单函数类似:它们得到一个隐含的_this_指针作为参数(例如在_thiscall_规约下,通过ecx得到)。尽管正常情况下,构造函数没有返回值,但构造函数的确通过eax返回_this_指针。

    RTTI(运行时类型鉴别)的实现


待续...

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (5)
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
没人回帖?连个拍砖的都没有?唉,失败……

    RTTI实现

RTTI是一种由编译器生成的特殊的信息,它用来支持像dynamic_cast<> 和typeid()这样的C++操作符,以及C++的异常。由于它的成因,所以,编译器只为多态的类,也就是有虚函数的类,生成RTTI信息。

MSVC编译器将一个结构体指针放在虚函数表之前,称之为“完整对象定位符(Complete Object Locator,COL)”。之所以这样称谓,是因为它允许编译器从特定的vftable指针(因为一个类可能有多个)找到完整的对象的位置。COL看起来像下面这样:
struct RTTICompleteObjectLocator
{
    DWORD signature; //总是0 ?
    DWORD offset;    //这个vftable在整个类中的偏移
    DWORD cdOffset;  //构造函数位移的偏移
    struct TypeDescriptor* pTypeDescriptor; //整个类的类型描述符
    struct RTTIClassHierarchyDescriptor* pClassDescriptor; //描述继承关系(hierarchy)
};


类关系描述符描述了类的继承关系。它由类的所有COL所共享。
struct RTTIClassHierarchyDescriptor
{
    DWORD signature;      //总是 zero?
    DWORD attributes;     //位0置位 = 多重继承(multiple inheritance), 位1置位 = 虚继承(virtual inheritance)
    DWORD numBaseClasses; //pBaseClassArray中类的数量
    struct RTTIBaseClassArray* pBaseClassArray;
};


基类数组描述所有的基类及一些信息,这些信息,在执行_dynamic_cast_操作符的时候让编译得以将继承类转成任一基类。每一个入口(基类描述符)有如下的结构:
struct RTTIBaseClassDescriptor
{
    struct TypeDescriptor* pTypeDescriptor; //类的类型描述符
    DWORD numContainedBases; //number of nested classes following in the Base Class Array
    struct PMD where;        //pointer-to-member displacement info
    DWORD attributes;        //flags, usually 0
};

struct PMD
{
    int mdisp;  //成员偏移
    int pdisp;  //vbtable偏移
    int vdisp;  //vbtable内偏移
};


PMD结构体描述一个基类是如何安置在一个完整的类里。如果是一个简单的继承,它被安置在从对象起始位置开始的一个固定的偏移,这个偏移就是_mdisp_字段。如果它是一个虚基类,那么需要从vbtable得到一个附加的偏移。下面的伪代码是将继承类的_this_指针调整为基类的指针:
    //char* pThis; struct PMD pmd;
    pThis+=pmd.mdisp;
    if (pmd.pdisp!=-1)
    {
      char *vbtable = pThis+pmd.pdisp;
      pThis += *(int*)(vbtable+pmd.vdisp);
    }


例如,我们的三个类的RTTI关系看起来像这样:
原图地址:http://www.openrce.org/articles/img/igor2_rtti1.gif


待续……
2008-6-10 10:33
0
雪    币: 2316
活跃值: (129)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
3
有人翻译过。
不过自己翻译下,学的更清楚。
2008-6-10 10:56
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
Thank you.
2008-6-10 11:59
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
支持翻译
2008-6-10 13:09
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
所以我不继续了
2008-6-14 20:29
0
游客
登录 | 注册 方可回帖
返回
//