Files
Geroi-Kodeksa/OxidePool-PWN/README.md
2026-03-02 21:44:22 +03:00

96 lines
4.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OxidePool
НУ ооочень простой протокол обмена сообщениями....
## Протокол
Сетевой протокол little-endian, пакет имеет такой layout:
`op:u8 flags:u8 seq:u16 len:u16 csum:u16` + `payload`
, где `csum` — сумма всех байтов payload по модулю `2^16`.
Команды:
- `0x10 HELLO`, payload: empty.
- Ответ: `nonce:u64` + `version:u32`.
- `0x11 AUTH`, payload: `token:u64`, где `token = nonce ^ 0xC0DEC0DEC0DEC0DE`.
- `0x20 ALLOC`, payload: `count:u16` (количество аллокаций в сессии).
- `0x21 FREE`, payload: `idx:u16` (освобождение слота).
- `0x22 SELECT`, payload: `idx:u16` (выбор активного слота).
- `0x30 WRITE`, payload: `offset:u16`, `len:u16`, `data[len]`.
- Работает если выставлен флаг `flags & 1`.
- `0x31 TRIGGER`, payload: empty (запускает handler).
- `0x40 LEAK`, payload: `stage:u8` (`0` или `1`).
## Идея эксплойта
Уязвимость в `Session` связана с неверной проверкой границ в `WRITE`:
- layout структуры:
```text
[ buf: [u8; 0xFFF0] ][ handler: Box<dyn Handler> ][ guard: u64 ]
```
- проверка границы идёт через `u16`:
```rust
let end = offset.wrapping_add(data.len() as u16);
if end <= BUF_SIZE as u16 {
ptr::copy(data.as_ptr(), buf.add(offset as usize), data.len());
}
```
Из-за `wrapping_add` и `u16` `end` переполняется, и запись с `offset=0xFFF0`, `len=16` проходит проверку, хотя фактически выходит за `buf`.
Это даёт OOB overwrite в `handler` (fat pointer: data ptr + vtable ptr) и позволяет переписать `vtable` на адрес из утёкших данных после декодирования.
ASLR/PIE включены, поэтому нужен leak ключа шифрования для расшифровки указателей.
## Решение
1. Сделать `HELLO`
- Отправить `0x10 HELLO`.
- Получить `nonce`.
2. Аутентифицироваться
- Отправить `0x11 AUTH` с токеном `nonce ^ 0xC0DEC0DEC0DEC0DE`.
- Получить обычный ACK.
3. Построить/подготовить session (`heap grooming`)
- `ALLOC` на 5 слотов (`count=5`).
- `FREE` одного слота (`idx=1`).
- `SELECT` слота с индексом 0 (`idx=0`).
4. Вытянуть замаскированные указатели через LEAK
- `LEAK` с `stage=0` -> получаем `masked_data` и `masked_vtable`.
- `LEAK` с `stage=1` -> получаем `key_hint`.
5. Вычислить XOR-ключ. Например, это можно сделать следующим образом:
```python
key = key_hint ^ (nonce + 0x9E3779B97F4A7C15)
key = ror(key, 11)
```
Где `ror` — циклический сдвиг вправо на 11 бит в 64-битах.
6. Расшифровать реальные поля:
```python
data = masked_data ^ key
vtable = masked_vtable ^ key
```
Тут `data` указывает на управляемый буфер, `vtable` — легитимный указатель на dispatch таблицу.
7. Записать OOB-переписыванием fat pointer
- Сформировать payload:
- `offset = 0xFFF0`
- `len = 16`
- `data = p64(data) + p64(vtable)`
- Отправить `0x30 WRITE` с `flags=1`.
За счёт переполнения `end` запись уходит в область после буфера и перезаписывает указатель обработчика на контролируемые значения.
8. Вызвать обработчик
- Отправить `0x31 TRIGGER`.
- Должен отработать изменённый callback.
9. Получить доступ к shell.