اسمبلیامنیت

مثال ساده برای مهندسی معکوس

مهندسی معکوس به همراه یک مثال ساده

در پست “ورود به دنیای مهندسی معکوس” با مهندسی معکوس آشنا شدیم. در این پست هم قصد داریم به یک مثال ساده در این مورد بپردازیم و نحوه انجام کار رو به صورت عملی ببینیم.

برنامه ای که برای این مثال در نظر گرفتم رو در ادامه میتونید ببینید.

reverse simple example 1 1024x576 - مثال ساده برای مهندسی معکوسابتدا اطلاعاتی رو در مورد فایل ELF با ابزار readelf می گیریم. همانطور که مشاهده می کنید، کلاس ELF64 هست که یعنی برای پردازنده های 64 بیتی میباشد. مدل داده هم مکمل 2 هست و little endian هم یعنی اعداد منفی از روش معکوس عدد بعلاوه یک به دست میان و همچنین داده ها که به صورت برعکس هستند، یعنی از آخر به اول چیده میشن. اطلاعات دیگه مثل نوع فایل که اجرایی هست و نوع سیستم عامل هایی که بر روی انها میتواند اجرا شود یعنی یونیکسی ها و BSD ها رو میتونید ببینید.

reverse simple example 3 1024x576 - مثال ساده برای مهندسی معکوس

خب، در اینجا با ابزار objdump یه disassembly از تابع main رو میبینیم و اگر پست قبلی را خوانده باشید میتونید از روی آدرس اون متوجه بشید که entry point همون تابع main نیست. ابزار objdump یک ابزار gnu هست و تمام ابزارهای گنو بطور پیشفرض از سینتکس at&t برای اسمبلی استفاده می کنند.

reverse simple example 4 1024x576 - مثال ساده برای مهندسی معکوس

خب در اینجا با ابزار readelf سمبل هایی که تابع هستند را می گیریم و توابع داخلی برنامه مثل save_command و create_tempfile و تابع main رو می بینیم که توی این لیست هستند.

reverse simple example 5 1024x576 - مثال ساده برای مهندسی معکوس

حالا ما یک فایل برای نوشتن شبه کد در سمت دیگر ایجاد می کنیم و بدنه ای برای این توابع داخلی می نویسیم.

reverse simple example 6 1024x576 - مثال ساده برای مهندسی معکوس

حالا ترجیحا با ابزار radare2 فایل رو باز می کنیم. ابتدا با دستور aaa آنالیز و hint فایل را فعال می کنیم، بعد از آن نقطه فعلی را می بریم به آدرس تابع main که البته ضرورتی هم نداره و صرفا برای این هست که هر دفعه در دستورات دیگر آدرس را مشخص نکنیم، بعد از اون با دستور pdf، disassembly از کل تابع میگیریم. سینتکس بطور پیشفرض روی اینتل هست، اگر با سینتکس at&t راحت تر هستید میتوانید با دستور e asm.syntax = att سینتکس را به at&t تغییر دهید.

reverse simple example 7 1024x576 - مثال ساده برای مهندسی معکوس

بذارید یک نگاه به 5 خط اول بیاندازیم. سه خط اول برای تنظیم حافظه stack است، یعنی حافظه ای که متغیرهای لوکال هر تابع در اون ذخیره میشن. خط اول محتویات ثبات rbp هست، یعنی همان base pointer که آدرس پایه رو ذخیره میکنه تا تابع بتونه استک خودش رو بسازه و آدرس استک قبلی یعنی استک تابعی که این تابع را صدا زده، حفظ شود تا در آخر تابع آن را برگرداند.

در خط دوم rsp را بر روی rbp میریزد که یعنی قرار است استک این تابع، پایین استک تابع قبلی قرار بگیرد. در خط سوم از rsp به هگز 0x40 یعنی 64 خانه کم می کند، یعنی استک 64 بایت می باشد.

در خط چهارم و پنجم از ثبات های rdi و rsi به ترتیب آرگومان های تابع را گرفته و در حافظه استک که ساخته است، ذخیره می کند. از آنجایی که ما می دانیم به تابع main چه آرگومان هایی داده می شود و با چه نوعی و به چه منظوری آنها رو در شبه کد می نویسیم. در اینجا فقط دو آرگومان گرفته شده پس فقط آنها را می نویسیم.

reverse simple example 8 1024x576 - مثال ساده برای مهندسی معکوس

خب در اینجا میبینیم که مکانیزم امنیتی پیشگیری از حملات stack smashing به نام stack guard بر روی این باینری صورت گرفته است. برای پیاده سازی این مکانیزم روی خروجیه باینری از gcc، کافیه فقط آپشن -fstack-protector را به آپشن های دیگه اضافه کنید. در مورد نحوه کارکرد این مکانیزم و روش دور زدن آن احتمالا در یک پست دیگر توضیح خواهم داد.

reverse simple example 9 1024x576 - مثال ساده برای مهندسی معکوس

خب در اینجا صدا کردن تابع puts رو می بینیم که یک رشته بعنوان آرگومان به آن داده اند. از آنجایی که با دستور aaa آنالیز انجام داده و hint ها را فعال کردیم بجای آدرس هگز، برنامه radare2 برای رشته ها برچسب گذاشته و از برچسب استفاده می کند.

reverse simple example 11 1024x576 - مثال ساده برای مهندسی معکوس

ما با استفاده از نام برچسب، مقدار نسبی اش را نوشتیم. اما برای اینکه مقدار دقیق را بدست بیاوریم از دستور ps که مخفف print string هست استفاده می کنیم.

reverse simple example 12 1024x576 - مثال ساده برای مهندسی معکوس

در این قطعه کد کاملا مشخص است که یک رشته را در یک آرایه بر روی استک ذخیره می کند.

