C&C++软件逆向技术

7、类与对象识别分析

1、课程目标

  1. 掌握C++对象的内存布局
  2. 理解虚函数表的实现机制
  3. 学会识别成员函数调用
  4. 掌握对象布局的还原方法

2、名词解释

术语 英文 说明
this指针 this Pointer 指向当前对象的指针
虚函数表 Virtual Table (vtable) 存储虚函数地址的表
虚表指针 vptr 指向虚函数表的指针
thiscall This Call C++成员函数调用约定
RTTI Runtime Type Information 运行时类型信息

3、使用工具

工具 用途
IDA Pro 识别类和虚函数表
Class Informer IDA插件,分析C++类

4、技术原理

4.1、对象内存布局

无虚函数的类:
+0x00  成员变量1
+0x04  成员变量2
...

有虚函数的类:
+0x00  vptr (虚表指针)
+0x04  成员变量1
+0x08  成员变量2
...

4.2、虚函数表结构

vtable:
+0x00  &虚函数1
+0x04  &虚函数2
+0x08  &虚函数3
...

5、代码实现

5.1、示例1:简单类的汇编特征

class Point {
public:
    int x, y;
    
    void Set(int _x, int _y) {
        x = _x;
        y = _y;
    }
    
    int GetX() { return x; }
};

// Point::Set 反汇编:
// mov eax, ecx           ; this在ECX
// mov edx, [esp+4]       ; _x
// mov [eax], edx         ; this->x = _x
// mov edx, [esp+8]       ; _y
// mov [eax+4], edx       ; this->y = _y
// ret 8

// Point::GetX 反汇编:
// mov eax, [ecx]         ; return this->x
// ret

5.2、示例2:虚函数调用

class Animal {
public:
    virtual void Speak() = 0;
    virtual void Move() = 0;
};

class Dog : public Animal {
public:
    void Speak() override { /* 汪汪 */ }
    void Move() override { /* 跑 */ }
};

void CallSpeak(Animal* p) {
    p->Speak();  // 虚函数调用
}

// CallSpeak 反汇编:
// mov eax, [ebp+8]       ; p
// mov edx, [eax]         ; vptr = *p
// mov ecx, eax           ; this = p
// call dword ptr [edx]   ; call vptr[0] (Speak)
// ret

// 虚函数调用特征:
// 1. 先读取对象第一个DWORD (vptr)
// 2. 再通过vptr间接调用
// 3. call [reg] 或 call [reg+offset]

5.3、示例3:识别虚函数表

class Base {
public:
    int value;
    virtual void Func1() { }
    virtual void Func2() { }
    virtual void Func3() { }
};

// 对象布局:
// +0x00  vptr -> [&Func1, &Func2, &Func3]
// +0x04  value

// 在IDA中识别vtable:
// 1. 找到.rdata段中的地址数组
// 2. 查看交叉引用,确认是否被构造函数引用
// 3. vtable之前可能有RTTI信息

/*
vtable_Base:
  dd offset Base::Func1
  dd offset Base::Func2  
  dd offset Base::Func3
*/

5.4、示例4:thiscall识别

class Calculator {
public:
    int result;
    
    int Add(int a, int b) {
        result = a + b;
        return result;
    }
};

// thiscall特征:
// 1. ECX = this指针
// 2. 其他参数通过栈传递
// 3. 被调用者清栈 (ret N)

// Calculator::Add 反汇编:
// push ebp
// mov ebp, esp
// mov eax, [ebp+8]       ; a
// add eax, [ebp+12]      ; a + b
// mov [ecx], eax         ; this->result = a + b
// pop ebp
// ret 8                  ; 清理8字节参数

5.5、示例5:类继承的布局

class Base {
public:
    int a;              // +0x04
    virtual void F() {}
};

class Derived : public Base {
public:
    int b;              // +0x08
    void F() override {}
};

// Base布局:
// +0x00  vptr
// +0x04  a

// Derived布局:
// +0x00  vptr (Derived的vtable)
// +0x04  a   (继承自Base)
// +0x08  b   (Derived自己的)

// Derived::F 的vptr指向Derived的vtable
// 覆写后的函数地址不Base不同

5.6、示例6:还原类结构

/*
从反汇编还原类结构步骤:

1. 找到构造函数:
   - 分配内存后的初始化
   - 写入vptr

2. 分析成员访问:
   - [ecx+offset]的访问模式
   - 确定各成员偏移和类型

3. 分析vtable:
   - 找到vtable地址
   - 确定虚函数数量

4. 分析成员函数:
   - thiscall调用约定
   - this->member访问

示例还原:
反汇编看到:
  mov dword ptr [ecx], offset vtable_xxx
  mov dword ptr [ecx+4], 0
  mov dword ptr [ecx+8], 0

可还原为:
class Unknown {
    virtual void* vptr;  // +0
    int member1;         // +4
    int member2;         // +8
};
*/

6、课后作业

  1. 基础练习:编写一个简单类,观察编译结果
  2. 虚函数分析:分析有虚函数的类的vtable
  3. 继承分析:分析继承关系的对象布局
  4. 实战还原:从反汇编还原一个完整的类定义