我目前正在尝试对电子烟的固件进行逆向工程,但是我找不到通用工具的任何线索。这是我到目前为止找到的所有内容。

即使使用-E-A,Binwalk也无法识别任何内容。
熵=每字节7.611435位。
最佳压缩将减小大小80896字节文件的4%。
80896样本的卡方分布为99630.01,并且随机
将超过该值的时间小于0.01%。是117.2996(127.5 =随机)。
Pi的蒙特卡洛值是3.078178312(误差2.02%)。
序列相关系数是0.107920(完全不相关= 0.0)。

这是熵的可视化:



这是熵:


这里是固件的下载。
https://www.file-upload.net/download-13422170/HW718V40_20171008.firmware.html

评论

您可以共享该文件的链接吗?如果不是,您可以发布由binwalk -E <您的文件>生成的熵图吗?高熵数据的分布不像我对压缩或加密数据所期望的那样均匀。此外,还有相当大的低熵区域。运行字符串-n 9 <您的文件>的结果是什么?您是否看过前几百个字节的十六进制转储?

嗨,我将下载内容添加到我的问题中。我查看了文件的十六进制转储,并尝试使用radare2和不同的样式对其进行分解,但没有任何帮助。我不在家,所以我很遗憾不能为您提供一个熵图。十六进制转储中的ASCII字符串对我来说也没有意义,但也许您找到了一些东西。感谢您的帮助。

首先确定处理器,然后(希望如此)确定指令集,这是否明智?

#1 楼

TL; DR

是的,文件已加密,它是密钥长度为0x800字节的简单XOR密码。

用于获取解密固件的Python脚本:



import struct

FILENAME = "HW718V40_20171008.firmware"

def xor_cipher(data, key):
    import sys
    from itertools import cycle
    if (sys.version_info > (3, 0)):
        return bytes(a ^ b for a, b in zip(data, cycle(key)))
    else:
        return bytes(bytearray(a ^ b for a, b in zip(bytearray(data), cycle(bytearray(key)))))

key = struct.pack('<I', 0x00001AE7) * 8 + struct.pack('<I', 0x000026EF) \
* 8 + struct.pack('<I', 0x0000565C) * 8 + struct.pack('<I',
    0x00001109) * 8 + struct.pack('<I', 0x00005618) * 8 \
+ struct.pack('<I', 0x000077D6) * 8 + struct.pack('<I', 0x00000F45) \
* 8 + struct.pack('<I', 0x000024AE) * 8 + struct.pack('<I',
    0x00001C42) * 8 + struct.pack('<I', 0x00006D4C) * 8 \
+ struct.pack('<I', 0x0000754E) * 8 + struct.pack('<I', 0x0000449C) \
* 8 + struct.pack('<I', 0x000056A2) * 8 + struct.pack('<I',
    0x00003EED) * 8 + struct.pack('<I', 0x0000106C) * 8 \
+ struct.pack('<I', 0x00000C7D) * 8 + struct.pack('<I', 0x0000578C) \
* 8 + struct.pack('<I', 0x000045CB) * 8 + struct.pack('<I',
    0x0000253C) * 8 + struct.pack('<I', 0x00007F0F) * 8 \
+ struct.pack('<I', 0x00006513) * 8 + struct.pack('<I', 0x000004C0) \
* 8 + struct.pack('<I', 0x00006BFF) * 8 + struct.pack('<I',
    0x0000711C) * 8 + struct.pack('<I', 0x00004829) * 8 \
+ struct.pack('<I', 0x00000B03) * 8 + struct.pack('<I', 0x000038E2) \
* 8 + struct.pack('<I', 0x000020AC) * 8 + struct.pack('<I',
    0x00005708) * 8 + struct.pack('<I', 0x000064FA) * 8 \
+ struct.pack('<I', 0x00007984) * 8 + struct.pack('<I', 0x00004C79) \
* 8 + struct.pack('<I', 0x00007ED1) * 8 + struct.pack('<I',
    0x00000F4F) * 8 + struct.pack('<I', 0x00002BB2) * 8 \
+ struct.pack('<I', 0x00005FA3) * 8 + struct.pack('<I', 0x00001073) \
* 8 + struct.pack('<I', 0x00002046) * 8 + struct.pack('<I',
    0x00003175) * 8 + struct.pack('<I', 0x000005A5) * 8 \
+ struct.pack('<I', 0x000054CF) * 8 + struct.pack('<I', 0x00001B4E) \
* 8 + struct.pack('<I', 0x0000264E) * 8 + struct.pack('<I',
    0x00003A88) * 8 + struct.pack('<I', 0x00002C1A) * 8 \
+ struct.pack('<I', 0x000022D6) * 8 + struct.pack('<I', 0x000019C0) \
* 8 + struct.pack('<I', 0x00002B57) * 8 + struct.pack('<I',
    0x00003C82) * 8 + struct.pack('<I', 0x00007C61) * 8 \
+ struct.pack('<I', 0x0000530F) * 8 + struct.pack('<I', 0x00007AD2) \
* 8 + struct.pack('<I', 0x00007414) * 8 + struct.pack('<I',
    0x00001ADD) * 8 + struct.pack('<I', 0xD6ADBCEF) * 8 \
