Сергей Попов

Администратор
30.12.2015
6 275
6 985
Специализация
  1. OSINT
  2. Веб-безопасность
Статус верификации
  1. ✓ Verified
Тёмная лаборатория red-team: изогнутый монитор с диаграммой состояний weird machine и подсвеченной цепочкой ROP-гаджетов, обходящей CFI, рядом экран с выводом Ropper.


При анализе 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'])
Четыре строки в pwntools - и ASLR нейтрализован для первого этапа. Второй этап строит финальную цепочку по утечённым адресам. Красиво, если задуматься: целая митигация уровня ОС ломается об один 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 через переиспользование подписанных указателей из другого контекста документирован в исследованиях
Ни одна из этих защит не обнуляет weird machine. Каждая сужает ленту, но не рвёт её.

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.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab