提取固件的问题

逆向工程 固件 固件分析
2021-07-05 03:27:57

我目前正在尝试对电子烟的固件进行逆向工程,但我找不到任何常用工具的线索。继承人到目前为止我发现的一切。

  • Binwalk 不识别任何东西,即使是-E-A

  • 熵 = 每字节 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

1个回答

TL; 博士

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

用于获取解密固件的 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)

演练

查看固件的标头,我们可以做出一些猜测(所有值都存储在 Little-endian 中):

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)

如果我们进一步查看固件 hexdump 并假设我们正在处理 CORTEX 设备或类似的东西,在偏移量 0x20 处,我们可以认为有一个向量表:

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  ¢Õ.. Õ..¾Õ..ï&..
  • 0x200036CF:初始SP值
  • 0x08001B3E:重置向量
  • 0x0800D5F2:NMI向量
  • 0x0800D5A4:硬故障
  • 0x0800D5A2:内存管理故障
  • 0x0800D5A0:总线故障
  • 0x0800D5BE:使用错误
  • 0x000026EF:保留
  • 0x0000565C:保留
  • 0x0000565C:保留
  • 0x0000565C:保留

在这里我们可以注意到几个问题:

  1. 为什么初始 SP 值不对齐?
  2. 为什么保留向量不为NULL?

在这一点上,grepp 字节序列EF 26 00 00在一个有趣的地方结束:

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..

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

我们很有可能在一些零页面内,这是一个简单的 XOR 密码,应用于整个文件。

可能 XOR 密钥的形式如下:

  • key[0] 重复 8 次
  • key[1] 重复 8 次
  • key[2] 重复 8 次
  • ...
  • key[63] 重复 8 次

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

希望我们可以在互联网上找到 3 个版本的固件:

  • f553e034ba70138c270dac03e7dcda6f: HW718V38_20170216.firmware
  • 7440def1011f50749ff7f1ca8f7bcced: HW718V39_20170221.firmware
  • 0850c80192dcad7a0ce9a5446fb97f16: HW718V40_20171008.firmware

为了提取完整的密钥,我们将每个固件文件分成 0x800 字节的块,并计算每 0x20 字节重复的每 0x04 字节的出现次数。

有了这个,我们就可以提取完整的密钥,使用以前的方法需要进行一些调整:

  • 键[34] = 0x2BB2
  • 键[38] = 0x3175
  • 键[54] = 0xD6ADBCEF # 4 字节密钥......哇!?
  • 键[58] = 0xDEADBEEF # 4 字节键...哇!?
  • 键[60] = 0x2A4F
  • 键[62] = 0x1162
  • 键[63] = 0xE3ADBEEF # 4 字节键...哇!?

最后一步是知道加载地址的确切值,通过在加载地址 0x08000000 和文件偏移量 0x20 处使用处理器 ARM little-endian 开始反汇编固件,我们可以找到一个有趣的反汇编:

ROM:0800B854                 LDR             R1, =0xE000ED08
ROM:0800B856                 LDR             R0, =0x8003C00
ROM:0800F458                 STR             R0, [R1]

0xE000ED08 是向量表偏移寄存器,其中写入值 0x8003C00。

现在,我们可以在加载地址 0x8003C00 和文件偏移量 0x20 处使用 little-endian 的 ARM 处理器重新加载固件,然后我们可以开始进行逆向工程。