Сергей Попов
Администратор
- 30.12.2015
- 6 275
- 6 985
- Специализация
- OSINT
- Веб-безопасность
- Статус верификации
- ✓ Verified
При анализе CTF-бинаря, скомпилированного с Clang CFI и полным ASLR,
ropper выдаёт больше тысячи потенциальных гаджетов. Прогоняю через cfgFilterGadgets - фильтрацию по допустимым целям CFI - остаётся меньше десяти. И этого хватает для рабочей цепочки. Вот тут weird machines и ROP JOP эксплуатация перестают быть параллельными академическими концепциями и сливаются в одну операционную модель: CFI сужает пространство допустимых переходов, но weird machine - скрытый вычислитель внутри программы - работает на оставшихся. Русскоязычных материалов, связывающих теорию weird machines Bratus–Dullien с практикой code reuse attacks, я не встречал. Разберём эту связь от модели до конкретных гаджетов.Weird machine: формальная модель эксплуатации уязвимостей
Weird machine - латентная вычислительная машина, которая возникает в программе непреднамеренно, как побочный продукт реализации. В работах Bratus, Locasto, Patterson, Sassaman она определена как набор состояний и переходов, которые программа способна выполнить, но которые не предусмотрены её спецификацией. Любая уязвимость, позволяющая перехватить управление или манипулировать данными, активирует weird machine - альтернативный вычислитель, использующий ресурсы целевой программы для выполнения произвольных операций атакующего.Loose contracts как корень эксплуатируемости
Trail of Bits в исследовании «The Good, the Bad, and the Weird» ввели практически полезное разграничение. Loose contract - код, в котором множество допустимых предусловий шире необходимого. Цитата: «a piece of code that not only implements the intended program, but does it in such a way that the program undergoes more state changes than it should». Противоположность - tight contract, где программа меняет состояние строго на предусмотренных входах.Конкретный пример из того же исследования: функция
ListItem::TrySetItem в исходном коде требует два валидных указателя на ListItem. После компиляции предусловия ослабляются - this превращается в любой указатель на минимум 8 байт аллоцированной памяти, второй параметр принимает произвольный тип. Атакующий, перезаписавший m_next, получает примитив чтения и записи по произвольному адресу. Loose contract → weird machine активирована.Каждая митигация - DEP, ASLR, CFI - по сути tightens the contract. По данным Trail of Bits, эволюция защит: Windows NT 1995 (read = execute, максимально loose контракт) → DEP 2003 (NX, разделение code и data) → ASLR 2006 (рандомизация адресного пространства) → CFG 2016 (проверка forward-edge branches). Контракт ужесточается с каждым шагом, но пока не обнулён - weird machine продолжает существовать. И это не баг, это свойство.
Предсказательная сила weird machine теории эксплуатации
Weird machine теория эксплуатации даёт исследователю формальный ответ на вопрос «эксплуатируема ли конкретная уязвимость?». Если описать программу как конечный автомат, вопрос сводится к достижимости: существует ли последовательность переходов из текущего состояния в целевое через weird machine. Это проверяемо символическим исполнением (angr, Manticore) или анализом достижимости в графе состояний.По данным Trail of Bits, автоматическая идентификация weird machines позволяет «triage properly whether a vulnerability is in fact exploitable and whether it would be unexploitable in the absence of weird machines». Программно weird machine описывается через тройки Хоара: предусловие → операция → постусловие. Предусловия шире необходимых - контракт loose, weird machine существует, эксплуатация принципиально возможна. Без этой формализации остаётся только перебор вслепую - а с ней можно отсечь тупиковые направления ещё до первого запуска ropper.
ROP JOP цепочки эксплуатации как экземпляры weird machine
Return Oriented Programming и Jump Oriented Programming - два конкретных экземпляра weird machine, различающихся механизмом диспетчеризации. Оба доказанно Тьюринг-полны, оба переиспользуют существующий код, но управляют вычислениями через разные «ленты».ROP: стек как лента weird machine
ROP-цепочка - канонический пример weird machine в действии. Шахам в 2007 году доказал Тьюринг-полноту ROP: из коротких последовательностей инструкций (гаджетов), каждая из которых заканчиваетсяret, строятся произвольные вычисления - циклы, ветвления, арифметика.В терминах weird machine: стек вызовов становится диспетчером - лентой weird machine. Каждый
ret читает адрес с вершины стека (входной символ) и выполняет переход к следующему гаджету (смена состояния). Программа «легально» исполняет инструкции из .text, но логика вычисления целиком под контролем атакующего. По классификации MITRE ATT&CK, результат - Exploitation for Client Execution (T1203) или Exploitation for Privilege Escalation (T1068).Работает если: контролируется содержимое стека (buffer overflow, format string), DEP включён (иначе проще инжектить шеллкод), бинарь и загруженные библиотеки содержат достаточный набор гаджетов.
Не работает если: включён аппаратный Shadow Stack (Intel CET) - каждый
ret проверяется против теневой копии адреса возврата (D3-SSC, Shadow Stack Comparisons по D3FEND). Или бинарь скомпилирован с fine-grained forward-edge CFI, ограничивающим начальную точку входа в цепочку.JOP: dispatcher gadget и независимость от стека
JOP убирает зависимость отret и стека. Вместо этого используется dispatcher gadget - специальный гаджет, который загружает адрес следующего функционального гаджета из dispatch table и выполняет косвенный переход jmp [reg]. По данным Deepwatch, «JOP evades return-address-focused shadow stack defenses because it never returns through the call stack in the conventional sense».В weird machine модели JOP заменяет ленту. В ROP стек - лента с последовательным чтением. В JOP dispatch table - лента с произвольным доступом, dispatcher gadget - головка чтения. Тьюринг-полнота JOP доказана (Bletsch et al.), что подтверждает: обе модели эксплуатации уязвимостей реализуют полноценную weird machine.
Работает если: Shadow Stack (D3-SSC) блокирует ROP; в бинаре есть
jmp [reg]/call [reg] гаджеты; атакующий контролирует область памяти для dispatch table.Не работает если: активен IBT (Indirect Branch Tracking, Intel) или BTI (Branch Target Identification, ARM) - каждая допустимая цель косвенного перехода обязана начинаться с
endbr64 или bti, что резко сужает множество доступных гаджетов.Поиск JOP-гаджетов:
ropper --binary ./target --type jop - выводит гаджеты с завершающим jmp/call через регистр. Для поиска по конкретному регистру - метод searchJmpReg(binary, regs). На практике JOP-гаджетов в стандартном ELF-бинаре в разы меньше, чем ROP-гаджетов. Собрать полную JOP-цепочку - задача на порядок сложнее, и это не преувеличение: в одном CTF-бинаре на ~200 КБ .text я нашёл 800+ ROP-гаджетов и 47 JOP. Из этих 47 для dispatcher'а подходили три.Обход защит DEP ASLR и техники обхода CFI
DEP и ASLR: предусловия для code reuse attacks
DEP (D3-PSEP - Process Segment Execution Prevention по D3FEND) делает страницы данных неисполняемыми и порождает code reuse attacks как класс. Нельзя принести свой код - переиспользуем существующий через return oriented programming техники и jump oriented programming (защита от которого появилась позже).ASLR (D3-SAOR - Segment Address Offset Randomization) рандомизирует базовые адреса стека, кучи, библиотек. Но ASLR в Linux работает с гранулярностью страниц (4 КБ) - младшие 12 бит адреса неизменны. Сдвиг (delta) единый для всего региона: утечка одного указателя из libc раскрывает базу всей библиотеки. В терминах weird machine: ASLR увеличивает энтропию входных символов, информационная утечка обнуляет её - и обход защит DEP ASLR становится двухшаговой задачей.
Стандартная цепочка: вызов
puts@plt с аргументом GOT[puts] для утечки реального адреса, затем вычисление базы libc:
Python:
from pwn import *
elf = ELF('./vuln')
rop = ROP(elf)
rop.call('puts', [elf.got['puts']])
rop.call(elf.symbols['main'])
puts.CFI: где weird machine модель предсказывает обход
Control Flow Integrity - наиболее серьёзный барьер для gadget chains эксплуатации. Контрмеры D3FEND включают D3-PCSV (Process Code Segment Verification), D3-SSC (Shadow Stack Comparisons), D3-SFCV (Stack Frame Canary Validation), D3-MBT (Memory Boundary Tracking).Coarse-grained CFI (Microsoft CFG, базовый LLVM CFI) группирует допустимые цели в крупные классы по сигнатуре типа. По данным Deepwatch, она «provides meaningful protection against basic ROP and JOP attacks». Но weird machine модель предсказывает: если в классе допустимых целей остаётся хотя бы несколько полезных гаджетов - weird machine продолжает работать. Ropper поддерживает фильтрацию по CFI-политике через
cfgFilterGadgets(binary, gadgets) - после фильтрации в нетривиальных бинарях остаётся достаточно для рабочей цепочки. Coarse-grained CFI - это замок на калитке при открытых воротах.Fine-grained CFI, Intel CET, ARM PAC - каждый ужесточает контракт дальше, но не обнуляет пространство weird machine:
- Intel CET Shadow Stack защищает
ret, но неjmp [reg]- JOP обходит Shadow Stack by design - Intel IBT требует
endbr64в начале каждой допустимой цели, ноendbr64стоит в начале каждой экспортированной функции - множество целей остаётся значительным - ARM PAC подписывает указатели криптографически, но PAC bypass через переиспользование подписанных указателей из другого контекста документирован в исследованиях
Gadget chains: практическое сравнение ROP и JOP
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Когда ROP достаточен: [Применимо: CTF, legacy-инфра без CET] - нет аппаратного Shadow Stack, нет fine-grained CFI. Типичный сценарий: ELF на Linux с DEP+ASLR без CET.
Когда нужен JOP: [Применимо: modern-инфра, Intel 12+ gen с CET] - Shadow Stack активен,
ret проверяется аппаратно. Или целевой бинарь содержит дополнительные проверки целостности стека, затрудняющие ROP.Следующий рубеж - Data-Oriented Programming (DOP). По данным Deepwatch, «DOP manipulates data values processed by existing conditional branches to achieve attacker-controlled computation while the program follows its normal control flow path». DOP - Тьюринг-полная weird machine, не нарушающая control flow graph. Все контрмеры уровня control flow - CFI, CET, PAC, Shadow Stack - против DOP бессильны, потому что control flow формально валиден. Это финальное подтверждение weird machine модели: эксплуатация - активация скрытого вычислителя, а control flow hijacking - лишь один из способов его запуска.
Пять лет назад weird machine казалась абстракцией, слишком далёкой от реальных gadget chains эксплуатации. Сейчас оцениваю иначе: каждый раз, когда собираю цепочку под бинарь с CFI и вижу, что из тысячи гаджетов после
cfgFilterGadgets осталось меньше десятка допустимых - weird machine модель объясняет, почему этого десятка хватает. Формализм Bratus–Dullien не заменяет ropper и pwntools, но отвечает на вопрос «почему конкретная защита не работает» быстрее, чем перебор вслепую. И вот что показательно: code reuse attacks (ROP, JOP) блокируют контрмерами уровня control flow - Shadow Stack, CFI, BTI - а DOP обходит их все, потому что оперирует данными, не трогая поток управления. Это не баг реализации. Это фундаментальное свойство нестандартных вычислительных моделей: пока существуют loose contracts, пространство для эксплуатации не обнуляется. Единственный путь - доказательно tighten каждый контракт до минимально необходимого множества состояний, что на кодовых базах в миллионы строк остаётся открытой исследовательской задачей. Если хочется собрать ROP/JOP-цепочку на живом стенде и увидеть, где теория ломается о реальные гаджеты - на HackerLab есть pwn-сценарии, где этот процесс идёт end-to-end.