• निर्माता के ऐप और 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.---.com connection शामिल था
  • Pi-hole के DNS query lookup feature से उस cloud server domain की पहचान की गई जिससे डिवाइस जुड़ता था
  • Pi-hole के Local DNS feature से उस domain को local workstation 192.168.0.10 पर भेजा गया, और Wireshark में डिवाइस IP 192.168.0.61 के traffic को filter किया गया
  • डिवाइस workstation के 41014 port पर 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 और RXD0 pins की पहचान की गई, और PCB पर debugging pinholes से जुड़ी traces को follow करके serial connection point ढूंढा गया
  • Flipper Zero के USB-UART Bridge से UART connection बनाया गया
    • Flipper Zero TX को ESP32 RX से जोड़ा गया
    • Flipper Zero RX को ESP32 TX से जोड़ा गया
    • GND को GND से जोड़ा गया
  • Putty में COM7, 115200 baud rate पर connect करते ही boot log दिखने लगा

boot log में दिखे files और server settings

  • serial log में ESP32 को 2 CPU cores, WiFi/BT/BLE, और 4MB external flash वाले chip के रूप में दिखाया गया
  • application factory partition से चल रही थी
  • FAT filesystem mount हुआ, और 122 KiB total space तथा 0 KiB available space दिखा
  • application निम्न files पढ़ रही थी
    • serial
    • dev_key.key
    • SmartDevice-root-ca.crt
    • SmartDevice-signer-ca.crt
    • server_config
  • server settings में smartdeviceep.---.com:41014 शामिल था

flash dump और partition structure

  • ESP32 को Download Boot mode में boot करने के लिए power on करते समय IO0 pin को GND से जोड़ा गया
  • esptool से पूरा 4MB flash dump किया गया
    • command था esptool -p COM7 -b 115200 read_flash 0 0x400000 flash.bin
  • dump कई बार किया गया ताकि सही reading की पुष्टि हो सके, और समस्या आने पर दोबारा flash करने के लिए backup रखा गया
  • esp32knife से dump का analysis करके partitions.csv प्राप्त किया गया
  • partition structure में निम्न entries शामिल थीं
    • nvs: 16K key-value storage
    • otadata: 8K OTA data
    • phy_init: 4K PHY data
    • factory: 768K app partition
    • ota_0, ota_1: क्रमशः 768K OTA app partitions
    • storage: 1M FAT data partition
  • एक पाठक की सूचना के अनुसार, अगर flash encryption enabled होती तो यह flash dump सुरक्षित रह सकता था, लेकिन इस डिवाइस में यह enabled नहीं थी

storage में मिली keys और certificates

  • nvs partition की latest state में WiFi SSID और password थे, और history log में पहले इस्तेमाल किए गए WiFi credentials भी दिखे
  • FAT storage partition को OSFMount से virtual disk की तरह mount करके जांचा गया
  • storage में निम्न files थीं
    • dev_info
    • dev_key.key
    • serial
    • server_config
    • SmartDevice-root-ca.crt
    • SmartDevice-signer-ca.crt
    • wifi_config
  • dev_key.key की शुरुआत -----BEGIN EC PRIVATE KEY----- से होती थी; यह एक Elliptic Curve private key थी, जिसकी पुष्टि openssl ec -in dev_key.key -text -noout से की गई
  • दोनों .crt files की शुरुआत -----BEGIN CERTIFICATE----- से होती थी, और openssl x509 से उनकी पुष्टि की गई
  • certificates और device key डिवाइस में संग्रहीत थे, इसलिए इनके UDP packet data encryption में इस्तेमाल होने की संभावना मजबूत हुई

Ghidra विश्लेषण वातावरण कॉन्फ़िगरेशन

  • चल रहे factory partition इमेज को Ghidra CodeBrowser में खोलकर विश्लेषण किया गया
  • ESP32, Xtensa instruction set का उपयोग करता है, इसलिए Tensilica Xtensa 32-bit little-endian language चुनी गई
  • raw partition इमेज virtual memory mapping को सही तरह नहीं दिखा पा रही थी, इसलिए esp32knife से part.3.factory.elf बनाकर उसे फिर से import किया गया
  • RTC_DATA segment को 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 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 एक 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 packet string से पता चलता है कि 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 के आसपास का function CapSense Init print करता था, इसलिए इसे front panel के capacitive input initialization logic के रूप में पहचाना गया
  • Ghidra में उस function का नाम InitCapSense और उसे call करने वाली service का नाम StartCapSenseService रखा गया
  • StartCapSenseService call instruction को nop में बदलकर control panel service startup हटा दिया गया
  • raw part.3.factory इमेज में bytes संशोधित कर 0x10000 offset पर फिर से 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 byte
    • 00 31: packet length
    • 02: message identifier
    • 01 23 45 67 89 AB CD EF FF: 9-byte device serial
  • message ID pattern इस प्रकार था
    • 0x02: smart device द्वारा भेजा गया पहला packet
    • 0x82: cloud server द्वारा भेजा गया पहला response
    • 0x01: इसके बाद smart device द्वारा भेजे गए packets
    • 0x81: इसके बाद server द्वारा भेजे गए responses
  • upper bit client request और server response में अंतर करता था, और lower bits initial exchange व बाद के packets में अंतर करते थे
  • Message CRC error string को reference करने वाले function का पीछा कर CRC verification logic की पुष्टि की गई
  • आखिरी 2 bytes, बाकी पूरे packet का CRC-16 checksum थे
    • polynomial 0x1021 था
    • initial value 0xFFFF थी
    • कई captured packets में यही verification method लागू पाई गई

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 secret
    • input: device द्वारा बनाई गई 32-byte random value
    • info: 9-byte device serial
    • output key size: 0x10, यानी 16 bytes
  • calling function, 00 01 के बाद 32-byte random value जोड़कर 0x22 bytes भेजता था, और यह 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 ID
    • 64 00: transaction ID
    • 29 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 AssistantMQTT BrokerCustom ServerSmart 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_data packet को 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 range 1~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 भी सेट की गई

अभी कोई टिप्पणी नहीं है.

अभी कोई टिप्पणी नहीं है.