وقتی یه فایل باینری رو مهندسی معکوس میکنی خیلی وقت ها سورس کد اصلی رو ندارید یعنی نمیدونید برنامه نویس چه ساختار هایی تعریف کرده و هر کدوم چه فیلدهایی داشتن
اینجا باید از روی نحوه دسترسی برنامه به حافظه ساختار داده ها رو دوباره بازسازی کنید به این کار میگیم Struct Reconstruction
فرض کنید برنامه اصلی همچین ساختاری داشته باشه
C
struct User {
int id;
int age;
char active;
};
این ساختار سه تا فیلد داره یکی برای شناسه کاربر یکی برای سن و یکی هم برای وضعیت فعال بودن.
ولی وقتی فایل کامپایل شده رو بررسی میکنی ممکنه اصلا اسم
User یا فیلد های id و age رو نبینید فقط دستور های اسمبلی رو دارید که با آدرس های حافظه کار میکننحالا ببین چطور میتونیم ساختار رو از روی همین دستورها تشخیص بدیم
پیدا کردن آدرس فیلد ها
فرض کنید توی اسمبلی به دستورهای زیر رسیدید
Asm
mov eax, dword ptr [rcx]
mov edx, dword ptr [rcx+4]
movzx eax, byte ptr [rcx+8]
بیایم یکی یکی بررسیشون کنیم
Asm
mov eax, dword ptr [rcx]
چهار بایت از آدرسی که داخل RCX قرار داره میخونه
Asm
mov edx, dword ptr [rcx+4]
چهار بایت از چهار بایت بعدی میخونه
Asm
movzx eax, byte ptr [rcx+8]
یک بایت از هشت بایت بعد از آدرس اولیه میخونه
حالا اطلاعاتی که داریم این شکلیه
`textآدرس پایه + 0 چهار بایت
آدرس پایه + 4 چهار بایت
آدرس پایه + 8 یک بایت
این الگو میتونه نشون بده که برنامه داره به سه فیلد مختلف از یه ساختار دسترسی پیدا میکنه
تشخیص اندازه و نوع فیلد ها
از روی اندازه دسترسی ها میتونیم حدس بزنیم هر فیلد چه نوعی داره
دسترسی چهار بایتی ممکنه مربوط به
int یا float باشهدسترسی یک بایتی ممکنه مربوط به
char یا یک مقدار بولی باشهاما حواستون باشه که اندازه به تنهایی نوع دقیق داده رو مشخص نمیکنه مثلا یه مقدار چهار بایتی میتونه بخشی از یه عدد بزرگتر هم باشه
برای تشخیص بهتر باید ببینید مقدار کجا استفاده میشه
اگه مقدار با دستورهای محاسباتی عددی استفاده بشه ممکنه یه عدد معمولی باشه اگه با دستور های مربوط به عملیات ممیز شناور کار کنه احتمال
float بودنش بیشترهبازسازی ساختار
با توجه به دسترسی هایی که دیدیم میتونیم یه ساختار احتمالی بسازیم
C
struct Unknown {
int field_0;
int field_4;
char field_8;
};
اسم فیلد ها رو فعلا بر اساس آفست حافظه گذاشتیم چون هنوز کاربرد واقعی اونها رو نمیدونیم
بعد از بررسی بخش های بیشتری از برنامه ممکنه بفهمیم
field_0 شناسه کاربره field_4 سن کاربره و field_8 وضعیت فعال بودنشهسم گذاری بر اساس معنی داده ها انجام میشه نه صرفا ظاهر اسمبلی
بررسی نوشتن روی فیلدها
فقط دستور های خوندن رو بررسی نکنید نوشتن روی حافظه هم اطلاعات مهمی بهمون میده
مثلا:
Asm
mov dword ptr [rcx], 123
mov dword ptr [rcx+4], 25
mov byte ptr [rcx+8], 1
این دستورها به ترتیب روی آفست های صفر چهار و هشت مینویسن
اگه جاهای مختلف برنامه ببینید که همین آفست ها برای خوندن و نوشتن استفاده میشن احتمال اینکه با فیلد های یه ساختار طرف باشی بیشتر میشه
البته هنوز باید بررسی کنید که RCX واقعا آدرس پایه یک ساختار مشترکه یا فقط در اون لحظه به یه بخش از حافظه اشاره میکنه
پیدا کردن اندازه واقعی ساختار
اینجا یه نکته مهم وجود داره
بزرگ ترین آفستی که پیدا میکنید لزوما اندازه کامل ساختار نیست
مثلا اگه آخرین فیلد از آفست هشت شروع بشه و یک بایت اندازه داشته باشه حداقل فضای مورد نیاز برای این فیلدها 9 بایته اما اندازه نهایی ساختار ممکنه به خاطر قوانین همترازی حافظه یا همون Alignment بزرگتر باشه
برای مثال کامپایلر ممکنه ساختار رو به 12 بایت گرد کنه تا همترازی مورد نیاز رعایت بشه
همچنین ممکنه ساختار فیلد های دیگهای داشته باشه که هنوز در مسیرهای بررسی شده بهشون دسترسی پیدا نکردیم
پس اندازه ساختار رو باید با بررسی تخصیص حافظه نحوه استفاده از اشارهگرها و تمام آفست های قابل مشاهده تخمین بزنید
این کار توی Ghidra چطور انجام میشه؟
وقتی ساختار احتمالی رو پیدا کردید میتونید از بخش Data Type Manager یه ساختار جدید بسازی و فیلدهاش رو تعریف کنید
بعد در صورت مشخص بودن نوع متغیر و آدرس پایه میتونید نوع ساختار رو به متغیر مربوط اختصاص بدید تا به جای آفستهای خام دسترسیها خواناتر بشن
مثلا بهجای اینکه فقط ببینید برنامه از
[rcx+4] میخونه بتونید تشخیص بدید که داره فیلد دوم ساختار رو میخونه