WHY2025 CTF: Badge CrackMe

Écrit par l’équipe HackGyver – Août 2025

1: Introduction

Lors de notre participation au CTF de la WHY2025, une épreuve de rétro-inginénerie était proposée
sur le badge de l'évènement.

La description de l'épreuve était:
We found an ELF file you run on your shiny new WHY2025 badge.
Can you figure out the password and get the flag?

Avec un lien de téléchargement vers le fichier ELF.

Le badge de la WHY2025 utilise un Espressif ESP32-P4-32MB, soit une architecture RISC-V.
Le code du firmware ainsi que le hardware sont open source, la team badge nous fournit une SDK pour développer nos propres applications.
Quelques applications sont aussi fournies de base dans le badge.

Durant notre séjour dans le camp et avant que cette épreuve du CTF soit officiellement disponible, nous avons contribué à l'effort d'assemblage des 3700 badges.


Cela nous a permis notamment de nous familiariser avec le hardware et grâce à nos nombres d'heures passées à les assembler de faire partie des premiers à le recevoir.
Nous avons ainsi esquivé plus de 2 heures de queue lors du démarrage de la distribution des badges.

2: L'épreuve

Après avoir reçu notre badge et effectué la mise à jour initiale de celui-ci, un crack me apparaît dans la liste des applications.
On l'ouvre et on appréciera l'interface démoscene de celui-ci.
Dommage qu'il n'y ait pas de haut-parleur sur le badge, il ne manque plus que de la musique à ce crackme :).

Ce challenge a été codé par Blasty, j'ai notamment reconnu son style via une de ses anciennes productions.
(un exploit qui affecte les imprimantes Canon i-Sensys MF743Cdw.)

3: Reverse

Le crackme est téléchargeable directement depuis l'interface du site du CTF.
On a donc un fichier ELF pour environnement RISC-V, on utilisera Ghidra pour le décompiler.
On tombe rapidement sur le schéma de license

La partie intéressante:

      memset(buffer,0,20);
      memcpy(buffer,&password,buffer_size);
      key = _DAT_5012d164;
      local_50 = _DAT_5012d164;
      rc4(buffer,buffer_size,(byte *)&key,8);
      iVar1 = memcmp(buffer,expected,expected_size);
      if (iVar1 == 0) {
        memset(&DAT_000151b0,0,0x27);
        memcpy(&DAT_000151b0,&DAT_00015184,0x26);
        rc4(&DAT_000151b0,0x26,(byte *)&key,8);
        memset(&decoded_string_part_1,0,0x14);
        memset(&decoded_string_part_2,0,0x14);
        memcpy(&decoded_string_part_1,&DAT_000151b0,0x13);
        memcpy(&decoded_string_part_2,&DAT_000151c3,0x13);
        DAT_000156d8 = 1;
      }
      iVar1 = strcmp(&password,"why2025");
      if (iVar1 != 0) {
        value_set_to_true = 1;
        buffer_size = 0;
        password = 0;
      }

Nous avons ici du RC4, le mot de passe est en hard dans le ELF et a une longueur de 20 caractères.
La clé quant à elle est de 8 bytes à l'adresse 0x5012d164, hors de notre fichier ELF.
Le crackme récupere la clé depuis la région eFuse...
C'est-à-dire qu'il va falloir accéder physiquement au badge pour avoir la clé.
Après le RC4, si celui-ci est correct alors une seconde chaine encodé RC4 est décoder avec cette même clé.

On note donc dans un coin:
12 51 3a 69 13 0d fa a6 ed 75 86 b6 aa 94 1c ff 2c ee ad 27
Ainsi que:
1a 0d 7b 68 59 4b fd b5 b5 71 9d e3 e9 93 0a f4 27 e0 ad 7b d6 78 86 47 3f 4b 98 96 86 85 b4 a8 c0 db fe f9 86 28

Maintenant il faut trouver un moyen de dump la mémoire.
On se rend sur le wiki de la why2025 qui nous explique comment prendre en main notre badge pour développer dessus.
On installe donc Espressif ESP-IDF v5.5

