环境配置
本次分析的固件版本为 AX3000_Pro_V16.03.49.26,其软件源码基于 openwrt。
固件分析
首先,使用 binwalk -E 命令分析固件的熵值。虽然固件已经过解密,但其特殊格式导致 binwalk 无法有效识别内部结构。

binwalk US_AX3000.Pro_V16.03.49.26_cn_JAX01.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
64 0x40 Flattened device tree, size: 3625955 bytes, version: 17
296 0x128 LZMA compressed data, properties: 0x6D, dictionary size: 8388608 bytes, uncompressed size: 10719240 bytes
3607560 0x370C08 Flattened device tree, size: 16962 bytes, version: 17
5273660 0x50783C PGP RSA encrypted session key - keyid: 3B592D4E D1353F41 RSA (Encrypt or Sign) 1024b
然而,使用 file 命令可以成功识别出固件包含的 u-boot 引导程序和 Linux 内核信息。
file US_AX3000.Pro_V16.03.49.26_cn_JAX01.bin
US_AX3000.Pro_V16.03.49.26_cn_JAX01.bin: u-boot legacy uImage, \002, Linux/ARM, OS Kernel Image (lzma), 14553088 bytes, Fri Jan 10 06:24:02 2025, Load Address: 0X46000000, Entry Point: 0XFFFFFFFF, Header CRC: 0XB19671AF, Data CRC: 0XC47DB558
使用 binwalk -Me US_AX3000.Pro_V16.03.49.26_cn_JAX01.bin 解压固件后,我们成功提取出了 Linux 内核文件。
binwalk 128
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 Linux kernel ARM64 image, load offset: 0x80000, image size: 11046912 bytes, little endian, 4k page size,
5498800 0x53E7B0 MPEG transport stream data
7561216 0x736000 ELF, 64-bit LSB shared object, version 1 (SYSV)
7580200 0x73AA28 gzip compressed data, maximum compression, from Unix, last modified: 1970-01-01 00:00:00 (null date)
7719424 0x75CA00 CRC32 polynomial table, little endian
...
10257728 0x9C8540 AES S-Box
10257984 0x9C8640 AES Inverse S-Box
使用 IDA 以 ARM64 位模式打开提取出的 128(内核)文件。虽然可以搜索到大量 squashfs 相关的函数字符串,但无法查看其调用地址和引用关系,这给逆向分析带来了困难。

为了克服这一障碍,我们可以利用 OpenWrt 的源代码,编译生成一个带有完整调试符号的内核文件。然后,利用这个文件为 IDA 生成签名文件(.sig),从而实现对固件内核中函数的重命名,极大地提升逆向分析的效率。
内核编译
首先,通过分析内核配置信息,确认该固件是基于 OpenWrt 提交 f86f8e568461681fc85caf3c972489045367030e 版本构建的。
-
下载并检出对应版本的OpenWrt源码:
git clone https://github.com/openwrt/openwrt.git
cd openwrt
git checkout openwrt-21.02
git checkout f86f8e568461681fc85caf3c972489045367030e
-
安装必要的编译环境(在Ubuntu/Debian系统中):
sudo apt-get install subversion g++ zlib1g-dev build-essential git python3 libncurses5-dev gawk get truth unzip libssl-dev wget
-
更新并安装所有软件包:
./scripts/feeds update -a
./scripts/feeds install -a
-
配置编译选项:
生成默认配置后,通过 menuconfig 界面进行详细配置。
make defconfig
make menuconfig

关键一步:在 Global build settings 选项中,将 Binary stripping method 设置为 none。这样可以确保编译出的内核保留所有符号信息,便于后续分析。

-
开始编译:
保存配置退出后,使用以下命令开始编译,-j8 指定并行编译的线程数,V=s 输出详细日志。
make -j8 V=s
编译完成后,找到生成的带符号内核文件 vmlinux.debug。
find . -name vmlinux.debug
./build_dir/target-aarch64_cortex-a53_musl/linux-armvirt_64/vmlinux.debug
将该文件导出到 Windows 系统,使用 IDA 加载它,并生成签名文件 vmlinux.sig。之后,在分析原始固件内核时加载此签名,即可恢复绝大部分函数的名称。

