تا اینجا یاد گرفتیم چطور از روی Memory Access ها و Offset ها یک Struct ناشناخته رو بازسازی کنیم
ولی وقتی با برنامه های + +C طرف میشیم قضیه یک مقدار پیچیده تر میشه
چون ممکنه یک Object فقط چند Field ساده نداشته باشه
ممکنه:
Class
↓
Inheritance
↓
Virtual Functions
↓
vptr
↓
vtable
داشته باشیم
اینجاست که باید از روی Assembly بفهمیم:
این Object چه Class ای هست؟
از چه Class ای ارثبری کرده؟
Virtual Function
ها کجا قرار دارن؟
و Offset های مختلف مربوط به کدوم بخش Object هستن؟
Virtual Function
رو بفهمیم
فرض کنید Source Code این شکلی باشه:
class Animal {
public:
virtual void speak();
};
و:
class Dog : public Animal {
public:
void speak() override;
};
چون "speak" یک Virtual Function هست Compiler معمولا برای این Object یک Pointer به جدول Virtual Function ها در نظر میگیره
به این Pointer معمولا میگیم:
vptr
و جدول:
vtable
به شکل مفهومی:
Object
│
├── vptr
│ │
│ ▼
│ vtable
│ │
│ ├── speak()
│ ├── ...
│
└── Other Fields
یعنی Object معمولا یک Pointer داره که به اطلاعات Virtual Functionهای مربوط به نوع Dynamic Object اشاره میکنه
البته جزئیات دقیق Layout به Compiler و ABI وابسته است.
داخل Assembly دنبال چه چیزی بگردیم؟
فرض کنید داخل Constructor چنین چیزی ببینیم:
mov qword ptr [rcx], offset vtable_Dog
اینجا یک سرنخ خیلی مهم داریم
یعنی مقدار ابتدای Object تنظیم شده:
Object + 0x00
↓
vptr
↓
vtable_Dog
پس میتونیم فعلا بگیم:
Dog Object
+0x00 vptr
+0x08 unknown
+0x10 unknown
هنوز نمیدونیم Field های بعدی چی هستن
ولی حداقل یک چیز مهم فهمیدیم:
+0x00
احتمالا vptr است
vtable
دقیقا چیه؟
خیلی ساده اگر بخوایم نگاهش کنیم:
vptr
│
▼
vtable
│
├── Function Pointer
├── Function Pointer
├── Function Pointer
└──
هر Entry در vtable معمولا یک Pointer به یک Function یا بخشی از مکانیزم Virtual Dispatch هست
مثلا فرض کنید Assembly این شکلی باشه:
mov rax, [rcx]
mov rax, [rax+0x10]
call rax
بیایم مرحله به مرحله نگاه کنیم
اول:
mov rax, [rcx
]
یعنی:
RCX
↓
Object
↓
+0x00
↓
vptr
بعد:
mov rax, [rax+0x10
]
یعنی از جدول مربوط به vptr یک Entry در Offset مشخص خونده شده
بعد:
call rax
یعنی اون مقدار برای Call استفاده شده
پس یک مسیر مفهومی داریم:
Object
↓
+0x00
↓
vptr
↓
vtable
↓
Function Pointer
↓
call
این یکی از الگوهای خیلی مهم در Reverse Engineering برنامههای C++ هست.
از روی این الگو چطور Virtual Function رو پیدا کنیم؟
فرض کنید این Assembly رو داریم:
mov rax, [rcx]
mov rax, [rax+0x18]
call rax
میتونیم فعلا این مدل رو بسازیم:
Object
|
+0x00 → vptr
|
↓
vtable
|
+0x18 → function pointer
اما یک نکته مهم:
نباید سریع بگیم:
+0x18 = Function X
چون هنوز نمیدونیم Entry دقیقا مربوط به چه Virtual Function ای هست
باید خود vtable و Reference های مربوط به اون رو بررسی کنیم
حالا Inheritance وارد میشه
فرض کنید داریم:
class Animal {
public:
virtual void speak();
};
class Dog : public Animal {
public:
void speak() override;
};
از نظر مفهومی:
Animal
↑
|
Dog
Dog
بخشهایی از Animal رو به ارث میبره
ممکنه Object چیزی شبیه این داشته باشه:
Dog Object
+0x00 vptr
+0x08 Animal-related field
+0x0C Dog-related field
این Layout دقیق نیست و صرفا یک مدل مفهومیه
چون Layout واقعی به Compiler، ABI، Alignment و نوع Inheritance بستگی داره
ولی نکته مهم اینه که:
Base Class
+
Derived Class
میتونن در یک Object واحد حضور داشته باشن
از روی Constructor میتونیم رابطه رو پیدا کنیم
فرض کن یک Constructor داریم:
mov qword ptr [rcx], offset vtable_Animal
و بعد در Constructor کلاس دیگه ای میبینیم:
call Animal::Animal
mov qword ptr [rcx], offset vtable_Dog
اینجا یک سرنخ خیلی مهم داریم
یعنی Constructor مربوط به Dog ابتدا بخش مربوط به Base Class رو مقدار دهی میکنه و بعد وضعیت Virtual Dispatch مربوط به Derived Class رو تنظیم میکنه
به شکل مفهومی:
Dog Constructor
|
├── Animal Constructor
|
└── Dog-specific initialization
این یکی از چیزهایی هست که میتونه به ما کمک کنه رابطه بین Class ها رو پیدا کنیم
@reverseengine