Post

BSides Badge 2025

Popis toho, jak jsem postupoval při analýze BSides Badge, zejména se pak jedná o své začátečnické poznámky a seznamování se s nástrojem Ghidra.

BSides Badge 2025

BSides Prague 2025

Upozornění: Článek je průběžně upravován.

Jedná se o druhou konferenci pořádanou v Praze, jejíž zaměření je kyber-bezpečnost.

Hlavní téma tohoto článku je ale elektronická badge, kterou bylo možno zakoupit při koupi vstupenky, kdy měli návštěvníci tak možnost získat i elektronickou badge, která byla sama o sobě zajímavá. Toho jsem následně využil. Zejména pak k rozšíření si technických znalostí, se kterými bych se s vámi rád podělil.

bsides-badge

Jak to celé fungovalo

Ve zkratce se jednalo o tři důležité komponenty ESP32-C3, X, token Vaším úkolem bylo připojit říkejme tomu třeba hw token do svojí badge, tím se zařízení spustilo. Hlavní myšlenkou bylo sbírat klíče ostatních účastníků a sbírat tak XP body, čím více lidí tím více XP. HW Token zároveň sloužil i jako vstupní bod k CTF, čemuž jsem bohužel nevěnoval tolik času, kolik bych rád. Dalším problémem bylo, že 3xAAA baterie, které byli dodány, byli nedostatečné a zařízení tak často končilo v nekonečné smyčce boot sekvence. Zařízení se připojovalo k Wi-Fi bsidesprg25 s heslem herewegoagain a ověřovalo se tak vůči serveru na https://apixp.bsides.cz

Stranou teď vše ostatní, mě zajímá ESP32.

esp32c3_scheme

Specifikace

1
2
3
4
CPU: ESP32-C3-MINI-1U
Display: 1.28inch 240x240 IPS
Frequency 160Mhz, 400KB SRAM, 384KB ROM
Flash size 4MB

Analýza a identifikace

Při připojení k PC přes USB-C jsem potřeboval zjistit, jak se samotné zařízení tváří pro OS.:

1
2
3
4
5
6
7
8
9
10
sudo dmesg
[ 1062.048138] usb 3-7: new full-speed USB device number 10 using xhci_hcd
[ 1062.173487] usb 3-7: New USB device found, idVendor=303a, idProduct=1001, bcdDevice= 1.01
[ 1062.173497] usb 3-7: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 1062.173501] usb 3-7: Product: USB JTAG/serial debug unit
[ 1062.173504] usb 3-7: Manufacturer: Espressif
[ 1062.173506] usb 3-7: SerialNumber: 98:XX:XX:XX:XX:XX
[ 1062.204015] cdc_acm 3-7:1.0: ttyACM0: USB ACM device
[ 1062.204059] usbcore: registered new interface driver cdc_acm
[ 1062.204062] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

Informace o čipu

Pro stažení firmware je potřeba mít esptool.py a pro zjištění základních informací o čipu.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
esptool.py --port /dev/ttyACM0 flash_id

esptool.py v4.8.1
Serial port /dev/ttyACM0
Connecting...
Detecting chip type... ESP32-C3
Chip is ESP32-C3 (QFN32) (revision v0.4)
Features: WiFi, BLE, Embedded Flash 4MB (XMC)
Crystal is 40MHz
MAC: 98:XX:XX:XX:XX:XX
Uploading stub...
Running stub...
Stub running...
Manufacturer: 20
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

Stažení firmware

Pro to, abychom si dále mohli se stávajícím firmware hrát jsem si stáhl jeho image pomocí.:

1
esptool.py --chip esp32c3 --port /dev/ttyACM0 read_flash 0x000000 0x400000 esp32c3_dump_4mb.bin
  • Proč 0x000000? Protože to je začátek paměti
  • Prox 0X400000? Protože to je konec paměti v hexadecimálním formátu. -> 4 x 1024 x 1024 = 4 194 304 bajtů = 4MB
  • tzn. že čteme celou paměť od 0-4 MB

Kontrola, zda je firmware nějakým způsobem šifrovaný.:

1
espefuse.py --chip esp32c3 --port /dev/ttyACM0 summary

SECURE_BOOT_EN <- jestli je aktivní secure boot DIS_DOWNLOAD_MANUAL_ENCRYPT <- Zakazuje čtení přes UART po zapnutí šifrování

Soubor esp32c3_dump_4mb.bin si raději rozkopíruji, ať si jej omylem nepřepíšu.

1
2
md5sum esp32c3_dump_4mb.bin
c418f0f71a50554739367c5ba012b1d8  esp32c3_dump_4mb.bin
  • Pokud by jste si chtěli též s dumpem pohrát tak je možné jej stáhnout zde.: GitHub

Extrakce souborů pomocí binwalk

Pomocí binwalk jsme schopni vyextrahovat další zajímavé soubory.:

1
binwalk -e esp32c3_dump_4mb.bin
  • Tím získáme několik obrázků .png

Identifikace partitions

Ruční metoda

1
esptool.py --chip esp32c3 --port /dev/ttyACM0 read_flash 0x8000 0x1000 partitions.bin

esp32 partitions začíná na offsetu 0x8000 a končí na 0x1000

1
2
3
4
5
6
strings partitions.bin

otadata
app0
spiffs
coredump

Metoda pomocí nástroje

Používám nástroj GitHub

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
python3 esp32_image_parser.py show_partitions ../esp32c3_dump_4mb.bin
reading partition table...
entry 0:
  label      : nvs
  offset     : 0x9000
  length     : 20480
  type       : 1 [DATA]
  sub type   : 2 [WIFI]

entry 1:
  label      : otadata
  offset     : 0xe000
  length     : 8192
  type       : 1 [DATA]
  sub type   : 0 [OTA]

entry 2:
  label      : app0
  offset     : 0x10000
  length     : 3145728
  type       : 0 [APP]
  sub type   : 16 [ota_0]

entry 3:
  label      : spiffs
  offset     : 0x310000
  length     : 917504
  type       : 1 [DATA]
  sub type   : 130 [unknown]

entry 4:
  label      : coredump
  offset     : 0x3f0000
  length     : 65536
  type       : 1 [DATA]
  sub type   : 3 [unknown]

MD5sum: 
98f492a471b542a1572d58e33e614dc8
Done

NVS Partition

Jedná se o non-volatile-storage partition, kde jsou většinou uloženy přístupové údaje, wi-fi údaje, uživatelské data, ale i historické, občas nejsou správně promazány.

  • Jak ji získat?
  • Použijeme nástroj esp32_image_parser GitHub
  • Doufal jsem, že tady najdeme potřebný certifikát, díky kterému badge mohla komunikovat s backendem šifrovaně…
1
python3 esp32_image_parser.py dump_nvs ../../esp32c3_dump_4mb.bin -partition nvs -nvs_output_type json

Wi-Fi SSID

