Навигация

PWN 946 pts

Сервис на Go, но парсер зовётся через CGo — и именно в этой части расположено классическое переполнение буфера. В глобальной структуре `Parser` лежит 64-байтное поле имени и три указателя на функции: ```c typedef struct { char name[64]; int (*validate)(const uint8_t*, size_t); void* (*transform)(const uint8_t*, size_t, size_t*); void (*cleanup)(void*); } Parser; ``` Сначала указатели выставляются на дефолтные реализации, а потом имя копируется через `strcpy()` без проверки длины. Намечаем вектор: имя длиннее 64 байт ляжет поверх указателя `validate`. А записать туда мы хотим адрес `win()`. ## Решение Протокол бинарный — 16-байтный little-endian заголовок: ```text magic = 0xDEADBEEF (4 байта) type (4 байта) length (4 байта) id (4 байта) ``` Команда `STATUS` выдаёт диагностику с адресами всех интересных функций, включая сам `win()` — ASLR снимается одним запросом: ```text STATUS worker=... cache=0x... parser=0x... win=0x... ``` Дальше отправляем `EXEC` с пейлоадом: ```mermaid block-beta columns 10 N["A × 64
64 B
→ name[64]"]:7 V["p64(win)
8 B
→ validate"]:1 X["transform + cleanup
нетронуты"]:2 classDef base fill:#e0e7ff,stroke:#6366f1,color:#312e81 classDef hit fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef muted fill:#f3f4f6,stroke:#9ca3af,color:#4b5563 class N base class V hit class X muted ``` Тут нюанс: `strcpy` остановится на первом нулевом байте, а в non-PIE x86-64 адрес `win()` выглядит как `0x00000000004xxxxx` — в начале у него нули. Но в little-endian значимые младшие байты идут первыми, а старшие нули и так уже лежат на нужном месте с момента установки `default_validate`. Поэтому [partial overwrite](https://ir0nstone.gitbook.io/notes/types/stack/partial-overwrites) перепишет только значимую часть, старшие нули оставит как есть — и указатель получается корректный. Триггер — обычный `PARSE`. Внутри `parse_data()` сервис по-прежнему зовёт `active_parser.validate(data, len)`, но теперь `validate` указывает не на дефолтную реализацию, а на `win()`, которая лезет в `/tmp/flag` и кладёт содержимое в глобальный буфер. Остаётся ещё раз дёрнуть `STATUS` — и сервис в ответе выплёвывает флаг. Вся цепочка: | # | Команда | Что происходит | |---|---|---| | 1 | `STATUS` | Утечка адреса `win()` | | 2 | `PARSE` | Проверка, что соединение живое | | 3 | `EXEC` | Переполнение `name[64]`, перезапись `validate` | | 4 | `PARSE` | Триггер `win()` — флаг читается в буфер | | 5 | `STATUS` | Читаем `flag=...` в ответе | ## Флаг `caplag{g0r0ut1n3_h1j4ck_cg0_pwn3d}`