# filter | pwn, beginner **Автор:** — ## Описание > Optimus Prime reporting for duty. > > Мы больше не пишем фаерволы руками. Наш новый shellcode-фаервол — это > маленький трансформер: он прочитал все эксплойты в интернете и сам решает, > какой код безопасно запускать. > > Если модель считает твой шелл-код безвредным — сервис его выполнит. > Модель ещё ни разу не ошибалась. Наверное. ## Деплой Per-instance: [deploy/](deploy/). ## Выдать участникам Только [public/filter.tar.gz](public/filter.tar.gz) — исходники сервиса (`filter.c`, `model.h`, `gen_model.py`, `Makefile`) и ровно тот бинарь `filter`, что крутится на сервере. Задача — не реверс флоатов, а обход фильтра. ## Идея Классический shellcode-таск с «нейросетевым» фильтром. Сервис принимает шелл-код и прогоняет его через крошечный байтовый трансформер-классификатор; если модель считает код безопасным, он исполняется из RWX-буфера. Протокол: ``` [4 байта длины little-endian][шелл-код] # длина <= 0x1000 ``` Трансформер обучен (а точнее — аналитически сконструирован) детектировать байтовые биграммы, которыми на x86-64 входят в ядро: | биграмма | инструкция | |----------|------------| | `0f 05` | `syscall` | | `cd 80` | `int 0x80` | | `0f 34` | `sysenter` | Любой шелл-код, содержащий такую биграмму буквально, получает score `+20` и отклоняется. Другого способа сделать системный вызов на x86-64 нет, поэтому «в лоб» задача не решается. Буфер с кодом — `PROT_READ|WRITE|EXEC`, поэтому задуманное решение — self-modifying shellcode: мы записываем байты `0f 05` в собственный буфер в рантайме (двумя отдельными записями, чтобы `0f` и `05` нигде не стояли рядом) и проваливаемся в них исполнением. Статически модель видит на этом месте `90 90` (nop) и считает код безвредным. Трансформер настоящий: эмбеддинг → каузальное оконное self-attention (relative-position bias тянет внимание на предыдущий байт) → ReLU-FFN → max-pooling по позициям. Внимание на «предыдущий токен» нужно, чтобы FFN мог сделать AND «текущий байт == 05» и «предыдущий байт == 0f». Флаг платформа кладёт в `/flag.txt` (`randomFlagFilename: false`). Пересборка раздатки: [build_public.sh](build_public.sh) (собирает бинарь в той же build-стадии Docker, что и деплой, и упаковывает `public/filter.tar.gz`). ## Решение Задуманное решение — self-modifying shellcode, который нигде не содержит байты `0f 05` буквально: 1. кладём в поток два безобидных байта `90 90` (`nop; nop`); 2. в рантайме двумя **отдельными** записями пишем поверх них `0f` и затем `05`, так что `0f` и `05` нигде в отправленном коде не стоят рядом; 3. проваливаемся исполнением в эти байты, которые теперь декодируются как `syscall`. Статически модель видит там только `90 90`, ставит score `-10` и исполняет код. Собранный пейлоад (42 байта) не содержит ни `0f 05`, ни `cd 80`, ни `0f 34`. ```sh python3 solve/exploit.py # [+] looks harmless to me. Executing... # uid=... # ctfcup{...} ``` Разбор: [solve/WRITEUP.md](solve/WRITEUP.md). Эксплоит: [solve/exploit.py](solve/exploit.py). ## Флаг ``` ctfcup{3f3f3558d1484e50275a5e354d7816bd_%s} ```