Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
8cryptdo — 8BitDo 固件加密的逆向工程文档和工具 | Kitploit
工具/GitHubGitHub/aeromodes/8cryptdo
嵌入式系统安全加密/解密工具物联网安全逆向工程密码学硬件与物联网安全二进制分析论文与研究固件分析
GitHubaeromodes/8cryptdo

8cryptdo

8BitDo 固件加密的逆向工程文档和工具

291天前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
查看仓库

8CryptDo

针对 8BitDo 在若干基于 GD32 的产品中所使用固件加密的逆向工程文档与工具。

有关应用本文档所述算法的解密与重新加密工具,请参见 8cryptdo.py。

该工具可能使创建自定义固件成为可能。然而,本仓库不包含任何官方固件数据。用于下载最新固件文件的脚本可在 fwupd/8bitdo-firmware 仓库中找到,其中还归档了一些较旧的固件二进制文件。

头部

从表面上看,固件 .dat 文件存在一个 28 字节的明文头部。

root@kitploit:~
import struct

raw = open("sn30-v2_07.dat", "rb").read()
version, addr, payload_len = struct.unpack("<III", raw[:12])
# version     = 207        -> firmware == v2.07
# addr        = 0x08003400 -> destination (probably)
# payload_len = 99328      -> section payload length in bytes

在较新的固件文件中,这些字段之后还有一个未知的 32 位值。头部的其余部分为零。

无密钥链式层

在某些固件文件上会出现一种模式:存在一个将每个字混入下一个字的层。

root@kitploit:~
def rotr(x, r):
    return ((x >> r) | (x << (32 - r))) & 0xFFFFFFFF

dechained = [words[0]]
for i in range(1, len(words)):
    dechained.append(words[i] ^ rotr(words[i - 1], 3))

这种链式处理在每个 128 字块边界处重置。块中的第一个字(pos = 0)没有前驱项,而 pos = 1..127 则从前一个字进行链式处理。

无密钥层不会增加任何安全性。将其剥离后会显露出更清晰的中间结果(dechained),此时唯一隐藏明文的就是密钥流。

root@kitploit:~
P[i] = dechained[i] ^ keystream[i]

密钥流分解

fwupd/8bitdo-firmware 仓库中有一份较旧固件的目录。这些固件似乎都带有链式层。

在移除链式层后将其中一些固件相互 XOR,会显露出大段连续的零以及可读的结构。

root@kitploit:~
plaintext_diff = [a ^ b for a, b in zip(dechained_a, dechained_b)]

如果两者使用相同的密钥流……

root@kitploit:~
a = dechained_a[i] ^ dechained_b[i]
b = (P_a[i] ^ keystream[i]) ^ (P_b[i] ^ keystream[i])
c = P_a[i] ^ P_b[i]
assert a == b == c

密钥流就会相互抵消。

这是一个经典的多次一密。这样的密钥流只有在仅使用一次时才是安全的。出于某种原因,8BitDo 决定不仅使用按产品区分的密钥流,而是在多个产品之间使用单一密钥流。这显著削弱了其密码的安全性,并使此处的其余分析成为可能。

读取密钥流

一旦你知道两个明文被 XOR 在一起,你就可以猜测其中一个的一部分,并减去你的猜测来读取另一个。这样就能在该位置恢复出 keystream。

root@kitploit:~
keystream[i] = dechained[i] ^ P[i]
for fw in all_fw:
    P[fw][i] = dechained[fw][i] ^ keystream[i]

用字符串来做会最容易,但这里最有效的技巧是 ARM 指令的跨版本重定位。一个函数可能以偏移后的位置出现在两个固件版本中。在某个版本中密钥流已知的位置,你可以在另一个版本中对齐共享代码,并获取新的密钥流值。

利用这一点,已知密钥流字数从约 1,500 个提升到数千个,大约占固件的 18% 可读。

密钥流规则

有了更大样本的 keystream 后,模式变得可见。它有一些相隔 16 个字的位置,以几乎恒定的量递增,并且每个字节以可预测的大小移动。

通过反复试验,发现一个块中的每个密钥流字都遵循一个公式:

root@kitploit:~
STEP  = 0x92A753FA
A_MUL = 0x80000301
ROT   = 18
UINT32_MAX  = 0xFFFFFFFF

def rotr(x, r):
    r &= 31
    return ((x >> r) | (x << (32 - r))) & UINT32_MAX if r else x

def block_base(block):
    return (block * A_MUL + block // 2) & UINT32_MAX

def keystream(block, pos, block_key):
    counter = (block_base(block) + pos * STEP) & UINT32_MAX
    mask = rotr(block_key, ROT * pos)
    return counter ^ mask

在一个块内,遍历一个简单的计数器(block_base(block) + pos*STEP),并将其与一个从 block_key 开始、每步旋转 18 位的掩码进行 XOR。整个 128 字的块由一个 32 位数字 block_key 决定。

一个已知字就能解锁整个块。重新排列公式可从任意已知密钥流字得出 block_key:

root@kitploit:~
def block_key_from_known(block, pos, known_keystream):
    counter = (block_base(block) + pos * STEP) & UINT32_MAX
    return rotr(known_keystream ^ counter, -ROT * pos & 31)

种子表

每个块的密钥从何而来?它分解为两个 16 位半部,取自一个 256 项的种子表:

root@kitploit:~
def block_key_of(block, seed_table):
    hi = seed_table[block]
    lo = seed_table[block ^ 0xF9]
    return (hi << 16) | lo

种子表本身似乎没有特定的公式,它很可能是存储在每个设备引导加载程序某处的 512 个常量字节。然而,与 0xF9 的 XOR 提供了一个漂亮的副作用:镜像律。一个块及其配对块 block ^ 0xF9 由相同的两个表项交换构建而成。

root@kitploit:~
mirror = block_key_of(block ^ 0xF9, seed_table)
assert mirror == rotr(block_key_of(block, seed_table), 16)

这破解了此前未知的区域。与其盲目猜测完整的 32 位 block_key,你可以猜测两个 16 位半部,并对哪种选择能将两个块都变成可信的字符串或 ARM 指令进行评分。

由此,使用此特定密码的所有固件数据实现了 100% 覆盖。

剩余工作

此密码似乎覆盖了 2020 年前后的大多数 8BitDo 产品,以及仍在使用 GD32 SoC 的当前产品(包括 SN30 Pro)。

一些设备如 M30 和 Zero 2 似乎是多段的,其中一些固件数据使用此密码,而另一些固件使用不同的加密方案。

较新的设备,至少那些使用不同 SoC 的设备,似乎具有更强的固件加密方案。一些初步分析表明它可能是共享的,但不确定。需要进一步调查。

下载工具