# 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 ][ 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.