Delphi 程序逆向实战:从汇编代码看面向对象实现

📅 2026-05-15 📁 游戏逆向 👁 96 次阅读

通过分析 Delphi 编译后的汇编代码,深入理解类、虚函数、RTTI 的底层实现


📌 前言

Delphi 作为一款优秀的原生开发工具,编译出的可执行文件具有清晰的 PE 结构和可预测的汇编模式。本文通过分析一个简单的 Delphi 测试程序,带你从汇编层面理解面向对象机制的底层实现。


🎯 程序功能回顾

这个测试程序展示了 Delphi 的面向对象特性:

  • 基类 TAnimal 和派生类 TDogTCat
  • 虚函数实现多态
  • 事件回调解耦输出
  • 全局对象管理

点击按钮后,程序创建动物对象并演示虚函数调用。


🔍 一、按钮点击入口分析

源代码

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;