44 lines
2.3 KiB
Markdown
44 lines
2.3 KiB
Markdown
<h1 align="center">GumboGate</h1>
|
||
|
||
<p align="center">
|
||
<img src="https://img.shields.io/badge/category-PWN-blueviolet" alt="PWN"/>
|
||
<img src="https://img.shields.io/badge/difficulty-easy-brightgreen" alt="easy"/>
|
||
</p>
|
||
|
||
На старте доступны сервис очистки HTML-накладных и бинарь `gumbogate`. Загружаем тег поставщика, затем запрашиваем отчёт. Сервис возвращает отчёт только после одобрения, а поле одобрения лежит сразу за массивом атрибутов.
|
||
|
||
## Решение
|
||
|
||
Смотрим структуру страницы в куче:
|
||
|
||
```c
|
||
struct attr { char name[8]; char value[8]; };
|
||
struct page {
|
||
struct attr attrs[8];
|
||
uint32_t approved;
|
||
char report[64];
|
||
};
|
||
```
|
||
|
||
Для экспорта нужно `approved == 0x52505041`. В little-endian это четыре ASCII-байта `APPR`.
|
||
|
||
Парсер выбирает корзину по длине имени: `bucket = strlen(name) & 7`. Затем берёт `slot = used[bucket]++` и пишет в `attrs[slot]`. Верхнюю границу счётчика не проверяют, поэтому девятый атрибут одной корзины попадает в несуществующий `attrs[8]`.
|
||
|
||
Один атрибут занимает 16 байт, поэтому девятый начинается по смещению `8 × 16 = 128`, ровно на поле `approved`. Первые четыре байта его имени зададут значение этого поля.
|
||
|
||
Все имена делаем длиной восемь символов, чтобы они попали в корзину `0`:
|
||
|
||
```python
|
||
attrs = [f"A{i:07d}=x" for i in range(8)]
|
||
attrs.append("APPR0000=x")
|
||
tag = "<" + " ".join(attrs) + ">"
|
||
```
|
||
|
||
Восемь первых имён заполняют массив, `APPR0000` выходит за его границу и выставляет нужную константу. Отправляем тег в `1) ingest supplier tag`, затем выбираем `2) export cleaned report`. Проверка одобрения проходит, сервис отдаёт флаг.
|
||
|
||
[Солвер](solve/solve.py).
|
||
|
||
## Флаг
|
||
|
||
`caplag{attribute_bucket_walked_past_the_sanitizer}`
|