![]() |
https://forum.antichat.xyz/attachmen...eb642f2e5e.png
Каждый раз, когда слышу «бинарные уязвимости мертвы, всё закрыто ASLR и DEP», вспоминаю свой последний CTF - три из четырёх pwn-тасков решались через классические memory corruption примитивы. Просто с поправкой на современные реалии. Митигации усложняют эксплуатацию, но не устраняют её. На подстанции тоже написано «не влезай - убьёт», а электрики как-то работают. Здесь разберу ключевые техники - от stack overflow до heap exploitation, от ROP-цепочек до JOP - и покажу, как каждая из них работает против ASLR, DEP и CFI. Не в вакууме, а на реальных CTF-задачах и пентестах. Почему эксплуатация бинарных уязвимостей всё ещё актуальна По данным CrowdStrike, memory corruption эксплойты остаются одним из сильнейших инструментов в арсенале red team. Они позволяют выполнять payload без взаимодействия с пользователем - тактика Exploitation for Client Execution (T1203) и Exploitation for Privilege Escalation (T1068) по MITRE ATT&CK. Современные ОС навалили десятки митигаций: DEP/NX, ASLR, stack canaries, CFI, CET, ARM PAC. Но каждая из них - барьер, а не стена. На Security StackExchange хорошо сформулировали: «Все эти техники делают эксплуатацию сложнее, но при достаточном усилии и хороших багах - она далеко не совершенна». Разберёмся, почему. Stack overflow эксплуатация: от классики к современности Переполнение буфера на стеке - та самая уязвимость, с которой начинается путь каждого pwn-игрока. Суть проста: программа пишет в буфер больше данных, чем он вмещает, и перезаписывает соседние области памяти - включая сохранённый адрес возврата (saved return address). В классическом сценарии без митигаций всё тривиально: перезаписываем RIP/EIP адресом шеллкода, лежащего тут же на стеке. Но сегодня такой подход блокируется тремя уровнями защиты одновременно. Stack canaries - первый барьер Компилятор вставляет случайное значение (канарейку) между локальными переменными и адресом возврата. Перед Код:
retОбход требует отдельного примитива: утечка через format string ( Код:
%pКод:
%xКод:
printfDEP/NX - запрет исполнения на стеке Data Execution Prevention маркирует страницы памяти стека и кучи как неисполняемые через биты PTE (Page Table Entry). Даже если вы контролируете содержимое стека - выполнить его как код не получится. Именно DEP породил целое семейство code-reuse техник: ROP, JOP, COP. Вместо инъекции собственного кода мы взаимствуем уже существующие фрагменты исполняемого кода программы. Раз нельзя принести своё - берём то, что уже лежит. ASLR - рандомизация адресного пространства ASLR рандомизирует базовые адреса стека, кучи, библиотек и самого бинаря при каждом запуске. Без знания адресов вы не можете ни перенаправить поток управления, ни построить ROP-цепочку. Ключевой момент (MIT SHD Labs это подчёркивают): ASLR в Linux работает на уровне гранулярности страниц (4 КБ). Младшие 12 бит адреса остаются неизменными. Более того, ASLR применяет константный сдвиг (delta) для каждого региона - утечка одного указателя из региона раскрывает все адреса в нём. Поэтому information leak - универсальный первый шаг почти любой современной эксплуатации. Heap exploitation техники: UAF, tcache poisoning, fastbin dup Если stack overflow - входной билет в мир pwn, то heap exploitation - то, что отделяет новичков от исследователей уязвимостей. Куча - динамическая память, управляемая аллокатором (в glibc - ptmalloc2), и у неё собственная сложная внутренняя кухня. Use-after-free эксплуатация Use-after-free возникает, когда программа освобождает объект через Код:
free()По MITRE ATT&CK это часто ведёт к Process Injection (T1055) или к Exploitation for Defense Evasion (T1211). Моя цепочка для UAF выглядит так: аллоцирую объект с виртуальной таблицей (vtable) или указателем на функцию, освобождаю его, через повторную аллокацию подменяю указатель на контролируемый адрес. При следующем вызове виртуального метода - перехватываю управление. Звучит просто, на практике нужно точно попасть в размер чанка. Tcache poisoning - специфика glibc 2.26+ С версии glibc 2.26 появился thread-local cache (tcache) - быстрый кэш освобождённых чанков для каждого потока. Tcache работает как односвязный список с минимальными проверками безопасности, что делает его лакомой целью. Суть tcache poisoning: перезаписываете указатель Код:
nextКод:
fdКод:
malloc()В glibc 2.32+ добавили safe-linking - XOR указателя Код:
fdFastbin dup и double free Double free - когда один и тот же блок памяти освобождается дважды. В fastbin (для мелких аллокаций) это приводит к циклу в связном списке: чанк указывает сам на себя. Два последовательных Код:
malloc()Классический fastbin dup в современных версиях glibc блокируется проверкой double-free в tcache. Обход прост: между двумя Код:
free()Код:
free()Разница между tcache poisoning и fastbin dup - не существенная. На CTF я всегда начинаю с проверки версии glibc ( Код:
strings libc.so.6 | grep "GNU C Library"ROP цепочки обход защит: Return Oriented Programming ROP - фундаментальная техника обхода DEP. Вместо инъекции шеллкода строим цепочку из коротких фрагментов существующего кода - гаджетов - каждый из которых заканчивается инструкцией Код:
retМеханика ROP Каждый гаджет делает одну элементарную операцию: Код:
pop rdi; retКод:
pop rsi; retТипичная цепочка для получения шелла на Linux x86_64 (System V AMD64 ABI): загружаем адрес строки "/bin/sh" в RDI через Код:
pop rdi; retКод:
execveДля поиска гаджетов я использую Код:
ROPgadget --binary ./vuln --rop --badbytes "0a|00"Код:
ropperКод:
ROPPython: Код:
fromКод:
putsКод:
mainRet2libc и ret2plt - частные случаи ROP Ret2libc - классическая вариация, где вместо произвольных гаджетов вызываем функции стандартной библиотеки: Код:
system("/bin/sh")Код:
mprotect()По данным Fortinet, ret2plt особенно хорошо работает на ARM-архитектуре: цепочка Код:
puts@pltКод:
systemJOP: Jump Oriented Programming как альтернатива ROP ROP-цепочки зависят от инструкции Код:
retКод:
retJOP (Jump Oriented Programming) использует гаджеты, заканчивающиеся на Код:
jmp regКод:
jmp [reg]Код:
retКод:
jmpКлючевое отличие JOP от ROP: в JOP нет автоматического продвижения по стеку через Код:
retНа практике JOP сложнее: нужен стабильный dispatcher и достаточное количество Код:
jmpКод:
call regПо данным CrowdStrike, в Windows код-реюз техники (ROP, COP, JOP) применяются для динамического вызова API-функций типа Код:
VirtualProtect()Код:
VirtualAlloc()Обход ASLR DEP: сравнение подходов Обход ASLR и DEP - почти всегда двухэтапный процесс. Сначала ломаем ASLR (утекаем адрес), потом ломаем DEP (строим ROP/JOP). Рассмотрим техники обхода ASLR в сравнении. Information leak - универсальный метод Самый надёжный способ: через уязвимость чтения (format string, out-of-bounds read, partial overwrite) утекаем один указатель из нужного региона. ASLR сохраняет относительные расстояния внутри модуля - одного указателя хватает для вычисления всех остальных адресов. Brute force - грубая сила На 32-bit системах энтропия ASLR низкая - порядка 8-16 бит для стека и библиотек. Это от 256 до 65536 возможных позиций. При наличии fork-сервера (ASLR не меняется в дочерних процессах) брутфорс занимает секунды. На 64-bit энтропия значительно выше, и прямой брутфорс нецелесообразен. Partial overwrite - перезапись только младших байтов адреса - снижает пространство перебора до нескольких бит. Heap spraying - вероятностный подход Heap spray заполняет кучу большим объёмом контролируемых данных, увеличивая вероятность того, что произвольный адрес попадёт в наш payload. Техника особенно живуча в браузерных эксплойтах в связке с JavaScript. По анализу Patsnap Eureka, heap spraying часто комбинируется с DEP-обходом. Микроархитектурные side-channel атаки По данным MIT SHD Labs, ASLR можно сломать аппаратно: через prefetch side channels (инструкция Код:
prefetchТехника обхода ASLRТребуемый примитивПрименимость 64-bitНадёжностьInformation leakЧтение по произвольному адресуДаВысокаяBrute forceМногократный запускНет (слишком долго)НизкаяPartial overwriteЗапись 1-2 байтЧастичноСредняяHeap sprayКонтроль аллокацийДаСредняяPrefetch side-channelЛокальное исполнениеДаВысокая Control Flow Integrity обход: передний край CFI - наиболее серьёзная из современных митигаций. Она проверяет, что каждый косвенный переход ( Код:
call regКод:
jmp regКод:
retОграничения CFI на практике На Security StackExchange хорошо сказано: «perfect CFI basically doesn't exist». И вот почему. Грубая гранулярность. CFG в Windows проверяет, что цель перехода - начало какой-либо функции. Но не проверяет, какой именно. Любая функция с подходящей сигнатурой - валидная цель. Если в программе есть Код:
system()Код:
WinExec()Модули без CFI. Если хотя бы одна загруженная DLL не скомпилирована с CFG, атакующий может перенаправить поток в неё. Как отмечают исследователи: «it can be bypassed by jumping into a module which doesn't use CFG». Одно слабое звено ломает всю цепочку. Утечка скрытых данных. PaX RAP (kernel CFI на Linux) XOR-ит адрес возврата с ключом, хранящимся в регистре. Но зашифрованный указатель лежит на стеке между вызовами функций. Если у атакующего есть ASLR-утечка и чтение стека - он вычисляет ключ, подделывает зашифрованный адрес возврата и обходит защиту. JOP вместо ROP. Shadow Stack от Intel CET защищает Код:
retКод:
jmp regКод:
jmpКод:
endbr64Сравнение техник эксплуатации и митигаций ТехникаОбходит DEPОбходит ASLRОбходит canariesОбходит CFIСложностьКлассический stack overflow + shellcodeНетНетНетНетНизкаяROP chainДаНужен leakНужен leak/обходЧастичноСредняяJOP chainДаНужен leakНужен leak/обходДа (Shadow Stack)ВысокаяRet2libc / ret2pltДаНужен leakНужен leak/обходЧастичноСредняяTcache poisoningДа (arbitrary write)Нужен heap leakНе применимоНе применимоСредняяUse-after-free + vtable overwriteДаНужен leakНе применимоЗависит от гранулярностиВысокаяHeap spray + pivotЧастичноЧастичноНе применимоНетСредняя Практический workflow: от анализа бинаря до шелла Вот пошаговый процесс, который я использую при решении pwn-тасков и при пентесте бинарных приложений. Потренировавшись CTF-таски уровня easy/medium, можно уверенно переходить к реальным целям. 🔓 Часть контента скрыта: Эксклюзивный контент для зарегистрированных пользователей. Зарегистрироваться или Войти Шаг 1. Разведка бинаря. Запускаем Код:
checksec ./vulnШаг 2. Поиск уязвимости. В GDB с pwndbg анализируем опасные функции: Код:
getsКод:
strcpyКод:
sprintfКод:
readКод:
aflКод:
pdf @ sym.vulnerable_functionКод:
free()Шаг 3. Получение leak. Утечка адреса libc через GOT: перезаписываем адрес возврата на Код:
puts@pltКод:
puts@gotКод:
putsКод:
main_arenaШаг 4. Построение цепочки. На втором проходе (после Код:
retКод:
mainКод:
execve("/bin/sh", NULL, NULL)Код:
system("/bin/sh")Python: Код:
fromШаг 5. Отладка. Если цепочка крэшит - подключаемся через Код:
gdb.attach(p)Код:
gdbКод:
pwndbgКод:
system()Код:
retКод:
systemКод:
retКод:
systemЧто дальше: аппаратные митигации и их обход Intel CET (Shadow Stack + IBT), ARM Pointer Authentication (PAC) и Memory Tagging Extension (MTE) - следующий рубеж обороны. Shadow Stack делает ROP значительно сложнее, PAC подписывает указатели криптографическим ключом, MTE тегирует каждое выделение памяти. Но исследования не стоят на месте. JOP обходит Shadow Stack. PAC-ключи можно восстановить через side-channel или через signing gadgets - фрагменты кода, которые легитимно подписывают указатели (по сути, заставляем программу подписать нашу подделку за нас). MTE имеет ограниченную энтропию (4 бита тега) и обходится через brute force или speculative execution. В терминах MITRE ATT&CK - развитие эксплойтов (T1587.004, Resource Development) и исследование уязвимостей (T1588.006, Resource Development). Понимание этих техник критично и для атакующей, и для оборонительной стороны. Вопрос к читателям При двухэтапной эксплуатации с leak через Код:
puts@pltКод:
IO_2_1_stdoutКод:
DynELF |
| Время: 05:47 |