1. 项目概述:从一台闲置路由器开始的探索
手头有一台几年前买的某米路由器,型号是R3G,已经吃灰很久了。最近整理东西翻出来,想着与其让它彻底报废,不如拿来练练手,看看能不能挖出点有意思的东西。这其实也是很多安全研究员和爱好者的一个常见起点:利用手边退役或闲置的设备,进行安全研究和漏洞挖掘实战。家用路由器,作为家庭网络的入口和核心,其安全性至关重要。一旦被攻破,意味着家庭内所有联网设备都可能暴露在风险之下。因此,对路由器进行安全分析,不仅是一次技术挑战,更是对“边界安全”最直接的实践。
这次分享的目标很明确,就是针对这台某米路由器R3G,进行一次从信息收集到漏洞验证的完整流程复现。我不会涉及任何高深的、未公开的0day挖掘,而是聚焦于一个安全研究者(或者说“脚本小子”进阶之路)的常规操作:固件提取、逆向分析、模拟调试、寻找潜在漏洞点。整个过程更像是一次“考古”和“解构”,我们将像拆解一个黑盒一样,一步步揭开这台路由器的内部构造,并尝试理解其中可能存在的安全隐患。无论你是对嵌入式安全感兴趣的新手,还是想了解硬件漏洞挖掘流程的开发者,希望这篇基于真实设备的长文能给你带来一些实用的思路和可复现的步骤。
2. 前期准备与固件获取
工欲善其事,必先利其器。在开始对路由器“动手”之前,我们需要搭建一个适合的分析环境,并想方设法拿到路由器的“灵魂”——它的固件。
2.1 分析环境搭建
我的主力分析环境是一台Ubuntu 22.04的虚拟机,分配了足够的CPU和内存(建议4核8G以上)。为什么用Linux?因为后续的很多工具链在Linux上运行最为顺畅。以下是核心工具的安装清单:
- 固件提取与分析工具:
binwalk:用于固件解包的神器,能自动识别并提取固件中的文件系统、内核等。firmware-mod-kit:一套固件修改工具包,有时用于重组固件。file命令:基础但重要,用于识别文件类型。
- 逆向工程工具:
IDA Pro或Ghidra:静态反汇编和分析二进制文件。Ghidra是NSA开源的工具,功能强大且免费,是我的首选。radare2:另一个强大的开源逆向工程框架,命令行操作,非常灵活。
- 模拟调试环境:
qemu-system:全系统模拟,可以模拟运行整个固件,包括内核和用户态程序。对于MIPS架构的路由器,需要安装qemu-system-mips等包。qemu-user-static:用户态模拟,用于在x86主机上直接运行MIPS/ARM架构的二进制程序,配合chroot使用,效率更高。firmadyne:一个自动化的固件模拟框架,但配置稍复杂,我们后续会用手动方式达到类似目的。
- 网络与调试工具:
gdb-multiarch:支持多架构的GNU调试器,配合QEMU使用进行动态调试。strace/ltrace:跟踪系统调用和库函数调用,理解程序行为。netcat,tcpdump,nmap:基本的网络探测和抓包工具。
安装这些工具在Ubuntu下非常方便,基本上通过apt-get install即可搞定大部分。例如:sudo apt-get install binwalk qemu-user-static gdb-multiarch strace ltrace netcat tcpdump nmap。Ghidra则需要去官网下载独立安装包。
2.2 固件获取的多种途径
对于某米路由器,获取固件有几种常见方法,我尝试了其中两种:
途径一:官方渠道下载这是最直接、最合法的方式。访问某米路由器的官方支持页面,查找对应型号(R3G)的历史固件版本。通常厂商会提供.bin格式的固件文件用于手动升级。我成功下载到了一个名为miwifi_r3g_firmware_2.28.xx.bin的文件。这种官方固件是进行漏洞挖掘的主要对象,因为它代表了设备实际运行的代码。
注意:务必下载与你设备硬件版本匹配的固件。不同版本间硬件可能有差异,固件不通用。同时,建议下载多个历史版本,有时旧版本漏洞更多,分析起来也更有代表性。
途径二:从设备本身提取如果无法从官网下载,或者想分析设备当前运行的、可能被修改过的固件,就需要从设备上提取。这通常需要一些硬件接口或利用已有的软件漏洞。对于某米R3G,一种常见的方法是通过设备的调试接口(如UART串口)。你需要拆开路由器,找到主板上的UART引脚(通常是TX、RX、GND,有时还有VCC),用USB转TTL模块连接电脑,使用串口终端工具(如minicom或screen)连接。如果运气好,引导程序(bootloader,如U-Boot)没有禁用交互,或者系统启动了串口登录,你就可以获取一个shell。在shell中,可以使用dd命令将整个MTD(Memory Technology Device)分区(如mtd0代表整个Flash)备份出来,或者直接备份/dev/mtdblockX。
例如,在获取shell后:
cat /proc/mtd # 查看MTD分区信息 dd if=/dev/mtdblock0 of=/tmp/full_flash.bin # 备份整个Flash(需注意空间)然后通过TFTP、SCP或者甚至base64编码后分段echo出来等方式,将备份文件传回分析主机。这个过程需要对Linux系统和路由器架构有一定了解,并且存在变砖风险,操作需谨慎。
我这次主要使用从官网下载的官方固件进行分析,因为它更干净、更标准,适合作为分析起点。
3. 固件解包与初步分析
拿到miwifi_r3g_firmware_2.28.xx.bin这个文件后,第一步就是“拆开”它,看看里面到底装了些什么。
3.1 使用Binwalk进行解包
binwalk是固件分析的第一步,也是最自动化的一步。在终端中执行:
binwalk miwifi_r3g_firmware_2.28.xx.bin输出结果会显示这个二进制文件中嵌入的所有可识别部分。典型的输出可能包含:
TRX firmware header:一种常见的路由器固件封装格式。LZMA compressed data:压缩的内核或文件系统。Squashfs filesystem:只读压缩文件系统,通常包含路由器的操作系统、Web管理界面、各种服务程序等。这是我们的主要目标。
接下来,使用binwalk的提取功能:
binwalk -e miwifi_r3g_firmware_2.28.xx.bin-e参数代表提取(extract)。命令执行后,会在当前目录生成一个_miwifi_r3g_firmware_2.28.xx.bin.extracted的文件夹。进入这个文件夹,你就能看到被提取出来的内容。通常,你会找到类似0.trx、120.squashfs这样的文件。120.squashfs就是包含根文件系统的Squashfs镜像。
3.2 挂载与探索文件系统
Squashfs是只读的,我们需要将其挂载到本地目录进行浏览。首先安装支持工具:sudo apt-get install squashfs-tools。然后创建挂载点并挂载:
mkdir squashfs-root sudo mount -t squashfs -o loop 120.squashfs squashfs-root/现在,进入squashfs-root目录,你就仿佛进入了路由器的根文件系统。可以开始探索了。
关键目录与文件:
/bin,/sbin,/usr/bin,/usr/sbin:存放可执行程序,包括busybox(嵌入式Linux的瑞士军刀)和各种守护进程(如httpd、telnetd、dropbear等)。/etc:配置文件目录。重点关注/etc/config/(OpenWrt类系统的配置)、/etc/init.d/(启动脚本)、/etc/passwd和/etc/shadow(用户和密码哈希,但家用路由器通常使用硬编码或动态生成)。/www或/web:Web管理界面的文件(CGI脚本、HTML、JS)。这是漏洞的高发区,因为它是面向外部的接口。/lib:共享库文件。分析程序依赖哪些库,库中是否有已知漏洞函数(如strcpy,sprintf等),是快速寻找漏洞点的方法。/proc和/sys:在真实系统或模拟环境中运行时才有内容,是内核信息的接口。
在探索过程中,我特别关注了/www目录,因为某米的Web管理界面功能丰富,交互复杂,是潜在的攻击面。果然,在/www/cgi-bin/目录下发现了多个CGI可执行文件,例如luci、api等。这些就是处理Web请求的后端程序。
3.3 识别目标与架构信息
使用file命令查看关键二进制文件的架构:
file squashfs-root/bin/busybox file squashfs-root/www/cgi-bin/luci输出显示为“ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked...”。这确认了该路由器使用MIPS架构。这对于后续的模拟和调试至关重要,因为我们需要对应架构的QEMU和工具链。
同时,查看/etc/os-release或/proc/version的模拟内容(如果有),可以了解系统是基于OpenWrt还是其他定制系统。某米路由器大多基于OpenWrt进行深度定制。
4. 建立模拟调试环境
静态分析固件文件能发现很多信息,但要真正理解程序逻辑、验证漏洞,动态调试是必不可少的。我们需要让这个MIPS架构的固件在我们的x86电脑上“跑起来”。
4.1 使用QEMU进行用户态模拟
全系统模拟(qemu-system-mips)比较重,启动慢,且需要适配内核。对于分析单个用户态程序(如Web CGI),用户态模拟(qemu-user-static)更加高效便捷。
步骤一:准备根文件系统我们已经有了squashfs-root,但它可能缺少一些运行时所需的设备节点。我们可以复制一份作为模拟环境的基础:
cp -r squashfs-root ./qemu-rootfs cd qemu-rootfs步骤二:使用chroot与qemu-user-static关键是要将qemu-mips-static这个解释器拷贝到目标文件系统中,并利用chroot“切换根目录”到该文件系统。
# 首先,将宿主机的qemu-mips-static拷贝到目标根文件系统 sudo cp $(which qemu-mips-static) ./qemu-mips-static # 然后,使用chroot并指定qemu解释器来运行目标shell sudo chroot . ./qemu-mips-static /bin/sh如果一切顺利,你会得到一个#或$提示符,这已经是在模拟的MIPS环境中了!你可以执行ls、ps等基本命令。但此时网络、/proc、/sys等可能还不完善。
步骤三:配置网络与必要目录为了让Web服务能运行,我们需要在chroot环境中挂载一些虚拟文件系统,并设置网络。这通常在chroot之前完成:
cd qemu-rootfs sudo mount -t proc /proc proc/ sudo mount -t sysfs /sys sys/ sudo mount -o bind /dev dev/ # 如果需要网络,可以设置一个TAP设备并桥接,但初期调试可以不联网然后再次执行sudo chroot . ./qemu-mips-static /bin/sh。
4.2 启动Web服务进行交互测试
在chroot环境中,尝试启动Web服务器。首先查看启动脚本:
cat /etc/init.d/httpd # 或 uhttpd, lighttpd,具体名称需查看找到启动命令,例如可能是/usr/sbin/uhttpd。尝试直接运行它,可能会因为缺少配置或环境而失败。需要根据错误信息调整,比如复制必要的配置文件到正确位置,或者设置环境变量(如PATH,LD_LIBRARY_PATH)。
一个更简单粗暴的方法是,直接运行我们感兴趣的CGI程序,并模拟HTTP请求。例如,我们可以写一个简单的C程序,使用execve调用CGI,并设置好环境变量REQUEST_METHOD,QUERY_STRING等来模拟GET/POST请求。或者,更直接地用echo通过管道传递数据:
echo "GET /cgi-bin/luci HTTP/1.0\r\n\r\n" | ./qemu-mips-static /www/cgi-bin/luci这可以测试CGI程序是否能被正常触发,以及是否有明显的崩溃。
4.3 使用GDB进行动态调试
动态调试是漏洞挖掘的核心。我们需要在QEMU用户态模拟中附加GDB。
步骤一:以调试模式启动程序在chroot环境外,使用qemu-mips-static的-g参数指定一个调试端口:
sudo chroot . ./qemu-mips-static -g 1234 /www/cgi-bin/luci这条命令会让luci这个CGI程序启动并暂停,等待GDB在端口1234上连接。
步骤二:使用GDB多架构调试打开另一个终端,启动gdb-multiarch:
gdb-multiarch (gdb) set architecture mips (gdb) target remote localhost:1234 (gdb) file ./qemu-rootfs/www/cgi-bin/luci # 加载带符号的二进制文件(如果有的话)连接成功后,你就可以像调试本地程序一样设置断点、单步执行、查看内存和寄存器了。例如,b *main在入口处断点,c继续运行。
实操心得:在MIPS架构下,函数调用的参数传递规则(前几个参数通过寄存器
$a0-$a3传递)和x86不同,需要熟悉MIPS汇编。Ghidra的反编译功能可以极大地帮助理解程序逻辑。将二进制文件导入Ghidra,进行初步分析,找到可疑函数(如处理HTTP参数、调用危险函数strcpy/sprintf的地方),记下其地址,然后在GDB中对这些地址下断点,进行动态跟踪,观察参数内容和缓冲区状态。
5. 漏洞挖掘实战:以Web CGI为例
有了模拟和调试环境,我们就可以开始有针对性的漏洞挖掘了。Web管理界面是路由器最暴露的攻击面,其中的CGI程序是重点目标。
5.1 攻击面枚举与危险函数定位
首先,对/www/cgi-bin/下的所有二进制文件进行初步筛查。使用file确认它们是MIPS ELF文件。然后,可以使用strings命令快速查看字符串,寻找可能包含参数名的字符串,如username、password、action、set等,这能提示我们程序的输入点。
更系统的方法是使用静态分析工具。将CGI程序加载到Ghidra中。在Ghidra中,我们可以:
- 搜索危险函数调用:在“Search > For Strings”或“Search > Program Text”中,搜索
strcpy,sprintf,strcat,gets,system,popen等不安全的C库函数。这些函数是缓冲区溢出和命令注入的典型源头。 - 分析
main函数或HTTP请求处理入口:找到程序入口,跟踪其如何解析QUERY_STRING或POST数据。通常,路由器CGI会使用getenv(“QUERY_STRING”)或从标准输入读取POST数据,然后使用strtok、sscanf或自定义函数进行解析。 - 关注字符串处理逻辑:仔细审查任何将用户输入复制到固定大小缓冲区的代码。注意数组或缓冲区的声明大小(在栈上或全局数据区),以及复制前是否进行了长度检查。
以我分析的luci程序为例,在Ghidra中搜索strcpy,发现了多处调用。其中一处位于一个名为parse_form_data的函数中。反编译代码显示,该函数从一个全局指针获取数据,并使用strcpy复制到一个局部数组,而该数组的大小是固定的(比如256字节)。如果源数据长度超过256字节,就会发生栈缓冲区溢出。
5.2 动态验证与POC构造
静态分析找到了可疑点,接下来需要用动态调试来验证。
- 定位函数地址:在Ghidra中,找到
parse_form_data函数的起始地址,例如0x00401234。 - 设置断点:在GDB中,连接到等待调试的
luci进程,然后设置断点:b *0x00401234。 - 构造输入:我们需要模拟一个HTTP请求来触发这个函数。编写一个简单的Python脚本,使用
socket或requests库向模拟环境(如果Web服务已启动)或直接向qemu-mips-static运行的进程发送HTTP请求。请求中需要包含超长的参数值。
如果直接通过管道测试,可以这样:# 示例:构造一个超长的“data”参数 import requests url = 'http://192.168.1.1/cgi-bin/luci' # 假设模拟环境IP是192.168.1.1 long_string = 'A' * 500 # 生成500个'A' data = {'action': 'set', 'data': long_string} response = requests.post(url, data=data) print(response.status_code)echo -e "POST /cgi-bin/luci HTTP/1.0\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 520\r\n\r\naction=set&data=$(python3 -c \"print('A'*500)\")" | sudo chroot . ./qemu-mips-static /www/cgi-bin/luci - 观察崩溃:发送请求后,在GDB中观察程序是否在
strcpy处崩溃。查看寄存器$pc(程序计数器)是否指向了异常地址,或者栈指针$sp是否被破坏。如果$pc被覆盖为0x41414141(‘A’的ASCII码是0x41),那就确认了存在栈溢出漏洞,并且可以控制执行流。
5.3 漏洞利用的挑战与思路
在路由器环境下,漏洞利用比在桌面系统上更具挑战性,主要原因有:
- DEP/NX(数据执行保护):现代路由器固件可能启用了NX位,使得栈上的数据不可执行。这意味着即使覆盖了返回地址,跳转到栈上也会导致崩溃。需要转向ROP(Return-Oriented Programming)技术。
- ASLR(地址空间布局随机化):内核和库的加载地址可能随机化,增加了定位gadget的难度。但很多嵌入式设备为了节省资源或兼容性,ASLR可能较弱甚至关闭。
- 架构差异:MIPS的指令集和内存模型与x86不同,ROP链的构造更复杂。例如,MIPS在函数调用时,返回地址保存在
$ra寄存器而非栈上,但某些情况下(如栈溢出覆盖了保存的寄存器区域)仍然可以劫持控制流。 - 权限限制:即使获得了代码执行能力,也可能只是在
www或nobody用户权限下,需要进一步提权到root。
初步利用思路:
- 信息泄露:首先需要绕过ASLR。可以寻找程序本身存在的内存信息泄露漏洞,比如一个格式化字符串漏洞,或者一个读取内存的CGI接口,来泄漏libc的基地址。
- ROP链构造:使用
ROPgadget这类工具针对目标二进制和libc库搜索可用的gadget。由于是MIPS架构,需要寻找如jalr $t9、lw $ra, X($sp);jr $ra这类能控制跳转的指令片段。 - 执行命令:最终目标是执行系统命令,比如启动telnet服务、下载并执行恶意程序。可以构造ROP链调用
system()函数,参数指向我们放置在内存中的命令字符串(如/bin/telnetd -l /bin/sh)。
注意事项:在实际漏洞挖掘中,从发现漏洞到完成利用是一个漫长且需要深厚功底的过程。对于初学者,能够通过动态调试确认漏洞的存在(使程序崩溃并控制
$pc或$ra),就已经是巨大的成功。这证明了漏洞的可利用性。完整的武器化利用(Weaponized Exploit)需要更多的研究和测试。
6. 其他攻击面与自动化辅助
除了Web CGI,路由器的攻击面还有很多:
- 其他网络服务:使用
netstat -an(在模拟环境中运行)或分析启动脚本,查看路由器还开放了哪些端口。常见的有:23端口(Telnet,可能被禁用)、22端口(SSH,如Dropbear)、53端口(DNS服务)、1900端口(UPnP)等。这些服务对应的二进制文件同样需要分析。 - 云管理功能:某米路由器通常有配套的手机App和云服务。这涉及到与云端通信的客户端程序,可能存在于固件中。分析其通信协议、认证逻辑,也可能发现漏洞。
- 硬件接口:如前所述的UART串口,如果未做保护,可以直接获取终端。JTAG接口更是能直接读写内存和Flash。
- 无线协议:Wi-Fi协议栈的实现(驱动和用户态工具)也可能存在漏洞,但分析难度更高。
为了提高效率,可以借助一些自动化工具进行初筛:
checksec:检查二进制文件的安全属性(NX, PIE, RELRO等)。ROPgadget/ropper:自动搜索ROP gadget。firmwalker:一个脚本,自动遍历提取出的文件系统,寻找敏感文件、密码、密钥、脚本等。- 基于符号执行或模糊测试的工具:如
AFL(American Fuzzy Lop)的QEMU模式,可以对CGI程序进行模糊测试,自动生成能触发崩溃的输入。但这需要将目标程序编译到支持AFL插桩,对于黑盒的固件二进制,需要利用afl-qemu模式,配置过程较为复杂。
7. 常见问题与排查技巧实录
在实际操作中,你会遇到各种各样的问题。这里记录了一些我踩过的坑和解决方法。
问题1:Binwalk提取失败或提取出的文件系统不完整。
- 原因:固件可能使用了非标准的封装格式、加密或自定义的压缩算法。
- 排查:
- 用
hexdump或xxd查看固件文件头尾,寻找特征魔术字。 - 尝试不同的提取工具或选项,如
binwalk -Me(递归提取)或firmware-mod-kit中的extract-firmware.sh。 - 手动搜索文件系统魔术字:
hexdump -C firmware.bin | grep -i 'sqsh'或... | grep -i 'hsqs'(Squashfs的反序魔数)。 - 如果怀疑加密,需要逆向分析升级程序或Bootloader,找到解密例程。
- 用
问题2:QEMU用户态模拟运行程序时,提示“找不到动态链接器”或“无法执行二进制文件”。
- 原因:
qemu-user-static需要与目标架构和动态链接器路径匹配。有时固件使用的动态链接器路径比较特殊(如/lib/ld-uClibc.so.0)。 - 排查:
file命令确认二进制架构是MIPS 32位小端(MIPSEL)。- 使用
readelf -l <binary>查看程序头(Program Headers),找到INTERP段指定的动态链接器路径。 - 在
chroot环境中,确保该路径下存在正确的动态链接器。如果没有,可能需要从固件其他位置复制或寻找兼容的版本。 - 直接指定链接器运行:
./qemu-mips-static -L /path/to/rootfs /path/to/binary,其中-L参数指定根文件系统路径。
问题3:在chroot的QEMU环境中,程序运行崩溃,错误信息模糊。
- 原因:缺少必要的环境变量、配置文件、设备节点或内核功能。
- 排查:
- 使用
strace:在宿主机上,用qemu-mips-static配合strace运行程序:sudo chroot . ./qemu-mips-static -strace /bin/ls 2>&1 | head -50。观察程序在哪个系统调用上失败(如open一个不存在的文件,ioctl调用失败)。 - 检查依赖:用
ldd命令(在模拟环境下)查看二进制缺失的库:./qemu-mips-static -L . /usr/bin/ldd /www/cgi-bin/luci。确保所有需要的.so文件都在/lib或/usr/lib下。 - 创建缺失的设备节点:有些程序需要
/dev/null,/dev/urandom等。在qemu-rootfs/dev/目录下使用sudo mknod创建它们。 - 简化环境:先尝试运行最简单的程序(如
/bin/busybox ls),确保基础环境没问题,再逐步排查目标程序。
- 使用
问题4:GDB连接QEMU后,设置断点无效,程序不中断。
- 原因:断点地址可能设置不正确(由于PIE或加载地址偏移),或者程序在GDB连接前已经执行过了断点。
- 排查:
- 在Ghidra中查看的地址通常是基于默认基址(如0x00400000)。但QEMU加载时,基址可能不同。在GDB连接后,使用
info files或info proc mappings查看程序段的实际加载地址。 - 使用带符号的地址:如果Ghidra分析出了函数名,在GDB中可以直接用函数名下断点,如
b parse_form_data,前提是正确加载了符号文件(编译时的调试信息,通常固件中不包含)。 - 在程序入口点下断点:
b *_start或b *main,确保在程序一开始就停住。
- 在Ghidra中查看的地址通常是基于默认基址(如0x00400000)。但QEMU加载时,基址可能不同。在GDB连接后,使用
问题5:发送超长HTTP请求测试时,程序没有崩溃,但返回了错误页面。
- 原因:可能存在输入长度检查,或者错误处理逻辑避免了崩溃。也可能是请求格式不对,未触发到有漏洞的代码路径。
- 排查:
- 静态分析更仔细:回到Ghidra,查看可疑函数附近是否有长度检查代码,如
strlen、if (len > sizeof(buf))等。 - 动态跟踪:在解析输入的函数开始处下断点,单步跟踪,观察我们的超长数据是否被正确传递到了危险的
strcpy函数,以及复制前缓冲区的状态。 - 尝试不同参数和请求方法:漏洞可能存在于特定的参数名(如
Cookie头)或特定的HTTP方法(GET/POST)处理中。参考其他路由器漏洞(如D-Link DIR-815的hedwig.cgi漏洞就是通过超长Cookie触发的),尝试构造类似的畸形请求。
- 静态分析更仔细:回到Ghidra,查看可疑函数附近是否有长度检查代码,如
漏洞挖掘是一个需要极大耐心和细致观察的过程。每一个异常现象背后都可能隐藏着一个漏洞,而每一个“失败”的测试都能帮助你更深入地理解目标程序。保持好奇心,多动手实践,从每一次崩溃和调试中积累经验,是提升技能的唯一途径。这台某米R3G路由器就像一座等待探索的微型城市,而你就是那位拿着放大镜和地图的探险家。