Зворотно-орієнтоване програмування

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку

Зворотно-орієнтоване програмування (англ. return oriented programming, ROP) — метод експлуатації вразливостей в програмному забезпеченні, використовуючи який атакуючий може виконати необхідний йому код за наявності в системі захисних технологій, наприклад технології, що забороняє виконання коду з певних сторінок пам'яті[1]. Метод полягає в тому, що атакуючий може отримати контроль над стеком викликів, знайти в коді послідовності інструкцій, які виконують потрібні дії і звані «гаджетами», виконати «гаджети» в потрібній послідовності[2]. «Гаджет», як правило, закінчується інструкцією повернення і розташовується в оперативній пам'яті в існуючому коді (в коді програми або в коді використовуваної бібліотеки). Атакуючий домагається послідовного виконання гаджетів за допомогою інструкцій повернення, складає послідовність гаджетів так, щоб виконати потрібні операції. Атака реалізувати навіть на системах, що мають механізми для запобігання більш простих атак.

Історія[ред. | ред. код]

Атака на переповнення буфера[ред. | ред. код]

Зворотно-орієнтоване програмування — це вдосконалена версія атаки на переповнення буфера. При цій атаці атакувальник використовує помилку в програмі, коли функція не перевіряє (або неправильно перевіряє) межі при запису в буфер даних, отриманих від користувача. Якщо користувач передає більше даних, ніж розмір буфера, то зайві дані потрапляють в область пам'яті, призначену для інших локальних змінних, а також можуть перезаписати адреси повернення. Якщо адреса повернення виявляється перезаписаною, то при поверненні з функції керування буде передано знову записаній адресі.

У найпростішій версії атаки на переповнення буферу, атакувальник поміщає код(«корисне навантаження») у стек, а потім перезаписує адресу повернення адресою тільки що записаних ним інструкцій. До кінця 90-х років більшість операційних систем не надавало ніякого захисту від цих атак. У системах Windows не було захисту від атак переповнювання буфера до 2004 року.[3] Врешті решт, операційні системи стали боротися з експлуатацією вразливостей переповнення буфера, позначаючи певні сторінки пам'яті як невиконувані (технологія, яка отримала назву «Запобігання виконання даних»). При включеному запобіганні виконання даних машина відмовиться виконувати код у сторінках пам'яті, позначені «тільки для даних», включаючи сторінки, що містять стек. Це не дозволяє помістити корисне навантаження в стек, а потім перейти на нього, перезаписавши адреси повернення. Пізніше для посилення захисту з'явилася апаратна підтримка запобігання виконання даних.

Атака повернення в бібліотеку[ред. | ред. код]

Запобігання виконання даних не дозволяє провести атаку методом, описаним вище. Атакуючий обмежений кодом, вже наявними в атакуючій програмі і подключених бібліотеках. Проте колективні бібліотеки, зокрема libc, часто містять функції для здійснення системних викликів та інші корисні для атакуючого функції, що дозволяє використовувати ці функції при атаці.

При атаці повернення до бібліотеки також експлуатується переповнення буфера. Адреса повернення буде точкою входу потрібної бібліотечної функції. Також перезаписуються комірки над адресою повернення, щоб передати функції параметри або зв'язати в ланцюжок кілька викликів. Ця техніка була вперше представлена Олександром Песляком (відомим як Solar Designer) у 1997 році,[4] а потім була розширена, дозволяючи зробити необмежений ланцюжок викликів функцій.[5]

Запозичення шматків коду[ред. | ред. код]

З поширенням 64-розрядного обладнання і операційних систем стало складніше проводити атаку повернення в бібліотеку: у використовуваних в 64-розрядних системах угодах про виклик перші параметри передаються функції не в стеку, а в регістрах. Це ускладнює підготовку параметрів для виклику в процесі атаки. Крім того, розробники поділюваних бібліотек стали прибирати з бібліотек або обмежувати «небезпечні» функції, такі як обгортки системних викликів.

