基于静态分析的路由器固件二进制漏洞RCE技术经验分享
xubeining IoT安全 1615浏览 · 2025-04-09 07:32

前言:

本篇文章主要是记录自己在路由器二进制漏洞挖掘过程中的进步,在此之前曾经发布过第一篇文章,这篇是记录拒绝服务的。

[原创]基于静态分析的路由器固件二进制漏洞挖掘经验分享-二进制漏洞-看雪-安全社区|安全招聘|kanxue.com

从打出拒绝服务到稳定打出rce大概花了半个月左右的时间,也是自己成功交了几个rce的漏洞,本篇文章主要记录了常用rce的技术以及在rce的过程中每一处细节和出现的一些问题,分为分析前人漏洞,分析自己遇到的有价值的漏洞,平时遇到的一些问题以及总结与对下一阶段的规划进行叙述。

第一步,吸取前人经验:推荐一个篇文章,然后复现一个简单的漏洞



推荐大家看一下吾爱破解上的一篇文章,很出名的cve-2018-5767



https://www.52pojie.cn/thread-1674625-1-1.html



但是由于这个漏洞有人很详细的讲述了,并且不适合新手学习,下面我在相同固件上找到了另一个栈溢出漏洞,这个漏洞也有人交过了,但是我以自己的方式给大家打一遍,梳理一下流程,这个漏洞涵盖了最基础的rce攻击方式,作为本篇文章的基础进行讲解。

挖漏洞前的准备工作

binwalk的安装我就不另外赘述了,但是下面要安装一个非常重要的东西:



Plain Text
复制代码
这条指令的主要作用是安装 qemu-user-static 工具以及 ARM 架构的 GNU C 库及其开发包,从而让你能够在 x86 架构的主机上运行和开发 ARM 架构的程序,qemu我就不另外说明了,但是如果不安装arm的libc库,那么在进行gdb调试时会出现输入continue命令后程序直接退出的情况。

下一步就是使用binwalk解包,解包后,我们进入文件夹

image.png
图片加载失败




这里有个细节,我们会发现我们binwalk解压后的文件是锁上的,我们进入这个文件夹后,运行命令

这样子可以给这个文件夹加上写的权限,方便我们待会进行httpd的替换,如果命令运行成功了,文件夹上的锁就会消失。







image.png


里面有个叫squashfs-root的文件夹,打开文件夹后就是我们工作的目录,我们漏洞挖掘的对象是bin文件夹中的httpd文件,所以我们需要去对该文件进行分析,但是我们先进行路由器的仿真

image.png
图片加载失败


接着我们尝试仿真,首先建立虚拟网桥

然后将qemu-arm-static复制到当前目录,并且启动qemu仿真

image.png
图片加载失败


这个时候猜测是httpd文件出了问题,我们把这个文件拖到windows桌面,打开IDA进行分析。

image.png




我们看到开始运行后有个WeLoveLinux的字符串,我们(shift+F12)搜索这个字符串:



image.png
图片加载失败


点击字符串进入它的内存页面

image.png
图片加载失败


然后看看哪个地方调用了这个字符串,查看交叉引用

image.png
图片加载失败


进入这个调用的函数,F5反汇编后



image.png
图片加载失败


发现这里面有一个死循环以及一个判断语句



image.png
图片加载失败




而我们看到if语句的else块中正好有connect cfm failed!的字符串,而且



我们需要把这个地方patch掉,首先第一个地方



image.png
图片加载失败




可以看到第一个循环在这里,我们希望能够让这个循环永远不执行



image.png
图片加载失败




发现这里有个MOV R3,R0,然后跳转的条件是BGT,也就是



而BGT的意思,GPT 的解释是



image.png
图片加载失败




我们这里是希望它跳转的,所以需要让这个BGT 成为百分百跳转的语句,一个方法很简单,就是把MOV R3,R0修改成为MOV R3,#1,这样就可以使得我们的while循环永远不会执行。



我们这边直接去对汇编语言进行修改(尽量不让小白去乱安装插件),而且汇编的修改方式也特别简单。



image.png
图片加载失败




点击要修改的语句,然后找到如图所示的这个地方,点击进入。



image.png
图片加载失败




然后具体修改方式如下:首先看到E1,在ARM机器码中E1代表寄存器,而我们要改成立即数,那么就是E3,而第一位00则是第0号的意思,我们需要的是第一号立即数也就是1,那么就把第一位改成1,这样我们就完成了patch的任务了。



image.png
图片加载失败




这样,1永远大于0,所以每次启动的时候BGT都一定会跳转。



包括if语句哪个地方也是一样的patch方式



image.png
图片加载失败




这个时候我们再看F5的结果



image.png
图片加载失败




我们发现循环和if语句都没了,这个时候就完成了patch。



然后我们需要保存patch结果



image.png
图片加载失败




直接按照上图点击相应位置,直接保存在当前文件就行



image.png
图片加载失败




下方output出现这个信息就说明patch成功



image.png
图片加载失败




