ویرگول
ورودثبت نام
sia
siaیک script kiddie plus که اینجا از چیزای که یاد گرفته می نویسد :)
sia
sia
خواندن ۱۲ دقیقه·۶ سال پیش

bypass NX - PIE in linux X86 (writeup about an picoctf2018's challange)

سلام خدمت دوستان عزیز :)

لازم می دونم به شما اعلام بدارم که مفتخر شدید اولین پست من را بخوانید :)

امیدورام در حین خواندن خسته نشوید چون مبحث کمی طولانی و سنگینه !

بعضی از مطالب رو عمدا جا انداختم که بحث بیشتر از این باز نشود و نوشته خیلی طولانی نشود اما اگر جای سوال داشتید و براتون غیرقابل فهم بود، حتما سوال کنید من جوابتان را خواهم داد.

قبل از شروع با این نکته هم اشاره کنم که سعی کردم کلمات قلمبه و سلمبه را زیاد به کار نبرم تا کمی از سنگینی مطلب بکاهم .

یک مقدمه کوچک

یکی از مشکلاتی که هنگام اکسپلویت کردن یک نرم افزار پیش می آید،بیت `NX (Non - Execute)` است.در واقع یک قانون ساده برای باینری فایل های لینوکس (elf) تعیین می کند که به هیچ وجه بر روی پشته (stack) هیچ دستوری اجرا نشود!

با این حال ما نمی توانیم از شلکد برای مقاصد خود استفاده کنیم!

پس چیکار کنیم؟‌

یکی از راه های که برای دور زدن این محدودیت استفاده می شود بازگشت به کتابخانه libc است.

بلای دیگری که وجود دارد `PIE(Position-independent code)` است، مکانیزم `PIE` آدرس کد های که در حافظه بارگزاری می شوند را تصادفی می کند و دسترسی به توابع را کمی سخت می کند.

تقریبا تمام برنامه های که امروزه استفاده می شوند به صورت پیش فرض این مکانیزم ها را برای محافظ از خود به کار میگیرند.

ببینیم libc چی هست !

اگر به man پیج `libc` نگاه کنیم می بینیم که نوشته شده است :

اصطلاح libc به صورت گسترده به جای مخفف standard C libarary استفاده می شود.

در واقع کتابخانه استاندارد سی دارای تمام توابع استانداری است که در برنامه های سی استفاده می شوند، توابعی همچون puts , system و …

برای درک بیشتر این که چرا به `libc` باز می گردیم ، یک برنامه ساده C را بدون هیچ include می نویسیم و کامپایل می کنیم و با دستور `ldd` بررسی می کنیم که به چه کتابخانه های لینک شده و همانطور که می بینید به صورت خودکار کتابخانه `libc` به برنامه ما لینک شده، و دلیل استفاده از `libc` هم برای دور زدن محدودیت همین ویژگی است،

یعنی اینکه در بدترین شرایط هم ما می توانیم از کتابخانه libc استفاده کنیم !
first example
first example

چطور از libc استفاده کنیم؟

وقتی تابعی در برنامه فراخوانی می شود `EIP` برابر با آدرس تابع قرار می گیرد و تابع اجرا می شود و این موضوع هم برای توابع کتابخانه ای ثابت است و وقتی بتوانیم آدرس `EIP` را باز نویسی کنیم ، می توانیم تابع دلخواه خود را اجرا کنیم،‌که در این مثال می خواهیم تابع `system` را برای در اجرای دستور دلخواه خود فراخوانی کنیم.

مشکل نه چندان مشکل ! (PIE)

خب کمی هم در مورد `PIE` حرف بزنیم،اول اینکه با `ASLR` چند شباهت و تفاوت دارند.

اولین شباهتشون این است که کار هر دو تصادفی کردن مکان های از حافظه است

تفاوتشون این است که `ASLR` پشته را تصادفی می کند اما `PIE` آدرس های بخش code که در پایین تر قسمت حافظه بارگزاری می شود را تصادفی می کند !

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


بالا زدن آستین ها !

در آغاز ،برنامه ی را که می خواهیم برای آن اکسپلویت بنویسیم ، هم نگاهی بندازیم.

