Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
8cryptdo — Documentação e ferramentas de engenharia reversa para a criptografia de firmware da 8BitDo | Kitploit
Ferramentas/GitHubGitHub/aeromodes/8cryptdo
Segurança de Sistemas EmbarcadosFerramentas de Criptografia/DescriptografiaSegurança IoTEngenharia ReversaCriptografiaSegurança de Hardware e IoTAnálise de BináriosPapers e PesquisaAnálise de Firmware
GitHubaeromodes/8cryptdo

8cryptdo

29há 1 diaAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Documentação e ferramentas de engenharia reversa para a criptografia de firmware da 8BitDo

Ver Repositório

8CryptDo

Documentação e ferramentas de engenharia reversa para a criptografia de firmware usada pela 8BitDo em vários produtos baseados em GD32.

Consulte 8cryptdo.py para uma ferramenta de descriptografia e recriptografia que aplica o algoritmo descrito neste documento.

A ferramenta possibilita potencialmente a criação de firmware personalizado. No entanto, este repositório não inclui nenhum dos dados oficiais de firmware. Um script para baixar os arquivos de firmware mais recentes pode ser encontrado no repositório fwupd/8bitdo-firmware, com alguns binários de firmware mais antigos arquivados.

Cabeçalho

Observando um arquivo de firmware .dat superficialmente, há um cabeçalho de texto simples de 28 bytes presente.

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

Com arquivos de firmware recentes, há um valor de 32 bits desconhecido colocado após estes. O restante do cabeçalho é zero.

Camada de encadeamento sem chave

Em certos arquivos de firmware, um padrão aparece: há uma camada que mistura cada palavra na seguinte.

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

Este encadeamento é reiniciado a cada limite de bloco de 128 palavras. A primeira palavra de um bloco (pos = 0) não tem termo predecessor, e pos = 1..127 encadeiam a partir da palavra anterior.

Uma camada sem chave não adiciona segurança. Removê-la revela um intermediário mais limpo (dechained), onde a única coisa que ainda esconde o texto simples é o keystream.

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

Fatoração do keystream

O repositório fwupd/8bitdo-firmware tem um catálogo de firmware mais antigo. Esses firmwares pareciam todos ter a camada de encadeamento.

Aplicar XOR entre alguns deles após remover a camada de encadeamento revela longas sequências de zeros e estrutura legível.

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

Se o mesmo keystream for usado em ambos...

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

o keystream se cancela.

Este é um clássico many-time pad. Tal keystream só é seguro se usado uma vez. Por alguma razão, a 8BitDo decidiu usar não apenas um keystream por produto, mas um único keystream em vários produtos. Isso enfraqueceu significativamente a segurança de sua cifra e tornou possível o restante da análise aqui apresentada.

Lendo o keystream

Uma vez que você sabe que dois textos simples estão combinados por XOR, você pode adivinhar parte de um e subtrair seu palpite para ler o outro. Isso recupera o keystream naquele ponto.

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

Isso seria mais fácil com strings, mas o truque mais produtivo aqui foi a relocação entre versões de instruções ARM. Uma função pode aparecer em duas versões de firmware em posições deslocadas. Onde o keystream é conhecido em uma versão, você pode alinhar o código compartilhado em outra e colher novos valores de keystream.

Explorar isso elevou as palavras de keystream conhecidas de ~1.500 para vários milhares, aproximadamente 18% do firmware legível.

Regra do keystream

Com uma amostra maior do keystream, padrões tornaram-se visíveis. Havia posições com 16 palavras de distância que avançavam por uma quantidade quase constante, e cada byte se movia por um tamanho previsível.

Por tentativa e erro, descobriu-se que cada palavra de keystream em um bloco segue uma fórmula:

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

Dentro de um bloco, percorra um contador simples (block_base(block) + pos*STEP) e aplique XOR com uma máscara que começa em block_key e rotaciona 18 bits a cada passo. Todo o bloco de 128 palavras é determinado por um único número de 32 bits, block_key.

Uma palavra conhecida desbloqueia um bloco inteiro. Reorganizar a fórmula fornece o block_key a partir de qualquer palavra de keystream conhecida:

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)

Tabela de sementes

De onde veio a chave de cada bloco? Ela se fatora em duas metades de 16 bits extraídas de uma tabela de sementes de 256 entradas:

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

A própria tabela de sementes parece não ter uma fórmula específica, provavelmente são 512 bytes constantes armazenados em algum lugar no bootloader de cada dispositivo. No entanto, o XOR com 0xF9 fornece um belo efeito colateral: uma lei de espelho. Um bloco e seu parceiro block ^ 0xF9 são construídos a partir das mesmas duas entradas da tabela trocadas.

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

Isso decifrou regiões anteriormente desconhecidas. Em vez de adivinhar um block_key completo de 32 bits às cegas, você pode adivinhar duas metades de 16 bits e pontuar qual escolha transforma ambos os blocos em strings ou instruções ARM verossímeis.

Com isso, obteve-se 100% de cobertura de todos os dados de firmware que usam essa cifra específica.

Trabalho restante

Esta cifra parece cobrir a maioria dos produtos 8BitDo por volta de 2020, e produtos atuais que ainda usam SoCs GD32 (incluindo o SN30 Pro).

Alguns dispositivos como o M30 e o Zero 2 parecem ser multisseção, com alguns dados de firmware usando esta cifra e outro firmware com um esquema de criptografia diferente.

Dispositivos mais recentes, pelo menos aqueles com um SoC diferente, parecem ter um esquema de criptografia de firmware mais forte. Algumas análises ingênuas mostram que ele pode potencialmente ser compartilhado, mas não há certeza. É preciso investigar isso mais a fundo.

Baixar ferramenta