mips 基础知识
再次感谢轩哥和轩哥媳妇送的cheatsheet
mips架构为RISC,常见的mips芯片流水线操作为五级
IF = instruction fetch 指令提取阶段
ID = Instruction decode 指令译码阶段
EX = 执行阶段
MEM = 存储器访问阶段
WB = 寄存器写回阶段
以i对应的时刻往后推移,最早的指令已经到了WB阶段,最新的指令正在进行指令提取。对于跳转/分支指令,当其到达执行阶段且新的程序计数器意已经产生时,紧随其后的下一条指令实际上已经开始执行了,mips规定分支之后的指令总是在分支目标指令之前进行,紧随分支指令之后的位置称为分支延迟槽,在没有任何可用操作时,延迟槽将填充nop占位。
关于调用约定,常用的mips寄存器作用如下
- “
$a0“
– “$a3“
:函数调用时的参数传递,若参数超过 4 个,则多余的使用堆栈传递- “
$t0“
-“$t7“
:临时寄存器- “
$s0“
– “$s7“
:保存寄存器,使用时需将用到的寄存器保存到堆栈- “
$gp“
:全局指针,用于取数据(32K访问内);“$sp“
:栈指针,指向栈顶- “
$fp“
:栈帧指针;- “
$ra“
:存储返回地址;
其他的参见最上面的图:-)
比较重要的一点是,当本函数是叶子函数的时候(即不会再调用其他函数),ra寄存器是不会入栈的;非叶子函数的时候,ra寄存器入栈,有可以通过栈溢出来劫持控制流的机会。
cisco路由器 RV110W 漏洞复现
题目固件:https://xuanxuanblingbling.github.io/assets/attachment/RV110W_FW_1.2.2.5.bin
虽然网上有docker环境可以用,但是还是建议买一个实体机,某鱼上有
基础分析
端口扫描,扫描常用端口,发现开了telnet,和web服务的端口,并且经过curl测试,80会重定向到443
固件解包,直接用binwalk,需要安装sasquatch以解开非标准的SquashFS文件系统
sudo apt-get install zlib1g-dev liblzma-dev liblzo2-dev |
漏洞信息
根据xuanxuan师傅的描述,当时考虑是1day,就开始搜cve
在符合赛题要求的前提下找到三个相关漏洞CVE-2020-3330(telnet弱口令),CVE-2020-3331 & CVE-2020-3323(Web服务)
telnet弱口令
可以看到打码并没有打全2333
全局搜索下这个字符串find . | xargs grep -ri "aUzX1IiE"
发现匹配的都在sbin目录下,并且大多软连接到rc文件
直接strings rc,网上随便找个在线的hash解密网站一把梭
然后就可以telnet到路由器了,注意,明确一点,虽然getshell,但是,这只是方便我们本地调试,远程并没有开telnet服务,只有443端口开了
漏洞挖掘
这两个cve都说的是web漏洞,所以我们要先找到web对应的binary
在浏览器端访问网址的时候,对应的url为10.10.10.1/login.cgi,所以全局搜索下
只有httpd匹配
因为发现文件系统中有wget可以用,也可以利用本机起一个简单的http-server,通过telnet到路由器下载一个包含netstat的busybox,不过懒,就没搞
然后将二进制程序载入ida,因为有新版本的固件,使用diaphora diff一下
因为目标是前台getshell,所以guest_logout_cgi比较可疑,查看伪代码如下
发现砍了sscanf
其中sscanf的条件"%[^;];%*[^=]=%[^\n]"
里,% 表示选择,%* 表示过滤,中括号括起来的是类似正则
%[^;]
:分号前的所有字符都要;%*[^=]
:分号后,等号前的字符都不要=%[^\n]
:等号后,换行符前的所有字符都要
也就是说,如果输入字符串”aaa;bbb=ccc”,会将aaa和ccc写入对应变量,并没有限制长度,会导致栈溢出
分析一下如何到达这个sscanf所在分支
- cmac:mac地址格式
- cip:ipv4地址格式
- submit_button:包含status_guestnet.asp字符串
继续测试漏洞,通过发包测试来判断参数是通过GET参数传递还是通过POST参数来传递的
Get.py
import requests |
Post.py
import requests |
经过测试发现是通过post来传递参数的(post发过去就崩了,就像平常的pwn程序崩溃一样)
然后具体调试下,本地开个服务器,路由器作客户端下载个gdbserver过来
感谢海特实验室搜集的各种gdbserver,感谢X1ng师傅试出来gdbserver-7.12-mipsel-mips32rel2-v1-sysv可以用
注意,下到tmp目录,其他目录不可写
通过ps查看httpd的进程号ps | grep httpd
,然后gdbserver挂一下
然后远程gdb调remote
gdb-multiarch -q httpd # quiet |
payload 用cyclic生成的字符一发打过去果然崩溃了
算下偏移,注意到pc指针现在的值
漏洞利用
mips架构硬件并不支持nx,所以利用方式通常为劫持程序流执行shellcode
由于sscanf栈溢出,所以不能有空字节,而程序本身的gadget都是有空字节的。。。
这时候自然想到用libc的gadget,但是,比较诡异的一点是,它的libc基址每次都不变:
思科的这个设备,httpd进程的libc基址就是
2af98000
,无论你是重启进程,还是升级版本,这个基址都不变问了常老师,再次猜测可能是为了效率,编译的时候就把内核的这个功能干掉了,或者当前平台压根就不支持这个功能。先存疑,总之我们发现动态库的基址都是不变的,故我们可以使用程序加载的动态库中的gadget。
彳亍,那就用mipsrop来找gadget,虽然有很多动态库,但是选libc比较好,用ida加载/lib/libc.so.0
mipsrop.help()可查看用法
因为要控制register,所以找能将栈地址加载进寄存器的gadget
两条很有用的gadget
| 0x000257A0 | addiu $a0,$sp,0x58+var_40 | jalr $s0 | |
利用手法:将shellcode写入到$sp + 0x10的位置处,然后覆盖s0为第二条gadget的地址,将ra寄存器覆盖为第一条gadget的地址
这样会造成什么效果呢?程序返回时,程序执行流被控制为0x257a0,去执行第一条gadget,a0 = sp + 0x18,jmp到s0寄存器,s0寄存器存的是第二条gadget,继而去执行第二条gadget,将a0放到t9,然后jmp到a0,a0存的是shellcode的地址,于是程序就会执行shellcode
shellcode生成用msfvenom,生成的shellcode没有\x00字节的存在,生成一个回连的shell
exp如下:
import requests |
Reference
https://xuanxuanblingbling.github.io/iot/2020/10/26/rv110w/
https://www.anquanke.com/post/id/224301#h3-7
https://github.com/firmianay/IoT-vulhub/tree/master/Cisco/CVE-2020-3331
https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/mips/rop/