برنامه ی ما یکی از چالش های PicoCTF2018 در بخش binary explotion با 250 امتیاز می باشد.

vuln code
vuln code

با کمی بررسی می توانیم ببینیم که در تابع`main` آدرس چند تابع استاندار c را برای ما نمایش می دهد و بعدش تابع آسیب پزیر فراخوانی می شود.

یک متغیر هم وجود دارد که دارای رشته `"/bin/sh"` برای اجرا کردن یه پوسته می باشد.

اگر دقت کنید می بینید که در تابع `vuln` یک رشته از کاربر دریافت می شود و دوباره همان رشته چاپ می شود.

اما تابعی که رشته را می گیرد باعث تمام این خراب کاری ها است ، تابع `gets` !

تابع دردسر ساز (gets)

این تابع هیچ کنترلی برای میزان رشته وارد شده ندارد و هر چه را دریافت کرده باشد در متغیری که به عنوان اولین آرگومان به او داده شده ،می نویسد فارغ از آن که این متغیر ظرفیت آن را دارد یا خیر !

این مشکل می تواند باعث باگی به اسم سرریز جریان حافظه یا `buffer over flow` شود; نحوه رخ دادن مشکل هم به صورتی است که وقتی فضای تعبیه شده برای متغیر پر می شود از قسمت های بعدی حافظه برای ذخیره داده استفاده می کند.

عمق ماجرا !

چند ثبات که لازم است با آنها آشنا باشید را معرفی کنم:

  • ثبات `ESP`، این ثبات به ابتدای پشته یا همان stack تابع اشاره می کند.
  • ثبات `EBP` ، این ثبات به انتهای پشته اشاره می کند.
  • ثبات `EIP` ، آدرس دستور بعدی که قرار است توسط cpu اجرا شود را در خود ذخیره می کند !

اولین جای که بعد از پر شدن حافظه تعبیه شده برای متغیر/متغیر ها تحت تاثیر قرار می گیرد `EBP` یا اشاره گر پایه است و بعد از آن `ReturnAddress` است!

اگر ما بتوانیم آدرس بازگشت را بازنویسی کنیم ، در واقع جریان برنامه را در دست گرفتیم ! چطور ؟

به این صورت که وقتی یک تابع از جای فراخوانی می شود ، آدرس آن مکان به stack وارد می شود و بعد از اتمام کار تابع فراخوانی شده، آن آدرس در `EIP` قرار می گیرد و بدین صورت اگر ما آن را باز نویسی کنیم ، برنامه را می توانیم در دستانمان بچرخانیم :).

شروع تحلیل

برنامه را با آپشن m32- کامپایل می کنیم که مطمئن باشیم ۳۲ بیت خواهد بود.

`$ gcc vuln.c -m32 -o vuln`

جالبی موضوع اینجاست که `gcc‍‍` به صورت پیش فرض مکانیزم های nx - pie رو برای ما فعال خواهد کرد :)‌))))

چون سورس کد برنامه را در دست داریم تحلیل کردن ایستا و پویا دیگر معنای ندارد و انجام دادن آن فقط وقت ما را خواهد گرفت پس مستقیم پیش به سوی بایپس `PIE` می رویم.

اگر یادتان باشد گفتیم که می خواهیم تابع `system` را به کار بگیریم و آدرس آن را در `ReturnAddress` بازنویسی کنیم ، ولی چطور آدرس آن را پیدا کنیم ؟

خب با دیباگر `gdb` شروع به کار می کنیم‌ و آدرس تابع را از او می پرسیم!

برای این کار برنامه را با `gdb` اجرا می کنیم و دستورات زیر را به ترتیب در پوسته `gdb` وارد می کنیم.


(gdb) br main (gdb) r (gdb) x system
Examine system address
Examine system address

شاید برایتان سوال باشد که چرا ما یک توقف بر روی تابع `main` گذاشتیم؟

فکر کنم تا اینجا متوجه شده باشید که وقتی برنامه را اجرا کنیم نمی توانیم کار خاصی با دیباگر انجام دهیم و اگر هم آن را اجرا نکنیم آدرسی وجود نخواهد داشت تا ما دنبال آن بگردیم ، پس توقف بر روی تابع `main` همیشه بهترین انتخاب برای نقطه توقف است.(البته نه همیشه!)

