1809 字
9 分鐘
🎓【C++】多型與Vtable|六周目

目錄#


多型概念速查#

類型說明例子
編譯期多型透過 函式多載模板 (Templates),在編譯階段解析呼叫。void print(int); void print(std::string);
執行期多型透過 虛函式 (virtual function) 與 動態繫結 (dynamic dispatch) 在執行期決定呼叫目標。Base* p = new Derived; p->foo();

一句話記憶虛函式 + 指標/參考執行期多型
提示:虛函式是實現執行期多型的關鍵。

為何需要執行期多型?#

執行期多型是 C++ 物件導向設計的基石,因為它能:

  • 抽象接口:讓使用者僅依賴基底類別,降低耦合。
  • 開放/封閉原則:允許在不修改既有程式碼的情況下擴充行為。
  • 策略模式、工廠模式 等 OO 設計模式核心。

虛函式、vptr 與 vtable#

名詞定義#

名詞作用位址範圍記憶體位置
vtable (virtual table)儲存此類別所有虛函式的函式指標陣列。文字區 (R/O) 或資料區,依 ABI 而定。通常位於唯讀區
vptr (virtual pointer)每個物件的隱藏成員,指向對應類別的 vtable。物件首部 (GCC/Clang Itanium);MSVC 可在首部或尾部視繼承情況。物件內部

簡易範例:單一繼承#

class Base {
public:
virtual void speak() { std::cout << "Base\n"; }
virtual ~Base() {} // 虛擬解構子,確保資源正確釋放,避免洩漏
};
class Derived : public Base {
public:
void speak() override { std::cout << "Derived\n"; }
};
  • 編譯器生成 Derived 的 vtable:[Derived::speak, Base::~Base]
  • Derived 物件:[vptr] [其餘資料成員…]
  • 呼叫 p->speak() 底層步驟:
    1. 讀取 p 第一欄位 vptr。
    2. 讀取 vptr[0] 的函式位址。
    3. jmp/rjmp 至該位址。

性能成本:多一次間接尋址(指標解參照);現代 CPU 分支預測佳,開銷約 1–2ns。若函式本身複雜,影響可忽略。


編譯器實作差異#

編譯器ABIvtable 位置vptr 名稱其他特點
GCC / ClangItanium C++ ABI.rodata (只讀區)__vptr$Class (ID 尾綴)支援 thunk 修正 this 指標偏移。
MSVCMicrosoft ABI.rdata or .data??_7Class@@6B@每個虛基類可能額外有 vbtable 虛基表。

建議:跨編譯器逆向時,先確認 ABI 差異,再找 vtable。IDA/Ghidra 的 auto-analysis 會標示 vftable, vftable_ref 等符號。
ABI 重要性:ABI(Application Binary Interface)定義了二進位層級的相容性,逆向工程時必須關注 ABI 差異以正確解析 vtable 和 vptr。


多重繼承與虛擬繼承#

注意:這部分較為複雜,建議初學者先跳過,待熟悉單一繼承後再回頭學習。

class A { virtual void fa(); };
class B { virtual void fb(); };
class C : public A, public B { void fa() override; void fb() override; };
  • C 物件內部佈局(Itanium ABI 簡化示意):
+0 vptr_A -> vtable_C_for_A (offset-to-top = 0)
+8 vptr_B -> vtable_C_for_B (offset-to-top = -8)
  • offset-to-top 是 thunk 修正 this 回到物件實際起始位址。MSVC 以兩張獨立 vtable + vfptr adjustor 實現。

虛擬繼承 (virtual inheritance) 與菱形問題#

class VBase { ... };
class A : virtual public VBase { ... };
class B : virtual public VBase { ... };
class C : public A, public B { ... };
  • 編譯器增設 vbptr (虛基指標) 指向 vbtable,用於計算虛基類的偏移。
  • 物件大小增長,存取虛基成員多一次間接級。

鑽石問題:多重繼承可能導致重複繼承,虛擬繼承能解決此問題(建議搭配圖示說明)。


RTTI 與動態轉型#

功能說明重要細節使用時機
typeid(expr)執行期取得 std::type_info & 類名。需至少一個虛函式 (有 vtable),否則回傳靜態類型。檢查物件實際型別
dynamic_cast<T*>(ptr)安全向下轉型,失敗回傳 nullptr透過 vtable 內的 RTTI 結構 比對。在繼承層次中安全轉型

逆向小技巧:觀察 vtable + RTTI 結構 (符號如 __RTTI_Type_Descriptor) 可以迅速鎖定類名(建議搭配 IDA Pro 截圖展示)。


