🔧 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;