Aller au contenu
Tous les challenges
Reverse Résolu

Silent Beacon

Dump flash ESP32-C6 en RISC-V : recalculer le code d'activation du beacon.

Scripts
2 fichiers

Énoncé (résumé)

Un beacon de tracking s’annonce en USB mais refuse de livrer son « provisioning record » sans code d’activation à 8 chiffres hex. Chaque unité recalcule son code à partir de ce qu’elle sait d’elle-même. Fichier : beacon_flash.bin. Un faux flag est visible avec strings.

Reconnaissance

  • file ne reconnaît rien, mais l’en-tête commence par E9 (magic image ESP) et l’octet à l’offset 12 vaut 0x0d : ESP32-C6, donc RISC-V 32 bits (avec extension compressée). Chaîne v5.5.5 : ESP-IDF 5.5.5.
  • C’est un dump complet de flash : table de partitions en 0x8000 (nvs, otadata, app0 en 0x10000, app1, spiffs, coredump). Le dump est tronqué à 0x52d60, la NVS est vide (0xFF).
  • Segments de l’app0 : DROM 0x42030020 (fichier 0x10020), IROM 0x42000020 (fichier 0x30020), IRAM en 0x40800000.
  • strings donne BA26{n0t_th15_0n3_k33p_d1gg1ng} : leurre. À côté, dans la DROM : BA26-BEACON-01 (0x42039d20), LilyGO T7-C6 (0x42039d30), puis 31 octets non imprimables (0x42039d40), le record chiffré.
  • Chaînes « Provisioning record is SEALED / Enter the 8-digit hex activation code » : la routine de vérification est proche.

Point clé

Désassemblage RISC-V avec capstone ; recherche des références lui/auipc + addi vers 0x42039d20–0x42039d40. Elles tombent dans une fonction du code applicatif (≈ 0x42000124) :

  1. h1 = FNV-1a32("LilyGO T7-C6"), h2 = FNV-1a32("BA26-BEACON-01") (offset 0x811c9dc5, prime 0x01000193).
  2. Le code saisi (strtoul, base 16) doit valoir rotl32(h1, 7) ^ h2. Il est donc entièrement dérivable du firmware, sans l’appareil.
  3. Si OK, un générateur de type xorshift est initialisé avec (a4, a5) = (h1, h2) et déchiffre 31 octets par XOR :
    a1 = (a5>>19)|(a4<<13); a6 = a5<<13; a4 ^= a1; a5 ^= a6
    a1 = (a5>>7)|(a4<<25);  a6 = a4>>7;  a5 ^= a1; a4 ^= a6
    a1 = (a5>>15)|(a4<<17); a4 ^= a1
    a5 ^= a5<<17
    out[i] = ct[i] ^ (a5 & 0xff)

Résolution reproductible

python3 -m venv .venv && .venv/bin/pip install capstone   # uniquement pour le désassemblage
python3 -I solve.py files/beacon_flash.bin

Sortie : code d’activation b80fef82 puis le flag.

Script : solve.py (extrait les chaînes et le chiffré du dump, recalcule le code, déchiffre). Outils d’analyse : parse_parts.py (partitions), xref.py (références), dis.py (désassemblage).

Note

Le code n’a pas été testé sur un appareil réel ; le déchiffrement produit directement un flag lisible au bon format.

Code

#!/usr/bin/env python3
# python -I solve.py files/beacon_flash.bin
import sys
d = open(sys.argv[1] if len(sys.argv) > 1 else 'files/beacon_flash.bin', 'rb').read()
M = 0xffffffff
# DROM: vaddr 0x42030020 <-> file 0x10020 ; code ref'd 0x42039d20/30/40
def drom(va): return 0x10020 + (va - 0x42030020)
def cstr(va):
    o = drom(va); return d[o:d.index(b'\0', o)]
s_id, s_board = cstr(0x42039d20), cstr(0x42039d30)
ct = d[drom(0x42039d40):drom(0x42039d40) + 31]
def fnv(b):
    h = 0x811c9dc5
    for c in b: h = ((h ^ c) * 0x01000193) & M
    return h
rotl = lambda x, n: ((x << n) | (x >> (32 - n))) & M
h1, h2 = fnv(s_board), fnv(s_id)          # a4, a5
code = rotl(h1, 7) ^ h2
print('id=%r board=%r' % (s_id, s_board))
print('activation code: %08x' % code)
a4, a5 = h1, h2
out = bytearray()
for c in ct:
    a1 = ((a5 >> 19) | (a4 << 13)) & M; a6 = (a5 << 13) & M
    a4 ^= a1; a5 ^= a6
    a1 = ((a5 >> 7) | (a4 << 25)) & M; a6 = a4 >> 7
    a5 ^= a1; a4 ^= a6
    a1 = ((a5 >> 15) | (a4 << 17)) & M
    a4 ^= a1
    a5 ^= (a5 << 17) & M
    out.append(c ^ (a5 & 0xff))
print(out.decode(errors='replace'))