اینجا ممکنه RBX یک Pointer به Object باشه که به عنوان Argument به process_user داده شده
Calling Convention
هم خیلی مهمه
مثلاً در System V x86-64 معمولا Argumentهای ابتدایی از این Register ها عبور میکنن:
RDI
RSI
RDX
RCX
R8
R9
ولی در Windows x64 چهار Argument اول معمولا از:
RCX
RDX
R8
R9
عبور میکنن
پس مثلا توی یک Binary ویندوزی:
mov eax, [rcx+0x8]
میتونه سرنخ مهمی باشه که RCX یک Pointer به Object یا Struct ورودی تابع باشه
البته باز هم باید Context تابع رو بررسی کنیم
از روی نحوه استفاده از Field نوعش رو حدس بزنید
فرض کنید داریم:
mov eax, [rcx+0x4]
cmp eax, 10
این میتونه این احتمال رو ایجاد کنه:
+0x4 = integer-like field
حالا:
mov rax, [rcx+0x8]
mov rdx, [rax]
اینجا احتمال Pointer بودن +0x8 بیشتره
یا:
mov rax, [rcx+0x10]
call rax
اینجا احتمال Function Pointer بودن مقدار +0x10 مطرح میشه
یا:
mov rax, [rcx+0x18]
lea rdx, [rax+...]
ممکنه +0x18 یک Pointer به Buffer یا Object دیگه باشه
ولی یک نکته مهم:
هیچکدوم از اینها به تنهایی اثبات قطعی نیستن
باید Instruction های اطراف Cross-Reference ها تابع های دیگه و مسیر Data Flow رو هم بررسی کنیم
Nested Struct ها
Struct
میتونه داخل Struct دیگه قرار بگیره
مثلا:
struct Address {
int city;
int zip;
};
struct User {
int id;
struct Address address;
};
حالا ممکنه توی Binary ببینیم:
mov eax, [rcx+0x8]
و جای دیگه:
mov eax, [rcx+0xC]
اگر Context مناسب باشه ممکنه به این نتیجه برسیم که:
User
|
+0x00 id
|
+0x08 address
|
+0x00 city
+0x04 zip
یعنی:
User + 0x08 = Address
User + 0x0C = Address.zip
در Source Code ممکنه خیلی راحت ببینیم:
user->address.zip
ولی در Binary معمولا چنین اسمی وجود نداره.
چیزی که میبینیم ممکنه فقط این باشه:
mov eax, [rcx+0xC]
پس باید از روی Offset ها و نحوه استفاده از داده ساختار منطقی برنامه رو دوباره بسازیم
Padding و Alignment
رو فراموش نکنید
یکی از اشتباهات رایج اینه که فکر کنیم Field های Struct همیشه دقیقا پشت سر هم قرار میگیرن
مثلا:
struct Example {
char flag;
void *ptr;
};
ممکنه روی یک ABI معمول 64 بیتی Layout چیزی شبیه این باشه:
+0x00 flag 1 byte
+0x01 padding
+0x02 padding
+0x03 padding
+0x04 padding
+0x05 padding
+0x06 padding
+0x07 padding
+0x08 ptr 8 bytes
چرا؟
چون Pointer معمولا نیاز به Alignment مناسب خودش داره
پس نباید فکر کنیم:
char
pointer
یعنی Pointer حتما از +0x01 شروع میشه
ممکنه Compiler بین Field ها Padding قرار بده
بنابراین وقتی Offset ها رو بازسازی میکنید همیشه این دو مورد رو هم در نظر بگیرید:
Alignment
Padding
Array
ها یک سرنخ خیلی خوب هستن
فرض کنید توی Binary اینو ببینیم:
mov eax, [rcx+rax*4]
اینجا تقریبا داریم این مدل Address Calculation رو میبینیم:
base + index * 4
این میتونه نشون بده که با Array ای از عناصر 4 بایتی طرفیم
مثلا:
Element 0 → +0x00
Element 1 → +0x04
Element 2 → +0x08
Element 3 → +0x0C
حالا اگر ببینیم:
mov rax, [rcx+rax*8]
احتمالا هر Element هشت بایت فاصله داره
اما اگر ببینیم:
mov eax, [rcx+rax*0x20]
میتونیم حدس بزنیم فاصله بین شروع Elementها حدودا:
0x20 bytes
هست.
به این فاصله میگیم:
Stride
یعنی فاصله شروع یک Element تا شروع Element بعدی
مثلا:
Element 0
|
| 0x20
v
Element 1
|
| 0x20
v
Element 2
این موضوع موقع Reverse Engineering ساختارهای ناشناخته خیلی به درد میخوره
یک مثال واقعی تر
فرض کنید این Assembly رو پیدا کردیم:
mov eax, [rcx+0x4]
test eax, eax
je short loc_140001250
mov rax, [rcx+0x8]
mov rdx, [rax]
call rdx
mov rax, [rcx+0x10]
mov eax, [rax+0x4]
حالا بدون Source Code چی میتونیم بفهمیم؟
اول:
mov eax, [rcx+0x4]
یعنی یک مقدار 32 بیتی از Offset 0x4 خونده شده
بعد:
test eax, eax
و نتیجه برای یک شرط استفاده شده
پس میتونیم حدس بزنیم:
+0x4 = integer-like / flag-like field
بعد:
mov rax, [rcx+0x8]
یک مقدار 64 بیتی از +0x8 خونده شده
بعد:
mov rdx, [rax]
یعنی مقدار خوندهشده به عنوان Address استفاده شده
و بعد:
call rdx
این Address برای یک Call استفاده شده
پس احتمال Pointer بودن Field در +0x8 بیشتر میشه و حتی ممکنه Pointer به چیزی باشه که از طریق اون یک Function Pointer یا جدول Functionها قابل دسترسیه
بعد میرسیم به:
Post #1844
51