Статья Разработка эксплойта для CVE: пример полного цикла от crash до рабочего PoC

Треснувшая сургучная печать тёмно-красного цвета на матовом чёрном коврике, под ней — плата с гравировкой. Тёплый свет настольной лампы, бирюзовое свечение монитора, мелкозернистые тени.


Фаззер отработал, процесс лёг с segfault. Или ты нашёл advisory для CVE без публичного PoC - только описание и CVSS-вектор. Перед тобой единственный артефакт: crash. Между ним и контролируемым выполнением кода лежит цепочка конкретных шагов, которые русскоязычные источники почти никогда не раскрывают от начала до конца. Большинство статей по эксплуатации уязвимостей памяти останавливаются либо на теории стека, либо на запуске чужого PoC из Exploit-DB. А дальше - «ну вы поняли».

Здесь разберу полный цикл разработки эксплойта - от триажа краша в отладчике до обхода DEP и ASLR на примерах реальных CVE. Каждый шаг - с командами, инструментами и объяснением, почему именно так. Без воды.

Требования к окружению​

Прежде чем лезть в отладчик - пара слов о стенде. Разработка PoC уязвимости всегда начинается с изолированной среды, где можно безопасно ронять процесс десятки раз подряд и не бояться последствий.

Для Linux-таргетов: виртуалка с целевой ОС, GDB с pwndbg или PEDA (Я предпочитаю pwndbg - у PEDA нет встроенной heap-визуализации, а pwndbg даёт heap, bins, tcache из коробки), pwntools для автоматизации, ROPgadget или ropper для поиска гаджетов, Ghidra для статики. Состояние ASLR проверяется через sysctl kernel.randomize_va_space - значение 2 означает полную рандомизацию.

Для Windows-таргетов: WinDbg Preview (или классический из Windows SDK), x64dbg для интерактивного ковыряния, Immunity Debugger с Mona.py для классических 32-битных стеков, msf-pattern_create и msf-pattern_offset из Metasploit. Для статики - та же Ghidra или IDA Free.

Отдельный момент - checksec. На Linux утилита из pwntools (pwn checksec ./binary) покажет митигации: NX (DEP), PIE, Stack Canary, RELRO. На Windows аналогичную информацию выдаст !exploitable в WinDbg или просмотр флагов PE-заголовка. Без этих данных ты вслепую - не знаешь, с чем бороться.

Триаж краша: первые действия в отладчике​

1776882860709.webp


Процесс упал. Первые 10 минут в отладчике решают, стоит ли вообще тратить время на написание эксплойта с нуля - или crash тупиковый.

Открываешь core dump или присоединяешься к процессу и воспроизводишь crash. Смотришь на три вещи: регистры, стек и инструкцию, на которой всё легло.

В GDB с pwndbg после crash набираешь context - получаешь полную картину: регистры, дизассемблированный код вокруг текущей инструкции, содержимое стека. Если в RIP/EIP сидит значение, которое ты контролируешь (кусок входных данных) - хороший знак. Если crash на инструкции вида call [eax] или mov [eax], edx, а EAX содержит твои данные - ещё лучше: потенциальный write-примитив.

В WinDbg после access violation команда !analyze -v даёт автоматический разбор краша с классификацией. r покажет регистры, kb - call stack. Видишь в стеке перезаписанный адрес возврата с паттерном из входных данных - перед тобой классическое переполнение буфера.

По данным CrowdStrike, memory corruption эксплойты исторически были одним из самых результативных инструментов в арсенале red team - выполнение payload без взаимодействия с пользователем. Но прежде чем эксплуатировать, надо понять класс бага.

Определение класса уязвимости​

Анализ краша в отладчике позволяет быстро классифицировать проблему:

Stack buffer overflow - crash при возврате из функции, EIP/RIP затёрт данными из буфера. В стеке видно, что return address перезаписан. Классика, с которой начинается любой exploit development туториал.

