BROP - Blind Return Oriented Programming
Основна інформація
Мета цієї атаки полягає в тому, щоб зловживати ROP через переповнення буфера без будь-якої інформації про вразливий бінарний файл. Ця атака базується на наступному сценарії:
Вразливість стека та знання про те, як її викликати.
Серверний додаток, який перезапускається після збою.
Атака
1. Знайти вразливий офсет відправляючи ще один символ, поки не буде виявлено збій сервера
2. Брутфорс канарейку для її витоку
3. Брутфорс збережених адрес RBP та RIP у стеку для їх витоку
Ви можете знайти більше інформації про ці процеси тут (BF Forked & Threaded Stack Canaries) та тут (BF Addresses in the Stack).
4. Знайти стоп-гаджет
Цей гаджет в основному дозволяє підтвердити, що щось цікаве було виконано гаджетом ROP, оскільки виконання не призвело до збою. Зазвичай цей гаджет буде чимось, що зупиняє виконання і розташоване в кінці ланцюга ROP, коли шукають гаджети ROP, щоб підтвердити, що конкретний гаджет ROP був виконаний.
5. Знайти гаджет BROP
Ця техніка використовує гаджет ret2csu. І це тому, що якщо ви отримуєте доступ до цього гаджета посеред деяких інструкцій, ви отримуєте гаджети для контролю rsi
та rdi
:
Це будуть гаджети:
pop rsi; pop r15; ret
pop rdi; ret
Зверніть увагу, як з цими гаджетами можна контролювати 2 аргументи функції для виклику.
Також зверніть увагу, що гаджет ret2csu має дуже унікальний підпис, оскільки він буде витягувати 6 регістрів зі стеку. Тому відправляючи ланцюг, як:
'A' * offset + canary + rbp + ADDR + 0xdead * 6 + STOP
Якщо STOP виконується, це в основному означає, що адреса, яка витягує 6 регістрів зі стеку, була використана. Або що використана адреса також була адресою STOP.
Щоб усунути цю останню опцію, виконується новий ланцюг, як наступний, і він не повинен виконувати гаджет STOP, щоб підтвердити, що попередній дійсно витягнув 6 регістрів:
'A' * offset + canary + rbp + ADDR
Знаючи адресу гаджета ret2csu, можна вивести адресу гаджетів для контролю rsi
та rdi
.
6. Знайти PLT
Таблицю PLT можна шукати з 0x400000 або з витягнутої адреси RIP зі стеку (якщо PIE використовується). Записи таблиці відокремлені на 16B (0x10B), і коли викликається одна функція, сервер не зривається, навіть якщо аргументи неправильні. Також перевірка адреси запису в PLT + 6B також не призводить до збою, оскільки це перший код, що виконується.
Отже, можна знайти таблицю PLT, перевіряючи наступні поведінки:
'A' * offset + canary + rbp + ADDR + STOP
-> немає збою'A' * offset + canary + rbp + (ADDR + 0x6) + STOP
-> немає збою'A' * offset + canary + rbp + (ADDR + 0x10) + STOP
-> немає збою
7. Знайти strcmp
Функція strcmp
встановлює регістр rdx
на довжину рядка, що порівнюється. Зверніть увагу, що rdx
є третім аргументом, і нам потрібно, щоб він був більшим за 0, щоб пізніше використовувати write
для витоку програми.
Можна знайти місце розташування strcmp
в PLT на основі його поведінки, використовуючи той факт, що ми тепер можемо контролювати 2 перші аргументи функцій:
strcmp(<не читати addr>, <не читати addr>) -> збій
strcmp(<не читати addr>, <читати addr>) -> збій
strcmp(<читати addr>, <не читати addr>) -> збій
strcmp(<читати addr>, <читати addr>) -> немає збою
Це можна перевірити, викликавши кожен запис таблиці PLT або використовуючи PLT повільний шлях, який в основному полягає в виклику запису в таблиці PLT + 0xb (який викликає dlresolve
) з наступним у стеку номер запису, який потрібно перевірити (починаючи з нуля), щоб просканувати всі записи PLT з першого:
strcmp(<не читати addr>, <читати addr>) -> збій
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
-> Виникне збійstrcmp(<читати addr>, <не читати addr>) -> збій
b'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
strcmp(<читати addr>, <читати addr>) -> немає збою
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
Пам'ятайте, що:
BROP + 0x7 вказує на
pop RSI; pop R15; ret;
BROP + 0x9 вказує на
pop RDI; ret;
PLT + 0xb вказує на виклик dl_resolve.
Знайшовши strcmp
, можна встановити rdx
на значення більше 0.
Зверніть увагу, що зазвичай rdx
вже міститиме значення більше 0, тому цей крок може бути не обов'язковим.
8. Знайти Write або еквівалент
Нарешті, потрібен гаджет, який ексфільтрує дані, щоб ексфільтрувати бінарний файл. І в цей момент можна контролювати 2 аргументи та встановити rdx
більше 0.
Є 3 загальні функції, які можна зловживати для цього:
puts(data)
dprintf(fd, data)
write(fd, data, len(data)
Однак оригінальна стаття згадує лише про write
, тому давайте поговоримо про це:
Поточна проблема полягає в тому, що ми не знаємо, де функція write знаходиться всередині PLT і ми не знаємо номер fd, щоб надіслати дані до нашого сокету.
Однак ми знаємо, де знаходиться таблиця PLT, і можна знайти write на основі його поведінки. І ми можемо створити кілька з'єднань з сервером і використовувати високий FD, сподіваючись, що він відповідає деяким з наших з'єднань.
Поведінкові сигнатури для знаходження цих функцій:
'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0) + p64(0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Якщо дані надруковані, тоді знайдено puts'A' * offset + canary + rbp + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Якщо дані надруковані, тоді знайдено dprintf'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + (RIP + 0x1) + p64(0x0) + (PLT + 0xb ) + p64(STRCMP ENTRY) + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Якщо дані надруковані, тоді знайдено write
Автоматичне експлуатація
Посилання
Оригінальна стаття: https://www.scs.stanford.edu/brop/bittau-brop.pdf
Last updated