家用路由器固件常用解包方法 & D-Link DIR-878 BUGS

Catalpa 网络安全爱好者

DIR-878 是友讯科技生产的家用路由器。

家用路由器固件常用解包方法

某些厂商为了保护自己的产品,增加逆向分析的成本,通常会将路由器的固件进行加密,经过加密的固件使用一般的解包方法不能提取。要想分析这种固件,首先就要找到其对应的加密方法。

实际上固件加密并不是随随便便拿来一种算法就可以的,由于家用路由器机能有限,所以 RSA 等效率较低的加密体系用的比较少,AES 或者 RC4 这些效率高的算法通常是首选的固件加密手段。

家用路由器固件加密原理

当买回来一台路由器的时候,其内部的固件版本通常不是最新的,这时就需要进行固件升级,升级的原理很简单,从官方网站下载最新版本的固件,然后对固件进行检验,满足某些条件之后解压固件替换原始文件。

现在的问题是如果下载到的固件是加密的,路由器内部应该如何解密呢?

最简单的想法就是内部放置了对应的解密程序,可以先解密固件再执行正常的升级过程。这里就引出了家用路由器固件加密的几种常见形式:

第一种: 固件从设备推出就处于加密状态,在更换固件加密算法的时候会涉及到中间版本,更换逻辑如下:

第二种: 固件在出厂的时候由于某些原因没有采用加密算法,在中间某一个版本突然要增加固件加密:

还有一种更换逻辑是:

前序版本进行了加密,但中间版本提供了未加密的程序。

其中第一种方法解包比较困难,中间版本也是经过加密的,如果想要解密固件就必须先从真机上拿到解密之后的内容进行逆向分析,寻找固件加密方式。

其余两种就比较简单了,通过中间版本能直接拿到固件的解密程序或者算法,通过算法再去解密新的固件。

以我们今天的主角 DIR-878 为例,它所采用的是上述第三种迭代方法,即中间版本没有加密,可以透过 binwalk 直接获取固件。

首先从官网 ftp://ftp2.dlink.com/PRODUCTS/DIR-878/REVA/ 下载所有能看见的固件,因为我们不知道哪个版本是中间版本。

下载固件之后又一个判断中间版本的技巧,那就是利用 binwalk 的 -E 选项,大致原理就是一个系统越有序,其熵值就越低,经过加密的固件属于无序度很大的系统,所以 -E 的结果通常是一根直线。

不过对于 878 的固件不用那么麻烦,依次解压+binwalk 分析固件,最终在 1.10B05 这个压缩包中获取到了中间版本的固件:DIR878A1_FW104B05_Middle_FW_Unencrypt

binwalk 解包即可得到对应的文件系统。并且经过进一步查看,仅仅在这个版本更换了加密算法,所以最新版本的固件也可以利用此固件中的算法解密。

我们找到了中间版本,现在的问题是去哪里寻找解密程序?按照固件升级的思路,肯定有一个升级程序,在这个程序内部可能存在着加密算法,我们首要目的就是去寻找这个升级程序。

不过对于 878 这台路由器,我们的工作量可以大大缩减,我在翻找程序目录的时候,发现了一个名为 imgdecrypt 的二进制文件,就位于 /bin 文件夹下面。

这是踏破铁鞋无觅处的最好代表(hh),看名字就知道是用来解密镜像的,用下面的命令模拟运行

1
qemu-mipsel -L . ./bin/imgdecrypt

输出

1
./bin/imgdecrypt <sourceFile>

用最新版的固件来试一下:

1
qemu-mipsel -L . ./bin/imgdecrypt ./DIR_878_FW120B05.BIN

执行后再用 binwalk 检测发现解密成功,至此我们就可以成功提取中间版本之后任意一个版本的固件了(只要未来不更换加密算法)!

漏洞1 未授权用户构造恶意请求造成栈溢出

由于接下来的两枚漏洞初步看是 0day,所以敏感信息会进行隐藏,漏洞已经报告厂商。

我们拿到最新版本(1.20 2019-7) 的固件进行解密、解包,得到文件系统,在 etc_ro 目录下面看到很多和服务相关的文件,初步判定这是路由器的核心文件目录。

etc_ro 目录下有一个名为 lighttpd 的文件夹,lighttpd 是一个轻量级的 web 服务器,进去之后发现其配置文件 lighttpd.conf,初步分析配置文件,发现几乎所有 web 请求都被定向到了 /bin 目录下的 prog.cgi 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fastcgi.server = ( 
"/HNAP1/" =>
((
"socket" => "/var/prog.fcgi.socket-0",
"check-local" => "enable",
"bin-path" => "/bin/prog.cgi",
"idle-timeout" => 10,
"min-procs" => 1,
"max-procs" => 1
)),
".fcgi" =>
((
"socket" => "/var/prog.fcgi.socket-0",
"check-local" => "enable",
"bin-path" => "/bin/prog.cgi",
"idle-timeout" => 10,
"min-procs" => 1,
"max-procs" => 1
))
)

