برنامه‌نویسی بازگشت محور

برنامه‌نویسی بازگشت محور (به انگلیسی: Return Oriented Programming) یکی از تکنیک‌هایی می‌باشد برای انجام exploit در زمان‌هایی که مکانیزم‌های امنیتی مانند مکانیزم‌هایی که جلوگیری از اجرای کد در قسمت‌های مختلف حافظه پیاده‌سازی شده‌اند استفاده می‌شود. در این تکنیک حمله کنند بعد از به دست گرفتن control flow برنامه، لیستی از دستورات اسمبلی مناسب داخل برنامه را پیدا کرده که به آنها ابزارک گفته می‌شود، که هرکدام از این ابزارکها قسمتی از کار اصلی مخرب را انجام می‌دهند، و معمولاً این ابزارکها با دستور return خاتمه پیدا می‌کنند و محل آنها می‌تواند داخل خود کد برنامهٔ مورد هدف باشد یا داخل کد کتابخانه‌های بارگذاری شده در حافظه. حمله کننده به ترکیب این ابزارکها و پیدا کردن ترتیب مناسب برای پرش به هرکدام کار مخرب خود را انجام می‌دهد.

پیش زمینه ویرایش

 
An example layout of a call stack. The subroutine DrawLine has been called by DrawSquare. Note that the stack is growing upwards in this diagram.

برنامه‌نویسی بازگشت محور یکی از حملات پیشرفته‌ای می‌باشد که زیر مجموعهٔ حملات stack smashing می‌باشد. معمولاً اینگونه حملات با سوءاستفاده از یک باگ داخل کد برنامه call stack برنامه را تغییر می‌دهند تا کنترل به دست حمله کننده بیافتد، در اینگونه باگ‌ها به دلیل اینکه برنامه‌نویس اندازهٔ ورودی دریافت شده را چک نکرده‌است حمله کننده می‌تواند مقدار دلخواه ورودی به برنامه بفرستند و با اینکار محتوای stack را تغییر دهد، اینکار باعث می‌شود که مقدار return address که داخل stack می‌باشد نیز تغییر کرده و در نتیجه کنترل رجیستر Instruction Pointer به دست حمله کننده بایفتد و در نتیجه control flow برنامه تحت نظر حمله کننده خواهد بود.

در حملات stack smashing ساده حمله کنند مقدار آدرس return address را به آدرسی داخل خود stack تغییر خواهد داد و در نتیجه کد یا payload خود را داخل stack می‌نویسد، و با پرش به کد خود کار مخرب را انجام خواهد داد. اما بسیاری از سیستم‌های عامل و کامپایلرها برای جلوگیری از اینگونه حملات مکانیزم‌های امنیتی مختلفی را معرفی کردند که در این تکنیک محل‌هایی از حافظه که داده‌ها در آنها ذخیره می‌شوند مانند stack که قابلیت write نیز دارند نباید قابلیت اجرایی داشته باشند، تکنیکی که بعدها به W^X معروف شد که به معنای این می‌باشد که محل‌هایی از حافظه که قابلیت write دارند نباید قابلیت executable نیز داشته باشند زیرا حمله کننده می‌تواند با تغییر مقادیر این محل‌ها کنترل برنامه را به دست بگیرد.

با این مکانیزم امنیتی دیگر حمله کننده‌ها نمی‌توانند به محل‌هایی از حافظه که قابلیت write در آنها می‌باشد مانند stack برای اجرای کد خود پرش کنند و در نتیجه نمی‌توانند payload خود را داخل stack نوشته و برای اجرایش به آن پرش کنند، در نتیجه حمله کننده‌ها برای دور زدن این مکانیزم‌های امنیتی به تکنیک‌های دیگری رو آوردند که از کدهای موجود داخل برنامه و کتابخانه‌ها برای اجرای کارهای مخرب خود استفاده می‌کند. در کل حمله کنندگان دو انتخاب برای پرش دارند، یکی استفاده از کدهای کتابخانه‌های لود شده در حافظه که به روش Ret2Lib معروف است و دیگری استفاده از کدهای خود برنامهٔ مورد هدف.

Ret2Lib ویرایش