reverse simple example 13 1024x576 - مثال ساده برای مهندسی معکوس

از آنجایی که byte order داده این فایل little endian است، یعنی داده ها از آخر به اول نوشته می شوند، ما بعد از تبدیل آن کد هگز به رشته کاراکتری آن را معکوس می کنیم.

reverse simple example 14 1024x576 - مثال ساده برای مهندسی معکوس

از آنجایی که عینا چند بار تکرار شده ما حدس میزنیم که آن ماکرو باشد که البته فرق زیادی نمی کند اما در خوانایی کد تاثیر دارد.

reverse simple example 15 1024x576 - مثال ساده برای مهندسی معکوس

در اینجا می بینیم که برنامه تابع داخلی create_tempfile را با یک آرگومان که همان رشته اسم فایل است صدا می کند. از این رو مشخصا نوع آرگومان char* می باشد.

reverse simple example 16 1024x576 - مثال ساده برای مهندسی معکوس

در اینجا خروجی تابع create_tempfile در یک متغیر از نوع int ذخیره شده و با عدد -1 مقایسه می شود. اکثر اوقات و تقریبا همیشه شرط پرش بعد از مقایسه برعکس شرط داخل پرانتز است و دستورات بعد از آن دستورات داخل بلوک ان شرط هستند. از آن دستور پرش در آدرس 0x4009be و نبود هیچ مقایسه ای بعد از آن و همینطور از دستور پرشی که در آدرس 0x400984 دیده ایم مشخص است که اون یک بلوک else هست.

reverse simple example 17 1024x576 - مثال ساده برای مهندسی معکوس

ما در اینجا یک صدا کردن تابع perror با یک آرگومان را میبینیم و بعد از آن میبینیم که عدد 1 را در ثبات eax میریزد که این مشخصا به این معنی است که خروجی تابع را تنظیم میکند و آن پرش مسلما به آخر تابع هست.

reverse simple example 18 1024x576 - مثال ساده برای مهندسی معکوس

خب حالا برمیگردیم به بلوک if و آن را ترجمه میکنیم. در ضمن دستوری که در آدرس 0x400992 میبینید صرفا برای پاکسازی اون ثبات برای تابعی که قرار است صدا کند می باشد.

reverse simple example 19 1024x576 - مثال ساده برای مهندسی معکوس

خب در اینجا تابع save_command را با سه آرگومان صدا میزند و خروجی تابع را بطور مستقیم با -1 مقایسه میکند. آن پرش به آدرس 0x4009d1 مشخص میکند که بلوک ما از آنجا شروع میشود. این شرط بعد از بلوک else قرار دارد، بنظر کامپایلر احتمالا بخاطر optimization این شرط را اینجا گذاشته. متاسفانه بعد از اتمام عکسبرداری ها متوجه این موضوع شدم. برای همین در صورت امکان این کدها را بعد از بلوک else در نظر بگیرید.

reverse simple example 20 1024x576 - مثال ساده برای مهندسی معکوس

خب حالا قسمت آن بلوک را ترجمه میکنیم.

reverse simple example 21 1024x576 - مثال ساده برای مهندسی معکوس

حالا میرویم به قسمتی که شرط if در صورت برقرار نبودن به آنجا پرش میکند که مشخصا بیرون از بلوک است، از آنجایی که ادامه بلوک مستقیما به آن میرسد.

reverse simple example 22 1024x576 - مثال ساده برای مهندسی معکوس

و ادامه را ترجمه و مقادیر دقیق رشته ها را میگذاریم.

reverse simple example 23 1024x576 - مثال ساده برای مهندسی معکوس

خب در اینجا میبینیم یک پرش به پایین انجام میگیرد، یک شرط چک میشود و درصورت برقرار بودن یک پرش به بالا انجام میشود تا یکسری دستورات اجرا شوند و دوباره به آن شرط برسند تا موقعی که آن شرط برقرار نشود. خب… این مشخصا یک حلقهمی باشد، حلقه while برای آن مناسب تر است.

reverse simple example 24 1024x576 - مثال ساده برای مهندسی معکوس

خب این شرط حلقه می باشد، و میبینیم در rbp-0x25 یک متغیر هست که از قبل مقدار دهی نشده است. این متغیر مشخصا از نوع char هست، بخاطر تبدیل به بایت و جابجایی sign دار در آدرس های 0x400a16 و 0x400a1a.

reverse simple example 25 1024x576 - مثال ساده برای مهندسی معکوس

این از بدنه و بلوک حلقه که آن متغیر کاراکتری را که خوانده چاپ میکند.

reverse simple example 26 1024x576 - مثال ساده برای مهندسی معکوس

اینجا هم بیرون از حلقه کاراکتر خط جدید یا همان ‘\n’ را چاپ میکند.

reverse simple example 27 1024x576 - مثال ساده برای مهندسی معکوس

خب در اینجا میبینیم که میخواهد مقدار صفر را برگرداند و مقدار stack canary را که اوایل تابع ذخیره کرده بود چک میکند. هر عددی که با خودش xor شود صفر خواهد شد، اینجا هم به همین ترتیب چک میکند تا ببیند که آن مقدار تغییر نکرده باشد، چرا که اگر عوض شده باشد، یعنی یک عامل خارجی آن را تغییر داده، بنابراین آن تابع را که میبینید صدا کرده تا خطا را نمایش دهد و به کاربر هشدار دهد، در غیر اینصورت به تابع صدا کننده برمیگردد.

reverse simple example 28 1024x576 - مثال ساده برای مهندسی معکوس

این هم از تابع create_tempfile. حالا فقط تابع save_command باقی میماند که اونم بعنوان تمرین خودتون انجام میدین.

موفق و پیروز باشید.

یک دیدگاه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *


دکمه بازگشت به بالا