file 查看 cgi 的文件格式,是 MIPS32 小端架构。初步想法是先把固件模拟起来,然后对照功能去调试,但是我试了很多方法,包括 firmadyne、qemu 全模拟、单文件模拟等等,都达不到理想的效果。只好购买真机来进行测试。

第一个漏洞出现在 prog.cgi 的 main 函数中,是处理 HNAP 请求的第一个函数。在这个函数中存在栈溢出,并且不需要用户身份认证即可触发。

main 函数某处存在这样的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
else {
__s1 = strstr((char *)piVar5[0x31],"method=");
if (__s1 == (char *)0x0) {
__s1 = strstr((char *)piVar5[0x35],"http://purenetworks.com");
if (__s1 == (char *)0x0) {
iVar3 = bstrdupNoBalloc(piVar5[0x31],"fastcgi.c",0xf2);
piVar5[0x31] = iVar3;
}
else {
__s1 = __s1 + 0x17;
__haystack = strchr(__s1,0x22);
if (__haystack == (char *)0x0) {
iVar3 = bstrdupNoBalloc(__s1,"fastcgi.c",0xef);
piVar5[0x31] = iVar3;
}
else {
memset(&cStack1292,0,0xfe);
strncpy(&cStack1292,__s1,(size_t)(__haystack + -(int)__s1));
iVar3 = bstrdupNoBalloc(&cStack1292,"fastcgi.c",0xed);
piVar5[0x31] = iVar3;
}
}
}

是用于处理 HNAP 的 SOAPaction 字段的,仔细观察发现 strncpy 函数的长度字段是用户可控的,它的计算逻辑是利用 strchr 函数从 SOAPaction 字段定位字符 “ ,然后利用指针减法运算得到整个字符串的长度。显然如果恶意用户构造一个超长的字符串传入请求头,就会触发 strncpy 函数拷贝很长的内容到 stack 空间,这样会破坏其他函数的栈帧,使程序崩溃。

由于我现在没有合适的调试环境,所以暂时无法确定是否能够利用此处漏洞达到 RCE 的功能,但是考虑到溢出任意字符数量,RCE 应该是可行的。

初步测试如果一直向路由器发送畸形数据包,会导致管理页面 500 或 503 崩溃。

漏洞2 授权用户命令注入

这个漏洞比较有趣,它是在 2019 年 6 月左右已经被提交的漏洞,厂商也发布了对应的补丁,但是由于补丁的修复效果有限,问题依然存在。

DIR-878 路由器有很多功能,其中站点过滤功能允许用户添加特定的网站进行拦截,放置其他用户访问。

但是这个功能的后端实现使用了 execv 函数,并且对于用户传入的 URL 检验不严格,导致恶意用户可以拼接任意命令到 execv 函数中执行,严重的导致可以获取路由器的交互式 shell。

漏洞依然出现在 prog.cgi 中,在 SetWebFilterSettings 接口绑定函数中对于 URL 调用了 tbsCheckHttpUrl 函数进行过滤(在之前的版本中不存在过滤),这个函数位于 librcm.so 中。

利用 IDA 打开 so 文件可以定位到此函数(ghidra 不知道问什么无法解析到这个函数),内部实现流程大致是利用一段正则表达式去匹配输入的 URL,如果匹配失败就拒绝此 URL,涉及的部分正则如下

1
^(http://)?(([0-9a-zA-Z_!~*'(*a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}\.)*{1,4})|(:6*0-4][0-9]{2})|(:655[0-2][0-9])|(:6553[0-5]))?((/?)|(/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+/?)$

虽然正则过滤掉了一部分危险输入(主要是空格符),但是我们可以利用多种办法来绕过空格符的过滤,由于这个 API 接口除了 tbsCheckHttpUrl 处理 URL 之外就没有其他防护了,绕过过滤之后依然可执行任意 shell 命令。

拿到交互式的 shell 之后即可上传一份调试器,再去调试固件就方便多了。

  • Title: 家用路由器固件常用解包方法 & D-Link DIR-878 BUGS
  • Author: Catalpa
  • Created at : 2019-09-18 00:00:00
  • Updated at : 2024-10-17 08:48:04
  • Link: https://wzt.ac.cn/2019/09/18/D-Link_BUG/
  • License: This work is licensed under CC BY-SA 4.0.