Smart Home डिवाइस हैकिंग (2024)
(jmswrnr.com)- निर्माता के ऐप और cloud से बंधे ESP32-आधारित air purifier को Home Assistant में सीधे नियंत्रित करने के लिए, remote control path का reverse engineering करके उसे local server से बदल दिया गया
- ऐप analysis, DNS bypass, और Wireshark capture से पुष्टि हुई कि डिवाइस
smartdeviceep.---.com:41014पर UDP packets भेजता है और standard DTLS के बजाय अपना proprietary protocol इस्तेमाल करता है - UART connection और 4MB flash dump के जरिए
dev_key.key, certificates, server settings, और WiFi settings हासिल किए गए, और Ghidra·esp32knife से firmware structure का analysis किया गया - packets में 13-byte header, आखिर के 2 bytes में CRC-16, ECDH/HKDF key generation,
AES-128-CBC, और MessagePack serialization का संयोजन था; firmware patch करके shared secret को serial log में प्रिंट कराया गया और decryption सफल रही - अंतिम setup MITM proxy, local server, और Mosquitto-आधारित MQTT bridge से बना, और Home Assistant के MQTT Fan के जरिए power और fan speed को कई हफ्तों तक स्थिर रूप से नियंत्रित किया गया
cloud-निर्भर air purifier को local control में बदलना
- लक्ष्य यह था कि उस air purifier को Home Assistant में नियंत्रित किया जाए, जो सिर्फ निर्माता के mobile app और cloud account से ही जुड़ता था
- फोन में Bluetooth, WiFi, और 5G को toggle करके जांचने पर पता चला कि ऐप local Bluetooth या WiFi नहीं, बल्कि सिर्फ internet connection के जरिए डिवाइस को नियंत्रित करता है
- डिवाइस और cloud server के बीच कहीं fan speed जैसे control values का आदान-प्रदान हो रहा था, इसलिए network segment मुख्य attack point बन गया
- traffic intercept करके और values बदलकर डिवाइस को नियंत्रित किया जा सकता था
- server response को emulate किया जाए तो internet और निर्माता के cloud के बिना भी इसे चलाया जा सकता था
- reverse engineering की सामग्री शैक्षणिक उद्देश्य के लिए है, और product-specific sensitive जानकारी जैसे private key, domain, और API endpoint को obfuscate या हटाया गया है
- डिवाइस में modification करने से warranty अमान्य हो सकती है या डिवाइस स्थायी रूप से खराब हो सकता है
ऐप analysis और UDP traffic capture
- Android ऐप की
.apkनिकाली गई, औरclasses.dexको dex2jar तथा jd-gui से खोलकर भीतर की संरचना देखी गई MainActivity.classमें पुष्टि हुई कि ऐप React Native-आधारित है, औरassets/index.android.bundleमें secure WebSocket connection मिला- उदाहरण code में
wss://smartdeviceapi.---.comconnection शामिल था
- उदाहरण code में
- Pi-hole के DNS query lookup feature से उस cloud server domain की पहचान की गई जिससे डिवाइस जुड़ता था
- Pi-hole के
Local DNSfeature से उस domain को local workstation192.168.0.10पर भेजा गया, और Wireshark में डिवाइस IP192.168.0.61के traffic को filter किया गया - डिवाइस workstation के
41014port पर UDP packets भेज रहा था
relay setup और proprietary protocol के संकेत
- क्योंकि local DNS cloud domain को workstation पर resolve कर रहा था, इसलिए वास्तविक server IP को Cloudflare DNS resolver
1.1.1.1से query किया गया - node-udp-forwarder का उपयोग करके workstation को डिवाइस और cloud server के बीच UDP relay की भूमिका दी गई
- boot के समय पहला packet और server response capture किया गया, लेकिन पढ़ने योग्य strings नहीं थीं और वह random bytes जैसा दिख रहा था, इसलिए encryption की संभावना थी
- Wireshark ने packets को DTLS के रूप में नहीं पहचाना, और DTLS specification का header format भी captured packets से मेल नहीं खाता था
- चूंकि यह standard protocol नहीं लग रहा था, इसलिए packet structure और encryption method का reverse engineering सीधे करना पड़ा
ESP32 teardown और serial access
- डिवाइस खोलने पर main PCB, fan connection port, और front control panel ribbon cable दिखाई दिए
- मुख्य controller पर
ESP32-WROOM-32Dलिखा था, जो WiFi और Bluetooth क्षमता वाला ESP32 series microcontroller था - ESP32-reversing repository से ESP32 reverse engineering से जुड़ी सामग्री का संदर्भ लिया गया
- ESP32 datasheet में
TXD0औरRXD0pins की पहचान की गई, और PCB पर debugging pinholes से जुड़ी traces को follow करके serial connection point ढूंढा गया - Flipper Zero के
USB-UART Bridgeसे UART connection बनाया गया- Flipper Zero
TXको ESP32RXसे जोड़ा गया - Flipper Zero
RXको ESP32TXसे जोड़ा गया GNDकोGNDसे जोड़ा गया
- Flipper Zero
- Putty में
COM7,115200baud rate पर connect करते ही boot log दिखने लगा
boot log में दिखे files और server settings
- serial log में ESP32 को 2 CPU cores, WiFi/BT/BLE, और 4MB external flash वाले chip के रूप में दिखाया गया
- application
factorypartition से चल रही थी - FAT filesystem mount हुआ, और
122 KiBtotal space तथा0 KiBavailable space दिखा - application निम्न files पढ़ रही थी
serialdev_key.keySmartDevice-root-ca.crtSmartDevice-signer-ca.crtserver_config
- server settings में
smartdeviceep.---.com:41014शामिल था
flash dump और partition structure
- ESP32 को
Download Bootmode में boot करने के लिए power on करते समयIO0pin कोGNDसे जोड़ा गया - esptool से पूरा 4MB flash dump किया गया
- command था
esptool -p COM7 -b 115200 read_flash 0 0x400000 flash.bin
- command था
- dump कई बार किया गया ताकि सही reading की पुष्टि हो सके, और समस्या आने पर दोबारा flash करने के लिए backup रखा गया
- esp32knife से dump का analysis करके
partitions.csvप्राप्त किया गया - partition structure में निम्न entries शामिल थीं
nvs: 16K key-value storageotadata: 8K OTA dataphy_init: 4K PHY datafactory: 768K app partitionota_0,ota_1: क्रमशः 768K OTA app partitionsstorage: 1M FAT data partition
- एक पाठक की सूचना के अनुसार, अगर flash encryption enabled होती तो यह flash dump सुरक्षित रह सकता था, लेकिन इस डिवाइस में यह enabled नहीं थी
storage में मिली keys और certificates
nvspartition की latest state में WiFi SSID और password थे, और history log में पहले इस्तेमाल किए गए WiFi credentials भी दिखे- FAT
storagepartition को OSFMount से virtual disk की तरह mount करके जांचा गया - storage में निम्न files थीं
dev_infodev_key.keyserialserver_configSmartDevice-root-ca.crtSmartDevice-signer-ca.crtwifi_config
dev_key.keyकी शुरुआत-----BEGIN EC PRIVATE KEY-----से होती थी; यह एक Elliptic Curve private key थी, जिसकी पुष्टिopenssl ec -in dev_key.key -text -nooutसे की गई- दोनों
.crtfiles की शुरुआत-----BEGIN CERTIFICATE-----से होती थी, औरopenssl x509से उनकी पुष्टि की गई - certificates और device key डिवाइस में संग्रहीत थे, इसलिए इनके UDP packet data encryption में इस्तेमाल होने की संभावना मजबूत हुई
Ghidra विश्लेषण वातावरण कॉन्फ़िगरेशन
- चल रहे
factorypartition इमेज को Ghidra CodeBrowser में खोलकर विश्लेषण किया गया - ESP32, Xtensa instruction set का उपयोग करता है, इसलिए
Tensilica Xtensa 32-bit little-endianlanguage चुनी गई - raw partition इमेज virtual memory mapping को सही तरह नहीं दिखा पा रही थी, इसलिए esp32knife से
part.3.factory.elfबनाकर उसे फिर से import किया गया RTC_DATAsegment को support करने के लिए esp32knife में संशोधन वाला commit भी सार्वजनिक किया गया- SVD-Loader-Ghidra से ESP32 peripheral structure और memory map लोड किए गए
- Ghidra के
SymbolImportScriptसे ESP32 ROM function labels लोड किए गए ताकिprintfजैसे common ROM functions को पहचानना आसान हो सके
स्ट्रिंग्स से मिले एन्क्रिप्शन के सुराग
- Ghidra के
Defined Stringsमें serial log में दिखी strings और उनके आसपास की strings को ट्रैक किया गया - आसपास की strings में ये सुराग मिले
Message CRC errorSeed ErrorPRNG failECDH setup failedmbedtls_ecdh_gen_public failedmbedtls_ecdh_compute_shared failedMBED HKDF failedWrite ECC conn packet
- mbedtls एक open source library है जो cryptographic primitives, X509 certificate manipulation, और SSL/TLS व DTLS को implement करती है
- ECDH और HKDF functions का सीधा उपयोग हो रहा था और DTLS नहीं दिख रहा था, इससे विश्लेषण हुआ कि key exchange और key derivation किसी custom protocol के भीतर implement किए गए थे
ECC conn packetstring से पता चलता है कि initial connection packet, ECDH key exchange process से जुड़ा था
कंट्रोल पैनल निर्भरता हटाने के लिए firmware patch
- PCB को fan और control panel से जोड़कर विश्लेषण करना असुविधाजनक था, इसलिए control panel अलग किया गया, लेकिन boot के दौरान
No Cap device found!log के साथ panic हुआ No Cap device found!string के आसपास का functionCapSense Initprint करता था, इसलिए इसे front panel के capacitive input initialization logic के रूप में पहचाना गया- Ghidra में उस function का नाम
InitCapSenseऔर उसे call करने वाली service का नामStartCapSenseServiceरखा गया StartCapSenseServicecall instruction कोnopमें बदलकर control panel service startup हटा दिया गया- raw
part.3.factoryइमेज में bytes संशोधित कर0x10000offset पर फिर से flash किया गया, लेकिन ESP32 image checksum error के कारण boot नहीं हुआ - esptool के internal logic के आधार पर app partition checksum ठीक करने वाली script जोड़ी गई
- checksum ठीक की गई इमेज flash करने पर device control panel के बिना भी सामान्य रूप से चला, और firmware modification सफल रही
पैकेट हेडर और CRC संरचना
- कई बार boot करके packets की तुलना करने पर, पहले 13 bytes समान दिखे और बाकी हिस्सा encrypted जैसा लगा
- packet header format इस प्रकार था
55: protocol पहचान के लिए magic byte00 31: packet length02: message identifier01 23 45 67 89 AB CD EF FF: 9-byte device serial
- message ID pattern इस प्रकार था
0x02: smart device द्वारा भेजा गया पहला packet0x82: cloud server द्वारा भेजा गया पहला response0x01: इसके बाद smart device द्वारा भेजे गए packets0x81: इसके बाद server द्वारा भेजे गए responses
- upper bit client request और server response में अंतर करता था, और lower bits initial exchange व बाद के packets में अंतर करते थे
Message CRC errorstring को reference करने वाले function का पीछा कर CRC verification logic की पुष्टि की गई- आखिरी 2 bytes, बाकी पूरे packet का CRC-16 checksum थे
- polynomial
0x1021था - initial value
0xFFFFथी - कई captured packets में यही verification method लागू पाई गई
- polynomial
ECDH/HKDF key generation flow
- initial key exchange जैसा दिखने वाले packet में, 13-byte header और 2-byte CRC को छोड़कर data 32 bytes का था, जो 256-bit public key size से मेल खाता था
- client request में आगे
00 01जुड़ा था, और यह हर boot पर नहीं बदलता था, इसलिए इसे data descriptor की तरह माना गया - Ghidra में error strings को ट्रेस करके key generation function खोजा गया और mbedtls source से तुलना कर pseudocode स्तर पर व्यवस्थित किया गया
- key generation function ये काम करता था
mbedtls_ecdh_gen_publicसे ECDH key pair बनाता था- बनी हुई key को memory में मौजूद दूसरी key से overwrite किया जाता हुआ दिखा
- दूसरी public key लोड की जाती थी
mbedtls_ecdh_compute_sharedसे shared secret compute किया जाता थाmbedtls_ctr_drbg_randomसे 32-byte random value बनाई जाती थीmbedtls_hkdfसे final key derive की जाती थी
- HKDF configuration इस प्रकार थी
- hash:
SHA-256 salt: ECDH shared secretinput: device द्वारा बनाई गई 32-byte random valueinfo: 9-byte device serial- output key size:
0x10, यानी 16 bytes
- hash:
- calling function,
00 01के बाद 32-byte random value जोड़कर0x22bytes भेजता था, और यह captured initial key exchange packet format से मेल खाता था
shared secret output और AES decryption
- final decryption key निकालने के लिए ECDH shared secret की जरूरत थी
- JTAG debugging के बजाय, पहले से disabled CapSense logic की जगह पर custom function overwrite करके firmware को इस तरह patch किया गया कि shared secret serial पर print हो सके
GenerateNetworkKeyमें shared secret बनते ही function call insert किया गया, और register में मौजूद key pointer से 32 bytes print किए गए- boot के समय
Write ECC conn packetके बाद shared secret hex में print हुआ, और कई बार reboot करने पर भी उसका मान नहीं बदला - HKDF output key भी अलग patch से verify की गई, और captured packets में वही key generation logic पुन: उत्पन्न की जा सकी
- encryption function के भीतर
63 7C 77 7B F2 6B 6F C5से शुरू होने वाली static table मिली, जो mbedtls की AES Forward S-Box से मेल खाती थी - अंतिम encryption method AES-128-CBC था, और packet के भीतर मौजूद 16-byte random value को IV के रूप में इस्तेमाल किया गया
- decrypted packets में
mirror_data_get,FAN_SPEED,BOOST,FILTER1,FILTER2जैसे पढ़ने योग्य मान मिले
MITM proxy implementation
- device private key और key derivation logic मिल चुके थे, और जरूरी dynamic data network पर exposed था, इसलिए firmware patch के बिना MITM proxy लिखा जा सका
- Node.js script ने local UDP socket और cloud server के लिए UDP socket बनाए और packets को दोनों दिशाओं में forward किया
- smart device से मिले packets को log करने के बाद cloud server को भेजा गया, और cloud server से मिले packets को log करने के बाद smart device को भेजा गया
messageIdका मान2होने पर packet को key exchange packet माना गया, और उसके भीतर की random value से बाद के packets के लिए AES key calculate की गई- mobile app से device को नियंत्रित करते हुए MITM logs इकट्ठे किए गए ताकि local server implementation के लिए जरूरी request और response formats की पुष्टि की जा सके
MessagePack संदेश संरचना
- डिक्रिप्ट किया गया डेटा अभी भी binary serialization format में था
- आंतरिक डेटा header little-endian में ID और length जैसा दिख रहा था
01 00: packet ID64 00: transaction ID29 00: serialized data length
- serialization format का कुछ हिस्सा सीधे reverse engineer किया गया था, लेकिन जांच करने पर पता चला कि यह MessagePack था
msgpackrजैसी implementation का उपयोग करने पर binary data को आसानी से JSON रूप में खोला जा सकता था- पुष्टि किए गए मुख्य संदेश इस प्रकार थे
- key exchange: डिवाइस HKDF में उपयोग के लिए random bytes सर्वर को भेजता है
mirror_data_get: boot के समय शुरुआती state सर्वर से लाता हैconnect: मौजूदा firmware UUID भेजता है, और सर्वर firmware·settings·time·server address की जानकारी जवाब में देता हैmirror_data: सर्वर डिवाइस की state बदलता है, या डिवाइस बदली हुई state सर्वर को रिपोर्ट करता हैkeep_alive: डिवाइस समय-समय पर RSSI, RTT, packet drop, connection count, uptime आदि status भेजता है
MQTT ब्रिज और Home Assistant इंटीग्रेशन
- Home Assistant और custom server को जोड़ने के लिए MQTT का उपयोग किया गया
- Home Assistant में open source MQTT broker Mosquitto addon सेट किया गया
- connection structure
Home Assistant↔MQTT Broker↔Custom Server↔Smart Deviceके रूप में था - custom server इस तरह काम करता है
- जब डिवाइस
mirror_data_getसे state request करता है, तो MQTT broker के retained value का उपयोग किया जाता है या default value के साथ जवाब दिया जाता है - जब Home Assistant state change command को MQTT topic पर भेजता है, तो custom server उसे डिवाइस तक पहुंचाता है
- जब भी किसी भी कारण से डिवाइस state बदलती है, custom server डिवाइस के
mirror_datapacket को MQTT broker पर publish करके retain करता है
- जब डिवाइस
- state का source of truth हमेशा डिवाइस ही है
- अगर state update विफल हो जाए, तो MQTT broker पर उसे updated दिखाया नहीं जाता
- physical control panel से state बदलने पर भी वह MQTT broker में reflect होती है
- Home Assistant के MQTT Fan integration का उपयोग करके air purifier को fan device के रूप में map किया गया
configuration.yamlमें power state topic, command topic, fan speed state topic, fan speed command topic, और speed range1~4सेट किया गया- Pi-hole local DNS को इस तरह सेट किया गया कि निर्माता के cloud domain का resolution custom server पर हो, ताकि local server डिवाइस के server की भूमिका निभा सके
सुरक्षा मूल्यांकन और परिणाम
- निर्माता ने DTLS जैसे standard protocol के बजाय अपना खुद का protocol implement किया था
- यह स्पष्ट नहीं था कि हर डिवाइस की अपनी unique private key है या नहीं, लेकिन दोनों ही स्थितियों में नुकसान थे
- अगर सभी डिवाइस एक ही firmware private key साझा करते हैं, तो सिर्फ एक डिवाइस को reverse engineer करके बाकी डिवाइसों पर MITM attack की कोशिश की जा सकती है
- अगर हर डिवाइस की unique private key है, तो सर्वर को serial number और device key mapping सहेजकर रखनी होगी, और वह डेटा खो जाने पर सर्वर डिवाइस संचार का जवाब नहीं दे पाएगा
- firmware में static private key शामिल थी, इसलिए attacker एक ही firmware dump से key हासिल कर MITM attack कर सकता था
- implementation सुरक्षा के दृष्टिकोण से पूरी तरह खराब नहीं थी, और attack के लिए फिर भी physical access की जरूरत थी
- custom implementation ने network communication को opaque बनाया, लेकिन Security through obscurity अधिकतम इतना ही करती है कि standard implementations को निशाना बनाने वाले सामान्य हमलों को थोड़ी देर के लिए रोके; attacker के लिए यह पार की जा सकने वाली बाधा थी
- अंतिम लक्ष्य, यानी Home Assistant integration, हासिल कर लिया गया, और air purifier कई हफ्तों तक बिना समस्या के चलता रहा
- एक अलग air monitor से PM2.5 या VOC level बहुत बढ़ जाने पर air purifier को कुछ समय के लिए boost करने वाली automation भी सेट की गई
अभी कोई टिप्पणी नहीं है.