格式分析
跳转到固件偏移地址 0x380040 处,可以发现固件格式的魔数(Magic Number)被修改过:
- 原本 SquashFS 文件系统的魔数
SQUASHFS_MAGIC (hsqs) 被更改为了 nice。
- 原本 XZ 压缩流的头部魔数
lzma_header_magic (ý7zXZ) 被更改为了 Tenda。

在标准 OpenWrt 代码中,对 SQUASHFS_MAGIC 的检查通常位于 mtd_check_rootfs_magic 函数内。但在此修改过的固件中,该函数已被改名。我们可以通过追踪 mtd_check_oob_ops 等函数的交叉引用来定位到实际的检查函数,例如 sub_3AC088。

同理,对 lzma_header_magic 的检查原本在 xz_dec_run 函数中。通过追踪 xz_dec_reset 等引用,可以找到修改后的检查函数 sub_2B0DC0。

进一步分析发现,CRC32 校验表 crc32_table 的值也被修改了。


代码补丁
为了能够正常解压此修改过的固件,我们需要对相关工具的源代码打上补丁,使其识别修改后的魔数和校验表。
1. 修改 xz-5.2.5 库
xz 库的源码路径通常为:./build_dir/host/xz-5.2.5
coder.c 文件:
374c374,375
< static const uint8_t magic[6] = { 0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00 };
---
> // static const uint8_t magic[6] = { 0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00 };
> static const uint8_t magic[6] = { 0x54, 0x65, 0x6E, 0x64, 0x61, 0x00 };
stream_flags_common.c 文件:
15,16c15,16
<
< const uint8_t lzma_header_magic[6] = { 0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00 };
---
> // const uint8_t lzma_header_magic[6] = { 0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00 };
> const uint8_t lzma_header_magic[6] = { 0x54, 0x65, 0x6E, 0x64, 0x61, 0x00 };
crc32_table_le.h 文件:
需要用固件中提取的新 CRC32 表替换整个文件内容(原文件内容很长,此处仅示意)。
5,523c5,523
< 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
< 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
< ... (标准CRC32表数据)
---
> 0x... , // 此处替换为从固件中逆向得到的新CRC32表数据
> ...
2. 修改 squashfs-tools
squashfs-tools 的源码路径通常为:./build_dir/host/squashfskit-v4.14/squashfs-tools
- 修改
Makefile:
启用 XZ 支持,并链接我们修改后编译的静态库。
XZ_SUPPORT = 1
LZMA_LIB := ../../xz-5.2.5/src/liblzma/.libs/liblzma.a
- 修改
squashfs_fs.h 头文件:
将 SquashFS 魔数定义改为 0x6563696E(对应 nice 的 ASCII 码)。
#define SQUASHFS_CACHED_FRAGMENTS CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE
#define SQUASHFS_MAJOR 4
#define SQUASHFS_MINOR 0
//#define SQUASHFS_MAGIC 0x73717368
#define SQUASHFS_MAGIC 0x6563696E
//#define SQUASHFS_MAGIC_SWAP 0x68737173
#define SQUASHFS_MAGIC_SWAP 0x6E696365
#define SQUASHFS_START 0
固件解压
完成代码修改后,按以下步骤操作:
-
编译修改后的 xz 静态库:
cd ./build_dir/host/xz-5.2.5
./configure
make
-
编译生成定制的 unsquashfs 程序:
cd ./build_dir/host/squashfskit-v4.14/squashfs-tools
make
编译成功后会得到 unsquashfs 可执行文件。
-
提取并解压文件系统:
首先,使用 dd 命令从固件指定偏移(0x380040)处提取 SquashFS 映像。
dd if=US_AX3000.Pro_V16.03.49.26_cn_JAX01.bin of=US_AX3000.squashfs bs=1 skip=$((0x380040))
然后,使用我们编译的 unsquashfs 程序解压该映像。
./unsquashfs US_AX3000.squashfs
Parallel unsquashfs: Using 10 processors
879 inodes (998 blocks) to write
create_inode: could not create character device squashfs-root/dev/console, because you're not superuser!
...
[==========================================================================================================================================| ] 991/998 99%
created 697 files
created 97 directories
created 175 symlinks
created 0 devices
created 0 fifos
解压完成后,即可在 squashfs-root 目录中查看和分析路由器的完整文件系统。