匯編觀察:動態呼叫流程#

以 GCC 14 x86-64 為例:

mov rax, QWORD PTR [rdi] ; 讀取 vptr (rdi 是 this 指標)
mov rax, QWORD PTR [rax] ; 從 vtable 取出 speak() 的位址
call rax ; 呼叫 Derived::speak
  • 若多重繼承,可能有 thunk
add rdi, -0x8 ; 修正 this 指標偏移
jmp Derived::fb

步驟解說

  1. [rdi] 取出 vptr。
  2. [rax] 從 vtable 取函式位址。
  3. call 跳轉執行。
    thunk 作用:在多重繼承中調整 this 指標,確保指向正確子物件。

攻擊面:vtable 劫持#

警告:本節內容僅用於教育目的,切勿用於非法活動。

手法條件成功效果
Use‑After‑Free (UAF)可控制已釋放物件,再分配攻擊者資料覆寫 vptr。呼叫虛函式跳到惡意程式碼。
Buffer Overflowvptr 與其他資料在同一 buffer 內,溢出覆寫。類似劫持。
Type Confusion錯誤 cast 造成以錯誤 vtable 呼叫。arbitrary code execution / logic flaw

POC 簡例#

struct Cmd { virtual void run() { puts("run"); } };
void exploit() {
Cmd* p = new Cmd();
delete p; // 釋放物件,造成 UAF
char* buf = new char[sizeof(Cmd)]; // 重新分配記憶體
*(uintptr_t*)buf = (uintptr_t)evil_vtable; // 覆寫 vptr
p->run(); // 呼叫虛函式,跳轉到惡意程式碼
}

註解

  • delete p 釋放記憶體但未置空指標。
  • buf 覆蓋原物件位置,控制 vptr。
  • p->run() 觸發劫持。

防禦技術與最佳實踐#

類型技術說明適用場景
編譯期-fstack-protector, -fcontrol-flow-guard, /guard:cf生成 CFG 表;執行期檢查 vtable 目標是否在許可集。保護堆疊、控制流
執行期ASLR, DEP, CET (Shadow Stack)分散位址、阻止可寫可執行、保護返回位址。系統級防護
語言層final, override, 智能指標 (std::unique_ptr)防止未預期覆寫;自動管理生命週期,避免 UAF。代碼級防護

實務建議

  1. 使用 RAII 和智能指標(如 std::unique_ptr),自動管理資源,有效防止 UAF 漏洞。
  2. 開啟所有硬體/編譯器防禦(/guard:cf, -fcf-protection)。
  3. 多型僅用於必要抽象;效能敏感路徑考慮 final 或 CRTP (Curiously Recurring Template Pattern)。

常見問答與誤區#

問題解答
Q: virtual 只在 header 宣告就好嗎?virtual 關鍵字僅需在第一處宣告即可,定義處可省略。
Q: 物件沒有虛函式就沒有 vtable?正確。無虛函式則無 vptr/vtable。
Q: 關閉 RTTI 可增添安全?部分。減少類名曝光,但無法阻止 vtable 劫持。
Q: dynamic_cast 很慢?取決於繼承複雜度;常數次 table lookup,微測約 10–50ns,通常可接受。
Q: 虛函式和純虛函式的區別是什麼?純虛函式必須在子類別中實作,否則該類別為抽象類別。
Q: 為何不建議在建構式中呼叫虛函式?建構期間物件型別尚未完全形成,呼叫虛函式可能導致未定義行為。

參考資源#

  • Itanium C++ ABI:https://itanium-cxx-abi.github.io/cxx-abi/abi.html
  • Microsoft PE/COFF ABI:MSDN 文件
  • 《Deep C++》Chapter 6 — Virtual Table
  • IDA Pro / Ghidra 官方文件:RTTI 與 Class Structure Recovery
  • 安全研究:《VTable Hijacking Revisited》(BlackHat 2023)
  • 入門資源
    • 線上課程:Coursera 的「C++ For C Programmers」
    • 教學影片:YouTube 上的「C++ Polymorphism Explained」

IMPORTANT

再次感謝,馬斯克創立的AI〖Grok〗幫我整理文章。(時代的進步真好ಥ_ಥ)

🎓【C++】多型與Vtable|六周目
https://illumi.love/posts/指南向/多型與vtable完全指南/
作者
Illumi糖糖
發布於
2025-07-25
許可協議
🔒CC BY-NC-ND 4.0