Puis on fait notre outil pour avoir un dump à partir de l'adresse 0x5012D000 sur 0x1000

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

#define EFUSE_BASE_ADDR   0x5012D000u
#define EFUSE_SIZE_BYTES  0x1000u  /* 4 KB */
#define LINE_BYTES        16u

static void hex_dump_region(uintptr_t base, size_t len) {
    volatile const uint8_t *p = (volatile const uint8_t *)base;

    for (size_t off = 0; off < len; off += LINE_BYTES) {
        uint8_t buf[LINE_BYTES];
        size_t chunk = (off + LINE_BYTES <= len) ? LINE_BYTES : (len - off);

        for (size_t i = 0; i < chunk; ++i) buf[i] = p[off + i];

        printf("%08" PRIxPTR "  ", (uintptr_t)(base + off));
        for (size_t i = 0; i < LINE_BYTES; ++i) {
            if (i < chunk) printf("%02X ", buf[i]); else printf("   ");
            if (i == 7) printf(" ");
        }
        printf(" |");
        for (size_t i = 0; i < chunk; ++i) {
            uint8_t c = buf[i];
            putchar((c >= 32 && c <= 126) ? c : &apos;.&apos;);
        }
        printf("|
");
    }
}

int main(int argc, char **argv) {
    (void)argc; (void)argv;
    printf("\n=== [Hackgyver] eFuse raw dump: 0x%08" PRIxPTR " - 0x%08" PRIxPTR " (%u bytes) ===\n",
           (uintptr_t)EFUSE_BASE_ADDR,
           (uintptr_t)(EFUSE_BASE_ADDR + EFUSE_SIZE_BYTES - 1u),
           (unsigned)EFUSE_SIZE_BYTES);

    hex_dump_region(EFUSE_BASE_ADDR, EFUSE_SIZE_BYTES);
    printf("=== End of eFuse dump ===\n");
    return 0;
}

On compile ça, et il n'y a plus qu'à mettre notre outil sur la carte SD dans notre badge.
Puis d'attacher notre moniteur IDF et d'exécuter l'application sur le badge.
Le dump est echo dans la console.

La clé serait donc 88 88 88 88 88 88 88 88.

⚠️ Attention avec RC4

Une clé composée du même octet répété (88, 8888, etc.) donne le même résultat, quelle que soit sa longueur.
Dans notre cas, on peut donc simplement réduire la clé à 0x88.

Il n'y a plus qu'à tester notre clé avec un outil tel que CyberChef
Nous obtenons les chaines: n0 f1r3 h4z4rd h3h3h (le mot de passe)
flag{44300aa1c6c8f345dbc6833fd1e476b9} (la seconde chaine en RC4)

Le memcpy avec 0x13 correspond à la découpe du flag pour le mettre sur deux lignes.

Voilà.
Le mot de passe de ce crackme fait echo a un drama avec les batteries du badge, article.

Le crackme et notre dumper d'efuse sont disponibles ici:
📥 badge_crackme.zip


4: Conclusion

Cette épreuve sur le badge était fun à résoudre.
On avait un mix de reverse, de hardware et un peu de dev pour arriver au flag.
Le fait que la clé soit planquée dans la region eFuse force à jouer directement avec le badge.
Ce n'était pas la seule façon de résoudre ce challenge, mais c’était la plus adaptée à l’esprit de l’épreuve.

On aurait pu par exemple patcher la condition du premier RC4 pour afficher directement le mot de passe
Ou bien recompiler le firmware du badge en modifiant la fonction memcmp afin d'echo les comparaisons.
Si on ne souhaitait pas mettre les mains dans le cambouis, il était aussi possible de bruteforcer la clé RC4.
Connaissant le skill de l’auteur, on devine que ce choix de schéma de licence très rudimentaire était volontaire.
Une épreuve accessible, conçue pour que chacun puisse trouver son chemin vers la solution.
Merci à Blasty pour ce challenge.