در این روش حمله کننده از کدهای موجود در کتابخانه‌های بارگذاری شده در حافظه برای انجام اعمال مخرب خود استفاده می‌کند، یعنی به جای پرش به داخل خود stack و اجرای payload از داخل آن، به یک سری از توابع داخل کتابخانه‌های بارگذاری شده پرش می‌کند. در این روش معمولاً توابعی که قابلیت اجرای کد را به برنامه‌نویس می‌دهند استفاده می‌شود، برای مثال تابع system در libc به عنوان یکی از ورودی‌های خود رشته‌ای دریافت می‌کند که مسیر برنامه‌ای می‌باشد که برنامه‌نویس می‌خواهد آنرا اجرا کند، در نتیجه حمله کننده می‌تواند ابتدا آدرس return را به این تابع تغییر داده و به عنوان ارگومان نیز آدرس رشتهٔ /bin/sh را داخل stack قرار دهد، در نتیجه برنامه به جای بازگشت به محل اصلی، به تابع system پرش می‌کند و با اجرای /bin/sh باعث می‌شود حمله کننده یک shell از سیستم بگیرد.

Borrowed Code Chunk ویرایش

با ظهور پردازشگرهای x64 نحوهٔ پاس دادن ارگومان‌ها به توابع تغییر کرد، و به جای اینکه ارگومان‌ها داخل stack قرار داده شوند، داخل یک سری از رجیسترها قرار داده می‌شوند، در نتیجه دیگر حمله کننده‌ها نمی‌توانستند به راحتی به توابع کتابخانه‌ای پرش کرده و ارگومان را داخل stack قرار دهند و همچنین توسعه‌دهندگان کتابخانه‌ها نیز بعضی از توابع مورد هدف حمله کننده‌ها را از کد خود حذف کردند، در نتیجه انجام حملات ret2lib پس از این تغییرات بسیار دشوارتر شد.

برای دور زدن این محدودیت‌ها حمله کننده‌ها به جای پرش مستقیم به توابع، از قسمت‌هایی از کدهای موجود در توابع استفاده کرده و به آنها پرش می‌کردند که کارهای مورد نیاز آنها را انجام دهد، برای مثال به قسمت‌هایی پرش می‌کردند که مقادیر داخل stack را در داخل رجیسترهای مورد هدف قرار می‌دهد و در نتیجه با اینکار آرگومان‌هایی مورد نیاز را به توابع پاس می‌دادند، باقی حمله نیز مانند ret2lib انجام می‌شد.

حملات ویرایش

در روش برنامه‌نویسی بازگشت محور از Chunkهای مختلف از کدهای داخل برنامه برای انجام اعمال مخرب استفاده می‌شود که در واقع یک عملکرد Turning Complete را به حمله کننده می‌دهد که شامل loopها و پرش‌های شرطی می‌باشد. در واقع برنامه‌نویسی بازگشت محور یک زبان کامل و کارا را در اختیار حمله کننده می‌گذارد تا هر نوع عمل مورد نیاز را توسط آن انجام دهد و در سال ۲۰۰۷، Hovav Shacham نشان داده که تمامی اعمال مهم برنامه‌نویسی را با این روش می‌توان انجام داد.

اینگونه حمله نسبت به دیگر حملات هم از لحاظ قدرت و هم از لحاظ دور زدن مکانیزهای امنیتی برتری دارد. هیچ‌یک از مکانیزهای امنیتی اشاره شده امکان مقابله با این حمله را ندارند.

حمله به معماری x86 ویرایش

با اینکه حملهٔ برنامه‌نویسی بازگشت محور در هر معماری قابل انجام است، تمرکز مقالهٔ Shacham روی معماری X86 می‌باشد. x86 یک معماری CISC می‌باشد که طول دستورات متفاوت می‌باشد. حملهٔ برنامه‌نویسی بازگشت محور در x86 از این خاصیت معماری استفاده می‌کند که هر ترتیبی از بایت‌های به احتمال بالا می‌توانند به عنوان دستورات x86 استفاده شوند. در نتیجه می‌توان به دنبال بایت‌هایی مانند 0xC3 گشت که دستور ret می‌باشد، و بایت‌های قبل آن را برای استفاده به عنوان دستور چک کرد و در صورتی که کدهای قابل اجرا و مفید باشند، آنرا می‌توان به عنوان یک ابزارک در نظر گرفت و به لیست ابزارکهای قابل استفاده اضافه کرد.

در این روش حمله ابزارکهایی از کد خود برنامه را به دقت انتخاب می‌کند، به گونه‌ای که با اجرای پشت سرهم آنها کار مخربش انجام شود. بنابرین حمله کننده آدرس‌های این ابزارکهای که معمولاً نیز با دستور ret ختم می‌شوند را پیدا می‌کند، و آدرس‌های آنها را داخل stack به ترتیب اجرا قرار می‌دهد. بنابرین آدرس return address اصلی پس از حمله به آدرس اولین ابزارک تغییر پیدا می‌کند (برعکس حملهٔ بالا که به آدرس یک تابع در یک کتابخانه تغییر پیدا می‌کند) و بعد از آن نیز داخل stack به ترتیب آدرس ابزارکهای بعدی قرار داده می‌شود، در نتیجه برنامه پس از پرش به ابزارک اول و اجرای کد آن، با رسیدن به دستور ret، به ابزارک بعدی که آدرس آن داخل stack قرار داده شده پرش می‌کند و به همین ترتیب همهٔ ابزارکها اجرا می‌شوند.

