На недавнем CTF один pwn-таск решили всего 12 команд из 200. Бинарник принимал строку через
fgets, отправлял её прямиком в printf — и всё. Ни переполнения буфера, ни use-after-free, ни heap-примитивов. Команды, привыкшие к классическому BOF, зависли: переполнять нечего. А ведь это и есть format string — одна из самых элегантных атак в binary exploitation.🔍 Суть проблемы укладывается в одно сравнение:
•
printf("%s", buf) — безопасно. Ввод трактуется как текст.•
printf(buf) — уязвимо. Ввод трактуется как инструкция форматирования.Когда printf встречает спецификатор вроде
%p или %x, она лезет на стек за следующим аргументом. Проблема в том, что макрос va_arg понятия не имеет, сколько аргументов реально передано. Он просто сдвигает указатель дальше по стеку — без проверки границ. Три спецификатора %x в строке — три значения со стека, даже если дополнительных аргументов ноль.⚙️ Что это даёт атакующему? Целый арсенал примитивов:
•
%p / %x — чтение стека. Утечка адресов, обход ASLR, поиск канареек.•
%s — чтение по произвольному указателю. Если на стеке лежит адрес, printf прочитает строку по этому адресу.•
%n — запись в память. Записывает количество уже выведенных символов по адресу на стеке. Это arbitrary write — самый опасный примитив.Первый шаг в любом CTF-таске с format string — найти смещение (offset) до своего буфера на стеке. Отправляешь уникальный маркер типа
AAAA и серию %p, ищешь в выводе 0x41414141. Нашёл на седьмой позиции? Значит offset = 7, и теперь можно обращаться к нему напрямую через %7$p.На x86-64 есть нюанс: первые шесть аргументов идут через регистры (rdi, rsi, rdx, rcx, r8, r9), а на стек попадают только остальные. Поэтому смещение при переходе с 32-bit на 64-bit нужно определять заново.
Дальше — дело техники. В pwntools есть класс
FmtStr, который автоматизирует и поиск смещения, и генерацию payload для записи. Ты задаёшь адрес и значение — он сам рассчитывает нужное количество символов и расставляет %hhn для побайтовой записи. Без этого инструмента ручной расчёт превращается в ад с арифметикой.🎯 Format string — тема, которая кажется простой на поверхности, но в реальных тасках требует понимания раскладки стека, calling conventions и защитных механизмов.
Полный разбор с компиляцией уязвимого бинарника, пошаговой отладкой в GDB и готовым эксплойтом — в статье на форуме.
https://hackerlab.pro/blog/format-string-uyazvimost-v-ctf-razbiraem-printf-ataku-i-chitaem-pamyat-protsessa-shag-za-s