Heap overflow / Use-After-Free - crash при разыменовании указателя на освобождённую или повреждённую память. По данным «Лаборатории Касперского», CVE-2018-8453 (CVSS 7.8, HIGH, CWE-416 - Use-After-Free) эксплуатировалась APT-группой FruityArmor: UAF в обработчике xxxDestroyWindow через серию хуков на callback-функции fnDWORD, fnNCDESTROY и fnINLPCREATESTRUCT. Во время обработки WM_LBUTTONDOWN вызов DestroyWindow освобождал память окна, но ошибочная логика в NtUserSetWindowFNID позволяла изменить статус fnid без проверки FNID_FREED, оставляя висячую ссылку на класс SysShadow. Через heap spray атакующий возвращал контроль над освобождённым пулом и получал read/write примитив через GDI Bitmap. Более ранняя CVE-2017-0263 (CVSS 7.8, HIGH, CWE-416), использованная группировкой Sofacy - отдельная UAF в win32k с другой root cause, но тот же общий паттерн эксплуатации.

Command injection - процесс не падает, но выполняет непредусмотренную команду. Как в CVE-2024-3400 в PAN-OS GlobalProtect (CVSS 10.0, CRITICAL, CWE-20 и CWE-77): цепочка из двух стадий - path traversal через session cookie приводит к arbitrary file creation в /opt/panlogs/tmp/, а затем telemetry service выполняет содержимое созданного файла как команды (command injection stage). Неаутентифицированный атакующий получает выполнение произвольного кода с привилегиями root. Триажа краша тут нет как такового - эксплуатация уязвимостей этого класса идёт по другому маршруту.

Для stack overflow и heap corruption дальнейший путь к разработке эксплойта одинаков: определить offset для контроля flow, собрать обход митигаций, написать payload.

Контроль EIP/RIP: расчёт смещения для эксплойта​

Ты определил, что crash - stack buffer overflow с перезаписью return address. Следующий шаг - выяснить точное количество байт от начала буфера до EIP/RIP. Ни байтом больше, ни байтом меньше.

Метод, который использую всегда - циклический паттерн из Metasploit. Генерируешь уникальную последовательность: msf-pattern_create -l 3000 (длину бери с запасом от размера crash-буфера). Последовательность не содержит повторяющихся подстрок длиной 4 байта, поэтому значение в EIP/RIP после crash однозначно определяет смещение.

Отправляешь паттерн вместо оригинального input, воспроизводишь crash. Смотришь, что попало в EIP. Допустим, видишь 0x37714336. Скармливаешь в msf-pattern_offset -l 3000 -q 37714336 - получаешь offset, скажем 2060 байт. В пошаговом разборе от DOT Security для 32-битного переполнения в QuoteDB именно так был получен offset 2060 байт до перезаписи EIP.

Верифицируешь: формируешь буфер из 2060 байт \x41 ("A"), затем 4 байта \x42424242 ("B"), затем заполнение до исходного размера crash. Если после crash в EIP стоит 0x42424242 - offset верный. Ты контролируешь instruction pointer.

На этом же этапе определяешь ещё два параметра. Первый - доступное пространство после EIP для shellcode. В отладчике смотришь, сколько байт из буфера лежит в стеке после перезаписанного return address. 200 байт - маловато для staged payload, 900+ - комфортно. Второй - bad bytes. Нулевой байт (\x00) почти всегда запрещён в строковых переполнениях. Иногда к нему добавляются \x0a, \x0d и другие. Определяешь эмпирически: отправляешь все 256 значений и смотришь, какие обрезаются или искажаются.

Минимальный скелет проверки offset на pwntools:
Python:
from pwn import *

offset = 2060
payload  = b"A" * offset
payload += p32(0x42424242)  # контроль EIP - тут должны увидеть 42424242
payload += b"C" * 500       # замеряем пространство для shellcode

io = remote("192.168.1.100", 9999)
io.send(payload)
io.close()
Если после отправки в EIP стоит 0x42424242, а в стеке дальше лежат \x43 - offset подтверждён, пространство для payload измерено. Дальше - обход митигаций.

Обход DEP через ROP-цепочку​

1776882886646.webp

DEP (Data Execution Prevention) делает стек неисполняемым. Ты положил shellcode на стек, прыгнул туда через контроль EIP - а процесс падает с access violation: страница writable, но не executable. DEP (NX-бит для user-mode страниц) появился ещё в Windows XP SP2, но до сих пор ломает жизнь тем, кто пишет эксплойты «по старинке».

Обход строится на одном принципе: вместо выполнения собственного кода со стека ты переиспользуешь уже загруженный код из модулей процесса. Это и есть ROP - Return-Oriented Programming.

Поиск гаджетов и сборка ROP-цепочки​

