PWN
Эксплуатация race conditions
Race condition — это когда результат программы зависит от того, в каком порядке или с какой скоростью выполняются несколько потоков/процессов/операций, и ты можешь специально «вклиниться» между ними, чтобы изменить поведение. В CTF эта тема относится в первую очередь к категориям Pwn, Kernel и иногда Web/Misc. Чаще всего race conditions встречаются в задачах с многопоточными бинарниками, сетевыми сервисами, файловыми операциями и особенно в kernel-эксплойтах (pwnable kernel модули). Новичку важно хотя бы понимать, что это такое, потому что race condition — один из немногих способов обойти защиты, которые кажутся «неуязвимыми» в однопоточном коде. Когда ты научишься видеть места, где между проверкой и использованием есть окно — сможешь решать задачи, которые большинство участников пропускают как «невозможные».
Словарь терминов
race condition
Состояние гонки — когда порядок выполнения потоков меняет результат
многопоточность
Когда программа одновременно выполняет несколько потоков
mutex
Замок, который позволяет только одному потоку работать с данными
TOCTOU
Time-of-check to time-of-use — проверка, а потом использование
symlink race
Гонка при работе с символическими ссылками на файлы
file descriptor
Число, которым программа обращается к открытому файлу/сокету
double-fetch
Дважды чтение данных из пользовательского пространства в kernel
kernel race
Гонка внутри ядра операционной системы
thread
Отдельный поток выполнения внутри процесса
atomic
Операция, которая выполняется целиком и не может быть прервана
spinlock
Очень быстрый замок, который просто крутится в цикле
Race condition в многопоточных программах
Многопоточная программа запускает несколько потоков одновременно. Если два потока работают с одной и той же переменной/структурой без защиты (mutex, lock) — один поток может изменить данные в тот момент, когда второй их читает или использует.
Классические паттерны в CTF:
два потока: один проверяет «достаточно ли денег», второй снимает деньги → можно снять больше, чем есть
один поток выделяет память, другой её освобождает → use-after-free или double-free
гонка при установке/сбросе флага «админ» или «успех»
В CTF многопоточные race чаще всего эксплуатируют через многократные параллельные подключения к серверу или запуск множества потоков в скрипте.
TOCTOU (Time-of-check to time-of-use)
TOCTOU — это когда программа сначала проверяет условие (check), а потом использует результат (use), и между этими двумя шагами проходит время.
Примеры:
проверка, существует ли файл и принадлежит ли он пользователю → потом открытие файла → за это время кто-то может подменить файл
проверка длины строки → потом копирование строки → за это время другой поток может изменить длину
В CTF TOCTOU чаще всего встречается в файловых операциях и в kernel-модулях (проверка указателя из userspace, потом использование).
Эксплуатация простая: многократно повторять операцию в цикле, пока гонка не сработает (race win).
File descriptor race, symlink race
File descriptor race Программа открывает файл по пути, получает file descriptor (fd), потом использует этот fd. Между open и использованием fd другой процесс может подменить файл по этому пути.
Symlink race Программа проверяет файл по пути /tmp/secret → потом открывает его. За время между проверкой и открытием ты создаёшь символическую ссылку, которая указывает на настоящий флаг.
Типичная эксплуатация в CTF:
многократно создавать/удалять symlink в /tmp
параллельно запускать уязвимую программу много раз
ждать, пока она откроет не тот файл
Это классика в задачах с setuid-байнарями или временными файлами.
Kernel race и double-fetch bugs
В kernel-модулях race condition встречается, когда syscall читает данные из userspace дважды.
Double-fetch bug Kernel дважды копирует структуру из userspace:
первый раз — проверяет размер/права
второй раз — использует данные Между этими копированиями пользователь может изменить память → kernel использует другие данные, чем проверил.
Примеры:
первый fetch — размер буфера маленький → проверка проходит
второй fetch — размер буфера огромный → копируется слишком много → heap overflow в kernel
В CTF kernel race часто эксплуатируют через многократные вызовы ioctl или syscall в разных потоках, чтобы попасть в нужное окно времени.
Итог
Главное, что нужно запомнить: race condition возникает всегда, когда между проверкой и использованием или между двумя операциями есть окно, и несколько потоков/процессов могут вмешаться именно в это окно.
Ключевые слова и термины для запоминания: race condition, многопоточность, TOCTOU, symlink race, file descriptor race, double-fetch, kernel race, mutex, atomic.
Эти знания помогают:
увидеть в коде места вида «if (check()) { use(); }» и понять, что это потенциальная гонка
запускать несколько потоков/подключений параллельно, чтобы увеличить шанс выиграть race
эксплуатировать symlink race для чтения флага через временные файлы
понять double-fetch в kernel-модулях и подменять данные между двумя копированиями
комбинировать race с другими уязвимостями (UAF, overflow) для повышения шансов
Начни замечать в коде проверки перед использованием — и многие «странные» задачи перестанут казаться магией, а станут просто гонками, которые нужно выиграть многократными попытками.
Эксплуатация use-after-free
Use-after-free (UAF) — это когда программа после освобождения памяти (free/delete) продолжает использовать указатель на уже освобождённый кусок. В CTF эта тема относится в первую очередь к категории Pwn (бинарная эксплуатация, heap exploitation). Чаще всего встречается в задачах medium–hard уровня, где дают C/C++ программу с динамической памятью (new/delete, malloc/free) или JavaScript-движок в браузерных задачах. Новичку важно её понять, потому что UAF — один из самых мощных и частых примитивов для получения произвольного чтения/записи и выполнения кода, особенно когда классические переполнения закрыты. Когда освоишь базовые сценарии UAF и vtable hijacking — сможешь решать многие heap-задачи, которые на первый взгляд кажутся слишком сложными.
Словарь терминов
use-after-free
Использование указателя после того, как память уже освобождена
UAF
Коротко от use-after-free
dangling pointer
Указатель, который смотрит на уже освобождённую память
vtable
Таблица виртуальных функций объекта в C++
vtable hijacking
Подмена адреса vtable на свою фейковую таблицу
fake vtable
Поддельная таблица виртуальных функций, которую мы контролируем
double-free
Дважды освободить один и тот же кусок памяти
tcache
Кэш маленьких освобождённых блоков в современной glibc
freed chunk
Блок памяти, который уже отдали free, но ещё не переиспользован
realloc
Функция, которая может перевыделить память на том же адресе
JavaScript engine
Движок, который выполняет JS-код в браузере (V8, SpiderMonkey)
UAF в C/C++ программах
В C/C++ программе выделяется память через new или malloc, потом на неё ставится указатель. Если вызвать delete/free, память возвращается в кучу, но указатель остаётся тем же (dangling pointer). Если дальше через этот указатель читать или писать — программа может работать с уже переиспользованной памятью.
Типичные сценарии в CTF:
после free вызывается метод объекта → если память занял другой объект, можно вызвать чужие методы
после free пишут в структуру → можно подменить указатели внутри
после free читают → можно увидеть данные другого объекта (leak)
Самый частый паттерн:
new объект
free объект
new другой объект на то же место
через старый указатель пишем/читаем в новый объект
В CTF UAF в C/C++ почти всегда сочетается с контролем над выделяемыми объектами (размер, содержимое).
Vtable hijacking, fake vtable
В C++ почти все классы с виртуальными функциями имеют vtable — таблицу указателей на методы. Первый 8-байтный член объекта — это указатель на vtable. Если через UAF перезаписать этот указатель на свою фейковую vtable — при следующем вызове виртуального метода программа прыгнет на наш адрес.
Как это делают:
выделяют объект класса A
free объект
выделяют буфер того же размера и заполняют его фейковой vtable (адреса гаджетов или one_gadget)
через dangling pointer вызывают виртуальный метод → прыжок на наш код
В CTF vtable hijacking — один из самых надёжных способов получить RIP-контроль (управление потоком выполнения) в C++ heap-задачах.
Use-after-free в браузерах и JavaScript engines
В браузерных задачах (чаще всего V8 — движок Chrome) UAF возникает, когда JS-объект освобождается, но ссылка на него остаётся в коде. Самые частые места:
ArrayBuffer / TypedArray после detach
объекты после delete в WeakMap / WeakSet
объекты после GC (garbage collection), но с сохранённой ссылкой
Типичная цепочка:
Создаём объект
Делаем так, чтобы он освободился (например через optimization / deoptimization)
Выделяем на его место контролируемый объект (ArrayBuffer)
Через старую ссылку читаем/пишем в новый объект → leak / arbitrary read/write
В CTF браузерные UAF часто дают arbitrary read/write примитив, а дальше уже переписывают vtable или JIT-код.
UAF с double-free комбинацией
Double-free + UAF — очень мощная комбинация. Сначала дважды free один chunk → он попадает в tcache дважды. Потом через UAF используем dangling pointer для чтения/записи, пока chunk ещё не переиспользован.
Типичные сценарии:
double-free → tcache poisoning → UAF на старом указателе → пишем в __free_hook или GOT
UAF → читаем fd из tcache → double-free → poisoning на другой адрес
В CTF эта комбинация встречается в задачах, где дают delete + free или несколько new/delete в цикле.
Итог
Главное, что нужно запомнить: use-after-free — это когда указатель остаётся после free, и ты можешь читать/писать в память, которая уже отдана обратно в кучу и, скорее всего, переиспользована.
Ключевые слова и термины для запоминания: use-after-free, UAF, dangling pointer, vtable hijacking, fake vtable, double-free, tcache, freed chunk, JavaScript engine, realloc.
Эти знания помогают:
получить контроль над виртуальной функцией через подмену vtable в C++
слить адреса libc или кучи через чтение после UAF
комбинировать double-free с UAF для tcache poisoning и arbitrary alloc
понять, почему в задаче дают delete, а потом снова используют объект
эксплуатировать браузерные UAF для arbitrary read/write примитивов
Начни с простых C++ UAF + vtable hijacking — это решает большинство классических heap-задач на delete/new. Дальше добавляй double-free и браузерные сценарии, когда обычные способы не работают.
Форматные строки
Форматные строки — это когда программа использует printf-подобные функции неправильно: вместо printf("строка") вызывают printf(пользовательский_ввод), и тогда ты можешь через свою строку управлять тем, что программа читает или пишет в память. В CTF эта тема относится в первую очередь к категории Pwn (бинарная эксплуатация). Чаще всего встречается в задачах, где дают программу с уязвимым printf, sprintf или fprintf, и нужно либо слить важные адреса, либо переписать что-то в памяти (GOT, return address), чтобы получить shell или прочитать флаг. Новичку это одна из самых полезных тем после stack overflow, потому что format string даёт очень мощные примитивы: чтение и запись почти в любую память почти без ограничений. Когда освоишь %x, %s, %n и direct parameter access — сможешь решать почти все классические format-string задачи уровня easy–medium.
Словарь терминов
format string
Строка с плейсхолдерами (%s, %d, %x), которую printf читает и подставляет значения
%x
Выводит значение в шестнадцатеричном виде (обычно со стека)
%s
Выводит строку, начиная с адреса, который лежит на стеке
%n
Записывает количество уже выведенных символов в адрес на стеке
direct parameter access
Доступ к аргументу по номеру: %7$x — берёт 7-й аргумент
GOT
Таблица, где хранятся адреса функций из библиотек (printf, puts и т.д.)
leak
Получение значения из памяти (адреса libc, стека, PIE) через вывод
arbitrary write
Запись произвольного значения в произвольный адрес
one-shot write
Одноразовая запись через %n за один вызов printf
PIE
Защита: база программы каждый раз загружается по случайному адресу
ASLR
Случайное расположение libc, стека, кучи при каждом запуске
stack offset
Сколько аргументов нужно пропустить, чтобы достать до нужного значения на стеке
Format string vulnerabilities (%n, %s, %x, direct parameter access)
Функция printf ожидает форматную строку и потом аргументы. Если передать пользовательский ввод как форматную строку — printf начнёт читать со стека всё подряд как аргументы.
Основные спецификаторы в CTF:
%x — берёт 4 байта со стека и выводит как hex
%s — берёт 8 байт со стека как адрес и пытается вывести строку оттуда
%n — берёт 8 байт со стека как адрес и записывает туда количество уже выведенных символов
%7$x — берёт именно 7-й аргумент (direct parameter access, работает почти всегда)
Пример: если ввести "%7$x %8$x %9$x" — программа выведет три значения со стека, начиная с 7-го аргумента. Это самый простой способ посмотреть, что лежит на стеке.
В CTF чаще всего используют %x для поиска смещения до нужного значения и %s для чтения строк по адресам.
GOT overwrite через format string
GOT — это таблица, куда при первом вызове функции (puts, printf) записывается её настоящий адрес из libc. Если через %n перезаписать запись в GOT — следующий вызов этой функции прыгнет на твой адрес.
Классическая цепочка:
Слить адрес GOT (через %s или %x на нужном offset)
Посчитать, сколько символов нужно вывести, чтобы %n записал нужное значение
С помощью %n перезаписать GOT на адрес system или win-функции
В CTF это один из самых надёжных способов получить shell, когда нет переполнения буфера, но есть format string.
Leak стека, libc, PIE base через format string
Format string — лучший способ слить адреса в CTF.
Что обычно сливают:
адреса со стека (через %x или %p) — чтобы найти return address, canary, сохранённый rbp
строки со стека или .rodata (через %s) — часто там лежит "/bin/sh"
адреса GOT (через %s на offset до GOT) — чтобы вычислить базу libc
базу программы (PIE) — если в GOT или на стеке лежит адрес из .text
Типичный процесс:
выводим много %x или %p → ищем знакомые адреса (заканчиваются на 000 или содержат строки)
считаем offset до нужного значения
потом используем этот offset для точного %s или %n
В 90% format-string задач сначала делают leak, а уже потом write.
One-shot write и arbitrary write
One-shot write Один вызов printf, который через %n записывает нужное значение в нужный адрес. Обычно используют, когда нужно переписать один байт или короткое значение (например GOT на system).
Arbitrary write Несколько вызовов printf, чтобы записать любое 8-байтное значение в любой адрес. Обычно комбинируют:
%n для записи младших байт
%hn, %hhn для записи по 2 или 1 байту
несколько %n с разными значениями и смещениями
В CTF arbitrary write через format string — один из самых мощных примитивов: можно переписать __free_hook, __malloc_hook, GOT, даже return address на стеке.
Итог
Главное, что нужно запомнить: format string — это когда printf читает пользовательскую строку как формат, и ты можешь читать и писать почти в любую память через %x, %s, %n и direct parameter access.
Ключевые слова и термины для запоминания: format string, %n, %s, %x, direct parameter access, GOT overwrite, leak, arbitrary write, one-shot write, stack offset, PIE, ASLR.
Эти знания позволяют:
слить адрес libc и базу программы за один запрос
прочитать флаг или строку "/bin/sh" прямо из памяти
перезаписать GOT на system и получить shell при следующем вызове puts/printf
сделать точную запись одного байта или слова через %hhn/%hn
обойти ASLR и NX без переполнения буфера
Освой сначала поиск offset через %p.%p.%p..., потом leak адресов, а затем %n-запись — и большинство format-string задач превратятся в рутину. Это одна из самых сильных и элегантных техник в pwn.
Heap exploitation техники
Heap exploitation — это когда ты используешь ошибки в работе с динамической памятью (malloc/free), чтобы заставить программу выделить память по твоему адресу, перезаписать важные указатели или получить контроль над выполнением кода. В CTF эта тема относится в первую очередь к категории Pwn (бинарная эксплуатация, heap exploitation). Чаще всего такие техники встречаются в задачах уровня medium–hard, где стековые переполнения уже закрыты защитами, а уязвимости лежат именно в malloc/free (overflow, UAF, double-free). Новичку это нужно изучать после базового стека и format string, потому что heap даёт самые мощные примитивы: arbitrary alloc, arbitrary write, leak libc без format string. Когда освоишь tcache poisoning и unsorted bin leak — сможешь решать 60–70% всех heap-задач на соревнованиях.
Словарь терминов
chunk
Один блок памяти в куче (заголовок + данные)
unsorted bin
Список недавно освобождённых больших кусков
large bin
Список очень больших освобождённых кусков (сортировка по размеру)
tcache
Кэш маленьких кусков (до ~1 КБ), ускоряет выделение/освобождение
fastbin
Кэш для очень маленьких кусков (до 128 байт)
tcache poisoning
Подмена указателя в tcache, чтобы malloc вернул произвольный адрес
double-free
Дважды освободить один и тот же chunk
fastbin dup
Повторное использование одного чанка через double-free в fastbin
fastbin dup into stack
Перенос fastbin на стек, чтобы контролировать return address
house of spirit
Техника использования фейкового chunk на стеке или в .bss
house of force
Атака на top chunk с переполнением размера
house of einherjar
Техника с fake chunk и unsafe unlink в tcache
Unsorted bin attack, large bin attack
Unsorted bin attack Когда программа освобождает большой chunk → он попадает в unsorted bin. При следующем malloc большого размера unsorted bin сканируется → если подделать fd/bk чанка, можно записать адрес libc в произвольное место (обычно в GOT или __free_hook).
Large bin attack Large bin — отсортированный список больших кусков. При вставке нового чанка в large bin происходит unlink → можно подменить bk_nextsize и fd_nextsize, чтобы записать произвольное значение по произвольному адресу.
В CTF unsorted bin чаще всего используют для первого leak libc (fd указывает на main_arena), а large bin — для arbitrary write после получения контроля.
Tcache double-free и tcache poisoning
Double-free в tcache Если дважды free один chunk → он попадает в tcache дважды (tcache не проверяет дубликаты). При следующем malloc ты получишь тот же адрес два раза → второй раз можно подменить fd на нужный адрес.
Tcache poisoning Через UAF или double-free подменяешь fd указатель freed-чанке в tcache. Следующий malloc того же размера вернёт тебе произвольный адрес (GOT, __free_hook, stack, heap metadata).
Это самая популярная и простая техника в современных libc (2.26+). Обычно цепочка: double-free → poison fd → malloc → пишешь в __free_hook адрес one_gadget или system.
Fastbin dup и fastbin dup into stack
Fastbin dup Fastbin — односвязный список без проверок на размер. Double-free кладёт chunk дважды → следующий malloc того же размера вернёт его снова → можно подменить fd на фейковый chunk.
Fastbin dup into stack Подменяешь fd в fastbin на адрес на стеке (например перед return address). Malloc вернёт кусок памяти на стеке → следующий malloc позволит перезаписать return address или saved rbp.
В CTF fastbin техники используются, когда tcache отключён или заблокирован (очень старые libc или специальные патчи).
House of spirit, house of force, house of einherjar
House of spirit Создаёшь фейковый chunk на стеке или в .bss (с правильными размерами и флагами). Free на указатель, который смотрит на фейковый chunk → он попадает в fastbin/unsorted → потом malloc возвращает контролируемый адрес.
House of force Переполняешь top chunk (последний кусок кучи), меняешь его размер на огромное отрицательное число. Следующий большой malloc думает, что top огромный → выделяет память по любому адресу (arbitrary alloc).
House of einherjar Комбинация fake chunk + unsafe unlink в tcache. Создаёшь фейковый chunk, free на него → он попадает в tcache → poisoning + unlink позволяет записать по произвольному адресу.
Эти house-техники встречаются в hard-задачах, когда обычные tcache/fastbin заблокированы, и нужно обходить проверки glibc.
Итог
Главное, что нужно запомнить: куча — это связанные списки (tcache, fastbin, unsorted, large), и почти все атаки сводятся к подмене указателей fd/bk в этих списках, чтобы malloc вернул нужный адрес или произошла запись при unlink.
Ключевые слова и термины для запоминания: tcache poisoning, double-free, unsorted bin attack, large bin attack, fastbin dup, house of spirit, house of force, house of einherjar, chunk, top chunk, fastbin, unsorted bin.
Эти знания помогают:
отравить tcache и получить malloc на GOT или __free_hook
слить libc base через unsorted bin fd
перезаписать return address через fastbin dup into stack
обойти проверки tcache с помощью house-техник
понять, почему в задаче дают несколько malloc/free подряд и UAF
Начни с tcache poisoning и unsorted bin leak — это решает большинство heap-задач. House-техники оставь на потом, когда обычные способы не работают.
Инструменты для pwn задач
Инструменты для pwn — это специальные программы и библиотеки, которые сильно ускоряют написание эксплойтов, отладку бинарников, поиск гаджетов и анализ защит. В CTF эта тема относится в первую очередь к категории Pwn (бинарная эксплуатация). Они используются практически в каждой pwn-задаче: от простого ret2win до сложных heap-атак и kernel-эксплойтов. Новичку важно знать хотя бы основные из них, потому что без правильных инструментов ты будешь тратить часы там, где можно уложиться в 10 минут. Когда освоишь pwntools, gdb с плагинами и libc-database — скорость решения задач вырастет в разы, а количество ошибок упадёт.
Словарь терминов
pwntools
Библиотека на Python для быстрого написания эксплойтов
gdb
Отладчик, который показывает, что происходит внутри программы
gef
Плагин к gdb, который делает отладку намного удобнее
pwndbg
Ещё один мощный плагин к gdb для pwn-задач
ROPgadget
Программа, которая ищет гаджеты (кусочки кода с ret) в бинарнике
one_gadget
Готовый адрес в libc, который сразу даёт shell
libc-database
База, где можно найти версию libc по её утечке
checksec
Утилита, которая показывает, какие защиты включены в бинарнике
patchelf
Программа для изменения заголовков ELF-файла (например NX)
seccomp
Фильтр системных вызовов, который ограничивает, что можно вызвать
exploit
Готовый скрипт/программа, которая использует уязвимость
gadget
Короткий кусок кода, заканчивающийся ret
pwntools (Python framework для эксплойтов)
pwntools — это главная библиотека для pwn на Python. Она умеет почти всё, что нужно для написания эксплойтов: подключаться к удалённому серверу, отправлять и получать данные, упаковывать/распаковывать адреса, генерировать циклические паттерны, работать с ROP.
Основные возможности:
remote('host', port) — подключение к серверу
p64(addr), u64(bytes) — упаковка/распаковка 64-битных адресов (little-endian)
cyclic(200) — генерирует уникальную строку для поиска offset
flat([junk, pop_rdi, addr]) — удобная сборка ROP-цепочки
interactive() — переключает в ручной режим после отправки payload
В CTF pwntools используют в 90% всех pwn-задач. Без него писать эксплойты на чистом socket — это очень долго и с кучей ошибок.
gdb, gef, pwndbg, pwndbg++ для отладки
gdb — это классический отладчик для Linux. Сам по себе он не очень удобный, поэтому ставят плагины.
gef и pwndbg — два самых популярных плагина для pwn. Они добавляют:
красивый вывод стека, регистров, кучи
команды heap, vmmap, telescope, checksec
подсветку ROP-гаджетов
автоматический поиск offset до return address
pwndbg++ — более новый вариант pwndbg с улучшенным интерфейсом.
В CTF отладка нужна почти всегда: понять, где краш, посчитать точный offset, посмотреть, что лежит на стеке после переполнения.
ROPgadget, Ropper, one_gadget, libc-database
ROPgadget / Ropper — ищут гаджеты в бинарнике и libc (pop rdi; ret, mov rax, rbx; ret и т.д.). Очень нужны, когда собираешь ROP-цепочку.
one_gadget — база адресов в разных версиях libc, где один гаджет сразу вызывает execve("/bin/sh", NULL, NULL). Если знаешь базу libc — можно сразу прыгнуть на one_gadget и получить shell без аргументов.
libc-database — огромная коллекция разных libc. По утечке нескольких байт (например __libc_start_main+243) можно определить точную версию libc и скачать её.
В CTF эти инструменты спасают часы: вместо ручного поиска гаджетов — одна команда, вместо угадывания libc — автоматический поиск.
checksec, patchelf, seccomp-tools
checksec — показывает все защиты бинарника за одну команду:
NX — можно ли исполнять стек
Canary — есть ли защита стека
PIE — случайная база программы
RELRO — уровень защиты GOT
patchelf — позволяет патчить бинарник:
отключить NX для отладки
изменить интерпретатор
поменять rpath
seccomp-tools — анализирует seccomp-фильтры (какие системные вызовы разрешены). Очень полезно в задачах, где запрещены execve, open, read и т.д.
В CTF сначала запускаешь checksec, чтобы понять, с чем имеешь дело, потом смотришь seccomp, если нужно — патчишь бинарник для локальной отладки.
Итог
Главное, что нужно запомнить: в pwn-задачах 50% успеха — это умение быстро пользоваться правильными инструментами, а не только знать теорию эксплуатации.
Ключевые слова и термины для запоминания: pwntools, gdb, gef, pwndbg, ROPgadget, one_gadget, libc-database, checksec, patchelf, seccomp-tools, gadget, exploit.
Эти знания помогают:
за 5 строк на pwntools подключиться к серверу и отправить payload
в gdb с gef мгновенно увидеть стек и понять offset
найти гаджеты и one_gadget за секунды вместо часов
сразу понять, какие защиты включены и что можно обойти
определить версию libc по утечке и скачать точную копию
Освой сначала pwntools + checksec + gdb с gef/pwndbg — это база, без которой почти невозможно комфортно решать pwn. Остальные инструменты добавляй по мере роста сложности задач.
Основы бинарной эксплуатации
Бинарная эксплуатация — это когда ты заставляешь уже скомпилированную программу (бинарник) делать то, что ты хочешь, хотя автор этого не планировал: читать память, запускать свой код, получать shell. В CTF эта тема в первую очередь относится к категории Pwn (pwnable / binary exploitation). Чаще всего встречается в задачах, где дают исполняемый файл (ELF на Linux или PE на Windows) и удалённый сервер, а нужно либо получить флаг, либо запустить команду на сервере. Новичку понимать эти основы критически важно, потому что без знания, как устроен бинарник и память, ты просто не поймёшь, почему твой payload не работает и куда писать адрес. Когда разберёшься в структуре ELF/PE, секциях и стеке — большинство классических stack overflow и ret2win задач станут понятными и решаемыми.
Словарь терминов
ELF
Формат исполняемых файлов в Linux
PE
Формат исполняемых файлов в Windows (.exe, .dll)
заголовок
Начальная часть файла, где описано, как его загружать в память
секция
Кусок файла, который содержит код, данные или другую информацию
.text
Секция, где лежит машинный код программы (инструкции)
.data
Секция с инициализированными глобальными переменными
.bss
Секция для неинициализированных глобальных переменных (заполняется нулями)
.rodata
Секция только для чтения — обычно строки и константы
GOT
Таблица, куда во время работы записываются адреса библиотечных функций
PLT
Таблица, через которую программа вызывает функции из библиотек
стек
Область памяти для локальных переменных и адресов возврата
куча
Область памяти для динамически выделяемых данных (malloc)
mmap
Способ выделить память по произвольному адресу (часто для shellcode)
little-endian
Младший байт числа лежит по меньшему адресу
alignment
Выравнивание данных по определённому размеру (обычно 8 или 16 байт)
Формат ELF и PE файлов, структура заголовков
ELF (Executable and Linkable Format) — стандартный формат для Linux. Начинается с magic bytes 7f 45 4c 46. Основные части:
ELF Header — общая информация (32/64 бит, little/big endian, точка входа)
Program Headers — говорит, какие куски грузить в память и куда
Section Headers — описывает все секции (.text, .data и т.д.)
PE (Portable Executable) — формат Windows. Начинается с MZ (4D 5A), потом идёт PE-сигнатура (50 45). Основные части:
DOS Header
PE Header
Optional Header
Section Table
В CTF почти всегда дают 64-битный ELF на Linux — это самый частый случай. Знать структуру заголовков нужно, чтобы понимать, где начинается код, где данные и где можно найти полезные строки или адреса.
Секции .text, .data, .bss, .rodata, GOT, PLT
.text Здесь лежит исполняемый код — все функции программы. Обычно имеет права только на чтение и выполнение (no write).
.data Инициализированные глобальные переменные (int x = 1337;). Читается и пишется.
.bss Неинициализированные глобальные переменные (int y;). При запуске заполняется нулями. Тоже читается и пишется.
.rodata Только для чтения: строки, константы, форматные строки printf. Очень часто именно здесь лежат строки типа "/bin/sh" или "flag.txt".
GOT (Global Offset Table) Таблица, куда динамический линкователь записывает реальные адреса функций из библиотек (printf, system и т.д.). Если удаётся переписать GOT — можно подменить, какую функцию вызовет программа.
PLT (Procedure Linkage Table) Кусок кода, через который идут все вызовы внешних функций. PLT → GOT → настоящая функция.
В CTF эти знания нужны, чтобы:
найти строку "/bin/sh" в .rodata
понять, куда писать адрес system
использовать PLT для вызова нужных функций без знания их адресов
Адресация памяти, стек, куча, mmap
Когда программа запускается, операционная система выделяет ей виртуальную память. Основные области:
стек — растёт вниз (от высоких адресов к низким). Здесь локальные переменные, аргументы функций, адрес возврата.
куча — растёт вверх (от низких к высоким). Здесь всё, что выделено через malloc/new.
mmap — область для отображения файлов или произвольного выделения памяти (часто используется для shellcode).
библиотеки (libc) — загружаются в середину адресного пространства.
Типичный порядок адресов (64-bit Linux): 0x0000… → низкие адреса библиотеки → mmap → куча → … → стек → 0x7fff… высокие адреса
В CTF чаще всего эксплуатируют именно стек (stack overflow), потому что там лежит return address — адрес, куда программа вернётся после функции.
Endianness (little/big), alignment и padding
Endianness Little-endian (x86/x64): младший байт по младшему адресу. Число 0x41424344 лежит как 44 43 42 41. Big-endian: старший байт первым (почти не встречается в CTF на Linux).
Alignment Данные обычно выравниваются по 8 или 16 байтам. Например, int64 должен лежать по адресу, кратному 8.
Padding Дополнительные байты, которые добавляют, чтобы соблюсти alignment. В структурах между полями часто бывают «мусорные» байты.
В CTF это важно при:
упаковке адресов в payload (p64 в pwntools — little-endian)
понимании, почему после локальной переменной идёт мусор перед return address
расчёте точного смещения для переполнения
Итог
Главное, что нужно запомнить: программа — это просто набор байтов в памяти, и если ты знаешь, где лежит код, данные, стек и адреса возврата — ты можешь заставить её выполнять то, что хочешь.
Ключевые слова и термины для запоминания: ELF, PE, .text, .data, .bss, .rodata, GOT, PLT, стек, куча, mmap, little-endian, alignment, padding, return address.
Эти знания позволяют:
понять, сколько байт нужно отправить, чтобы достать до return address
найти адрес строки "/bin/sh" в бинарнике
посчитать смещение до GOT и подменить функцию
правильно упаковать 64-битные адреса в payload
отличать stack-based атаки от heap-based
Освой эти базовые понятия — и классические pwn-задачи типа ret2win, call system, write-what-where перестанут быть магией и станут понятной механикой.
Переполнение буфера на куче
Переполнение буфера на куче — это когда программа позволяет записать больше данных, чем выделено в динамической памяти (куче), и это портит метаданные или другие куски данных, которые там лежат. В CTF эта тема относится в первую очередь к категории Pwn (бинарная эксплуатация, heap exploitation). Чаще всего такие задачи встречаются на уровне medium–hard, когда классический stack overflow уже закрыт защитами (canary, NX, ASLR), и авторы дают уязвимости именно в работе с malloc/free. Новичку это нужно изучать после стека, потому что heap гораздо сложнее, но именно здесь прячутся самые интересные и мощные примитивы для получения произвольного чтения/записи и выполнения кода. Когда разберёшься в базовых атаках на tcache и fastbin — сможешь решать многие задачи, которые раньше казались невозможными.
Словарь терминов
куча
Динамическая память, из которой программа берёт память через malloc
chunk
Один кусок памяти в куче (заголовок + данные)
use-after-free
Использование указателя после того, как память уже freed
double-free
Дважды освободить один и тот же кусок памяти
heap overflow
Переполнение буфера внутри выделенного куска на куче
tcache
Кэш маленьких кусков (до ~1 КБ), ускоряет malloc/free
fastbin
Кэш для очень маленьких кусков (до 128 байт на 64-бит)
unsorted bin
Список больших освобождённых кусков, которые ещё не отсортированы
large bin
Список очень больших кусков (> 512 байт)
tcache poisoning
Подмена указателя в tcache, чтобы malloc вернул нужный адрес
fastbin attack
Атака на fastbin, похожая на tcache poisoning
unsafe-unlink
Эксплуатация unlink без проверок (старая техника)
house of orange
Техника для атаки на top chunk и получения произвольного выделения
heap spraying
Заполнение кучи большим количеством одинаковых объектов
Heap overflow, use-after-free, double-free
Heap overflow Когда в выделенный кусок (например 0x100 байт) пишут больше, чем выделили — перезаписывают заголовок следующего чанка или его данные. Можно испортить размер следующего чанка, указатель prev_size, fd/bk в freed-чанке.
Use-after-free (UAF) Программа сохраняет указатель на freed память и продолжает с ним работать. После free память может быть выделена заново → ты контролируешь, что там лежит, и можешь подменить указатели или vtable.
Double-free Дважды вызвать free на один и тот же указатель. В tcache это кладёт один и тот же chunk дважды в один список → при следующем malloc можно получить один и тот же адрес два раза → очень мощный примитив для подмены указателей.
В CTF эти три уязвимости — самые частые начальные примитивы. Обычно дают одну из них + возможность читать/писать в выделенные куски.
Heap spraying, tcache poisoning, fastbin attack
Heap spraying Создаём много одинаковых объектов на куче, чтобы увеличить шанс, что нужный адрес будет занят нашим контролируемым чанком.
Tcache poisoning Tcache — это односвязный список для маленьких кусков (до 0x408 байт). Если через UAF или overflow подменить указатель fd в freed-чанке на адрес, который мы хотим → следующий malloc вернёт нам произвольный адрес (например GOT, __free_hook, stack).
Fastbin attack Fastbin похож на tcache, но без проверки размера и с двойным освобождением. Можно заставить fastbin указывать на фейковый chunk, а потом выделить память по контролируемому адресу.
Tcache poisoning — самая популярная техника в современных 64-бит Linux libc (с версии 2.26). Обычно цепочка выглядит так: UAF / double-free → подмена fd → malloc → пишем в нужное место (GOT, free_hook).
Unsafe-unlink, house of orange, house of botcake
Unsafe-unlink Старая техника (до проверок в glibc 2.3.4). Если подделать fd и bk в freed-чанке → при unlink можно записать адрес чанка в произвольное место (Pwn классика прошлого).
House of Orange Атака на top chunk (последний большой кусок кучи). Через overflow или unlink заставляем _int_malloc думать, что top маленький → он вызывает sysmalloc → можно перехватить контроль над heap metadata и получить arbitrary alloc.
House of Botcake Современная вариация house-техник, работает с tcache и large bin consolidation. Обычно используется, когда нужно обойти проверки tcache и получить контроль над unsorted bin или large bin.
Эти house-атаки встречаются в hard-задачах, где обычный tcache poisoning заблокирован фильтрами или проверками.
Tcache, fastbin, unsorted bin, large bin атаки
Tcache Самый простой и популярный кэш. Односвязный список. Нет проверок на дубликаты. Очень легко отравить.
Fastbin Для кусков до 128 байт. Двойной free работает, но есть проверка на повторное добавление. Атаки похожи на tcache, но сложнее.
Unsorted bin Список освобождённых кусков, которые не попали в маленький кэш. При выделении большого куска unsorted bin сканируется → можно заставить записать адрес libc в контролируемое место (fd/bk overwrite).
Large bin Для очень больших кусков. Имеет сортировку. Атаки через large bin attack позволяют подменять указатели и получать произвольную запись.
В CTF чаще всего побеждает tcache poisoning → unsorted bin leak → large bin attack для arbitrary write.
Итог
Главное, что нужно запомнить: куча — это не хаос, а набор связанных списков (tcache, fastbin, unsorted, large), и большинство атак сводится к подмене указателей в этих списках, чтобы malloc вернул нужный адрес.
Ключевые слова и термины для запоминания: heap overflow, use-after-free, double-free, tcache poisoning, fastbin attack, unsorted bin, large bin, house of orange, unsafe-unlink, chunk, top chunk, heap spraying.
Эти знания помогают:
получить произвольное выделение памяти через tcache poisoning
слить адрес libc через unsorted bin fd/bk
переписать __free_hook или GOT для выполнения system("/bin/sh")
обойти маленькие буферы и сложные проверки через double-free + UAF
понять, почему в задаче дают именно malloc/free и несколько выделений подряд
Освой сначала tcache poisoning и unsorted bin leak — это откроет тебе 60–70% всех heap-задач среднего уровня. Дальше house-техники и large bin — для hard и expert.
Переполнение буфера на стеке
Переполнение буфера на стеке — это когда программа позволяет записать в маленький массив (буфер) больше данных, чем он может вместить, и лишние байты начинают затирать важные вещи дальше по памяти, в том числе адрес возврата из функции. В CTF эта тема в первую очередь относится к категории Pwn (бинарная эксплуатация). Чаще всего встречается в классических задачах, где дают программу с уязвимым gets/scanf/ strcpy и нужно либо прочитать флаг, либо получить shell на удалённом сервере. Новичку обязательно нужно это понять, потому что stack overflow — самая первая и самая понятная уязвимость, с которой начинают учить эксплуатацию. Когда освоишь перезапись return address и обход простых защит — ты сможешь решать 70–80% всех beginner и easy pwn-задач.
Словарь терминов
буфер
Массив фиксированного размера для хранения данных
переполнение буфера
Запись за пределы буфера, затирание того, что лежит дальше
стек
Область памяти сверху вниз: локальные переменные + адрес возврата
return address
Адрес в памяти, куда программа вернётся после завершения функции
stack smashing
Переполнение буфера на стеке с перезаписью return address
canary
Случайное секретное значение перед return address для обнаружения переполнения
GS / stack canary
Защита, которая проверяет canary перед возвратом из функции
leak
Получение значения из памяти (например canary) через уязвимость
ROP
Цепочка коротких кусочков кода из программы (gadgets), заканчивающихся ret
gadget
Короткая последовательность инструкций, заканчивающаяся ret
stack pivot
Изменение указателя стека rsp, чтобы использовать другой стек
fake stack
Поддельный стек, который мы контролируем и на который переключаемся
NX
Защита: память, помеченная как исполняемая или нет (no eXecute)
Stack smashing и перезапись return address
Программа выделяет на стеке буфер, например char buf[100]. Если ввести 150 символов — лишние 50 байт затирают то, что лежит выше по стеку.
Порядок на стеке (снизу вверх, адреса уменьшаются):
локальные переменные (buf)
сохранённый rbp (frame pointer)
return address (куда вернуться после функции)
Если переполнить buf и записать свой адрес вместо return address — при ret программа прыгнет туда, куда ты указал.
В CTF чаще всего:
ret2win — прыгаем на функцию win() / print_flag()
ret2shellcode — если NX выключен, прыгаем на свой shellcode в стеке
ret2system — прыгаем на system("/bin/sh")
Простейший случай без защит: считаешь offset от начала buf до return address (обычно 104–120 байт в 64-бит) и пишешь junk + адрес цели.
Canary/GS protection и bypass (leak, brute-force)
Canary — это случайное 8-байтное значение (часто заканчивается 00), которое программа кладёт между буфером и return address. Перед ret она проверяет: canary всё ещё то же? Если нет — программа крашится (stack smashing detected).
Как обойти:
leak — если есть другая уязвимость (format string, off-by-one, read после переполнения), сначала вытекает canary, потом используешь его в payload
brute-force — если сервер форкается (fork server) и canary не меняется между соединениями — перебираешь байты canary по одному (256 вариантов на байт, всего ~2¹⁶–2²⁴ попыток)
bypass через другую уязвимость — иногда можно перезаписать canary тем же значением, которое там уже лежит
В CTF без canary-задач почти не бывает на уровне выше beginner.
ROP (Return-Oriented Programming), гаджеты
Когда NX включён — свой код (shellcode) на стеке не запустится. ROP — способ обойти это: используем уже существующие куски кода в программе или libc, которые заканчиваются инструкцией ret.
Gadget — короткая последовательность инструкций вида: pop rdi; ret mov rax, rbx; ret add rsp, 0x20; ret
Типичная ROP-цепочка:
pop rdi; ret → кладём "/bin/sh" в rdi
pop rsi; ret → 0 в rsi
pop rdx; ret → 0 в rdx
system → вызов system("/bin/sh")
В CTF чаще всего:
ищешь гаджеты в бинарнике и libc
строишь цепочку для вызова system, execve, read/write
используешь один гаджет для ret2csu (если нет прямых pop)
Stack pivot и fake stack
Иногда буфер слишком маленький, чтобы уместить всю ROP-цепочку. Или return address перезаписывается, но гаджеты лежат далеко.
Stack pivot — меняем значение rsp (указатель стека) на адрес, который мы контролируем (например в .bss, heap, mmap).
Популярные гаджеты для pivot:
leave; ret → mov rsp, rbp; pop rbp; ret
add rsp, N; ret
pop rsp; ret
Fake stack — создаём свой контролируемый стек в .bss / heap и переключаемся на него.
В CTF это встречается, когда:
маленький буфер + большая цепочка
нужно вызвать несколько функций подряд
ASLR + частичный leak → pivot на известный адрес
Итог
Главное, что нужно запомнить: переполнение буфера на стеке — это про контроль return address, а современные защиты заставляют либо обходить canary, либо собирать ROP-цепочки из существующих гаджетов.
Ключевые слова и термины для запоминания: stack smashing, return address, canary, leak, brute-force, ROP, gadget, stack pivot, fake stack, NX, ret2win, ret2system.
Эти знания позволяют:
посчитать точный offset и перезаписать return address на win-функцию
слить canary через format string и потом использовать в payload
собрать простую ROP-цепочку для system("/bin/sh")
обойти маленький буфер через stack pivot на контролируемую область
понять, почему без NX можно вставить shellcode прямо в стек
Освой эти техники шаг за шагом — и большинство классических stack-based pwn-задач перестанут быть страшными, а станут логичной головоломкой.
Return-to-libc и ROP
Return-to-libc и ROP — это способы заставить программу выполнить чужой код, когда нельзя просто вставить свой shellcode из-за защиты NX (no-execute). Вместо записи своего кода ты используешь уже существующие функции из библиотеки libc (system, execve) или короткие кусочки кода (gadgets) из самой программы и libc. В CTF эта тема относится в первую очередь к категории Pwn (бинарная эксплуатация). Чаще всего встречается в задачах уровня easy–medium-hard, где есть переполнение буфера, но NX включён, а иногда и ASLR. Новичку это нужно освоить сразу после простого stack overflow, потому что ret2libc и ROP — основа почти всех современных pwn-задач без исполняемого стека. Когда научишься собирать простые цепочки и вызывать system("/bin/sh") — откроется огромный пласт решаемых задач.
Словарь терминов
ret2libc
Техника: прыгаем на функцию из libc (обычно system)
system
Функция libc, которая выполняет команду в shell
one_gadget
Готовый адрес в libc, который сразу даёт shell без аргументов
ROP
Return-Oriented Programming — цепочка гаджетов, заканчивающихся ret
gadget
Короткий кусок кода, заканчивающийся инструкцией ret
pop rdi
Гаджет, который кладёт значение со стека в регистр rdi
ret2csu
Техника использования __libc_csu_init для установки регистров
libc
Стандартная библиотека C, где лежат system, execve, puts и т.д.
ASLR
Защита: адреса libc и программы каждый раз разные
NX
Защита: стек и куча не могут исполняться как код
dlresolve
Механизм динамической линковки, который можно использовать для вызова функций
ret2dlresolve
Атака через подделку структур динамической линковки
PLT
Таблица, через которую вызываются функции libc
GOT
Таблица, куда записываются реальные адреса функций libc
ret2libc (system, execve, one_gadget)
ret2libc — самый простой и классический способ. Идея: вместо своего кода прыгаем на уже существующую функцию system из libc и передаём ей строку "/bin/sh".
Шаги:
найти адрес строки "/bin/sh" (в libc или в программе)
найти адрес функции system (через leak или частичный brute)
в payload: junk + адрес system + адрес возврата + адрес "/bin/sh"
execve — похож, но требует больше аргументов (pop rdi → "/bin/sh", pop rsi → 0, pop rdx → 0).
one_gadget — специальные адреса в libc, где один ret сразу вызывает execve("/bin/sh", NULL, NULL). Очень удобно, потому что не нужно готовить аргументы.
В CTF ret2libc — это первая техника, которую учат после базового переполнения. Если знаешь offset до return address — просто кладешь адрес system и "/bin/sh".
ROP chains (pop rdi, pop rsi, pop rdx)
Когда одной функции недостаточно — собираем цепочку гаджетов.
Типичная цепочка для system("/bin/sh"):
pop rdi; ret → кладём "/bin/sh" в rdi (первый аргумент)
pop rsi; ret → 0 в rsi (второй аргумент)
pop rdx; ret → 0 в rdx (третий аргумент)
system → вызов system("/bin/sh")
Каждый гаджет заканчивается ret → rsp сдвигается, следующий гаджет берёт свои аргументы со стека.
В CTF почти всегда нужно:
найти гаджеты pop rdi, pop rsi, pop rdx (они есть почти везде)
посчитать offset до return address
собрать payload: junk + гаджет1 + аргумент1 + гаджет2 + аргумент2 + … + system
Gadget finding (ROPgadget, Ropper, rp++), ret2csu
Gadgets — это любые последовательности инструкций, заканчивающиеся ret (c3). Их ищут автоматически по бинарнику и libc.
Самые полезные гаджеты:
pop rdi; ret
pop rsi; ret
pop rdx; ret
pop rax; ret
mov rdi, rax; ret
add rsp, N; ret (для stack pivot)
ret2csu — когда в бинарнике нет хороших pop-гаджетов, используют код из __libc_csu_init (функция инициализации). Там есть мощная последовательность, которая позволяет положить значения в rbx, rbp, r12, r13, r14, r15 и вызвать вызов по адресу.
ret2csu — спасение, когда libc не загружена или гаджетов мало.
В CTF сначала ищут простые pop-гаджеты, если их нет — переходят к ret2csu.
Ret2dlresolve, ret2dlruntime
ret2dlresolve — продвинутая техника, когда не удаётся слить libc или нет "/bin/sh". Использует механизм ленивой линковки (lazy binding): программа сама вызывает _dl_runtime_resolve, когда впервые вызывает функцию.
Идея:
подделать структуру JMPREL / SYMTAB / STRTAB на стеке
заставить программу думать, что нужно разрешить символ (например system)
_dl_runtime_resolve вызовет настоящий system
Это позволяет вызвать любую функцию libc без знания её адреса.
В CTF ret2dlresolve встречается в hard-задачах, где ASLR полный, нет утечек и нет строк "/bin/sh".
Итог
Главное, что нужно запомнить: когда NX запрещает исполнение стека — мы используем уже существующий код: либо готовые функции libc (ret2libc), либо короткие кусочки ret-гаджетов (ROP).
Ключевые слова и термины для запоминания: ret2libc, system, one_gadget, ROP, gadget, pop rdi, pop rsi, pop rdx, ret2csu, dlresolve, libc, ASLR, NX, PLT, GOT.
Эти знания помогают:
вызвать system("/bin/sh") простым ret2libc без гаджетов
собрать цепочку для execve с правильными регистрами
использовать ret2csu, когда обычных гаджетов не хватает
обойти полное ASLR через ret2dlresolve
понять, почему в payload после адреса идут ещё 8-байтные значения (аргументы гаджетов)
Освой сначала ret2libc с system, потом простую ROP-цепочку с pop-гаджетами, а затем ret2csu — и большинство pwn-задач с переполнением буфера перестанут быть проблемой. Это основа всей современной бинарной эксплуатации.
Защиты и их обход
Защиты — это механизмы, которые операционная система и компилятор включают в программу, чтобы сделать эксплуатацию уязвимостей (переполнений, format string и т.д.) гораздо сложнее или вообще невозможной. В CTF эта тема относится в первую очередь к категории Pwn (бинарная эксплуатация). Чаще всего защиты встречаются в задачах уровня easy–medium-hard, где без их обхода классическое переполнение буфера просто не сработает. Новичку важно понять, какие защиты существуют и как их обойти, потому что на реальных соревнованиях почти никогда не дают программу без хотя бы одной защиты. Когда ты научишься определять, какие защиты включены и как их нейтрализовать — ты перестанешь получать «Segmentation fault» и начнёшь получать shell там, где другие сдаются.
Словарь терминов
NX / DEP
Защита: память стека и кучи нельзя исполнять как код
ASLR
Случайное расположение адресов libc, стека, программы при запуске
PIE
Случайная база адресов самой программы (Position Independent Executable)
Canary
Случайное значение перед return address, проверяется при выходе из функции
RELRO
Защита таблицы GOT: делает её частично или полностью только для чтения
Partial RELRO
GOT можно перезаписать до первого вызова функции
Full RELRO
GOT только для чтения с самого начала
info leak
Утечка адресов через format string, UAF, heap overflow и т.д.
partial overwrite
Перезапись только младших байт адреса (например последние 2 байта)
brute-force
Перебор возможных значений (чаще всего canary или младших байт адреса)
NX (DEP), ASLR, PIE, Canary, RELRO
NX / DEP No-eXecute / Data Execution Prevention — стек и куча помечены как неисполняемые. Твой shellcode на стеке просто не запустится — программа упадёт с SIGSEGV.
ASLR Address Space Layout Randomization — каждый запуск программы libc, стек, куча и mmap-области лежат по случайным адресам. Без утечки адресов ты не знаешь, куда прыгать.
PIE Position Independent Executable — сама программа (не только библиотеки) загружается по случайному базовому адресу. Адреса функций main, win и гаджетов каждый раз разные.
Canary Случайное 8-байтное значение (часто заканчивается нулевым байтом) кладётся между буфером и return address. Перед ret проверяется: если canary изменился — программа крашится.
RELRO RELocation Read-Only — защита таблицы GOT. Partial RELRO — GOT перезаписывается до первого вызова функции. Full RELRO — GOT сразу только для чтения, перезаписать нельзя.
В CTF почти всегда включают хотя бы NX + Canary + ASLR. PIE и Full RELRO — признак более сложной задачи.
Stack canary leak и brute-force
Leak canary Самый надёжный способ — использовать другую уязвимость (format string, off-by-one read, heap UAF), чтобы прочитать canary до переполнения. Обычно canary лежит сразу после локальных переменных → можно вывести его через %x или %s.
Brute-force canary Если сервер форкается (каждое соединение — новый процесс с тем же canary) — можно перебирать canary по байтам. Обычно 6 байт (последний всегда 00) → 2¹⁶–2⁴⁸ попыток в худшем случае, но на практике часто 2¹⁶–2²⁴.
В CTF leak — предпочтительнее, brute-force — запасной вариант для fork-серверов.
Partial RELRO и full RELRO bypass
Partial RELRO GOT перезаписывается только после первого вызова функции (lazy binding). Если ты перезапишешь GOT до первого вызова (например через format string или heap overflow) — при следующем вызове printf/puts программа прыгнет на твой адрес.
Full RELRO GOT сразу read-only. Перезаписать GOT нельзя. Обход:
использовать ret2dlresolve (подделать структуры динамической линковки)
переписать другие указатели (__free_hook, __malloc_hook)
использовать one_gadget или гаджеты без GOT
В CTF Full RELRO заставляет переходить от GOT overwrite к более сложным техникам (dlresolve, hooks).
ASLR bypass (info leak, partial overwrite)
Info leak Самый частый и надёжный способ:
format string → %s или %x на GOT → адрес libc
heap leak → unsorted bin fd/bk → libc base
stack leak → canary + return address → PIE base + libc offset После утечки считаешь точные адреса system, "/bin/sh", гаджетов.
Partial overwrite Если знаешь старшие байты адреса (например libc base через leak), а младшие случайны — можно перезаписать только последние 1–2 байта. Часто используется в связке с heap overflow или format string %hhn.
В CTF ASLR почти никогда не обходят brute-force (слишком долго), а только через утечку или частичную перезапись.
Итог
Главное, что нужно запомнить: современные программы защищены сразу несколькими механизмами (NX, Canary, ASLR, PIE, RELRO), и задача эксплойтера — понять, какие именно включены, и найти способ их обойти (утечка, brute, partial write, альтернативные техники).
Ключевые слова и термины для запоминания: NX, ASLR, PIE, Canary, RELRO, Partial RELRO, Full RELRO, info leak, partial overwrite, brute-force, stack canary leak.
Эти знания помогают:
определить по поведению программы, включён ли canary (краш с «stack smashing detected»)
слить libc base через format string или heap и посчитать адрес system
обойти ASLR, зная только младшие байты адреса
понять, почему GOT overwrite не работает (Full RELRO) и перейти к ret2dlresolve
комбинировать утечку canary + утечку libc → полный контроль над программой
Освой сначала, как определять защиты (checksec или поведение краша), потом утечки адресов — и большинство защит перестанут быть преградой, а станут просто дополнительными шагами в эксплойте.