固件解密 前言 在分析爱快的路由器的时候遇到了固件被加密的情况,发现找到的一些固件解密的方法用不了,但是github上居然有对应的解密方法,果然多看看github
解密 加密固件更新的方式 固件制造商出于对产品安全的考虑,防止固件被恶意篡改,通常会采取各种防范手段,阻止固件被直接解包和破解。其中一种重要且有效的手段就是利用加密算法对固件进行加密,例如使用AES、DES等对称加密算法加密固件中的内核和文件系统,或使用XOR、ROT等简单置换算法对固件进行处理,这使得研究人员无法直接读取固件中的明文内容。
对固件进行解密之前,需提前了解厂商一般会以怎样的形式发布加密固件以及在设备启动和固件升级过程中,会在哪些地方对固件进行解密。厂商发布加密固件一般有以下三种场景:
固件在出厂时未加密,也未包含任何解密代码,后续为了发布加密固件,会提前发布一个包含解密程序的未加密版本作为过渡版本,这样后续发布加密固件时可使用该解密程序进行解密,这类情况在发布时间较早的设备中比较常见。
[ ](https://github.com/SecureNexusLab/IoTFirmwareAnalysisGuide/blob/main/images/Firmware-Encryption-Scheme-1.png )
对于此种情况,可寻找固件过渡版本v1.1,从中分析出所包含的解密逻辑和算法,从而实现对后续加密固件的解密。因为用户无法从v1.0直接升级v1.2的固件,所以在官方的固件发布描述中,一般会存在如下图所述的描述。
设备固件在原始版本中已加密。供应商决定更改加密方案并发布未加密的过渡版本 v1.2,其中包含新的解密程序。与方案 1 类似,可以从 v1.2 映像中获取解密例程,并将其应用于最新的加密固件。阅读固件版本的发行说明可能有助于识别未加密的过渡版本。发行说明通常会指导用户在升级到最新版本之前升级到中间版本,中间版本很可能是未加密的过渡固件
[ ](https://github.com/SecureNexusLab/IoTFirmwareAnalysisGuide/blob/main/images/Firmware-Encryption-Scheme-2.png )
以过渡版本v1.2作为分界线,分别对其前后版本固件的解密进行说明。对于v2及更高版本的固件即过渡版本之后的固件(假设都使用同一种加密方案,无其它过渡版本):可以通过寻找过渡版本v1.2,从中分析出v2的解密方法从而对v2固件进行解密。
设备固件在原始版本中已加密。但是供应商决定更改加密方案,并发布包含新解密例程的未加密过渡版本,在这种情况下,没有简单的方法来获得解密程序。一种方法是购买设备并直接从硬件中提取未加密的固件。
[ ](https://github.com/SecureNexusLab/IoTFirmwareAnalysisGuide/blob/main/images/Firmware-Encryption-Scheme-3.png )
解密的方法 针对物联网终端设备的固件解密,总结如下六种方法和技巧:
基于老版本未加密固件中的解密程序实现新版本加密固件的解密
对于固件出厂时未加密,后续发布的固件是加密的情况,可以通过对比边界版本,解包最后一个未加密版本逆向升级程序还原加密过程,以实现对加密固件的解密。
如果设备存在UART、JTAG等调试接口,可通过连接硬件接口获取设备的Shell,从而dump出设备的固件。
但由于某些设备安全限制较高导致无法进入Linux Shell,我们可尝试进入BootLoader Shell(最常见的是Uboot Shell)对固件进行提取。这里要说明的一点是部分设备更新固件后会将解密后的新版固件写回Flash,这种情况下dump出的固件是未加密的,而相反的是部分设备Flash中的固件一直是加密状态存在,只是在每次设备启动时进行动态解密。所以此种方法提取出的固件可能也是加密的,但好处在于可以避免因拆解设备Flash去读固件导致设备损坏的风险,并且可以获取到较为完整的固件(官方下载的固件可能只是某块数据的更新包)。
对于有Telnet、SSH等服务的设备,可以通过这些服务进入设备的Linux Shell进行固件提取。服务一般在设备的web管理页面中可手动开启,但需要说明的一点是某些厂商会开发自家的CLI屏蔽掉底层Linux Shell,连接这些服务进入的Shell只是厂商的CLI,也无法提取文件系统,不过某些设备(光猫居多)的CLI存在可进入Linux Shell的命令,具体可自行在互联网上搜索相应的方法。
基于低版本固件RCE漏洞获取设备Shell分析解密逻辑实现对新版固件的解密
如果设备历史加密版本固件出现过RCE漏洞,可将存在漏洞的固件刷入设备,通过RCE漏洞获取设备Linux Shell,再分析其包含的解密逻辑,最终通过该解密逻辑实现对更新版本固件的解密。需要注意的是存在RCE漏洞版本的固件所使用的加密方案需要与新版本固件一致。
常见情况下固件的解密逻辑肯定是存在Flash中的,当获取到完整版固件(拆机从Flash读取或者从BootLoader Shell中提取等)后,可以直接对整个固件进行逆向分析寻找解密逻辑代码实现对固件的解密,但此种方法难度较大,并且这类设备安全性一般较高,很有可能分析出了解密逻辑但拿不到解密密钥,如密钥单独存放在某个安全芯片中。
固件解密步骤
通过设备固件中间过渡版本或者通过物理设备的UART接口进入到系统中获取解密程序和算法(可以观察固件名称,带有Middle_FW_Unencrypt这种字符的基本都是中间版本,包含解密程序)
利用QEMU执行跨架构的chroot,获取有效SHELL。
使用获得的解密程序对加密固件进行解密
测试解密程序是否对新版本加密固件有效,若对新版本固件无效解密,很可能还包含其他中间过渡版本。
固件解密演示 解密DIR-878 以D-LINK设备 DIR-878 为例,通过中间版本能直接拿到固件的解密程序或者算法,通过解密程序再去解密新的固件。DIR-878 的固件中间过渡版本没有加密但是包含解密程序,可以通过 binwalk 直接获取固件的文件系统。
DIR-878固件下载地址:https://support.dlink.com.au/Download/download.aspx?product=DIR-878
将固件都下载到本地中,使用binwalk检查最早的固件版本,它将正确检测到uImage标头和LZMA压缩数据,能检测到就直接使用 -Me参数进行递归提取。
A1版本
其他版本
寻找中间版本 最终在 1.10B05 固件压缩包中获取到中间版本的固件:DIR878A1_FW104B05_Middle_FW_Unencrypt.bin(所谓中间版本就是该版本使用binwalk可以检测到数据,后面版本都不能检测到数据,就可以猜测该版本为中间版本) 提取中间版本(DIR878A1_FW104B05_Middle_FW_Unencrypt.bin)的文件系统
找到了中间版本,按照固件升级的思路,肯定有一个升级程序,在这个程序内部可能存在着加密算法,我们首要目的就是去寻找这个解密程序。最终在程序目录 /bin文件夹下面,发现了一个名为imgdecrypt
的二进制文件。
chroot模拟 因为主机(x86)和二进制文件(MIPS)之间的处理器体系结构上存在差异。
可以使用QEMU执行跨架构的chroot。为此,首先我们将qemu-mipsel-static二进制文件复制到固件根文件系统的/usr/bin/目录中,我们还将加密的固件复制到未加密的固件文件系统。最后,我们将chroot插入固件根目录并获得一个有效的shell,然后运行执行解密的二进制程序,按照提示输入需要解密的固件,输出key代表固件已经成功的被解密。(未将QEMU放在正确位置,启动任何程序都会报错No such file or directory。这个报错会有很多歧义,因此一定要自己确认一下QEMU确实在rootfs的“/usr/bin”目录中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 iot@research:~/Desktop/DIR878/_DIR878A1_FW104B05_Middleware.bin.extracted/cpio-root$ cp /usr/bin/qemu-mipsel-static ./usr/bin/ iot@research:~/Desktop/DIR878/_DIR878A1_FW104B05_Middleware.bin.extracted/cpio-root$ cp ../../../../DIR878A1_FW1.12B01.bin . iot@research:~/Desktop/DIR878/_DIR878A1_FW104B05_Middleware.bin.extracted/cpio-root$ sudo chroot . /bin/sh BusyBox v1.12.1 (2018-02-03 16:38:35 CST) built-in shell (ash) Enter 'help' for a list of built-in commands. home media lib private etc DIR878A1_FW1.12B01.bin dev var sbin tmp bin init sys share proc mnt usr etc_ro ./bin/imgdecrypt <sourceFile> key:C05FBF1936C99429CE2A0781F08D6AD8
使用binwalk再次对解密后的固件(1.10B05)进行检测,已经成功的获取到了固件的uImage标头和LZMA压缩数据。(执行binwalk前注意要给固件读写权限)这样就成功的解密了固件,并提取到了固件文件系统
解密脚本 当然这种方式还是比较麻烦,在GitHub中已经有大佬完善了整个解密过程,通过逆向分析解密程序imgdecrypt,还原解密算法并找到硬编码的密钥信息。该解密脚本目前能够支持的固件版本:包括但不仅限于DIR-825、DIR-867、DIR-878、DIR-882、DIR-1260、DIR-1960、DIR-2660、DIR-3060。
解密脚本链接:https://github.com/0xricksanchez/dlink-decrypt
逆向分析文章:
解密Linksys-EA6100 需要解密固件的版本是:FW_EA6100_1.1.6.181939_prod.gpg.img (Build 181939),固件下载地址:https://store.linksys.com/support-article?articleNum=47705
首先使用binwalk对下载好的固件1.1.6进行检测,并且尝试提取固件,但是并未得到任何有价值的信息。
查看固件的熵值 使用binwalk -E 查看固件的熵值,EA6100固件的信息熵接近1,可以说明固件是经过加密的。
在下载固件的时候,可以看到一个提示信息,需要先下载中间过渡版本。
提取密钥 根据提示下载好过渡版本的固件 FW_EA6100_1.1.5.172244_prod.img (Build 172244),并尝试提取固件。
可以看见成功的提取到了文件系统,由于固件更新的目标是加密的,所以猜测固件中存在解密的程序,尝试在解密的文件中寻找相关的解密程序。
在下载固件时,可以发现1.1.6版本的加密固件的命名多了gpg后缀(*.prod.gpg.img),GPG是不对称加密、数字签名的经典和标准,不但可以为个人的数字传输提供信用保障,用于保护个人之间通讯的隐私,保护敏感数据,而且可以在更大的范围内,如公司范围内的数据加密,电子产品的数字签名,知识产权保护等。可以知道新版本的固件很可能使用了gpg的方式来加密固件。
寻找解密文件,检索一些其他的密钥保存的格式之后,一般都是下面的这种方式
1 2 3 -----BEGIN PGP PUBLIC KEY BLOCK ----- Version : GnuPG v1-----END PGP PUBLIC KEY BLOCK -----
使用grep命令检索文件,找到了类似 gpg 密钥的文件keydata
解密固件 首先将keydata 加载到 主机系统中的 gpg 中,然后再进行对固件包解密
看起来没有出现什么问题,将解密之后的文件进行binwalk探测并尝试提取文件。最终成功的解密固件。
iKuai 固件解密 在尝试了其他的找中间件的情况,发现没有中间件,都是加密的,而且每个版本的都可以单独安装,也就不存在需要中间版本的可能,下面是网上找到的解密文件的库
其中最为核心的解密代码如下,这段代码即是解密固件的文件系统的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> uint8_t ext_key[1024 ];uint8_t key[17 ] = { 0x77 , 0xb1 , 0xfa , 0x93 , 0x74 , 0x2c , 0xb3 , 0x9d , 0x33 , 0x83 , 0x55 , 0x3e , 0x84 , 0x8a , 0x52 , 0x91 , 0x00 }; uint8_t hash_table[] = { 0x00 , 0x00 , 0x00 , 0x00 , 0x64 , 0x10 , 0xB7 , 0x1D , 0xC8 , 0x20 , 0x6E , 0x3B , 0xAC , 0x30 , 0xD9 , 0x26 , 0x90 , 0x41 , 0xDC , 0x76 , 0xF4 , 0x51 , 0x6B , 0x6B , 0x58 , 0x61 , 0xB2 , 0x4D , 0x3C , 0x71 , 0x05 , 0x50 , 0x20 , 0x83 , 0xB8 , 0xED , 0x44 , 0x93 , 0x0F , 0xF0 , 0xE8 , 0xA3 , 0xD6 , 0xD6 , 0x8C , 0xB3 , 0x61 , 0xCB , 0xB0 , 0xC2 , 0x64 , 0x9B , 0xD4 , 0xD2 , 0xD3 , 0x86 , 0x78 , 0xE2 , 0x0A , 0xA0 , 0x1C , 0xF2 , 0xBD , 0xBD }; int32_t hash (uint8_t * a1, uint32_t a2) { uint64_t v2 = 0 ; uint32_t result = 0xffffffff ; uint8_t v4; uint32_t v5; while (a2 != (uint32_t )v2) { v4 = a1[v2++]; v5 = hash_table[(v4 ^ (uint8_t )result) & 0xf ] ^ ((uint32_t )result >> 4 ); result = hash_table[((uint8_t )v5 ^ (v4 >> 4 )) & 0xf ] ^ (v5 >> 4 ); } return result; } int main () { int64_t key_index = 0 ; uint32_t initrd_real_size, tmp_var, hash_var; int8_t initrd_size_low_byte; int32_t var_0 = 0 , var_1 = 1 ; uint8_t * initrd_start; int64_t initrd_size; struct stat * initrd_stat ; FILE* f; uint8_t * p_hash; int64_t iter = 0 ; f = fopen("./rootfs.raw" , "rb" ); initrd_stat = (struct stat*)malloc (sizeof (struct stat)); fstat(fileno(f), initrd_stat); initrd_size = initrd_stat->st_size; initrd_start = (uint8_t *)malloc (initrd_size); fread(initrd_start, sizeof (uint8_t ), initrd_size, f); fclose(f); initrd_real_size = initrd_size - 4 ; initrd_size_low_byte = (int8_t )(initrd_size - 4 ); do { ext_key[key_index] = key[key_index & 0xf ] + 19916032 * ((int32_t )key_index + 1 ) / 131u ; ++key_index; } while (key_index != 1024 ); for (iter = 0 ; initrd_real_size > (uint32_t )iter; initrd_start[iter - 1 ] = ((initrd_start[iter - 1 ] - (uint8_t )tmp_var) << ((uint8_t )tmp_var % 7u + 1 )) | ((int32_t )(uint8_t )(initrd_start[iter - 1 ] - tmp_var) >> (8 - ((uint8_t )tmp_var % 7u + 1 )))) { tmp_var = var_0 + iter++; *(uint8_t *)&tmp_var = (uint8_t )(initrd_size_low_byte + ext_key[tmp_var % 1024u ] * var_1); } printf ("initrd_size:%ld\n" "initrd_real_size:%d\n" "iter:%ld\n" , initrd_size, initrd_real_size, iter); hash_var = __builtin_bswap32(~hash(initrd_start, initrd_size - 4 )); hash_var = __builtin_bswap32(~hash((uint8_t *)&hash_var, 4 )); p_hash = (uint8_t *)(initrd_start + initrd_size - 4 ); if (*(uint32_t *)p_hash == hash_var) { printf ("success\n" ); } f = fopen("./rootfs.decode" , "wb" ); fwrite(initrd_start, sizeof (uint8_t ), initrd_size, f); fclose(f); free (initrd_start); free (initrd_stat); return 0 ; }
再简单分析一下,发现主要步骤如下
初始化扩展密钥 :1 2 3 4 5 do { ext_key[key_index] = key[key_index & 0xf ] + 19916032 * ((int32_t )key_index + 1 ) / 131u ; ++key_index; } while (key_index != 1024 );
这一部分代码使用 key
数组和一些算术运算来生成 ext_key
数组。ext_key
数组长度为1024
解密过程 :1 2 3 4 5 6 for (iter = 0 ; initrd_real_size > (uint32_t )iter; initrd_start[iter - 1 ] = ((initrd_start[iter - 1 ] - (uint8_t )tmp_var) << ((uint8_t )tmp_var % 7u + 1 )) | ((int32_t )(uint8_t )(initrd_start[iter - 1 ] - tmp_var) >> (8 - ((uint8_t )tmp_var % 7u + 1 )))) { tmp_var = var_0 + iter++; *(uint8_t *)&tmp_var = (uint8_t )(initrd_size_low_byte + ext_key[tmp_var % 1024u ] * var_1); }
这个循环的核心是对 initrd_start
数组进行解密操作。具体的解密步骤如下
初始化 1 2 for (iter = 0 ; initrd_real_size > (uint32_t )iter;
iter
变量初始化为 0
,用于遍历 initrd_start
数组。
循环条件是 initrd_real_size
大于 iter
,确保遍历范围不超过 initrd_real_size
。
解密 1 2 initrd_start[iter - 1 ] = ((initrd_start[iter - 1 ] - (uint8_t )tmp_var) << ((uint8_t )tmp_var % 7u + 1 )) | ((int32_t )(uint8_t )(initrd_start[iter - 1 ] - tmp_var) >> (8 - ((uint8_t )tmp_var % 7u + 1 ))))
这是一个复杂的解密操作,涉及到移位和按位或运算。
initrd_start[iter - 1]
代表当前迭代所处理的字节。
(initrd_start[iter - 1] - (uint8_t)tmp_var)
减去 tmp_var
后进行左移和右移操作。
((initrd_start[iter - 1] - (uint8_t)tmp_var) << ((uint8_t)tmp_var % 7u + 1))
左移 tmp_var % 7 + 1
位。
((int32_t)(uint8_t)(initrd_start[iter - 1] - tmp_var) >> (8 - ((uint8_t)tmp_var % 7u + 1)))
右移 8 - (tmp_var % 7 + 1)
位。
使用按位或运算 |
将左移和右移的结果组合,最终得到解密后的值。
更新 tmp_var
1 tmp_var = var_0 + iter++;
tmp_var
被更新为 var_0 + iter
,然后 iter
自增。
修改 tmp_var
低字节 1 *(uint8_t *)&tmp_var = (uint8_t )(initrd_size_low_byte + ext_key[tmp_var % 1024u ] * var_1);
tmp_var
的低字节被更新为 (initrd_size_low_byte + ext_key[tmp_var % 1024u] * var_1)
。
ext_key[tmp_var % 1024u]
从 ext_key
数组中获取一个值。
乘以 var_1
(在该代码段中,var_1
始终为 1
)。
加上 initrd_size_low_byte
,最终结果赋值给 tmp_var
的低字节。
计算哈希值并比较 1 2 3 4 5 6 hash_var = __builtin_bswap32(~hash(initrd_start, initrd_size - 4 )); hash_var = __builtin_bswap32(~hash((uint8_t *)&hash_var, 4 )); p_hash = (uint8_t *)(initrd_start + initrd_size - 4 ); if (*(uint32_t *)p_hash == hash_var) { printf ("success\n" ); }
哈希值计算 :1 hash_var = __builtin_bswap32(~hash(initrd_start, initrd_size - 4 ));
调用 hash
函数对解密后的数据(除去最后 4 字节)进行哈希计算。
结果取反(按位取反运算符 ~
)。
使用 __builtin_bswap32
进行字节顺序交换(大端到小端或小端到大端)。
再次计算哈希值 :1 hash_var = __builtin_bswap32(~hash((uint8_t *)&hash_var, 4 ));
对上一步得到的 hash_var
进行哈希计算(长度为 4 字节)。
结果再次取反并进行字节顺序交换。
验证哈希值 获取原始数据中的哈希值 :1 p_hash = (uint8_t *)(initrd_start + initrd_size - 4 );
p_hash
指向 initrd_start
数组中最后 4 字节,这些字节包含了原始数据中的哈希值。
比较哈希值 :1 2 3 if (*(uint32_t *)p_hash == hash_var) { printf ("success\n" ); }
将 p_hash
指向的 4 字节数据解释为一个 uint32_t
类型的值,并与 hash_var
比较。
如果相等,打印 success
表示解密成功。
结论 这个解密算法首先生成一个扩展密钥,然后通过一系列位移和异或操作对固件文件进行解密,并通过哈希校验确保解密过程的正确性。该算法的核心在于扩展密钥的生成和解密过程中复杂的位移运算。
附录 最后还会遇到解压缩的问题,解决办法如下
https://blog.csdn.net/zzlufida/article/details/84594447
可以直接使用的解密脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 #!/bin/bash srcfile=$1 UPDATE_DIR=. headlen=$(printf "%u" 0x$(hexdump -v -n 4 $srcfile -e '4/1 "%02x"' )) echo $headlen printf "\x1f\x8b\x08\x00\x6f\x9b\x4b\x59\x02\x03" > $UPDATE_DIR /header.bindd if =$srcfile bs=1 skip=4 count=$headlen >> $UPDATE_DIR /header.bin 2>/dev/nullgunzip < $UPDATE_DIR /header.bin > $UPDATE_DIR /header_info.json dd if =$srcfile bs=$((headlen+4 )) skip=1 >> $UPDATE_DIR /firmware.bin 2>/dev/nullgzip -d < firmware.bin > firmware.bin.decompressed mkdir mountsudo mount firmware.bin.decompressed mountcp mount/boot/rootfs ./cp mount/boot/vmlinuz ./echo '#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> uint8_t ext_key[1024]; uint8_t key[17] = { 0x77, 0xb1, 0xfa, 0x93, 0x74, 0x2c, 0xb3, 0x9d, 0x33, 0x83, 0x55, 0x3e, 0x84, 0x8a, 0x52, 0x91, 0x00 }; uint8_t hash_table[] = { 0x00, 0x00, 0x00, 0x00, 0x64, 0x10, 0xB7, 0x1D, 0xC8, 0x20, 0x6E, 0x3B, 0xAC, 0x30, 0xD9, 0x26, 0x90, 0x41, 0xDC, 0x76, 0xF4, 0x51, 0x6B, 0x6B, 0x58, 0x61, 0xB2, 0x4D, 0x3C, 0x71, 0x05, 0x50, 0x20, 0x83, 0xB8, 0xED, 0x44, 0x93, 0x0F, 0xF0, 0xE8, 0xA3, 0xD6, 0xD6, 0x8C, 0xB3, 0x61, 0xCB, 0xB0, 0xC2, 0x64, 0x9B, 0xD4, 0xD2, 0xD3, 0x86, 0x78, 0xE2, 0x0A, 0xA0, 0x1C, 0xF2, 0xBD, 0xBD }; int32_t hash(uint8_t* a1, uint32_t a2) { uint64_t v2 = 0; uint32_t result = 0xffffffff; uint8_t v4; uint32_t v5; while (a2 != (uint32_t)v2) { v4 = a1[v2++]; v5 = hash_table[(v4 ^ (uint8_t)result) & 0xf] ^ ((uint32_t)result >> 4); result = hash_table[((uint8_t)v5 ^ (v4 >> 4)) & 0xf] ^ (v5 >> 4); } return result; } int main() { int64_t key_index = 0; uint32_t initrd_real_size, tmp_var, hash_var; int8_t initrd_size_low_byte; int32_t var_0 = 0, var_1 = 1; uint8_t* initrd_start; int64_t initrd_size; struct stat* initrd_stat; FILE* f; uint8_t* p_hash; int64_t iter = 0; f = fopen("./rootfs.raw", "rb"); initrd_stat = (struct stat*)malloc(sizeof(struct stat)); fstat(fileno(f), initrd_stat); initrd_size = initrd_stat->st_size; initrd_start = (uint8_t*)malloc(initrd_size); fread(initrd_start, sizeof(uint8_t), initrd_size, f); fclose(f); // initrd_real_size = initrd_size - 4; initrd_real_size = initrd_size - 4; initrd_size_low_byte = (int8_t)(initrd_size - 4); do { ext_key[key_index] = key[key_index & 0xf] + 19916032 * ((int32_t)key_index + 1) / 131u; ++key_index; } while (key_index != 1024); for (iter = 0; initrd_real_size > (uint32_t)iter; initrd_start[iter - 1] = ((initrd_start[iter - 1] - (uint8_t)tmp_var) << ((uint8_t)tmp_var % 7u + 1)) | ((int32_t)(uint8_t)(initrd_start[iter - 1] - tmp_var) >> (8 - ((uint8_t)tmp_var % 7u + 1)))) { tmp_var = var_0 + iter++; *(uint8_t*)&tmp_var = (uint8_t)(initrd_size_low_byte + ext_key[tmp_var % 1024u] * var_1); } printf("initrd_size:%ld\n" "initrd_real_size:%d\n" "iter:%ld\n", initrd_size, initrd_real_size, iter); hash_var = __builtin_bswap32(~hash(initrd_start, initrd_size - 4)); hash_var = __builtin_bswap32(~hash((uint8_t*)&hash_var, 4)); p_hash = (uint8_t*)(initrd_start + initrd_size - 4); if (*(uint32_t*)p_hash == hash_var) { printf("success\n"); } f = fopen("./rootfs.decode", "wb"); fwrite(initrd_start, sizeof(uint8_t), initrd_size, f); fclose(f); free(initrd_start); free(initrd_stat); return 0; }' >dec.cgcc -o dec dec.c cp rootfs rootfs.raw./dec mv rootfs.decode rootfs.xzumount mount rm -r mountrm -r rootfsFILENAME="./rootfs.xz" SEQUENCE="\x59\x5A" HEX_SEQUENCE=$(echo -n -e "$SEQUENCE " | hexdump -v -e '/1 "%02x"' | tr -d '\n' ) FILE_SIZE=$(stat -c%s "$FILENAME " ) TMP_FILE=$(mktemp ) for OFFSET in $(seq 0 4096 $FILE_SIZE ); do READ_OFFSET=$((FILE_SIZE - OFFSET - 4096 )) [ $READ_OFFSET -lt 0 ] && READ_OFFSET=0 dd if ="$FILENAME " bs=1 skip="$READ_OFFSET " count=4096 2>/dev/null | hexdump -v -e '1/1 "%.2x"' > $TMP_FILE grep -ob "$HEX_SEQUENCE " $TMP_FILE | head -n 1 | cut -d: -f1 > /dev/null 2>&1 if [ $? -eq 0 ]; then FOUND_OFFSET=$((READ_OFFSET + $(grep -ob "$HEX_SEQUENCE " $TMP_FILE | head -n 1 | cut -d: -f1) / 2 )) break fi done rm -f $TMP_FILE if [ -z "$FOUND_OFFSET " ]; then echo "Sequence not found in the file." exit 1 fi NEW_SIZE=$((FOUND_OFFSET + 2 )) dd if ="$FILENAME " of="${FILENAME} .tmp" bs=1 count="$NEW_SIZE " status=nonemv "${FILENAME} .tmp" "$FILENAME " echo "File $FILENAME truncated after the first occurrence of the sequence from the end, keeping the sequence." sudo xz -d rootfs.xz
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 iot@research:~/Desktop/iKuai/boot$ sudo ./decode.sh iKuai8_x64_3.7.13_Build202406212115.bin 188 initrd_size:34528532 initrd_real_size:34528528 iter:34528528 File ./rootfs.xz truncated after the first occurrence of the sequence from the end, keeping the sequence. iot@research:~/Desktop/iKuai/boot$ file rootfs rootfs: Linux rev 1.0 ext2 filesystem data (mounted or unclean), UUID=57f8f4bc-abf4-655f-bf67-946fc0f9f25b (extents) (large files) iot@research:~/Desktop/iKuai/boot$ mkdir mount iot@research:~/Desktop/iKuai/boot$ sudo mount ./rootfs ./mount/ iot@research:~/Desktop/iKuai/boot$ ls ./mount/ bin dev etc lib lib64 lost+found mnt overlay proc rom root sbin sys tmp usr var www