最后我们去虚拟机上替换我们的httpd文件,然后运行 ,发现还是有问题



image.png
图片加载失败




没有执行权限,这个对于大家应该都很简单,但是本着对新手友好的理念,这边还是说一下,直接执行命令



就可以了



然后再运行



image.png
图片加载失败




发现成功了。

这个时候漏洞的准备工作就做好啦。

下面进行漏洞复现和调试

漏洞的复现和调试(这个漏洞我这边用的是栈溢出的方式打出来的,我记得是不是还有别的方式)



漏洞证明

首先看到我们的漏洞点,位于formexeCommand函数

image.png
图片加载失败


src接受了httpPOST请求的cmdinput参数,然后把src这个变量里的值copy给了栈上的s变量,导致栈溢出。

下面编写验证脚本,我们向cmdinput参数传递1000个a,在用户级模拟下对路由器仿真,然后发送脚本看看效果。

image.png
图片加载失败


运行脚本,发现路由器死机,并且网站也无法访问,知道出现了栈溢出。

image.png
图片加载失败


这里报了段错误,segmentation fault,这一步的目的是证明这里有漏洞,下面对漏洞进行利用,这个时候这个漏洞的价值就是中危,其实也就相当于没啥价值



漏洞利用

下面我们对这个栈溢出进行利用,打出rce。

首先我们需要做的就是计算缓冲区离栈底的位置,这边我们使用pwn中的一个技巧,就是利用pwntools里面的cyclic去进行计算,利用cyclic生成1000个字符。

image.png
图片加载失败


然后把生成的字符复制到我们的payload里面

image.png
图片加载失败


接下来我们就需要进行调试啦,调试所用的工具就是gdb-multiarch ,这个可以调试arm架构的程序,当然mips也是可以的。

然后我们在启动qemu的时候多加一个-g参数,设置端口

image.png


然后我们回车后,终端就会卡在那里,这就说明我们成功了

image.png


然后启动gdb-multiarch,后面跟上httpd的地址

image.png


回车后看到这个界面就对了

image.png
图片加载失败


然后我们直接输入target remote : 9999,回车后会出现以下界面

image.png
图片加载失败


这就说明我们成功了。

然后我们需要在formexeCommand函数结束的地方下一个断点,找到函数结束的地方

image.png
图片加载失败


这里会有一个end of的标识,然后复制这里的地址,在gdb里下断点

image.png
图片加载失败


成功断下来后,输入c,continue

image.png
图片加载失败


这个时候你会发现qemu也开始运行。然后gdb卡在了continuing,这是正确的。

接着就发送你填充了cyclic 1000的payload。

image.png
图片加载失败


然后python的脚本会卡在这边,并且程序成功在formexeCommand函数结束的地方断了下来。

image.png
图片加载失败


然后接下来我们要看返回地址被填充成了什么,首先我们需要知道的就是返回地址究竟是存在哪里的。

我们输入ni继续往下执行一步,可以看到pc寄存器变成了xaah,这里就可以知道我们的返回地址在xaah这个地方



image.png


发现cyclic中xaah的值是792,也就是说我们需要填充792个字节,但是还需要再加上4个字节,因为有四字节的栈帧,也就是填充796的字节数。

其实这里有个小技巧,就是可以直接看到r11寄存器那里,r11寄存器里面存的是yaah,放到cyclic里面看一下就知道这个字符串正好是796。这个并不是个例,而是我目前为止看到的大部分路由器都是这样的。

我们填充后就开始构造ROP去进行利用,我们需要找两个语句,一个是用于控制r0的,一个是pop到r3的。r0和r3主要是用于传递函数参数,也就是我们所需要的system函数的参数的,请出万能的GPT老师

image.png
图片加载失败


首先我们找关于r0的

image.png
图片加载失败
可以看到有一堆,我们找一个比较短的,可以看到0x00040cb8这条语句很符合

image.png


接下来找pop r3的

image.png


可以看到0x00018298这个地方的这条语句特别符合我们的需求。



接下来有了这两条指令的地址,还是不够,我们需要去找到libc的基地址才行,这个也很简单,我们让程序在formexeCommand函数的开头断下来,我这边是在开头后执行的几条语句内断下来,这样可以避免函数没有成功在断点处停止。我们需要打开gdb进行调试,这个和上面讲过的一样,不过多赘述

image.png
图片加载失败


我们选择在这边断下来

image.png
图片加载失败


在gdb内下断点后执行

image.png
图片加载失败


然后发送我们的payload,函数成功在断点处中断

image.png
图片加载失败


下面使用vmmap查看内存

image.png
图片加载失败


可以看到libc的基地址就是0xf65e5000

image.png


现在有了基地址后就可以找到libc库中的函数的地址以及操控r0和r3的两条语句了。

这里我们程序的行为是调用puts函数打印了一个字符串,这里栈为何这么排布,以及pwntools咋去用可以去查资料,属于pwn的基础内容,不过多赘述。

效果

下面看一下成功的效果,发送payload

image.png
图片加载失败


image.png
图片加载失败


程序退出,并且打印了一个helloworld的字符串,利用成功。

image.png


能用puts就能用别的函数。

但是这里有一点就是,如果想使用反弹shell的方式,这个可能在用户级模拟上比较难,因为qemu用户级仿真会找不到/bin/sh,各位师傅可以私下研究一下。



一个特殊的rce漏洞

漏洞的分析:

这个rce漏洞是我自己挖的,但是是一个比较特殊的漏洞,这个漏洞并不属于栈溢出,因为在rce的点甚至没有溢出缓冲区,下面跟着我进行分析,有一些重复的步骤比如仿真之类的就不提了。

首先我们看一下漏洞点

image.png
图片加载失败


漏洞点在13和14行

image.png
图片加载失败


首先验证payload可行。

用cyclic 500填充,然后开启gdb仿真,并且在函数末尾加断点,continue后发送payload,这个和上面一样的,不过多赘述。

发送payload后,

image.png
图片加载失败


程序在这边发生了错误,我们搜一下ldrb是什么

image.png
图片加载失败


GPT说的是8位,但是我们是32位的arm架构,所以加载的是四位的内存,但是无论如何都一定要是内存,而r3和r1都不是地址,我们发现这两个寄存器里都是字符串

image.png
图片加载失败


是cjaa,我们就需要把这里填充成真正的地址

image.png
图片加载失败


发现需要填充235的字节后,填充一个地址,再填充后面的字节。

我们这边随便找一个地址,就以整个程序开头的地址,也就是打印welovelinux那个程序的地址为准(因为这是一个合格的地址,而且如果真的执行成功可以看见程序多打印了一个welovelinux)

image.png
图片加载失败


而地址就是0x0064920,第二次测试代码如下,然后继续按照原有的步骤进行调试分析









可以看到这一次程序直接报了栈溢出错误

image.png
图片加载失败


而且发现PC寄存器被一些奇怪的字符串填充了,其实这里就是可以操控程序执行流的地方,而且这个字符串是残缺的,第一个字符被隐去了,按照别的师傅说,这是因为arm模式与thumb模式的切换问题。所以我们最后一个位置只能靠猜了

但是我们发现sp寄存器的值是bbaa,我们放在cyclic里面,发现是103

image.png




然后我们就开始猜第一个字符,从a开始

image.png
图片加载失败


可以发现,azaa与bbaa隔得最近,我们就先尝试99,修改payload

仿真路由器,运行脚本,我们发现精彩的一目出现了

image.png
图片加载失败


成功二次打印WeLoveLinux,证明这个地方是存在一个跳转漏洞的,但是这个应该不属于栈溢出,因为可以看到我的缓冲区有256字节,但是我填充了99字节就打出了rce,下面对漏洞进行利用,利用脚本的编写和之前一样,就跳过了,payload如下

运行后效果如下:

image.png
图片加载失败


可以看到打印了一串RCERCERCE

成功执行命令。

反思

我们再漏洞攻击的时候,一定要时刻关注程序的执行流,我们要精确控制程序的执行流,这个就需要我们细心地一个个去进行拆分,对整个程序的行为进行拆分,而且在进行分析时胆子要打,抱着“搞一下就知道”的心态去打漏洞,这样子才能有所提升。



平时出现的一些问题

IDA出现返回地址不能正确显示的问题

很多小伙伴在进行动态调试的时候进程喜欢利用IDA,因为在打出栈溢出的时候IDA可以显示出现问题的地址(也就是cyclic对应的字符串),从而算出缓冲区实际大小

但是有的时候会出现这种

image.png
图片加载失败


就是明明溢出了,但是就是没有显示出字符串。



这个问题的原因是,其实程序的执行流根本没有在漏洞点的函数里返回,而是被别的函数干扰了,就和我的第二个例子一样,真正造成程序崩溃的点不在返回地址的溢出上,而是某个语句加载了错误的地址,这个时候做法可以参照第二个例子去做。

gdb发送payload脚本后就中断的情况

image.png
图片加载失败


发送脚本后,并没有显示堆栈信息,而是中断,这可能是因为你没有下载arm的libc库或者mips的libc库,回到文章开头有解决这个问题,或者也有可能是gdb的架构没有设置正确,只需要把gdb的架构正确设置即可。

总结与规划

本篇文章还是比较长的,说了这么多总结一下,可以说这段时间研究rce给我的感受最大的就是要细心,缓冲区离栈底的距离,各种libc基地址的计算,rop语句的查找都要非常细心地去找。

对下一阶段的规划是:

1重点补一下IOT的基础,并且眼光不仅仅放在路由器,可以去尝试别的设备

2研究堆漏洞的利用。





3 条评论
某人
表情
可输入 255
用户RdvYEjQVSf
2025-04-11 12:56 0 回复
受益匪浅!
王*阳
2025-04-11 12:30 0 回复
非常好的文章,学习了~~~
用户JggLPqF4ts
2025-04-11 12:05 1 回复
非常深入