ROP - Return Oriented Programing
Основна інформація
Return-Oriented Programming (ROP) - це високорівнева техніка експлуатації, яка використовується для обходу заходів безпеки, таких як No-Execute (NX) або Data Execution Prevention (DEP). Замість впровадження та виконання shellcode, атакуючий використовує шматки коду, які вже присутні в бінарному файлі або завантажених бібліотеках, відомі як "гаджети". Кожен гаджет зазвичай закінчується інструкцією ret
та виконує невелику операцію, таку як переміщення даних між регістрами або виконання арифметичних операцій. З'єднавши ці гаджети, атакуючий може скласти навантаження для виконання довільних операцій, ефективно обходячи захист NX/DEP.
Конвенції виклику
Розуміння конвенцій виклику є важливим для побудови ефективних ланцюжків ROP, особливо при виклику функцій або маніпулюванні даними:
x86 (32-біт)
cdecl: Викликаючий очищає стек. Аргументи функцій виталюються на стек у зворотньому порядку (справа наліво). Аргументи виталюються на стек зправа наліво.
stdcall: Схоже на cdecl, але викликається відповідальність за очищення стеку.
x64 (64-біт)
Використовує конвенцію виклику System V AMD64 ABI на системах, подібних до Unix, де перші шість цілих або вказівникових аргументів передаються в регістри
RDI
,RSI
,RDX
,RCX
,R8
таR9
. Додаткові аргументи передаються на стеку. Результат повертається вRAX
.Конвенція виклику Windows x64 використовує
RCX
,RDX
,R8
таR9
для перших чотирьох цілих або вказівникових аргументів, додаткові аргументи передаються на стеку. Результат повертається вRAX
.Регістри: 64-бітні регістри включають
RAX
,RBX
,RCX
,RDX
,RSI
,RDI
,RBP
,RSP
таR8
доR15
.
З цих конвенцій виклику можна побачити, що в 32 біта аргументи до функцій передаються через стек, тоді як в x64 вони розміщуються в конкретних регістрах.
Як працює ROP
Перехоплення потоку керування: Спочатку атакуючому потрібно перехопити потік керування програми, зазвичай використовуючи переповнення буфера для перезапису збереженої адреси повернення на стеку.
Ланцюжок гаджетів: Потім атакуючий уважно вибирає та з'єднує гаджети для виконання бажаних дій. Це може включати підготовку аргументів для виклику функції, виклик функції (наприклад,
system("/bin/sh")
), та обробку будь-яких необхідних завершальних або додаткових операцій.Виконання навантаження: Коли вразлива функція повертається, замість повернення до законного місця вона починає виконувати ланцюжок гаджетів.
Ланцюжок ROP в x86
Припустимо гіпотетичний сценарій, де ми хочемо викликати system("/bin/sh")
за допомогою ROP у 32-бітному бінарному файлі:
Пошук гаджетів: Припустимо, що ми знайшли наступні гаджети в бінарному файлі або завантажених бібліотеках:
pop eax; ret
: Видаляє верхній елемент стеку вEAX
та повертається.pop ebx; ret
: Видаляє верхній елемент стеку вEAX
та повертається.mov [ebx], eax; ret
: Переміщує значення зEAX
в місце, на яке вказуєEBX
.Адреса
system
.
Підготовка ланцюжка: Нам потрібно підготувати стек, який виглядає так:
Адреса гаджета, який встановлює
EBX
.Адреса гаджета
pop eax; ret
.Адреса рядка
"/bin/sh"
в пам'яті (або там, де ми плануємо записати його).Адреса гаджета
mov [ebx], eax; ret
, щоб перемістити"/bin/sh"
в місце, на яке вказуєEBX
.Адреса функції
system
, зEBX
вказуючи наш рядок.
Виконання: Коли вразлива функція повертається, вона починає виконувати наш ланцюжок гаджетів, в кінцевому підсумку викликаючи
system("/bin/sh")
та відкриваючи оболонку.
ROP в x64
Розглянемо гіпотетичний сценарій, де ви хочете викликати execve("/bin/sh", NULL, NULL)
на системі x64 за допомогою конвенції виклику System V AMD64 ABI:
Пошук гаджетів: Спочатку вам потрібно знайти гаджети, які дозволять вам контролювати регістри
RDI
,RSI
таRDX
, оскільки вони будуть містити аргументи дляexecve
.Побудова ланцюжка:
Встановити
RDI
для вказівки на рядок"/bin/sh"
: Зазвичай це робиться за допомогою гаджетаpop RDI; ret
, за яким слідує адреса рядка (який може бути розміщений у навантаженні або знайдений в пам'яті).Обнулити
RSI
таRDX
: Оскільки другий і третій аргументи дляexecve
єNULL
, вам потрібні гаджети для обнулення цих регістрів, наприкладxor RSI, RSI; ret
таxor RDX, RDX; ret
.Викликати
execve
: Нарешті, потрібен гаджет, який переходить доexecve
(або викликає його опосередковано).
Виконання навантаження: Після побудови та відправлення цього навантаження до вразливої програми, ланцюжок ROP виконується, створюючи оболонку.
Оскільки x64 використовує регістри для перших кількох аргументів, часто він потребує менше гаджетів, ніж x86 для простих викликів функцій, але пошук та з'єднання правильних гаджетів може бути складнішим через збільшену кількість регістрів та більший адресний простір. Збільшена кількість регістрів та більший адресний простір у x64 архітектурі надають як можливості, так і виклики для розробки експлойтів, особливо в контексті Return-Oriented Programming (ROP).
Last updated