C&C++软件逆向技术

8、构造函数与析构函数识别分析

1、课程目标

  1. 掌握构造函数的汇编特征
  2. 理解析构函数的调用时机
  3. 学会识别各种构造方式
  4. 掌握RAII模式的识别

2、名词解释

术语 英文 说明
构造函数 Constructor 对象创建时自动调用
析构函数 Destructor 对象销毁时自动调用
拷贝构造 Copy Constructor 用已有对象初始化新对象
移动构造 Move Constructor C++11移动语义
栈展开 Stack Unwinding 异常时析构栈上对象

3、使用工具

工具 用途
IDA Pro 识别构造/析构函数
x64dbg 观察构造过程

4、技术原理

4.1、构造函数特征

特征 说明
返回this 通常返回对象指针
初始化vptr 有虚函数的类会写入vtable地址
初始化成员 设置成员变量的初始值
调用基类构造 继承时先构造基类

4.2、析构函数特征

特征 说明
析构顺序 与构造相反
释放资源 调用delete/free
清理vptr 在调用基类析构前

5、代码实现

5.1、示例1:基本构造函数

class Simple {
public:
    int a, b;
    
    Simple() : a(0), b(0) { }
    Simple(int x, int y) : a(x), b(y) { }
};

// 默认构造函数反汇编:
// Simple::Simple()
// push ebp
// mov ebp, esp
// mov eax, ecx               ; this
// mov dword ptr [eax], 0     ; this->a = 0
// mov dword ptr [eax+4], 0   ; this->b = 0
// ; 返回this指针(在EAX中)
// pop ebp
// ret

// 带参构造函数:
// Simple::Simple(int, int)
// mov eax, ecx
// mov edx, [esp+4]           ; x
// mov [eax], edx             ; this->a = x
// mov edx, [esp+8]           ; y
// mov [eax+4], edx           ; this->b = y
// ret 8

5.2、示例2:有虚函数的构造

class Base {
public:
    int value;
    virtual void Func() { }
    
    Base() : value(0) { }
};

// Base::Base() 反汇编:
// mov eax, ecx
// mov dword ptr [eax], offset vtable_Base  ; 写入vptr
// mov dword ptr [eax+4], 0                 ; this->value = 0
// ret

// 特征: 第一个操作是写入vtable地址

5.3、示例3:继承的构造顺序

class Parent {
public:
    int parentVal;
    Parent() : parentVal(100) { }
};

class Child : public Parent {
public:
    int childVal;
    Child() : childVal(200) { }
};

// Child::Child() 反汇编:
// push ebp
// mov ebp, esp
// push ecx                   ; 保存this
// mov ecx, [ebp-4]           ; this
// call Parent::Parent        ; 先构造基类
// mov ecx, [ebp-4]
// mov dword ptr [ecx+4], 200 ; this->childVal = 200
// mov eax, ecx               ; 返回this
// mov esp, ebp
// pop ebp
// ret

// 特征: 在初始化自己的成员前调用基类构造

5.4、示例4:析构函数

class Resource {
private:
    void* data;
public:
    Resource() {
        data = malloc(1024);
    }
    ~Resource() {
        free(data);
    }
};

// Resource::~Resource() 反汇编:
// push ebp
// mov ebp, esp
// mov eax, [ecx+4]           ; this->data
// push eax
// call free
// add esp, 4
// pop ebp
// ret

// 析构函数特征:
// 1. 释放资源(free, delete, CloseHandle等)
// 2. 如果有虚函数,可能重写vptr
// 3. 最后调用基类析构

5.5、示例5:堆对象创建

Resource* CreateResource() {
    return new Resource();
}

void DeleteResource(Resource* p) {
    delete p;
}

// CreateResource 反汇编:
// push 8                     ; sizeof(Resource)
// call operator new          ; 分配内存
// add esp, 4
// test eax, eax
// jz failed
// mov ecx, eax               ; this = 分配的内存
// call Resource::Resource    ; 调用构造函数
// ret

// DeleteResource 反汇编:
// mov eax, [ebp+8]           ; p
// test eax, eax
// jz skip_delete
// mov ecx, eax
// call Resource::~Resource   ; 调用析构函数
// push eax
// call operator delete       ; 释放内存
// add esp, 4
// skip_delete:
// ret

5.6、示例6:识别构造函数的技巧

/*
识别构造函数的特征:

1. 调用时机:
   - new之后立即调用
   - 函数开头对局部变量的初始化

2. 函数特征:
   - ECX = this
   - 写入vtable地址(有虚函数时)
   - 初始化成员变量
   - 可能调用基类构造函数
   - EAX返回this指针

3. 在IDA中:
   - 找到vtable的交叉引用
   - 查找 operator new 之后的调用
   - 查找写入地址指针的操作

4. 识别析构函数:
   - delete之前调用
   - 包含资源释放操作
   - 可能调用基类析构
   - 函数结束前对局部对象的清理
*/

5.7、示例7:栈对象的生命周期

void Function() {
    Resource res;      // 构造
    // ...使用res...
}   // 自动析构

// 反汇编:
// push ebp
// mov ebp, esp
// sub esp, 8             ; 为Resource分配栈空间
// lea ecx, [ebp-8]       ; this = &res
// call Resource::Resource
// 
// ... 函数体 ...
// 
// lea ecx, [ebp-8]       ; this = &res
// call Resource::~Resource  ; 返回前析构
// mov esp, ebp
// pop ebp
// ret

6、课后作业

  1. 基础练习:编写有构造/析构的类,观察汇编
  2. 继承分析:分析继承关系中构造/析构顺序
  3. 资源管理:识别RAII模式的析构调用
  4. 实战还原:从反汇编识别构造/析构函数