📚 Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Пошаговый workflow: от анализа краша до proof of concept​

Полный цикл разработки эксплойта CVE. Этот чеклист я использую на каждом проекте:

Шаг 1 - Сбор контекста. Изучи advisory, патч (diff между уязвимой и исправленной версией), NVD-запись. CVSS-вектор даёт быструю оценку: AV:N = удалённая эксплуатация, PR:N = без аутентификации, S:C = выход за пределы scope. Пример: CVE-2024-3400 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) - максимально привлекательный вектор для первоначального доступа (T1190, Initial Access по MITRE ATT&CK).

Шаг 2 - Триаж краша. Воспроизведи crash в отладчике. Определи класс уязвимости по состоянию регистров и стека. Stack overflow с перезаписью EIP - самый прямолинейный путь. Heap overflow / UAF - сложнее, нужен heap feng shui (как в CVE-2018-8453, CVSS 7.8, активно эксплуатируется, включая ransomware-кампании по данным CISA KEV - эксплойт использовал 5 разных функций heap spray под разные версии Windows).

Шаг 3 - Контроль instruction pointer. Cyclic pattern через msf-pattern_create/msf-pattern_offset или cyclic()/cyclic_find() из pwntools. Верификация через 0x42424242. Определи доступное пространство и bad bytes.

Шаг 4 - Разведка митигаций. checksec на бинарник, проверка ASLR на ОС, поиск non-ASLR модулей. Это определяет стратегию: DEP включён, но ASLR отключён для какого-то модуля - собирай ROP из него. ASLR полный - ищи information leak.

Шаг 5 - Построение ROP-цепочки. Для DEP: цепочка под VirtualProtect, VirtualAlloc или WriteProcessMemory (Windows) либо mprotect/ret2libc (Linux). ropper или ROPgadget для поиска гаджетов. Тестируй каждый гаджет по отдельности в отладчике, ставя breakpoint на его адрес - иначе будешь часами гадать, какой из двадцати гаджетов сломал цепочку.

Шаг 6 - Обход ASLR. Если нужен: найди information leak или используй non-ASLR модули. Для non-PIE Linux-бинарников - ret2plt. Для Windows - ищи DLL без DYNAMICBASE.

Шаг 7 - Интеграция payload. Shellcode через msfvenom с учётом bad bytes (-b "\x00\x0a\x0d"), встраиваешь в финальный буфер после ROP-цепочки. Для WriteProcessMemory - shellcode без энкодера (я уже говорил почему). Для VirtualProtect/VirtualAlloc - допустим энкодированный.

Шаг 8 - Стабилизация. На реальном таргете crash-в-crash не нужен. Восстанови поток выполнения после payload: ExitThread вместо ExitProcess (чтобы не уронить весь сервис), или возврат в легитимный код. Упавший сервис - это демаскировка и потеря точки входа.

Типичные ошибки, которые я вижу раз за разом: жёстко прописанные offset без проверки на конкретном билде (смещения плывут между минорными версиями - PoC для PAN-OS 11.1.2-h1 может молча падать на 11.1.2-h3); энкодированный shellcode с WriteProcessMemory; забытый bad byte, который обрезает payload на полпути. Последний - самый обидный: всё работает, кроме одного байта \x0d в середине адреса.

Между академическим пониманием переполнения буфера и рабочим PoC для конкретной CVE - десятки часов в отладчике. Но каждый следующий эксплойт пишется быстрее предыдущего. Паттерны повторяются, инструменты становятся привычными, и в какой-то момент начинаешь видеть ROP-цепочку в дизассемблере ещё до того, как запустил ropper.

Вопрос к читателям​

При сборке ROP-цепочки под Windows-таргет с DEP выбор между VirtualProtect(), VirtualAlloc() и WriteProcessMemory() зависит от доступных гаджетов и ограничений payload. Выше показан подход через WriteProcessMemory, но он ломается при энкодированном shellcode. Какой API-вызов вы предпочитаете для DEP bypass на 32-битных Windows-таргетах, и какой минимальный набор гаджетов (по типам - pop reg; ret, mov [reg], reg; ret, xchg) у вас получался для рабочей цепочки? Конкретный пример: целевой API + количество гаджетов + инструмент поиска (ropper, ROPgadget, Mona.py).
 
Последнее редактирование модератором:
Мы в соцсетях:

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

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab