1 poin oleh GN⁺ 2025-04-16 | Belum ada komentar. | Bagikan ke WhatsApp
  • Untuk mengendalikan langsung air purifier berbasis ESP32 yang terikat pada aplikasi dan cloud pabrikan dari Home Assistant, jalur kendali jarak jauhnya direkayasa balik lalu diganti dengan server lokal
  • Melalui analisis aplikasi, bypass DNS, dan capture Wireshark, diketahui bahwa perangkat mengirim paket UDP ke smartdeviceep.---.com:41014 dan memakai protokol kustom, bukan DTLS standar
  • Lewat koneksi UART dan dump flash 4MB, diperoleh dev_key.key, sertifikat, konfigurasi server, dan konfigurasi WiFi; struktur firmware dianalisis dengan Ghidra dan esp32knife
  • Paket menggabungkan header 13 byte dan CRC-16 2 byte terakhir, pembuatan kunci ECDH/HKDF, AES-128-CBC, serta serialisasi MessagePack; dekripsi berhasil setelah patch firmware membuat shared secret dicetak ke log serial
  • Konfigurasi akhir terdiri dari proxy MITM, server lokal, dan bridge MQTT berbasis Mosquitto; daya dan kecepatan kipas berhasil dikendalikan secara stabil selama beberapa minggu melalui MQTT Fan di Home Assistant

Mengubah air purifier yang bergantung pada cloud menjadi kendali lokal

  • Tujuannya adalah mengendalikan air purifier yang hanya terhubung ke aplikasi mobile dan akun cloud pabrikan dari Home Assistant
  • Setelah memeriksa dengan men-toggle Bluetooth, WiFi, dan 5G pada ponsel, ditemukan bahwa aplikasi mengendalikan perangkat hanya melalui koneksi internet, bukan Bluetooth atau WiFi lokal
  • Karena nilai kendali seperti kecepatan kipas dikirimkan di suatu titik antara perangkat dan server cloud, segmen jaringan menjadi titik serangan utama
    • Jika traffic dicegat dan nilainya diubah, perangkat dapat dikendalikan
    • Jika respons server diemulasikan, perangkat dapat dijalankan tanpa internet maupun cloud pabrikan
  • Materi rekayasa balik ini untuk tujuan edukasi, dan informasi sensitif per produk seperti private key, domain, serta endpoint API telah diobfusikasi atau dihapus
  • Modifikasi perangkat dapat membatalkan garansi atau merusak perangkat secara permanen

Analisis aplikasi dan capture traffic UDP

  • Mengekstrak .apk aplikasi Android, lalu membukanya dengan dex2jar dan jd-gui untuk melihat bagian dalamnya
  • Di MainActivity.class, diketahui bahwa aplikasi berbasis React Native, dan koneksi WebSocket aman ditemukan di assets/index.android.bundle
    • Kode contoh memuat koneksi ke wss://smartdeviceapi.---.com
  • Dengan fitur penelusuran kueri DNS di Pi-hole, domain server cloud yang diakses perangkat berhasil diidentifikasi
  • Menggunakan fitur Local DNS di Pi-hole, domain tersebut diarahkan ke workstation lokal 192.168.0.10, lalu traffic IP perangkat 192.168.0.61 difilter di Wireshark
  • Perangkat mengirim paket UDP ke port 41014 di workstation

Konfigurasi relay dan petunjuk protokol kustom

  • Karena DNS lokal dibuat agar domain cloud di-resolve ke workstation, IP server sebenarnya dicari melalui Cloudflare DNS resolver 1.1.1.1
  • Menggunakan node-udp-forwarder, workstation dijadikan relay UDP antara perangkat dan server cloud
  • Paket pertama saat boot dan respons server berhasil di-capture, tetapi tampak seperti byte acak tanpa string yang dapat dibaca, sehingga ada kemungkinan terenkripsi
  • Wireshark tidak mengenali paket tersebut sebagai DTLS, dan format header pada spesifikasi DTLS juga berbeda dari paket yang di-capture
  • Karena tampaknya bukan protokol standar, struktur paket dan metode enkripsinya harus direkayasa balik secara langsung

Membongkar ESP32 dan akses serial

  • Setelah perangkat dibongkar, terlihat PCB utama, port koneksi kipas, dan kabel ribbon panel kontrol depan
  • Kontroler utamanya bertanda ESP32-WROOM-32D, yaitu mikrokontroler keluarga ESP32 dengan fungsi WiFi dan Bluetooth
  • Referensi terkait rekayasa balik ESP32 diambil dari repositori ESP32-reversing
  • Dari datasheet ESP32, pin TXD0 dan RXD0 diidentifikasi, lalu titik koneksi serial ditemukan dengan mengikuti trace yang terhubung ke lubang pin debugging pada PCB
  • Koneksi UART disiapkan menggunakan USB-UART Bridge pada Flipper Zero
    • Flipper Zero TX dihubungkan ke ESP32 RX
    • Flipper Zero RX dihubungkan ke ESP32 TX
    • GND dihubungkan ke GND
  • Saat terhubung dari Putty pada COM7 dengan baud rate 115200, log boot ditampilkan

File dan konfigurasi server yang terungkap dari log boot

  • Log serial mencetak bahwa ESP32 adalah chip dengan 2 core CPU, WiFi/BT/BLE, dan flash eksternal 4MB
  • Aplikasi berjalan dari partisi factory
  • Sistem file FAT di-mount, dan ditampilkan total ruang 122 KiB dengan ruang tersedia 0 KiB
  • Aplikasi membaca file berikut
    • serial
    • dev_key.key
    • SmartDevice-root-ca.crt
    • SmartDevice-signer-ca.crt
    • server_config
  • Konfigurasi server memuat smartdeviceep.---.com:41014

Dump flash dan struktur partisi

  • Untuk mem-boot ESP32 ke mode Download Boot, perangkat dinyalakan sambil pin IO0 dihubungkan ke GND
  • Menggunakan esptool, seluruh flash 4MB di-dump
    • Perintahnya adalah esptool -p COM7 -b 115200 read_flash 0 0x400000 flash.bin
  • Dump dilakukan beberapa kali untuk memastikan pembacaan normal, dan dicadangkan agar dapat di-flash ulang jika terjadi masalah
  • Dump dianalisis dengan esp32knife untuk memperoleh partitions.csv
  • Struktur partisi mencakup item berikut
    • nvs: penyimpanan key-value 16K
    • otadata: data OTA 8K
    • phy_init: data PHY 4K
    • factory: partisi aplikasi 768K
    • ota_0, ota_1: masing-masing partisi aplikasi OTA 768K
    • storage: partisi data FAT 1M
  • Menurut laporan pembaca, dump flash ini bisa saja terlindungi jika enkripsi flash diaktifkan, tetapi pada perangkat ini fitur tersebut tidak aktif

Kunci dan sertifikat yang ditemukan di storage

  • Status terbaru partisi nvs berisi SSID dan kata sandi WiFi, dan log riwayat juga memperlihatkan kredensial WiFi yang pernah digunakan sebelumnya
  • Partisi FAT storage diperiksa dengan me-mount-nya sebagai disk virtual menggunakan OSFMount
  • Storage berisi file berikut
    • dev_info
    • dev_key.key
    • serial
    • server_config
    • SmartDevice-root-ca.crt
    • SmartDevice-signer-ca.crt
    • wifi_config
  • dev_key.key adalah private key Elliptic Curve yang diawali dengan -----BEGIN EC PRIVATE KEY-----, dan diverifikasi dengan openssl ec -in dev_key.key -text -noout
  • Dua file .crt adalah sertifikat yang diawali dengan -----BEGIN CERTIFICATE-----, dan diverifikasi dengan openssl x509
  • Karena sertifikat dan kunci perangkat tersimpan di perangkat, besar kemungkinan keduanya digunakan untuk mengenkripsi data paket UDP

Menyiapkan lingkungan analisis Ghidra

  • Image partisi factory yang sedang berjalan dibuka dan dianalisis di CodeBrowser Ghidra
  • Karena ESP32 menggunakan set instruksi Xtensa, bahasa Tensilica Xtensa 32-bit little-endian dipilih
  • Image partisi mentah tidak dapat merefleksikan pemetaan memori virtual dengan benar, sehingga part.3.factory.elf dibuat dengan esp32knife lalu diimpor kembali
  • Commit yang memodifikasi esp32knife agar mendukung segmen RTC_DATA juga dipublikasikan
  • Struktur periferal dan peta memori ESP32 dimuat dengan SVD-Loader-Ghidra
  • Label fungsi ROM ESP32 diimpor dengan SymbolImportScript milik Ghidra agar fungsi ROM umum seperti printf lebih mudah diidentifikasi

Petunjuk enkripsi yang ditemukan dari string

  • Di Defined Strings Ghidra, string yang terlihat di log serial dan string di sekitarnya ditelusuri
  • Di antara string di sekitarnya terdapat petunjuk berikut
    • Message CRC error
    • Seed Error
    • PRNG fail
    • ECDH setup failed
    • mbedtls_ecdh_gen_public failed
    • mbedtls_ecdh_compute_shared failed
    • MBED HKDF failed
    • Write ECC conn packet
  • mbedtls adalah library open source yang mengimplementasikan primitif kriptografi, manipulasi sertifikat X509, SSL/TLS, dan DTLS
  • Dari fakta bahwa fungsi ECDH dan HKDF digunakan langsung dan bukan DTLS, dianalisis bahwa pertukaran kunci dan derivasi kunci diimplementasikan di dalam protokol buatan sendiri
  • String ECC conn packet menunjukkan bahwa paket koneksi awal berkaitan dengan proses pertukaran kunci ECDH

Patch firmware untuk menghapus dependensi panel kontrol

  • Analisis sulit dilakukan saat PCB masih terhubung ke kipas dan panel kontrol, sehingga panel kontrol dilepas, tetapi saat boot terjadi panic disertai log No Cap device found!
  • Fungsi di sekitar string No Cap device found! mencetak CapSense Init, sehingga dinilai sebagai logika inisialisasi input kapasitif pada panel depan
  • Di Ghidra, fungsi tersebut diberi nama InitCapSense, dan layanan yang memanggilnya diberi nama StartCapSenseService
  • Instruksi pemanggilan StartCapSenseService diubah menjadi nop untuk menghapus dimulainya layanan panel kontrol
  • Byte pada image mentah part.3.factory dimodifikasi dan di-flash kembali ke offset 0x10000, tetapi perangkat tidak bisa boot karena error checksum image ESP32
  • Berdasarkan logika internal esptool, skrip untuk memperbaiki checksum partisi aplikasi ditambahkan
  • Setelah image yang checksumnya dipulihkan di-flash, perangkat berjalan normal tanpa panel kontrol, dan modifikasi firmware berhasil

Struktur header paket dan CRC

  • Setelah membandingkan paket dari beberapa kali boot, 13 byte pertama tampak serupa dan sisanya terlihat seperti terenkripsi
  • Format header paket adalah sebagai berikut
    • 55: byte magic untuk identifikasi protokol
    • 00 31: panjang paket
    • 02: pengidentifikasi pesan
    • 01 23 45 67 89 AB CD EF FF: serial perangkat 9 byte
  • Pola ID pesan adalah sebagai berikut
    • 0x02: paket pertama yang dikirim perangkat pintar
    • 0x82: respons pertama yang dikirim server cloud
    • 0x01: paket berikutnya yang dikirim perangkat pintar
    • 0x81: respons berikutnya yang dikirim server
  • Bit atas membedakan request klien dan respons server, sedangkan bit bawah membedakan pertukaran awal dan paket berikutnya
  • Dengan menelusuri fungsi yang mereferensikan string Message CRC error, logika verifikasi CRC dikonfirmasi
  • 2 byte terakhir adalah checksum CRC-16 untuk seluruh sisa paket
    • Polinomialnya 0x1021
    • Nilai awalnya 0xFFFF
    • Diverifikasi dengan cara yang sama pada beberapa paket hasil capture