با توجه به نتیجه، آدرس سمت چپ یعنی `0xf7deec00` مورد نیاز ما می باشد اما مشکل اینجاست که PIE در هر بار اجرا این آدرس را عوض می کند و تنها راه ما این است که فاصله آن را با یکی از توابع استاندارد دیگری که در ابتدای برنامه نمایش داده می شوند، محاسبه کرد و بعد از آن دوباره با همین فاصله بتوان آدرس دقیق آن را پیدا کرد, من تابع ‍`puts` را انتخاب می کنم و آدرسش را درخواست می کنم و بعد از تفریق کردن با آدرس `system` ، فاصله پیدا می شود :).

Examine puts address and calculate offset
Examine puts address and calculate offset

نوشتن اکسپلویت :)

وقت آن رسیده است با استفاده از ماژول `pwn` از فریمورک توسعه اکسپلویت و CTF ، به نام pwntools و زبان python اکسپلویت خودمان را بنویسیم.

اول از همه باید مکانی که در آن می توانیم ‍`EIP` را بازنویسی کنیم ، پیدا کنیم. این کار را می توانیم به صورت دستی انجام دهیم یا اینکه یک تابع ساده بنویسیم تا اینکار را برای ما انجام دهد.

پیدا کردن آفست eip

exploit part 1
exploit part 1

ماژول های مورد نیاز را وارد برنامه می کنیم و شروع به کد زدن می کنیم.

تابع ما یک elf دریافت می کند و آن را اجرا می کند و بعد با فرستادن یه الگوی ۳۰۰ کاراکتری به برنامه باعث شکست برنامه می شود که از طریق فایل core (بعد از شکست یک برنامه تحت دیباگر درست می شود و جزئیات برنامه هنگام شکست در آن وجود دارد) می توانیم مقدار رجیستر `EIP` را پیدا کنیم و با تابع `cyclic_find` میزان رشته مورد نیاز برای رسیدن به `EIP` را پیدا کنیم تا بتوانیم به درستی آن را بازنویسی کنیم ‌ و این فاصله را retrun می کنیم.دلیل استفاده از تابع `sleep` هم این است که فایل core به درستی درست شود چون ساختن آن کمی طول می کشد و زمان ساخت آن در هر سیستمی بستگی به حجم برنامه و منابع دارد که اگر این خط را حذف کنید می بینید که بعد از ۲ روز هم اکسپلویت اجرا نخواهد شد :)

چند خط زیر را هم به فایل exploit.py اضافه می کنیم.

exploit part 2
exploit part 2


در خط اول فایل elf را وارد برنامه کردم،و آن را به تابع خودمون فرستادم تا فاصله `eip` از `esp` را برای ما پیدا کند و بعد مقدار برگشتی تابع را با یکی از شئ های زیر مجموعه `pwn` که برای لاگ کردن بر روی ترمینال استفاده می شود روی ترمینال چاپ کردم و بعد هم فاصله `puts` و `system` را که در بالاتر به دست آوردیم به یک متغیر دادم.

پیدا کردن آدرس تابع system

حال باید برنامه را اجرا کنیم و آدرس `puts` را در آن اجرا پیدا کنیم تا آدرس `system` را به وسیله آن پیدا کنیم،چون `PIE` عزیز با هر بار اجرا آدرس ها را تغیر می دهد! خب اگر برنامه را اجرا کنید می بینید که خودش آدرس تابع `puts` را برای ما چاپ می کند,فقط ما باید آن را جدا کنیم که در اینجا کتابخانه `re` به کمک ما می آید.

exploit part 3
exploit part 3

من برنامه را اجرا کردم و خروجیش را در متغیر `prompt` ریختم و با استفاده از متود `findall` از ماژول `re` قسمت های مورد نظر خودمو جدا کردم و چون نوع آنها `str` است من به `int` تبدیل کردم و بعد هم چاپشون کردم.

آدرس `bin_sh` بعدا برای اجرا کردن یک پوسته توسط تابع `system` به کارمان می آمد ، پس آن را هم جدا میکنیم.

