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.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.

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 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.

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 11fastboot oem unlock funziona → 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 (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:

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.