Наступним витком розвитку атаки стало використання частин бібліотечних функцій замість функцій цілком.[6] У цій техніці шукаються частини функцій, які передають дані з стека в регістри. Ретельний підбір цих частин дозволяє підготувати потрібні параметри в регістрах для виклику функції за новою угодою. Далі атака проводиться так само, як атака повернення до бібліотеки.

Атака методом зворотно-орієнтованого програмування[ред. | ред. код]

Зворотно-орієнтоване програмування розширює підхід запозичення шматків коду, надаючи атакувальнику повну за Тюрингом функціональність, включаючи цикли і розгалуження.[7] Іншими словами, зворотно-орієнтоване програмування надає атакувальнику можливість виконати будь-яку операцію. Ховав Шахам опублікував опис методу у 2007 році[8] і продемонстрував його на програмі, що використовує стандартну бібліотеку мови Сі і містить вразливість переповнення буфера. Зворотно-орієнтоване програмування перевершує інші описані вище типи атак і з виразною потужністю, і по стійкості до захисних заходів. Жоден з вищеописаних методів протидії атакам, включаючи видалення небезпечних функцій з поділюваних бібліотек, не є ефективним проти зворотно-орієнтованого програмування.

На відміну від атаки повернення в бібліотеку, в якій використовуються функції цілком, у зворотно-орієнтованому програмуванні використовуються невеликі послідовності інструкцій, що закінчуються інструкцією повернення, так звані «гаджети». Гаджетами є, наприклад, закінчення наявних функцій. Однак на деяких платформах, зокрема x86, гаджети можуть виникати «між рядків», тобто при декодуванні з середини наявної інструкції. Наприклад, наступна послідовність інструкцій[8]:

test edi, 7        ; f7 c7 07 00 00 00
setnz byte[ebp-61] ; 0f 95 45 c3

при початку декодування на один байт пізніше, дає

mov dword[edi], 0f000000h ; c7 07 00 00 00 0f
xchg ebp, eax             ; 95
inc ebp                   ; 45
ret                       ; c3

Також гаджети можуть перебувати в даних, які  з тих чи інших причин знаходяться в секції коду. Це пов'язано з тим, що набір інструкцій архітектури x86 досить щільний, тобто велика ймовірність того, що довільний потік байтів буде інтерпретований як потік дійсних інструкцій. З іншого боку, в архітектурі MIPS всі інструкції мають довжину 4 байти, а можна виконувати тільки інструкції, вирівняні за адресами, кратним 4-м байтам. Тому там не можна отримати нову послідовність «читанням між рядків».

При атаці експлуатується вразливість переповнення буфера. Адреса повернення з поточної функції перезаписується адресою першого гаджета. На наступні позиції в стеку записуються адреси таких гаджетів і дані, використовувані гаджетами.

Приклади[ред. | ред. код]

Розширення[ред. | ред. код]

У своєму первісному варіанті для платформи x86, гаджети являють собою ланцюжки послідовно розташованих інструкцій без переходів, які завершуються інструкцією близького повернення. В розширених варіантах атаки інструкції ланцюжки можуть не обов'язково бути послідовно розташовані, а пов'язані інструкціями прямого переходу. Також роль завершальної інструкції може виконувати інша інструкція повернення (x86 є ще інструкція далекого повернення, інструкції близького і далекого повернення з очищенням стека), інструкція непрямого переходу або навіть непрямого виклику. Це ускладнює боротьбу з даним методом атаки.

Автоматична генерація[ред. | ред. код]

Існують інструменти для автоматичного знаходження гаджетів і конструювання атаки. Прикладом такого інструменту може служити ROPgadget.[9]

Захист від зворотно-орієнтованого програмування[ред. | ред. код]