و با استفاده از عددی که در هنگام تحلیل کردن، از تفریق کردن آدرس تابع `puts` و `system` به دست آوردیم، استفاده می کنیم تا دوباره بتوانیم آدرس `system` را پیدا کنیم، یک معادله جبری ساده :).

چینش payload

حال بیاید payalod خود را بچینیم و کار را به پایان برسانیم !

اما یه سوال دیگه هم اینجا پیش می آید که اگر آدرس `EIP` را با آدرس `system` هم بازنویسی کنیم خب چطوری به تابع پارامتر بفرستیم!؟

بعد از اینکه آدرسس `EIP` بازنویسی شد بعد از اون ۴ بایت junk میزاریم و بعد از اون هم پارمتر های تابع دقیقا به شکل زیر :

system + 'Junk' + params‍

در واقع ۴ بایت junk که ما گذاشتیم قرار است بعد از اتمام کار تابع `system` ،در `EIP` قرار گیرد و فرایند برنامه از آنجا ادامه پیدا کند و به زبان ساده تر `ReturnAddress` تابع `system` ما خواهد بود و چون این برای ما مهم نیست پس ۴ بایت دلخواه در آن می نویسیم.

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

پس چند خط زیر را در ادامه اضافه می کنیم :

exploit part 4 (end)
exploit part 4 (end)

به اندازه متغیر `eip_write_offset` در پیلودمان بایت اضافه می نویسیم تا بتوانیم دقیقا به پشت `EIP` برسیم و به درستی `EIP` را بازنویسی کنیم ،بعد آدرس تابع `system` را اضافه می کنیم و ۴ بایت دیگر هم برای `ReturnAddress` و در آخر هم آدرس رشته `"/bin/sh"` که خود برنامه به ما داده بود را به عنوان آرگومان برای تابع می فرستیم تا آن را برای ما اجرا کند.

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

این پیلود ما در واقع توسط تابع `gets` که در بالاتر به آن پرداختیم گرفته می شود و پردازش می شود و چون حجم داده ما از میزان حافظه تعبیه شده برای متغیر بیشتر است پس از حد خود فراتر رفته و موجب بازنویسی مکان های بر روی حافظه می شود.

من روی `system_addr` و `bin_sh` یک تابع فراخونی کردم،‌ کار اصلی این تابع این است که بر اساس آپشن های که به عنوان آرگومان به او داده می شود، آدرس ها را با فرمت `big_endian` یا `little_endian` به شما می دهد که به صورت پیش فرض `little_endian` است و یک نکته دیگر هم بگویم که چون برنامه ما ۳۲ بیت بود به همین خاطر `p32` را انتخاب کردم(نسخه ۶۴ بیت آن با اسم `p64` موجود است)،دلیل استفاده از این توابع این است که آدرس های مد نظر ما به درستی در حافظه قرار بگیرند.

تکمیل فرایند

اکسپلویت نهای به این صورت می باشد.

exploit code
exploit code

و حال موقع اجرای آن رسیده است :)))))))


$ python exploit.py
resualt
resualt

و بعد از تمام شدن کار می تونید ببینید که شل برقرار هستش ?

اما چند نکته هم اینجا بگم که تمام آدرس ها و افست ها به ریلیز سیستم عامل و `libc` بستگی دارد و متفاوت با اعداد من خواهند بود و اینم بگم که همیشه لازم نیست. شما آدرس دو تابع را بدانید و مشغول جمع تفریق شود ، در واقع می توانید با استفاده از آدرس یک تابع که توسط برنامه کشف می کنید ،با سایت های مثل https://libc.blukat.me در دیتابیس هایش سرچ کنید و با آدرس تابع خودتون ،آدرس تابع های مفید دیگر را هم پیدا کنید یا اینکه می توانید فاصله آن تابع را با آدرس پایه libc محاسبه کنید ، خلاصه بگم دستتون بازه ... :)

امیدورام خسته نشده باشید ;)

از شما ممنونم که وقت خود را صرف خواندن این نوشته کردید ، منتظر نظرات شما هستم :)


python
۱۱
۰
sia
sia
یک script kiddie plus که اینجا از چیزای که یاد گرفته می نویسد :)
شاید از این پست‌ها خوشتان بیاید