Alur pembuatan kunci ECDH/HKDF

  • Pada paket yang tampak sebagai pertukaran kunci awal, data setelah header 13 byte dan CRC 2 byte berukuran 32 byte, sesuai dengan ukuran kunci publik 256-bit
  • Pada request klien, terdapat 00 01 di depan, dan nilainya tidak berubah setiap boot, sehingga diperlakukan seperti deskriptor data
  • Di Ghidra, fungsi pembuatan kunci ditemukan dengan menelusuri string error, lalu dirangkum pada tingkat pseudocode dengan membandingkannya dengan source mbedtls
  • Fungsi pembuatan kunci melakukan operasi berikut
    • Membuat pasangan kunci ECDH dengan mbedtls_ecdh_gen_public
    • Terlihat pola menimpa kunci yang dibuat dengan kunci lain di memori
    • Memuat kunci publik lain
    • Menghitung shared secret dengan mbedtls_ecdh_compute_shared
    • Membuat nilai acak 32 byte dengan mbedtls_ctr_drbg_random
    • Menderivasi kunci akhir dengan mbedtls_hkdf
  • Pengaturan HKDF adalah sebagai berikut
    • Hash: SHA-256
    • salt: shared secret ECDH
    • input: nilai acak 32 byte yang dibuat perangkat
    • info: serial perangkat 9 byte
    • Ukuran kunci output: 0x10, yaitu 16 byte
  • Fungsi pemanggil menambahkan nilai acak 32 byte setelah 00 01 dan mengirim 0x22 byte, yang cocok dengan format paket pertukaran kunci awal yang dicapture

Output shared secret dan dekripsi AES

  • Untuk menghitung kunci dekripsi akhir, shared secret ECDH diperlukan
  • Alih-alih debugging JTAG, firmware dipatch dengan menimpa lokasi logika CapSense yang sudah dinonaktifkan dengan fungsi kustom agar shared secret dicetak lewat serial
  • Pemanggilan fungsi disisipkan tepat setelah shared secret dibuat di GenerateNetworkKey, lalu 32 byte dicetak menggunakan pointer kunci di register
  • Saat boot, shared secret tercetak dalam heksadesimal setelah Write ECC conn packet, dan nilainya tidak berubah meski di-reboot beberapa kali
  • Kunci output HKDF juga dikonfirmasi lewat patch terpisah, dan logika pembuatan kunci yang sama dapat direproduksi dari paket hasil capture
  • Di dalam fungsi enkripsi ditemukan tabel statis yang diawali 63 7C 77 7B F2 6B 6F C5, dan ini cocok dengan AES Forward S-Box milik mbedtls
  • Metode enkripsi akhirnya adalah AES-128-CBC, dan nilai acak 16 byte di dalam paket digunakan sebagai IV
  • Pada paket yang didekripsi, nilai yang dapat dibaca seperti mirror_data_get, FAN_SPEED, BOOST, FILTER1, dan FILTER2 terkonfirmasi

Implementasi proxy MITM

  • Kunci privat perangkat dan logika derivasi kunci telah diperoleh, dan data dinamis yang diperlukan terekspos di jaringan, sehingga proxy MITM dapat ditulis tanpa patch firmware
  • Skrip Node.js membuat socket UDP lokal dan socket UDP untuk server cloud, lalu meneruskan paket dua arah
  • Paket yang diterima dari perangkat pintar dicatat ke log lalu dikirim ke server cloud, sedangkan paket yang diterima dari server cloud dicatat ke log lalu dikirim ke perangkat pintar
  • Paket dengan messageId bernilai 2 dianggap sebagai paket pertukaran kunci, dan kunci AES untuk paket berikutnya dihitung menggunakan nilai acak di dalamnya
  • Saat mengoperasikan perangkat lewat aplikasi mobile, log MITM dikumpulkan untuk mengonfirmasi bentuk request dan respons yang diperlukan untuk implementasi server lokal

Struktur Pesan MessagePack

  • Data yang telah didekripsi masih berupa format serialisasi biner
  • Header data internal tampak seperti ID dan panjang dalam little-endian
    • 01 00: ID paket
    • 64 00: ID transaksi
    • 29 00: panjang data serialisasi
  • Format serialisasi sempat direkayasa balik sebagian secara manual, tetapi setelah diperiksa ternyata adalah MessagePack
  • Dengan menggunakan implementasi seperti msgpackr, data biner dapat dengan mudah diurai ke bentuk JSON
  • Pesan-pesan utama yang teridentifikasi adalah sebagai berikut
    • Pertukaran kunci: perangkat mengirim byte acak yang akan digunakan untuk HKDF ke server
    • mirror_data_get: mengambil status awal dari server saat booting
    • connect: mengirim UUID firmware saat ini, lalu server merespons dengan informasi firmware, konfigurasi, waktu, dan alamat server
    • mirror_data: server mengubah status perangkat, atau perangkat melaporkan status yang berubah ke server
    • keep_alive: perangkat mengirim status secara berkala seperti RSSI, RTT, packet drop, jumlah koneksi, uptime, dan sebagainya

Bridge MQTT dan Integrasi Home Assistant

  • MQTT digunakan untuk menghubungkan Home Assistant dengan server kustom
  • Di Home Assistant, add-on Mosquitto, broker MQTT open-source, dikonfigurasi
  • Struktur koneksinya berbentuk Home AssistantMQTT BrokerCustom ServerSmart Device
  • Server kustom bekerja dengan cara berikut
    • Saat perangkat meminta status dengan mirror_data_get, server merespons menggunakan nilai retained dari broker MQTT atau nilai default
    • Saat Home Assistant mengirim perintah perubahan status ke topik MQTT, server kustom meneruskannya ke perangkat
    • Jika status perangkat berubah karena alasan apa pun, paket mirror_data dari perangkat di-publish ke broker MQTT dan di-retain
  • Source of truth untuk status selalu ada pada perangkat
    • Jika pembaruan status gagal, broker MQTT tidak akan menampilkannya seolah-olah sudah diperbarui
    • Jika status berubah melalui panel kontrol fisik, perubahan itu juga tercermin di broker MQTT
  • Integrasi MQTT Fan di Home Assistant digunakan untuk memetakan air purifier sebagai perangkat kipas
  • Di configuration.yaml, dikonfigurasi topik status daya, topik perintah, topik status kecepatan kipas, topik perintah kecepatan kipas, serta rentang kecepatan 1~4
  • DNS lokal Pi-hole dikonfigurasi agar domain cloud milik produsen di-resolve ke server kustom, sehingga server lokal berperan sebagai server untuk perangkat

Evaluasi Keamanan dan Hasil

  • Produsen mengimplementasikan protokol sendiri alih-alih protokol standar seperti DTLS
  • Tidak jelas apakah setiap perangkat memiliki private key unik, tetapi dalam kedua kasus ada kelemahannya
    • Jika semua perangkat berbagi private key firmware yang sama, hanya dengan merekayasa balik satu perangkat saja serangan MITM dapat dicoba terhadap perangkat lain
    • Jika setiap perangkat memiliki private key unik, server harus menyimpan pemetaan antara nomor seri dan kunci perangkat, dan jika data tersebut hilang, server tidak akan bisa merespons komunikasi perangkat
  • Karena firmware berisi private key statis, penyerang dapat memperoleh kunci dari satu dump firmware dan melakukan serangan MITM
  • Implementasinya tidak sepenuhnya buruk dari sudut pandang keamanan, dan serangan masih memerlukan akses fisik
  • Implementasi buatan sendiri membuat komunikasi jaringan menjadi tidak transparan, tetapi security through obscurity lebih mirip sekadar menahan sementara serangan umum yang menargetkan implementasi standar, dan bagi penyerang itu adalah hambatan yang bisa dilewati
  • Tujuan akhir, yaitu integrasi Home Assistant, berhasil dicapai, dan air purifier berjalan tanpa masalah selama beberapa minggu
  • Otomasi juga dikonfigurasi agar air purifier masuk mode boost selama periode tertentu ketika nilai PM2.5 atau VOC dari monitor udara terpisah menjadi terlalu tinggi

Belum ada komentar.

Belum ada komentar.