Delphi 程序逆向实战:从汇编代码看面向对象实现
通过分析 Delphi 编译后的汇编代码,深入理解类、虚函数、RTTI 的底层实现
📌 前言
Delphi 作为一款优秀的原生开发工具,编译出的可执行文件具有清晰的 PE 结构和可预测的汇编模式。本文通过分析一个简单的 Delphi 测试程序,带你从汇编层面理解面向对象机制的底层实现。
🎯 程序功能回顾
这个测试程序展示了 Delphi 的面向对象特性:
- 基类
TAnimal和派生类TDog、TCat - 虚函数实现多态
- 事件回调解耦输出
- 全局对象管理
点击按钮后,程序创建动物对象并演示虚函数调用。
🔍 一、按钮点击入口分析
源代码
procedure TForm1.btn1Click(Sender: TObject);
begin
mmo1.Clear; // 清空 memo
汇编代码
_TForm1_btn1Click:
push ebp
mov ebp, esp
add esp, -16 ; 分配栈空间
push ebx
push esi
push edi
mov esi, eax ; 保存eax = Self (TForm1)
解读
| 指令 | 含义 |
|---|---|
eax |
传入 Self 指针(相当于 C++ 的 this,TForm1) |
esi |
保存 Self,后续通过 [esi+offset] 访问成员 |
🔍 二、访问窗体控件
汇编代码
mov eax, [esi+2FCh] ; 获取 mmo1 控件
mov edx, [eax] ;得到mmo1的虚函数表
call dword ptr [edx+0E0h] ; 调用 Clear 方法
解读
| 偏移 | 含义 |
|---|---|
2FCh (764) |
mmo1 控件在 TForm1 对象中的位置 |
[eax] |
获取控件的虚函数表 |
+0E0h (224) |
Clear 方法在虚表中的偏移 |
验证
窗体(TForm1):在CE点断点得到esi(Self 指针)=02211EF4, (*self)=[02211EF4]=00450460,
在IDA中跳转到, 正是TForm的虚函数表首地址👇
CODE:00450460 _cls_VirtualUnit_TForm1 dd offset TWinControl::AssignTo
CODE:00450464 dd offset TCustomForm::DefineProperties
...
控件(mmo1): 地址eax=[esi+0x2FC]=02213BB0,虚函数表[eax]=[02213BB0]=00426CD0,
[eax]+0E0h =00426DB0 ,[00426DB0 ]=004282C8=mmo1.clear函数地址
CODE:00426CD0 _cls_StdCtrls_TMemo dd offset TWinControl::AssignTo
CODE:00426CD4 dd offset TWinControl::DefineProperties
.。。
CODE:00426DB0 dd offset TCustomEdit::Clear(void)
CODE:004282C8 ; _DWORD __fastcall Stdctrls::TCustomEdit::Clear(Stdctrls::TCustomEdit *__hidden this)
CODE:004282C8 push ebx
CODE:004282C9 mov ebx, eax
CODE:004282CB push offset byte_4282E0 ; lpString
CODE:004282D0 mov eax, ebx ; this
CODE:004282D2 call @Controls@TWinControl@GetHandle$qqrv
CODE:004282D7 push eax ; hWnd
CODE:004282D8 call SetWindowTextA
CODE:004282DD pop ebx
CODE:004282DE retn
CODE:004282DE @Stdctrls@TCustomEdit@Clear$qqrv endp
结论:Delphi 的控件成员在对象中按声明顺序排列,通过固定偏移访问。
🔍 三、创建对象
源代码
// 创建对象并存入全局数组
g_AnimalList[0] := TDog.Create('Rex', 5);
g_AnimalList[1] := TCat.Create('Whiskers', 3);
g_AnimalList[2] := TDog.Create('Max', 2);
汇编代码
; 创建 TDog
push 5 ;Create参数
mov ecx, offset _str_Rex.Text ;字符串Rex
mov dl, 1 ;字符串类型标志 (AnsiString)
mov eax, off_45034C ; TDog 类虚函数表指针
call sub_4508B4 ; TDog.Create
mov ds:dword_454BD4, eax ; 返回TDog对象存入 g_AnimalList[0]
; 创建 TCat
push 3
mov ecx, offset _str_Whiskers.Text
mov dl, 1
mov eax, off_4503B0 ; TCat 类虚函数表指针
call sub_450B88 ; TCat.Create
mov ds:dword_454BD8, eax ; 存入 g_AnimalList[1]
; 创建第二个 TDog
push 2
mov ecx, offset _str_Max.Text ; 'Max'
mov dl, 1
mov eax, off_45034C ; TDog 类虚函数表指针
call sub_4508B4 ; TDog.Create
mov ds:dword_454BDC, eax ; 存入 g_AnimalList[2]
解读
| 地址 | 内容 |
|---|---|
off_45034C |
TDog 类虚函数表指针 |
off_4503B0 |
TCat 类虚函数表指针 |
dword_454BD4 |
g_AnimalList 数组基址, 连续三个地址 |
在IDA看到数组定义
BSS:00454BD4 dword_454BD4 dd ?
BSS:00454BD8 dd ?
BSS:00454BDC dd ?
关键点:
- 对象通过类虚函数表动态创建
- 创建后返回对象指针
- 存入全局数组管理
这一章讲到这里,后续接着分析下面代码循环和虚函数表
procedure TestVirtualMethods();
var
I: Integer;
begin
for I := 0 to MAX_ANIMALS - 1 do
begin
if g_AnimalList[I] <> nil then
begin
g_AnimalList[I].Speak; // 虚函数调用
g_AnimalList[I].Move; // 虚函数调用
end;
end;
end;