همچنین ابزاری نوشته شده‌است که به صورت خودکار این ابزارک‌های داخل برنامه را پیدا کرده، و ترتیب‌های مناسبی از آنها را پیدا می‌کند که یک shell را ایجاد کرده و به حمله کننده بدهند، نام این ابزار ROPابزارک می‌باشد.

به تصادفی سازی آدرس‌ها یا ASLR ویرایش

ASLR که در واقع محل بارگذاری ماژول‌های مختلف برنامه را در حافظه در هر بار اجرا تغییر می‌دهد نیز نسبت به این حمله می‌تواند شکست بخورد و آسیب‌پذیر است، در سیستم‌های ۳۲ بیتی فقط ۱۶ بیت از آدرس حافظه قابلیت تصادفی شدن دارند، و این ۱۶ بیت را می‌توان با حملهٔ Brute Force شکست داد. در سیستم‌های ۶۴ بیت نیز ۴۰ بیت از ۶۴ بیت آدرس قابل استفاده برای تصادفی کردن هستند که حملهٔ Brute Force برای ۴۰ بیت نیز ممکن می‌باشد ولی به راحتی قابل تشخیص است به دلیل فرستاده شدن تعداد درخواست‌های بسیار بالا به برنامه. همچنین حتی با وجود تصادفی سازی ایده‌آل نیز در صورتی که محتوای قسمت‌های خاصی از حافظه لو برود می‌توان ASLR را دور زد، برای مثال در صورتی که آدرس شروغ libc با استفاده از حملات heap به دست بیاید می‌توان ASLR را دور زد.

ابزارکهای بدون ret ویرایش

نشان داده شده‌است که می‌توان در سیستم‌های x86 و ARM, ابزارکهایی داشته باشیم که با ret ختم نمی‌شوند، نحوهٔ این روش نیز ساده است و برمیگردد به عملی که دستور ret انجام می‌دهد، این دستور ابتدا آدرس موجود که در بالای stack را در رجیستر Instruction pointer قرار داده (pop می‌کند) و سپس به آن پرش می‌کند. در نتیجه در صورتی که دستوری پیدا شود که اعمال pop و jmp را به ترتیب انجام می‌دهد می‌توان از آن به عنوان ret استفاده کرد. در نتیجه در این روش بعضی مکانیزم‌های دفاعی که فقط به دنبال ret در ابزارکها هستند دور زده می‌شوند، اما در صورتی که پرش‌ها نیز چک شوند این روش قابل شناسایی است.

دفاع ویرایش

G-Free ویرایش

این روش تمامی دستورات unaligned پرشی را مانند RET و CALL را از داخل فایل اجرایی حذف می‌کند و باعث می‌شوند حمله کننده نتواند از دستورات پرشی استفاده کند. نحوهٔ محافظ آدرس بازگشتی آن نیز مانند روش XOR canary می‌باشد. همچنین برای چک کردن صحت فراخوانی‌های توابع از اضافه کردن یک validation block استفاده می‌کند. اگر مقدار مورد انتظار پیدا نشود برنامه کرش می‌کند.

ASLR ویرایش

در این روش آدرس ماژول‌های مختلف برنامه مانند توابع کتابخانه‌ای لود شده و stack و آدرس لود شدن خود برنامه با هر بار اجرای برنامه تغییر می‌کنند، در نتیجه حمله کننده قبل از اجرا نمی‌تواند آدرس دقیق محل‌هایی که قرار است به آنها پرش کند را بداند و در نتیجه آدرس ابزارکها با هر بار اجرای برنامه تغییر می‌کنند، اما همچنان می‌توان این روش را با bruteforce شکست داد به خصوص در سیستم‌های ۳۲ بیتی، همچنین در صورتی که در برنامه نشط اطلاعات وجود داشته باشد و برای مثال آدرس شروع بعضی توابع یا کتابخانه‌ها قابل به دست آمدن باشد می‌توان ASLR را دور زد. البته ماژول‌هایی که قابلیت random سازی برای آدرس شروع آنها موجود است بسته به نحوهٔ کامپایل شدن آنها و همچنین سربار قابل تحمل در سیستم می‌باشد و برای مثال ممکن است در مواردی فقط آدرس شروع کتابخانه‌ها تصادفی باشد.

