OnePlus 7 CN - NetHunter
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.
Lo scrivo come l’ho vissuto, dead-end compresi. Anzi, soprattutto quelli.
Il 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.
Modello: 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.
Il muro: “technical reason”#
Sblocco OEM attivato, sys.oem_unlock_allowed=1, telefono già resettato. Lancio
il comando di rito:
$ 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.
Poi la diagnostica che smonta l’ipotesi:
$ 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.
Provato e fallito, dalla parte “facile”#
Prima di tirare fuori l’artiglieria ho esaurito le vie a basso rischio:
- Cavo/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.41e 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.
Scendere in EDL#
EDL (Emergency Download Mode, Qualcomm 9008) parla il protocollo Sahara + Firehose. Setup su Kali, interamente da Linux (niente MSM Tool Windows):
edl-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:
$ 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:
Avvii
edlPRIMA (resta in “Waiting for the device”), POI mandiadb reboot edl. Così edl aggancia il telefono nell’istante esatto in cui appare e parte subito.
Il loader#
Gap teorico: nel repo Loaders non c’è il firehose del OnePlus 7 liscio. Provo per disperazione quello del 7T — e funziona:
HWID: 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.
Tentativo 1: flippare il bit in devinfo#
La partizione devinfo (Lun4, magic ANDROID-BOOT!) ha uno stato leggibile:
offset 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:
$ 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.
Tentativo 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.
Primo 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.
Vado 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.)
Patch di una riga:
# 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.
Riflasho 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.
Tentativo 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:
$ wget --referer="https://support.halabtech.com/" "<cdn-url>"
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.
$ 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.
La 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:
Scendi ad Android 11 →
fastboot oem unlockfunziona → installi subito la ROM custom, senza tornare ad Android 12.
Il 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.
Un 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:
IsAllowUnlock
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.
Dove sono ora#
Il piano risolutivo è chiaro, manca solo il file giusto:
- Target:
guacamoleb_14_H.01— HydrogenOS cinese Android 11 (branchH). 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:
ofp_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.
E 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.
Continua. Aggiorno appena il download di guacamoleb_14_H.01 va in porto.