1
2
3
4
5
6
7
8
9
10
11
12
      {
        "entry_state": "Written",
        "entry_ns_index": 2,
        "entry_ns": "nvs.net80211",
        "entry_type": "BLOB_DATA",
        "entry_span": 3,
        "entry_chunk_index": 0,
        "entry_key": "sta.ssid",
        "entry_data_type": "BLOB_DATA",
        "entry_data_size": 36,
        "entry_data": "CwAAAGJzaWRlc3ByZzI1AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      }
1
2
echo "CwAAAGJzaWRlc3ByZzI1AAAAAAAAAAAAAAAAAAAAAAAAAAAA" | base64 -d
bsideprg25

Wi-Fi PASS

1
2
3
4
5
6
7
8
9
10
11
12
      {
        "entry_state": "Written",
        "entry_ns_index": 2,
        "entry_ns": "nvs.net80211",
        "entry_type": "BLOB_DATA",
        "entry_span": 4,
        "entry_chunk_index": 0,
        "entry_key": "sta.pswd",
        "entry_data_type": "BLOB_DATA",
        "entry_data_size": 65,
        "entry_data": "aGVyZXdlZ29hZ2FpbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
      }
1
2
echo "aGVyZXdlZ29hZ2FpbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" | base64 -d
herewegoagain

SPIFFS partition

Bohužel certifikát jsme stále nenašli, tak půjdeme dál …

SPIFFS (Serial Peripheral Interface Flash File System) je zkrátka uložiště, které zůstává i po odpojení napájení u ESP32. Může být použito pro logy, ukládání souborů, nebo uživatelských konfigurací. Třeba se zde může uchovávat i aktualizace pro ESP32, které bylo aktualizováno pomocí OTA.

Více o SPIFFS zde

Potřebujeme vyextrahovat partition spiffs:

1
python3 esp32_image_parser.py dump_partition ../../esp32c3_dump_4mb.bin -partition spiffs -output esp32c3_spiff_filesystem 
  • Nástroj na čtení SPIFFS GitHub

pokus č.1 Po úspěšné kompilaci nástroje mkspiffs jsem zkusil.:

1
2
3
4
5
6
7
8
9
./mkspiffs -u output ../../esp32_image_parser/esp32c3_spiff_filesystem
/bid.txt	 > ./output/bid.txt	size: 18 Bytes
/kid.txt	 > ./output/kid.txt	size: 18 Bytes
/badgename.txt	 > ./output/badgename.txt	size: 9 Bytes
/uid.txt	 > ./output/uid.txt	size: 10 Bytes
/flags.txt	 > ./output/flags.txt	size: 3 Bytes
/totps.txt	 > ./output/totps.txt	size: 48 Bytes
/avatar.png	 > ./output/avatar.png	size: 20692 Bytes
/paired.txt	 > ./output/paired.txt	size: 3 Bytes
  • Bohužel, žádný ze souborů nenabídl zajímavé data, jen pár hexadecimálních znaků, avatar.png rozhodně neobsahoval magic bytes obrázku PNG apod.
  • I přesto, že je tam pár náznaků, tak se mi nepovedlo obrázek zrekonstruovat…

OTADATA

Tady by se měli uchovávat nové firmware provedené via OTA když si vzpomeneme na partition map:

1
2
3
4
5
6
entry 1:
  label      : otadata
  offset     : 0xe000
  length     : 8192
  type       : 1 [DATA]
  sub type   : 0 [OTA]

Tak pomocí hexdump dojdeme k závěru, že na badge neproběhla žádná OTA aktualizace, protože 0x00-0x03 obsahuje první sekvenci 01 00 00 00 a další sekvence nejsou zaplněny. ff ff ff ff

1
2
3
4
hexdump -C -n 32 -s 0xe000 esp32c3_dump_4mb.bin
0000e000  01 00 00 00 ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
0000e010  ff ff ff ff ff ff ff ff  ff ff ff ff 9a 98 43 47  |..............CG|
0000e020

Ghidra

Záleží co zde chceme analyzovat, sám jsem zatím nevyřešil vnitřní otázku, zda je lepší analyzovat celý dump esp32c3_dump_4mb.bin a nebo až vyextrahovanou partition app0.

  • Tak pokud používáme doplněk pro ghidru viz. níže tak můžeme použít celý dump, jinak je lepší analyzovat konkrétní partition app0 protože se pak vyhneme složitému mapování paměti.
  • Doplněk pro Ghidru k importování ESP32 flash images GitHub

    Bez tohoto doplňku jsem měl problém s tím namapovat správně paměť. Analýza takového firmware pak dostala úplně nový rozměr.

ghidra-strings

Dekompilace

ghidra-fnce

První myšlenkou bylo, že se zkusím něco nového přiučit s Ghidrou a třeba se mi podaří pochopit i to, jak ESP32-C3 formuluje dotazy na backend. Díky tomu se mi podařilo zjistit bohužel jen pár střípků.

  1. Dotaz bude určitě na apixp.bsides.cz
  2. zařízení ESP32 má v sobě uložen PUBLIC RSA KEY, ten mi k ničemu moc není, zejména pokud by byla komunikace šifrovaná…
  3. Dotaz bude s klíčem cmd a hodnota klíče by měla být "getinfo"
  4. Bude zde taky klíč kid který bude pro každou badge unikátní, a je pravděpodobně načítána pávě z hardwarového klíče, který se do badge vloží.
  5. Badge bude očekávat v JSON odpovědi badgename, uid, flags, score.

  • Super, to bychom měli, ale jak si to doopravdy potvrdit a hlavně jak zjistit onen kid, protože ze samotného hardwarového klíče tuto hodnotu zatím nevím jak zjistit.

  • Mohl bych se tedy vrátit k myšlence Man-In-The-Middle útoku, resp odposlechu pomocí Access point a třeba se něco dozvím …

Evil AP

Myšlenka je taková, že vytvořím AP, které bude nastaveno dle toho, co očekává badge. tedy.:

1
2
BSSID: bsidesprg25
Password: herewegoagain
  • Kvůli své lenivosti jsem vytáhl PineApple Mark VII, ale byl bych to schopný udělat i s obyčejným Wi-Fi donglem a síťovým připojením pomocí nástroje EvilTwin

  • Budu poslouchat provoz a zjistím přesnou strukturu HTTP požadavků, které ze zařízení odchází.

  • Tuto metodu jsem nakonec použil s připojením k internetu, abych rovnou získal i případnou odpověď, a taky hlavně proto, protože se mi v offline režimu nepodařilo ESP32 donutit k dotazu na apixp.bsides.cz.

  • Ač jsem se této metodě bránil, protože jsem žil v domnění, že bude využito HTTPS. Já bych tak viděl stejně jen dotaz na DNS a pak navázání spojení a tím by to pro mě končilo… Tak jsem se konečně odhodlal zvolit tuto metodu, a zjistil jsem, že komunikace probíhá jen pomocí HTTP a žádné šifrování zde není … HURÁ! XoXo

wireshark

  • Následně jsem si pak zopakoval akci v BurpSuite, s tím že zkusím zjistit, zda neexistuje více cmd příkazů, bohužel jsem další možný nenašel.

HTTP REQUEST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /?cmd=getinfo&kid=CDAB356475BC77CC HTTP/2
Host: apixp.bsides.cz
Sec-Ch-Ua: "Not:A-Brand";v="24", "Chromium";v="134"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Content-Length: 2

HTTP REPLY

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/2 200 OK
Date: Fri, 11 Apr 2025 11:28:39 GMT
Server: ATS
Vary: User-Agent,Accept-Encoding
Cache-Control: max-age=0, no-cache, no-store, must-revalidate
Expires: Sat, 1 Jan 2000 01:00:00 GMT
Pragma: no-cache
Content-Length: 95
Content-Type: application/json
Age: 0

{"status":"success","badgename":"Charlie","uid":"22235234","flags":0,"score":{"*":60,"cea":60}}
This post is licensed under CC BY 4.0 by the author.