یک روش دیگر که توسط kBouncer استفاده می‌شود این است که چک می‌شود که محلی که آدرس return به آن اشاره می‌کند آیا دستور قبلی آن آدرس دستور call بوده‌است یا نه که سربار زیادی را متحمل سیستم عامل خواهد کرد و همچنین در مقابل حملاتی که از دستور Jmp استفاده می‌کنند مقاوم تیست.

تصادفی سازی کد باینری ویرایش

در این روش در سیستم‌هایی که قابلیت کامپایل کردن به صورت on-the-fly را دارند، که یعنی به هنگام پخش کردن کد کامپایل انجام می‌شود، هر نمونه از کد کامپایل شده نسبت به دیگری تفاوت‌های مختلفی دارد و در نتیجه هر نسخهٔ کامپایل شده با نسخهٔ دیگر متفاوت است و باعث می‌شود که اگر حمله‌ای روی یک نمونه کار کند روی نمونه‌های دیگر آن برنامه کار نکند، که این روش به خصوص در هنگام آپدیت کردن دستگاه‌ها به کار می‌آید. اما ضعف این روش این است که قبل از پخش کد تست روی آن برای اطمینان از اجرای صحیح آن نمی‌توان انجام شود، در نتیجه این روش در الگوریتم‌های پیچیده بهتر است استفاده نشود.

W^X ویرایش

مبنای این مکانیزم امنیتی این است که قسمت‌هایی از حافظه که قابلیت write شدن روی آنها می‌باشد نباید قابلیت execute شدن نیز داشته باشند (در واقع pageهای حافظه) در نتیجه stack که قابلیت write شدن روی آن هست نباید قابلیت اجرایی نیز داشته باشد. البته W^X در مقابل برنامه‌نویسی بازگشت محور مقاوم نیست اما مانند ASLR می‌تواند حمله را سخت‌تر کرده و برنامه را در مقابل حملات مقاوم تر کند.

SEHOP ویرایش

این مکانیزم که در ویندوز پیاده‌سازی شده، برای مقابله با حملاتی می‌باشد که SEH را مورد حمله قرار می‌دهند (SEH یک linked list در ویندوز می‌باشد که هر نود آن آدرس تابعی را شامل می‌باشد که یک exception خاص را جواب می‌دهد).

مقابله با حملات control flow ویرایش

در این روش با استفاده از Memory Access Controlهای مبتنی بر دستورات که در سخت‌افزار پیاده‌سازی می‌شود، سیستم‌های ارزان قیمت نهفته در مقابل حملات stack overflow محافظت می‌شوند، در این روش با جدا کردن return stack و data stack جلوی حملاتی که آدرس بازگشتی را تغییر می‌دهند گرفته می‌شود. البته با توجه به محدودیت‌های حافظه‌ای که بعضی از سیستم‌های نهفته دارند این روش در همهٔ آنها قابل پیاده‌سازی نیست.

مقابله با روتکیت‌های return oriented ویرایش

در این روش با تغییر کامپایلر، تمامی دستورات call f به pushl $index; jmp f تبدیل می‌شوند و تمامی دستورات ret به popl %ebx; jmp table(%ebx) تبدیل می‌شوند که در اینجا table در واقع یک جدولی از آدرس‌های قابل بازگشت مجاز است که در هنگام کامپایل کردن مشخص می‌شوند و index نیز یکی از درایه‌های جدول است. این روش باعث می‌شود که از حملاتی که به محل‌های دیگری که خارج جدول هستند بازگشت می‌کنند جلوگیری شود. نویسندگان این روش ادعا می‌کنند که روششان باعث ضعیف شدن برنامه‌نویسی بازگشت محور و تضعیف آن به حملات ret2lib می‌شود.

PAC ویرایش

معماری ARMv8.3-A یک قابلیت جدید در سطح سخت‌افزار را معرفی کرد که از بیت‌های استفاده نشده در فضای آدرس برای sign کردن اشاره‌گرهای آدرس‌ها با استفاده از یک block cipher استفاده می‌کند که در واقع مقدار مورد نظر را با یک مقدار محلی sign می‌کند. قبل از انجام عملیات‌های حساس مانند مانند بازگشت به آدرس ذخیره شده می‌توان با چک کردن امضا به تغییرات ناخواسته یا استفاده‌های نادرست پی برد. اپل در آیفون و لینوکس در حال حاضر از این تکنولوژی استفاده می‌کنند.