Solution de ThierryBesancon pour Maintenant, l'ESP !

hardware forensics Wi-Fi

15 juillet 2026

Etape 1 : reconnaissance du terrain.

Il s’agit d’un challenge portant sur un PCAP entre 2 équipements à base de ESP32. L’énoncé indique que ce trafic sera du protocole ESP-NOW. D’où le titre du challenge.

Ce n’est pas mon domaine du tout. Je cherche donc de la documentation sur Internet comme d’habitude.

Avec un PCAP, le mieux serait de trouver un dissector WIRESHARK pour ESP-NOW. Je n’en trouverai pas au final. Par contre, je sais que cela existe car un document en évoque un et un hacker bulgare semble travailler sur un tel dissecteur depuis plusieurs années.

Le document qui me donne en gros la solution du challenge est un PDF disponible à cette adressse web :

https://cdn.prod.website-files.com/645a4534705010e2cb244f50/67e3ab1eec0b2be8f630f641_Nozomi-Networks-WP-ESP-NOW.pdf

La vraie adresse est néanmoins https://www.nozominetworks.com/resources/esp-now-protocol-analysis-and-vulnerability-research

(C’est ce PDF qui évoque un dissecteur LUA pour ESP-NOW mais je n’en ai pas trouvé trace, il n’est peut-être pas open sourcé ?)

Etape 2 : Analyse du PCAP.

Le PCAP commence par des paquets de broadcast qui ne semblent pas intéressants. Vraisemblablement le second ESP32 n’a pas encore démarré sa couche ESP-NOW jusqu’à présent.

On comprend néanmoins au vu de l’ensemble des frames du PCAP que l’ESP32 de macaddr se terminant en d2:9c:d8 parle en premier et que l’autre ESP32 de macaddr se terminant en d3:36:30 lui répond ensuite. Par la suite, je comprendrai qu’en gros le premier ESP32 émet un char et l’autre ESP32 lui fait écho.

Page 7 du PDF, on voit le schéma du paquet WIFI :

MAC Header ............... 24 bytes
Category Code ............ 1 byte --------+
Organization Identifier .. 3 bytes        | Partie Wireless Management
Random values ............ 4 bytes        |
Vendor Specific Content .. 7-257 bytes ---+
FCS ...................... 4 bytes

Page 7 du PDF, on comprend :

MAC Header ............... 24 bytes
Category Code ............ 1 byte ......... 0x7f
Organization Identifier .. 3 bytes ........ 0x18fe34
Random values ............ 4 bytes ........ 4 bytes aléatoires anti rejeu
Vendor Specific Content .. 7-257 bytes .... on trouvera ici toujours 13 bytes
FCS ...................... 4 bytes

Page 8 du PDF, on voit le schéma de la partie Vendor Specific Content :

Element ID ............... 1 bytes
Length ................... 1 byte
Organization Identifier .. 3 bytes
Type ..................... 1 byte
Version .................. 1 byte
Body ..................... 0-250 bytes

Page 8 du PDF, on comprend donc :

Element ID ............... 1 bytes ...... 0xdd
Length ................... 1 byte ....... 0x07
Organization Identifier .. 3 bytes ...... 0x18fe34
Type ..................... 1 byte ....... 0x04
Version .................. 1 byte ....... 0x02
Body ..................... 0-250 bytes .. taille 7 - 3 - 1 - 1 = 2

Avec tout cela, on s’y retrouve facilement dans les frames.

En gros chaque trame intéressante transporte donc un payload de 2 bytes. Sur le principe que cela transportera le flag au final, on va trouver au mieux 2 chars du flag par paquet. Ce sera un char au final.

Si je dumpe le payload de 2 bytes par frame de type Action, on voit :

  • Le premier byte du payload vaut 0x02 ou 0x03 selon qui parle.
  • Le premier byte du payload est un char et on trouve successivement FCSC{...}.

On trouve presque le flag car en effet, il manque parfois des frames. Mais on peut facilement deviner ce qui manque :

Frame  18 [d8-30 SN=18] : 02 46 F
Frame  20 [30-d8 SN=01] : 03 46 F
Frame  21 [d8-30 SN=19] : 02 43 C
Frame  23 [30-d8 SN=02] : 03 43 C
Frame  24 [d8-30 SN=20] : 02 53 S
Frame  26 [30-d8 SN=03] : 03 53 S
Frame  27 [d8-30 SN=21] : 02 43 C
Frame  29 [30-d8 SN=04] : 03 43 C
Frame  30 [d8-30 SN=22] : 02 7b {
Frame  32 [30-d8 SN=05] : 03 7b {
Frame  33 [d8-30 SN=23] : 02 57 W
Frame  35 [30-d8 SN=06] : 03 57 W

>>> manque la frame d8-30 SN=24 => 1

Frame  37 [30-d8 SN=07] : 03 31 1
Frame  38 [d8-30 SN=25] : 02 66 f
Frame  40 [30-d8 SN=08] : 03 66 f
Frame  41 [d8-30 SN=26] : 02 49 I

>>> manque la frame 30-d8 SN=09 => I
>>> manque la frame 30-d8 SN=27 => _

Frame  44 [30-d8 SN=10] : 03 5f _
Frame  45 [d8-30 SN=28] : 02 34 4
Frame  47 [30-d8 SN=11] : 03 34 4
Frame  48 [d8-30 SN=29] : 02 63 c
Frame  50 [30-d8 SN=12] : 03 63 c
Frame  51 [d8-30 SN=30] : 02 54 T
Frame  53 [30-d8 SN=13] : 03 54 T
Frame  54 [d8-30 SN=31] : 02 69 i
Frame  56 [30-d8 SN=14] : 03 69 i
Frame  57 [d8-30 SN=32] : 02 30 0
Frame  59 [30-d8 SN=15] : 03 30 0
Frame  60 [d8-30 SN=33] : 02 6e n
Frame  62 [30-d8 SN=16] : 03 6e n
Frame  63 [d8-30 SN=34] : 02 5f _
Frame  65 [30-d8 SN=17] : 03 5f _
Frame  66 [d8-30 SN=35] : 02 46 F
Frame  68 [30-d8 SN=18] : 03 46 F
Frame  69 [d8-30 SN=36] : 02 72 r
Frame  71 [30-d8 SN=19] : 03 72 r
Frame  72 [d8-30 SN=37] : 02 34 4
Frame  74 [30-d8 SN=20] : 03 34 4
Frame  75 [d8-30 SN=38] : 02 4d M
Frame  77 [30-d8 SN=21] : 03 4d M
Frame  78 [d8-30 SN=39] : 02 65 e
Frame  80 [30-d8 SN=22] : 03 65 e
Frame  81 [d8-30 SN=40] : 02 53 S
Frame  83 [30-d8 SN=23] : 03 53 S
Frame  84 [d8-30 SN=41] : 02 5f _
Frame  86 [30-d8 SN=24] : 03 5f _
Frame  87 [d8-30 SN=42] : 02 38 8
Frame  89 [30-d8 SN=25] : 03 38 8
Frame  90 [d8-30 SN=43] : 02 33 3
Frame  92 [30-d8 SN=26] : 03 33 3
Frame  93 [d8-30 SN=44] : 02 66 f
Frame  95 [30-d8 SN=27] : 03 66 f
Frame  96 [d8-30 SN=45] : 02 30 0

>>> manque la frame 30-d8 SN=28 => 0

Frame  98 [d8-30 SN=46] : 02 64 d
Frame 100 [30-d8 SN=29] : 03 64 d
Frame 101 [d8-30 SN=47] : 02 65 e
Frame 103 [30-d8 SN=30] : 03 65 e

>>> manque la frame d8-30 SN=48 => f

Frame 105 [30-d8 SN=31] : 03 66 f
Frame 106 [d8-30 SN=49] : 02 35 5
Frame 108 [30-d8 SN=32] : 03 35 5
Frame 109 [d8-30 SN=50] : 02 39 9

>>> manque la frame 30-d8 SN=33 => 9

Frame 111 [d8-30 SN=51] : 02 31 1
Frame 113 [30-d8 SN=34] : 03 31 1
Frame 114 [d8-30 SN=52] : 02 66 f
Frame 116 [30-d8 SN=35] : 03 66 f
Frame 117 [d8-30 SN=53] : 02 63 c
Frame 119 [30-d8 SN=36] : 03 63 c
Frame 120 [d8-30 SN=54] : 02 63 c
Frame 122 [30-d8 SN=37] : 03 63 c
Frame 123 [d8-30 SN=55] : 02 65 e
Frame 125 [30-d8 SN=38] : 03 65 e

>>> manque la frame d8-30 SN=56 => e

Frame 127 [30-d8 SN=39] : 03 65 e
Frame 128 [d8-30 SN=57] : 02 61 a
Frame 130 [30-d8 SN=40] : 03 61 a
Frame 131 [d8-30 SN=58] : 02 7d }
Frame 133 [30-d8 SN=41] : 03 7d }

Donc en gros :

  • soit on a le dialogue echo - reply et on un char du flag en double
  • soit on a le echo mais pas le reply mais le echo donne le char du flag
  • soit on a le reply mais pas le echo mais le reply donne le char du flag

En réassemblant, le flag doit donc être :

FCSC{W1fI_4cTi0n_Fr4MeS_83f0def591fcceea}

Bingo. Le flag se valide.

A noter que le challenge est possible car ici le trafic en ESP-NOW est en mode non chiffré. Le PDF explique qu’il existe ce mode en clair et deux modes chiffrés.

QED.

🐒