Init. commit
This commit is contained in:
70
pwn-навигация/WRITEUP.md
Normal file
70
pwn-навигация/WRITEUP.md
Normal file
@@ -0,0 +1,70 @@
|
||||
<h1 align="center">Навигация</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/category-PWN-blueviolet" alt="PWN"/>
|
||||
<img src="https://img.shields.io/badge/points-946-orange" alt="946 pts"/>
|
||||
</p>
|
||||
|
||||
Сервис на 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<br/>64 B<br/>→ name[64]"]:7
|
||||
V["p64(win)<br/>8 B<br/>→ validate"]:1
|
||||
X["transform + cleanup<br/>нетронуты"]: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}`
|
||||
Reference in New Issue
Block a user