思科RV110W路由器0day漏洞分析及利用报告

思科RV110W是一款家用路由器,其固件版本为1.2.1.7,前段时间和同事对该路由器进行模糊测试时发现了一个令其崩溃的poc脚本,一直没时间分析,最近终于静下心来好好分析一下,这也是第一次分析MIPS架构的漏洞,简单记录一下,供大家参考。

首先,拆开RV110W,发现其串口接口直接有标示,利用电烙铁焊接后,效果如下图所示。图中黄色圈中部分即为焊接的UART接口。

将导出的数据线连接到U转串工具上,只需连接GND、RX、TX三个即可,注意路由器的RX线和U转串工具的TX线相连,路由器的TX线和U转串工具的RX线相连,然后将U转串工具插入到笔记本的usb接口上,让其连接到Ubuntu虚拟机中,如下图中发现“ttyACM0”,说明已正常连接。

然后命令行执行“sudo minicom -s”,将串口设备设置为“ttyACM0”,保存并退出(其他数据如速率、校验方式等一般不用修改,若下面连接不成功再修改相关参数)。再在命令行执行“sudo minicom”,变为等待连接窗口,将路由器上电开启,正常情况下,该窗口中将出现路由器的启动信息,部分内容如下图所示。

最终路由器启动后,可得到shell窗口,如下图所示。

使用top命令,可以看到路由器中启动的进程,如下图所示。

为了能够远程调试httpd进程,需要将编译生成的运行于mips平台下的gdbserver复制进路由器中。使用网线将笔记本与路由器相连,保证虚拟机与路由器能够ping通。在虚拟机中打开shell,将路径切换到gdbserver所在目录,然后执行“python -m SimpleHTTPServer 80”即可打开一个简单的http环境,在路由器shell中切换到/data目录,然后执行“wget http://虚拟机ip/gdbserver”即可将gdbserver下载到/data目录中。从上图可以看到,其存在两个httpd进程,pid分别是356和348,通过执行样本前后top命令结果变化,发现崩溃的是pid为356的进程。故在路由器shell中执行“./gdbserver 192.168.1.1:4444 –attach 356”(192.168.1.1是路由器ip地址),然后在虚拟机shell中执行“./mipsel-linux-gdb”(这是我编译生成的gdb的名称),再执行“target remote 192.168.1.1:4444”,若一切正常,即可连接成功并中断下来,效果如下图所示。在调试过程中,经常由于“SIGPIPE”信号中断,为了去除该信号的干扰,可在gdb中首先执行”handle SIGPIPE nostop print”即可,然后输入“c”回车让程序继续执行。

由于崩溃样本需要已登录管理员的session id方可,故需要首先在ie中登录其管理界面,记录下session id,然后将poc.py中的session id更改为记录下的值(一共两处,只需修改后一处),然后在命令行下执行”python poc.py”,poc.py中关键部分在于发送的post请求中设置了“name0:‘AAAA’1000”。观察gdb调试界面如下图所示,可以发现,程序发生段错误而中断。

此时,查看pc指向的指令,并查看寄存器的值,结果如下图所示。

程序停止的位置为0x2afc57c4,通过查看路由器中“/proc/356/maps”文件,可以发现该地址位于动态链接库libc.so中,到思科官网下载该版本(1.2.1.7)的固件,利用binwalk提取固件中的文件系统,可在/lib目录中发现该动态链接库,将其加载到ida中,ida反汇编后为相对地址,故需要将0x2afc57c4转换为相对地址,通过“/proc/356/maps”发现该动态链接库加载的内存地址为0x2af98000,故其相对地址为0x2afc57c4-0x2af98000=0x2d7c4,在ida中跳转到该地址,结果如下图所示。

可以看出,该地址位于memcpy函数中,根据mips函数调用的传参规则,可知a0,a1,a2三个寄存器为memcpy调用的三个参数,查看a1地址对应的字符串,如下图所示。

在ida下加载httpd程序(位于前面固件提取目录/usr/sbin下),查看字符串窗口,查找“name%d”字符串,结果如下图所示。

