[{"content":"Questo è un post vivo: lo aggiorno man mano che la ricerca sui device Tuya va avanti. Non un singolo bersaglio, ma l’ecosistema — perché una volta capito il protocollo e il pattern hardware, la stessa chiave apre mezza casa.\nTuya è la piattaforma white-label che sta sotto a un’infinità di gadget economici: telecamere, prese, irrigatori, sensori. Cambia il marchio sulla scatola, ma dentro c’è quasi sempre lo stesso SoC, la stessa app (Smart Life / Tuya Smart) e lo stesso protocollo. Bersaglio perfetto per imparare: ne ho un lab pieno, e sono tutti cugini. Bussola metodologica: The Hardware Hacking Handbook e Practical IoT Hacking.\nLa mappa del lab Dopo il ping sweep, gli inquilini IoT della rete:\n192.168.1.3 Camera Tuya v3.5 6668/tcp + 6682/tcp 192.168.1.18 Dongle irrigatore v3.4 (gateway WiFi radio) 192.168.1.16 IoT ignoto (Qingdao) da identificare Da subito emerge la doppia natura del problema. Alcuni device parlano IP (la camera, il dongle): li attacchi via LAN. Altri non hanno un IP — l’irrigatore vero, per esempio, parla al suo dongle via radio (sub-GHz, BLE o Zigbee, da capire). Due mondi, due cassette degli attrezzi:\nWired — device con una board da aprire: apertura del case, UART, dump della flash SPI, analisi del firmware. È il binario principale, quello da cui esce la local key. Radio — quello che vive nell’etere: sniffare il link tra irrigatore e dongle, i sensori BLE/Zigbee, il sub-GHz. Serve a mappare i device senza IP e, dove si può, a parlarci. Ricognizione (e il primo falso positivo) Prima regola dell’IoT: la ricognizione decide tutto, e non ci si fida del primo host che sembra giusto. Lezione imparata subito, sbagliando. Un nmap mi aveva fatto puntare un host con una porta aperta che credevo la camera:\n$ nmap -p- 192.168.1.10 3000/tcp open # ...aspetta La 3000 non è una telecamera: è il mio OWASP Juice Shop in locale su quella macchina. Avevo puntato la mia stessa station di pentest. Ping sweep rifatto con calma, e la camera salta fuori pulita:\nIP: 192.168.1.3 Protocollo: Tuya v3.5 Device ID: bffa7e4eb71e87da57rrxa Product ID: keymf7afuxvw85qt Porte: 6668/tcp (protocollo locale Tuya) 6682/tcp (non documentata — media/P2P?) La 6668 è il canale locale di Tuya, quello che l’app usa sulla LAN senza cloud. La 6682 non è nel protocollo locale documentato: profuma di stream/P2P, me la segno per una cattura dedicata. Nel giro trovo anche il .18 (dongle WiFi dell’irrigatore, v3.4, bfae977fd2b319d166aoux) e un .16 ancora da identificare.\nIl muro: la local key Il nodo di tutti i Tuya locali è la local key: il protocollo sulla 6668 è cifrato (AES-GCM in v3.5), e senza quella chiave non decifri il traffico né mandi comandi. Con IP e Device ID sei arrivato alla porta, ma la chiave ce l’ha il device.\nC’è la via comoda — il wizard di tinytuya che scarica le local key dal cloud Tuya via account developer — e funziona. La salto di proposito.\nLa local key la estraggo dal firmware. Ogni device → dump della sua flash → la sua chiave. Il Device ID, che ho già per ognuno, fa da ancora di ricerca nel dump.\nNon è masochismo: l’obiettivo è capire il device, non solo pilotarlo. Nel dump non c’è solo la local key — endpoint, credenziali, magari altre chiavi. La scorciatoia cloud mi darebbe il risultato saltando tutto quello che voglio imparare.\nL’arsenale Il banco wired è ordinato (arrivo a breve): saldatore TS101, stazione ad aria calda per il chip-off, programmatore CH341A con clip SOIC-8 per la flash, analizzatore logico per trovare e validare la UART, USB-TTL CP2102 per la seriale, multimetro TRMS, più flux, treccia, terza mano e tappetino ESD. Copre tutta la catena: apri → salda/dissalda → dumpa → analizza.\nSul fronte radio ho già Flipper Zero e WiFi Pineapple, e in arrivo due SONOFF Zigbee CC2652P (uno coordinatore per una rete Zigbee di lab, uno sniffer) e un dongle nRF52840 per BLE e 802.15.4. Più avanti, senza fretta: ST-Link V2 (JTAG/SWD), un RTL-SDR genuino per l’SDR sub-GHz (occhio ai cloni su Amazon), e un ChipWhisperer per glitching e side-channel.\nI prossimi passi Camera prima (più superficie d’attacco): apri, foto della board, sigle di SoC e flash, poi dump — clip in-circuit o chip-off a seconda della board. binwalk + analisi: estrarre il filesystem, cercare la local key usando il Device ID come ancora. Validare la chiave: con la local key vera, tinytuya sulla LAN deve decifrare la 6668 e farmi leggere/comandare il device in locale, cloud escluso. Poi il resto: stesso trattamento agli altri Tuya, e apertura del binario radio per l’irrigatore e i device senza IP. Un ecosistema, un metodo ripetibile. Bucane uno per bene e gli altri vengono dietro.\nAggiornamenti 2026-09-08 — Ricognizione di rete completata. Camera individuata a .3 (v3.5), mappati dongle irrigatore e device ignoto. Banco da saldatura ordinato. Prossimo atto: apertura della camera e prima foto della board. Tutto su device miei, in laboratorio, a scopo di studio. Aggiorno a ogni tappa.\n","href":"/posts/tuya/","summary":"Questo è un post vivo: lo aggiorno man mano che la ricerca sui device Tuya va avanti. Non un singolo bersaglio, ma l’ecosistema — perché una volta capito il protocollo e il pattern hardware, la stessa chiave apre mezza casa.\nTuya è la piattaforma white-label che sta sotto a un’infinità di gadget economici: telecamere, prese, irrigatori, sensori. Cambia il marchio sulla scatola, ma dentro c’è quasi sempre lo stesso SoC, la stessa app (Smart Life / Tuya Smart) e lo stesso protocollo. Bersaglio perfetto per imparare: ne ho un lab pieno, e sono tutti cugini. Bussola metodologica: The Hardware Hacking Handbook e Practical IoT Hacking.\n","tags":["iot","tuya","hardware-hacking","radio","firmware"],"title":"Tuya - diario di ricerca"},{"content":"Questo è il diario di un progetto che doveva essere una serata e si è trasformato in una piccola masterclass di EDL, firehose e reverse engineering di bootloader. Obiettivo semplice: far girare Kali NetHunter su un OnePlus 7 GM1900. Realtà: il telefono si rifiuta di sbloccare il bootloader con un laconico “Device cannot be unlocked for technical reason”, e per capire il perché ho dovuto scendere fino a scrivere partizioni grezze via Qualcomm EDL.\nLo scrivo come l’ho vissuto, dead-end compresi. Anzi, soprattutto quelli.\nIl device (e il primo campanello) Il paziente: OnePlus 7 GM1900, codename guacamoleb, Snapdragon 855 (SM8150), storage UFS, 256 GB, slot A/B. Letto tutto via adb/fastboot senza sudo.\nModello: GM1900 (guacamoleb) Android: 12 / OxygenOS 12 (build GM1900_11_H.41) Region mark: US (ro.oem.key1=US, regionmark=US) Ed ecco il primo campanello: hardware GM1900 = variante cinese, ma software e region mark US/Global. È un OnePlus 7 cinese convertito a OxygenOS Global in qualche vita precedente. Tienilo a mente, perché è il filo rosso di tutta la storia.\nIl muro: “technical reason” Sblocco OEM attivato, sys.oem_unlock_allowed=1, telefono già resettato. Lancio il comando di rito:\n$ fastboot flashing unlock FAILED (remote: 'Device cannot be unlocked for technical reason') $ fastboot oem unlock FAILED (remote: 'Device cannot be unlocked for technical reason') Nessuna schermata di conferma, solo il muro. La prima ipotesi ovvia è il cooldown anti-furto di OnePlus dopo un reset: il token di sblocco viene provisionato server-side solo dopo un po’ di utilizzo. Aspetto giorni, tengo il telefono online. Niente.\nPoi la diagnostica che smonta l’ipotesi:\n$ fastboot flashing get_unlock_ability get_unlock_ability: 1 1 = il bootloader autorizza lo sblocco. Eppure i comandi falliscono. Quindi non è policy, non è cooldown. È qualcosa a un livello più basso. Nota di servizio: i getvar su singola variabile sul OP7 falliscono a caso con “Variable Not found”, usa sempre fastboot getvar all.\nProvato e fallito, dalla parte “facile” Prima di tirare fuori l’artiglieria ho esaurito le vie a basso rischio:\nCavo/porta USB diversi — nessun cambiamento. Downgrade a OOS11 via updater — impossibile, l’app serve solo l’ultima versione. Re-flash della OOS Global via local upgrade (3.25 GB, ufficiale) — eseguito, ma dopo il flash il device si identifica ancora come GM1900_11_H.41 e resta bloccato. Il local upgrade non tocca le partizioni di basso livello (persist, oem, opproduct) dove vivono identità e stato di lock. Diagnosi intermedia: il mismatch di identità sta nelle partizioni basse. Per riscriverle serve EDL.\nScendere in EDL EDL (Emergency Download Mode, Qualcomm 9008) parla il protocollo Sahara + Firehose. Setup su Kali, interamente da Linux (niente MSM Tool Windows):\nedl-venv/ # edlclient 3.62 (bkerler), installato da sorgente su Python 3.14 Loaders/ # 887 firehose loader oppo_decrypt/ # opscrypto.py + ofp_qc_decrypt.py Regola udev per 05c6:9008, gruppo plugdev, e — dettaglio che fa perdere ore — va fermato ModemManager, che altrimenti si mangia il device appena appare:\n$ sudo systemctl stop ModemManager Poi il quirk numero uno del OnePlus 7: non resta in EDL abbastanza a lungo per completare l’handshake Sahara. La mossa vincente è coordinata:\nAvvii edl PRIMA (resta in “Waiting for the device”), POI mandi adb reboot edl. Così edl aggancia il telefono nell’istante esatto in cui appare e parte subito.\nIl loader Gap teorico: nel repo Loaders non c’è il firehose del OnePlus 7 liscio. Provo per disperazione quello del 7T — e funziona:\nHWID: 0x000a50e100514985 MSM_ID: 0x000a50e1 CPU: SM8150 PK_HASH: 2acf3a85fde334e2... Il 7 e il 7T condividono msm_id e pk_hash, quindi il PBL accetta il loader del 7T. (Provare un loader è a rischio zero: se l’ID non combacia, il PBL lo rifiuta e basta.) Firehose connesso, UFS Samsung, GPT completa dumpata su 6 LUN. Backup read-only di devinfo, config, param, frp, keystore, secdata, dip. Restore point in tasca prima di scrivere un solo byte.\nTentativo 1: flippare il bit in devinfo La partizione devinfo (Lun4, magic ANDROID-BOOT!) ha uno stato leggibile:\noffset 0x0D is_unlocked = 0x00 (locked) offset 0x0E is_tampered = 0x00 offset 0x0F is_verified = 0x01 Teoria: se metto is_unlocked = 0x01 e riscrivo, l’ABL all’avvio si crede sbloccato senza passare dal comando fastboot. Preparo il file con un solo byte di diff, verifico, scrivo:\n$ edl w devinfo dumps/devinfo-unlocked.bin Wrote to sector 962718 Reboot. Ignorato. L’ABL resetta il flag: flash.locked=1, verifiedbootstate=green, unlocked false. Il OnePlus 7 cross-verifica devinfo contro il blob config (un HMAC firmato per-device, opaco, non falsificabile). devinfo non è il lever. Nessun danno, ma vicolo cieco.\nTentativo 2: restore MSM completo (e un bug in edl) Se il problema è il mismatch di identità, la cura è un restore di fabbrica tipo MSM. Il pacchetto OnePlus è un .ops cifrato → lo estraggo con opscrypto.py in rawprogram*.xml + patch*.xml + immagini + firehose, poi flasho con edl qfil.\nPrimo bersaglio: OxygenOS 10.3.8 Global (GM57AA). Flash che dichiara successo… ma il device è ancora OOS12 e tutti i write sono a 0.00 MB/s, “finito subito”. Qualcosa non scrive davvero.\nVado a leggere il sorgente di edl e trovo il bug: il ramo qfil in firehose_client.py chiama cmd_program senza prima chiamare writeprepare() — che invece i rami w/wl/wf chiamano. writeprepare() manda l’auth OnePlus (demacia + setprojmodel), che sblocca la protezione di scrittura. Senza, le scritture sulle partizioni protette vengono rifiutate in silenzio. (Ecco perché edl w devinfo aveva scritto: passava da writeprepare.)\nPatch di una riga:\n# firehose_client.py, ramo qfil, subito dopo \"[qfil] raw programming...\" self.firehose.modules.writeprepare() Il messaggio “Oneplus protection with prjid 100439 detected” mi conferma il progetto: 100439 in decimale è 0x18857 = 18857, il nostro device esatto.\nRiflasho la Global OOS10 con la patch: stavolta write veri. Ma al boot → SMT / bootloop. La Global su hardware cinese va in crisi di region (provisioning persist/oem incoerente). Serve la ROM cinese nativa.\nTentativo 3: HydrogenOS cinese nativa La trovo su Halab Tech: GM1900domestic_11_H.40_0323, ProjectNo 18857, base Android 12. È un .ofp (non .ops), quindi si decripta con ofp_qc_decrypt.py, che sputa fuori direttamente rawprogram*.xml + patch*.xml + firehose + immagini. Dettaglio download: il link Halab rimbalza sul CDN OnePlus che dà 403 al click → si scarica con wget aggiungendo il Referer:\n$ wget --referer=\"https://support.halabtech.com/\" \"\u003ccdn-url\u003e\" Flash via edl qfil con la mossa coordinata e la patch writeprepare: write reali a 14–27 MB/s, patching della GPT eseguito, is now bootable. Reboot → logo OnePlus → setup di HydrogenOS in cinese. Il telefono è tornato nativo e boota. Vittoria? No.\n$ fastboot flashing unlock FAILED (remote: 'Device cannot be unlocked for technical reason') Ancora. Su firmware cinese nativo, identità coerente, OEM unlock attivo, online. Il muro sopravvive a tre firmware completi. A questo punto la teoria del mismatch CN/Global è ufficialmente morta.\nLa svolta: non è hardware, è un bug di Android 12 Incrocio il reverse engineering con la ricerca in community. E salta fuori la verità, documentata su community.e.foundation (thread guacamoleb) e reddit r/oneplus: “technical reason” sul OnePlus 7 è un bug noto dell’abl di Android 12. Il fix della community è secco:\nScendi ad Android 11 → fastboot oem unlock funziona → installi subito la ROM custom, senza tornare ad Android 12.\nIl motivo per cui sbattevo sempre la testa: sono sempre stato su Android 12. La GM1900 originale (H.41) è A12, la HydrogenOS H.40 è A12. Il bug è nell’abl A12. La Global OOS10 (A10, senza bug) l’avrebbe sbloccata, ma andava in SMT per la region. Ho esplorato tutte le combinazioni tranne quella giusta: cinese + Android 11.\nUn po’ di reversing dell’abl Per curiosità sono andato dentro l’abl.elf: è un ELF firmato → volume UEFI FV → un blob LZMA a 0x3078, decomprimibile con il modulo lzma di Python in formato FORMAT_ALONE (643272 byte). Nel decompresso, la logica di sblocco in chiaro:\nIsAllowUnlock Error Reading FRP partition Unable set the unlock value set_boot_info_to_rpmb / km_client_read_rpmb_boot_info Lo stato di lock vive in RPMB (Replay Protected Memory Block), i dati di unlock su oplusreserve1, con verifica RSA del token. Tradotto: patchare l’abl non serve — con secure boot locked il device rifiuta un abl non firmato, e per aggirarlo servirebbe glitching del SoC. Fuori portata (e fuori scopo). La strada pulita resta il downgrade ad Android 11.\nDove sono ora Il piano risolutivo è chiaro, manca solo il file giusto:\nTarget: guacamoleb_14_H.01 — HydrogenOS cinese Android 11 (branch H). Cinese → boota nativo senza SMT. A11 → niente bug di sblocco. Salto minimo da A12. Blocco attuale, non tecnico: limite giornaliero di download su Halab. Aspetto il reset o trovo un mirror. La sequenza finale, quando avrò il firmware in casa:\nofp_qc_decrypt.py → rawprogram/patch/immagini edl qfil ... (mossa coordinata + patch writeprepare) reboot → boota Android 11 CN fastboot oem unlock ← senza il bug A12, dovrebbe passare → LineageOS 23.2 (dtbo/vbmeta/boot + sideload) + Magisk + modulo NetHunter Cosa mi porto a casa (anche se non ho ancora finito) Il telefono è vivo e funzionante su firmware nativo, ho un restore point completo via EDL, e soprattutto ho un ambiente edl + loader + oppo_decrypt pronto per qualsiasi device Qualcomm futuro. Ho toccato con mano Sahara/Firehose, l’auth OnePlus (demacia/setprojmodel), MSM/OFP, qfil, un bug reale in un tool open-source, secure boot, anti-rollback e RPMB.\nE ho imparato la lezione più vecchia dell’hacking hardware: il muro che non cede quasi mai è quello che stai attaccando dal lato sbagliato. Qui il lato giusto non era una partizione o un exploit, era una versione di Android.\nContinua. Aggiorno appena il download di guacamoleb_14_H.01 va in porto.\n","href":"/posts/oneplus-7-cn-nethunter/","summary":"Questo è il diario di un progetto che doveva essere una serata e si è trasformato in una piccola masterclass di EDL, firehose e reverse engineering di bootloader. Obiettivo semplice: far girare Kali NetHunter su un OnePlus 7 GM1900. Realtà: il telefono si rifiuta di sbloccare il bootloader con un laconico “Device cannot be unlocked for technical reason”, e per capire il perché ho dovuto scendere fino a scrivere partizioni grezze via Qualcomm EDL.\n","tags":["android","edl","oneplus","nethunter","firmware","reversing"],"title":"OnePlus 7 CN - NetHunter"}]