+ struct.pack('<I', 0x00003E66) * 8 + struct.pack('<I', 0x000061DD) \
* 8 + struct.pack('<I', 0x00002330) * 8 + struct.pack('<I',
    0xDEADBEEF) * 8 + struct.pack('<I', 0x0000475D) * 8 \
+ struct.pack('<I', 0x00002A4F) * 8 + struct.pack('<I', 0x00001F15) \
* 8 + struct.pack('<I', 0x00001162) * 8 + struct.pack('<I',
    0xE3ADBEEF) * 8

buf = open(FILENAME, "rb").read()
xbuf = xor_cipher(buf, key)
open(FILENAME + ".out", "wb").write(xbuf)


遍历

看看固件的标头,我们可以做一些猜测(所有值存储在小端):

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000  04 83 57 48 9C 3B 01 00 C8 CF 28 00 00 1E 2B 6A  .ƒWHœ;..ÈÏ(...+j
00000010  47 00 0B 00 80 01 00 00 E4 CF 28 00 38 FE 28 00  G...€...äÏ(.8þ(.



0x48578304:可能是签名
0x00013B9C:几乎固件文件长度的长度(最初为0x13C00) br />
如果我们进一步查看固件hexdump,并假设我们正在处理CORTEX设备或类似产品,则在偏移量0x20处,我们可能会认为存在一个向量表:




0x200036CF:初始SP值
0x08001B3E:重置矢量
0x0800D5F2:NMI矢量
0x0800D5A4:硬故障
0x0800D5A2:内存管理故障
0x0800D5A0:总线故障
0x0800D5BE:使用故障
0x000026EF:保留
0x0000565C:保留
0x0000565C: RESERVED
0x0000565C:RESERVED

在这里我们可以注意到一些问题:


为什么初始SP值未对齐?
为什么保留的向量不为NULL?

此时,greping字节序列EF 26 00 00会出现在一个有趣的地方:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000020  CF 36 00 20 3E 1B 00 08 F2 D5 00 08 A4 D5 00 08  Ï6. >...òÕ..¤Õ..
00000030  A2 D5 00 08 A0 D5 00 08 BE D5 00 08 EF 26 00 00  ¢Õ.. Õ..¾Õ..ï&..


我们看到0x000026EF的值重复了8次,而0x0000565C(另一个保留向量)的值重复了8次,还有一些其他值也被重复了。

有我们位于某些ZERO页面中的机会很大,这是一个应用于整个文件的简单XOR密码。

也许XOR密码密钥的格式如下: />
密钥[0]重复8次
密钥[1]重复8次
密钥[2]重复8次
...
密钥[63]重复8次

通过查看所有存储值0x000026EF的偏移量,我们可以计算它们之间的增量,我们可以猜测密钥长度为0x800字节。

我们可以找到3个版本的互联网上的固件: >
为了提取完整密钥,我们将每个固件文件分成0x800字节块的块,并计算每0x20字节重复的每0x04字节的出现次数。

我们能够提取完整密钥,需要使用以前的方法进行一些调整:


密钥[34] = 0x2BB2
密钥[38] = 0x3175
keys [54] = 0xD6ADBCEF#4个字节的键... wat!?
key [58] = 0xDEADBEEFÂ#4个字节的key ... wat!键[62] = 0x1162
键[63] = 0xE3ADBEEF #4个字节的密钥... wat! 0x08000000和文件偏移量0x20,我们可以找到有趣的反汇编:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00012000  E7 1A 00 00 E7 1A 00 00 E7 1A 00 00 E7 1A 00 00  ç...ç...ç...ç...
00012010  E7 1A 00 00 E7 1A 00 00 E7 1A 00 00 E7 1A 00 00  ç...ç...ç...ç...
00012020  EF 26 00 00 EF 26 00 00 EF 26 00 00 EF 26 00 00  ï&..ï&..ï&..ï&.. << 0x000026EF REPEATED 4 TIMES
00012030  EF 26 00 00 EF 26 00 00 EF 26 00 00 EF 26 00 00  ï&..ï&..ï&..ï&.. << 0x000026EF REPEATED 4 TIMES
00012040  5C 56 00 00 5C 56 00 00 5C 56 00 00 5C 56 00 00  \V..\V..\V..\V..
00012050  5C 56 00 00 5C 56 00 00 5C 56 00 00 5C 56 00 00  \V..\V..\V..\V..
00012060  09 11 00 00 09 11 00 00 09 11 00 00 09 11 00 00  ................
00012070  09 11 00 00 09 11 00 00 09 11 00 00 09 11 00 00  ................
00012080  18 56 00 00 18 56 00 00 18 56 00 00 18 56 00 00  .V...V...V...V..
00012090  18 56 00 00 18 56 00 00 18 56 00 00 18 56 00 00  .V...V...V...V..


0xE000ED08是向量表偏移量寄存器,值0x8003C00写入其中。
现在,我们可以在加载地址0x8003C00和文件偏移量0x20的低端字节序中使用ARM处理器重新加载固件,然后我们就可以开始进行逆向工程。