Існує кілька методів захисту від зворотно-орієнтованого програмування.[10] Більшість покладаються на розташування коду програми і бібліотек за відносно безпідставного адресою, так що атакуючий не може точно передбачити розташування інструкцій, що можуть бути корисними в гаджетах, а значить не може побудувати ланцюжок гаджетів для атаки. Одна з реалізацій цього методу, ASLR, завантажує колективні бібліотеки за адресою, різною при кожному запуску програми. Однак, хоча ця технологія широко застосовується в сучасних операційних системах, вона є вразливою до атак витоку інформації та інших атак, що дозволяє визначити положення відомої бібліотечної функції. Якщо атакуючий може визначити розташування однієї функції, він може визначити розташування всіх інструкцій бібліотеки і провести атаку методом зворотно-орієнтованого програмування.

Можна переставляти не лише бібліотеки цілком, а й окремі інструкції програм і бібліотек.[11] Однак це вимагає великої підтримки під час виконання, наприклад, динамічної трансляції, щоб повернути переставлені інструкції у правильний порядок для їх виконання. Цей метод ускладнює знаходження і використання гаджетів, однак має великі накладні витрати.

Підхід, що застосовується в kBouncer[12], полягає в перевірці того, що інструкція повернення передає управління на інструкцію, безпосередньо наступну за інструкцією виклику. Це сильно скорочує кількість можливих гаджетів, але також викликає значне зниження продуктивності. Крім того в розширеному варіанті зворотно-орієнтованого програмування гаджети можна пов'язувати не тільки інструкцією повернення, але і інструкцією непрямого переходу або виклику. Проти такої розширеної атаки kBouncer буде неефективний.

Примітки[ред. | ред. код]

  1. Shacham, Hovav; Buchanan, Erik; Roemer, Ryan; Savage, Stefan. Return-Oriented Programming: Exploits Without Code Injection. Архів оригіналу за 1 червня 2010. Процитовано 12 серпня 2009.
  2. Erik Buchanan, Ryan Roemer, Hovav Shacham and Stefan Savage; When Good Instructions Go Bad: Generalizing Return-Oriented Programming to RISC [Архівовано 11 серпня 2017 у Wayback Machine.], in Proceedings of CCS 2008, ACM Press, October 2008.
  3. Microsoft Windows XP SP2 Data Execution Prevention (англ.). Microsoft. Архів оригіналу за 14 квітня 2018. Процитовано 19 квітня 2018.
  4. Solar Designer, Return-into-lib(c) exploits [Архівовано 23 лютого 2018 у Wayback Machine.], Bugtraq
  5. Nergal, Phrack 58 Article 4, return-into-lib(c) exploits [Архівовано 25 квітня 2013 у Wayback Machine.]
  6. Sebastian Krahmer, x86-64 buffer overflow exploits and the borrowed code chunks exploitation technique [Архівовано 22 грудня 2018 у Wayback Machine.], September 28, 2005
  7. Abadi, Martín; Budiu, Mihai; Erlingsson, Úlfar; Ligatti, Jay (November 2005). Catherine Meadows and Ari Juels (ред.). Control-Flow Integrity: Principles, Implementations, and Applications. Proceedings of CCS 2005 (англ.). ACM.
  8. а б Hovav Shacham. The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86). In Proceedings of the 14th ACM conference on Computer and communications security (CCS '07). ACM 2007
  9. Salwan, Jonathan; Wirth, Allan. ROPgadget — Gadgets finder and auto-roper (англ.). Архів оригіналу за 15 квітня 2018. Процитовано 19 квітня 2018.
  10. Richard Skowyra, Kelly Casteel, Hamed Okhravi and William Streilein; Systematic Analysis of Defenses Against Return-Oriented Programming [Архівовано 22 лютого 2014 у Wayback Machine.], In Proceedings of RAID 2013, Lecture Notes in Computer Science (LNCS), Vol. 8145, pp. 82-102, 2013.
  11. Jason Hiser, Anh Nguyen-Tuong, Michele Co, Matthew Hall, JackW. Davidson, ILR: Where’d My Gadgets Go? Proceedings of the IEEE Symposium on Security and Privacy, May 2012, San Francisco, CA
  12. Vasilis Pappas. kBouncer: Efficient and Transparent ROP Mitigation [Архівовано 30 серпня 2017 у Wayback Machine.]. April 2012.