最近家里的移动宽带到期了,闲置了一台H3-2S的光猫,原本我也有一台MIPS的友华光猫,固件修改后我自己也很满意了。但这台闲置的光猫是ARMv7l的,测试后发现性能更好一些(是MIPS的一倍多)。于是就想着也改改,加一些自启脚本,装frpc、alist、kms、aria2c、clash、甚至还能跑一点自己写的Java程序。
通过分析一通之后,我发现这个光猫的rcS相关的自启内容里一点破绽都没有,原本的alipay的dns也注释了,要加自启脚本就必须改rootfs里的rcS。由于系统里没有dd,但有nandump,我给dump出来,使用binwalk分析了一下,果然从kernel里解出了文件系统。整个固件的文件结构大概是这样的:中兴文件结构,占480bytes,然后是uImage头64bytes,然后会执行一段程序,解压gzip。gzip解压出来之后,解压的字节中又有一段程序,然后再解压一个lzma块,这个lzma块解压出来就是rootfs.cpio了。
这个cpio是没有任何压缩的。但我用python的cpio库libarchive的时候遇到了不小的挑战,就是cpio解压后什么都不改,再压回去,对比改动发现差异很大,里边有一些数据的大小写都发生了变化,小写全都变成了大写。我想探究里边的原因,耗费了一天时间也没找出来,于是我只好人肉修改cpio的16进制内容,找到rcS部分,把alipay.sh的注释打开,然后改一小段。
剩下的就是还原回去并刷进去的工作了,这个还原工作异常艰难:
首先是gzip解压的时候不知道gzip块的长度,不知道什么时候会结束,虽然不影响解压,但肯定影响还原啊。好在gzip解压的时候,异常信息里会提示无法解码的字节,那直接搜索这个字节就能找到结束的附近了。然后观察右侧的块特征,终于找到了结束位,顺带还发现了dtb的开始位置和整个分区的结束位置、还有crc32的偏移量。
然后是lzma的结束位,这个就比gzip难多了,因为这个lzma解压的时候没有报任何异常,直接就解压出来了,再尝试压回去会发现大小完全对不上,大了1M多不止。尝试了好几个库和xz\tar等格式,发现也对不上,应该就是lzma的压缩格式。后来我开始怀疑是不是lzma的结束位置不对,可没有任何异常啊,应该不会不对吧。
我带着这个怀疑,使用linux中的lzma去检查这个文件块,发现lzma竟然无法识别,而我压缩回去的lzma则能识别,而且完全没问题。我好奇它究竟用的什么lzma算法啊,难道老版本的不支持?于是我开始翻python的lzma库源代码,发现它果然捕获了异常,而且直接把异常吃掉了。真狗!我把那段代码拷贝出来。
1
2
3
4
5
6
7
8
9
10
11
12
|
data = f.read(buff) results = [] while True : decomp = lzma.LZMADecompressor(lzma.FORMAT_AUTO) try : res = decomp.decompress(data) except lzma.LZMAError as e: if results: print ( "LZMAError: %s" % e) break # Leftover data is not a valid LZMA/XZ stream; ignore it. else : raise # Error on the first iteration; bail out. |
lzma则是非常难,因为lzma的那次压缩是在gzip解压之后,又执行了一段,那偏移量完全都不知道怎么对上,载入到IDA里也是一堆未识别的地址。好在它的库函数会有俩个不同的异常,一个是流未结束,一个是字节无法解析。流未结束就是截取少了,字节无法解析就是截取多了,然后写了一个二分查找的方法,找到了lzma解压的位置,位置结束后俩个字节处就是lzma的长度数值。
然后我测试了一下,将截取的lzma解压出rootfs.cpio后再原封不动的压缩回去,大小竟然比原来的小了几十字节。这可不行,我又尝试了一下用linux自带的lzma压缩:
1
|
cat . / rootfs.cpio |lzma - 9 > lzma.lzma |
果然还原了,和原文件md5值一字不差,看来linux的lzma算法还是和python的lzma有些小差异。
至此,整个逆向过程告一段落。IDA可以直接载入uImage的文件去查看gzip的解压逻辑,来验证gzip的结束偏移的地址是否正确。
经过一番验证,也找到了双固件切换的的方法,也完成了文件系统提取也压缩回去的程序工具,但改完的固件始终无法启动,查看gzip解压的出来的数据块,发现了大量证书内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
DECIMAL HEXADECIMAL DESCRIPTION - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 65728 0x100C0 AES Inverse S - Box 70336 0x112C0 SHA256 hash constants, little endian 98393 0x18059 Certificate in DER format (x509 v3), header length: 4 , sequence length: 5384 98405 0x18065 Certificate in DER format (x509 v3), header length: 4 , sequence length: 5392 98409 0x18069 Certificate in DER format (x509 v3), header length: 4 , sequence length: 5396 745165 0xB5ECD Certificate in DER format (x509 v3), header length: 4 , sequence length: 1284 3911129 0x3BADD9 Certificate in DER format (x509 v3), header length: 4 , sequence length: 4736 4316777 0x41DE69 Certificate in DER format (x509 v3), header length: 4 , sequence length: 1456 5909712 0x5A2CD0 gzip compressed data, maximum compression, from Unix, last modified: 1970 - 01 - 01 00 : 00 : 00 (null date) 6121672 0x5D68C8 DES SP2, little endian 6122440 0x5D6BC8 DES SP1, little endian 6142464 0x5DBA00 CRC32 polynomial table, little endian |
感觉要裹足不前了……,目前的水平对arm反汇编的能力严重不足。
所以把成果文件贡献出来,看有没有大神有兴趣挑战,如果成功,我将分享我开发的中兴固件拆包打包工具(现在不分享是因为打包的固件无法启动,有变砖风险)。
https://cloud.189.cn/web/share?code=aYVVNzMvA7ze(访问码:6hyx)
文件说明:
该文件为吉比特,中兴芯片光猫固件:
- uImage.bin为从uboot启动的uImage镜像文件,从该文件中可解出 zImage.bin文件;
- zImage.bin文件,可解出kernel.gzip文件和dtb.bin文件;
- kernel.gzip文件可解出kernel.bin文件,该文件中可看到大量证书内容和加密相关内容;
- kernel.bin文件可解出lzma.lzma文件,该文件为rootfs.cpio压缩包,即linux文件系统;
- 由于uboot中禁用了earlyprintk,所以无法通过串口得到任何有用的信息,但是可以通过uboot的tftp功能,将文件传输到内存中,然后通过uboot的bootm命令启动内核,从而实现调试。
- uImage.bin和kernel.bin文件可以使用IDA pro载入,能看到程序的大致结构。
- 由于启动不了也可能是uboot里有相关验证,所以补充一个mtd1_u-boot.bin,这是该光猫的u-boot分区备份。
2023年5月8日 我已自己挑战成功,实现了通用工具:
- 可编辑zImage中嵌入了编译型initramfs.cpio的kernel
- 可对cpio中的文件实现增删改,而无需解压和再打包
- 可生成中兴uboot启动的固件,无需刷第三方uboot,从而避免变砖风险
原文始发于看雪社区(XYUU):[原创]中兴ZTE ZX279128S芯片固件解密