可以看到,该字符串共有三处引用,结合poc.py中的http请求头部“Referer”域的信息,推测是第二个引用处发生错误,定位该地址为0x458070,如下图所示。

重新启动路由器,按前面步骤打开gdbserver,并将gdb连接到gdbserver上,然后执行
“b
0x458070”下断点,然后继续登录路由器管理界面,将session id 记录下,修改poc.py中session id的值,重新执行“Python poc.py”,中断在断点处,查看寄存器的值,a0不等于0x41414141,继续执行,再次中断于断点,查看寄存器的值,a0的值为0x41414141,如下图所示。

从前面图中0x458060处指令可以看出,a0寄存器的值来自于($sp+0x280-0x40),仔细检查$sp上面的值,最终可以发现从地址($sp+0x170)处覆盖为poc.py中的“AAAA”1000,如下图所示。

而前面a0的值来自地址($sp+0x280-0x40),显然在此范围内,从而造成后面执行memcpy时出现错误,下一步就是研究如何控制pc的值。观察图12中从0x458060开始一直到0x458294处的”jr ra”指令(同时可发现ra的值来自于*($sp+x0280-4),显然可以通过覆盖控制ra的值),这中间的指令多次调用了snprintf函数,并多次利用了($sp+0x170)以上空间的值,分别为var_3c、var_40、var_4c、var_48、var 40、var_44、var_30、var_38,故溢出时尽量不重写这些位置的值(另一种思路是研究如何设定溢出覆盖的值,让程序提前跳转到结束位置,这里我采用了比较笨的方法,但后面利用时发现可行),在正常中断于0x458070地址时,查看这些位置的值,如下图所示。

故重新改写poc.py处的溢出字符串,如下图所示。

重新启动路由器,打开gdbserver,并将gdb连接到gdbserver上,登录路由器管理界面,将session id 记录下,修改poc.py中session id的值,并将溢出字符串修改为图16中的值,重新执行“Python poc.py”,结果如图17所示。可以看到,成功将pc寄存器覆盖为0x42424242(即“BBBB”)。

下面我们展示一下利用该漏洞实现路由器重启,首先ida加载libc.so,发现system函数的相对虚拟地址为 0x4c7e0,转换为内存地址为0x2afe47e0,重启的命令为“reboot”,故调用时需要将“reboot”字符串写入栈上,并将其地址写入a0寄存器中。在ida中执行“mips rop gadgets”选项,使该插件(插件安装方式可见《揭秘家用路由器…》一书52页)初始化,然后在ida下方文本框中输入命令“mipsrop.stackfinders()”,显示libc.so中所有把堆栈数据放入寄存器的命令,部分结果如下图所示。

通过观察筛选,发现地址0x250a8处指令非常符合我们的需求,如下图所示。该地址对应的内存地址为0x2AFBD0A8。

在本次利用中,可以将“reboot”字符串放入($sp+0x2a0-0x278)的栈地址上,将$fp寄存器的值覆盖为“system”函数的地址即可,此时编写的溢出字符串如下图所示。

实际测试了一下,漏洞利用脚本能够实现路由器的自动重启,但个别情况利用不成功,下一步有时间再仔细分析一下如何稳定利用该漏洞。

该漏洞需要获得管理员的session id方能利用,故不能直接远程利用,可考虑结合XSS漏洞等一起实现远程对路由器的控制等。分析了一下发生溢出时的函数调用,位于httpd地址0x457f3c处的sprinf调用,此时,函数参数分别为a0=0x7fea20f0(=$sp+0x160),a1指向”%s,%s,%s,%s,%s,%s”字符串,a2=0x4d7c74,a3=0x4d7e27,查看a2、a3相关内容如下图所示。

从上图可以看出,让图中任意一个字符串超长都可实现溢出,又修改了一下poc.py,使得上图中“tcp”字符串变为“A”*1000,原来溢出的字符串修改为“AAAA”,重新执行一下,发现路由器的web服务果然也崩溃了。

第一次分析自己和同事一起发现的路由器0day漏洞,可能分析的还不够清晰完整,但个人感觉收获很大,漏洞利用成功那一刻也很有成就感啊!

(完)