Vai al contenuto
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after a top-up of $10 or more. Until Nov 30.

How it works

Che cos'è il reverse engineering? Usi, limiti e nozioni legali di base

Che cosa scarta la compilazione, in che cosa i disassembler differiscono dai decompilatori e quali limiti legali conoscere prima di analizzare un binario.

Base9 min di letturaDi NotHoly

Il reverse engineering consiste nel capire come è stata costruita una cosa esaminando il prodotto finito. Per il software, il prodotto finito è di solito un programma compilato: un eseguibile, una libreria condivisa o un'immagine firmware. Il codice sorgente manca o non è mai stato rilasciato, quindi parti dal codice macchina e risali a che cosa fa il programma e perché.

Lo si fa per molti motivi, e per la maggior parte sono ordinari. Un ingegnere della sicurezza controlla il binario di un vendor prima di fidarsene. Uno sviluppatore cerca di far funzionare un formato di file chiuso con uno strumento aperto. Uno studente vuole vedere che cosa produce davvero un compilatore. Questo articolo tratta che cosa scarta la compilazione, la differenza tra analisi statica e dinamica, disassembler e decompilatori, i motivi legittimi di questo lavoro e i limiti legali da conoscere prima di cominciare. Il prossimo articolo della serie fa una prima analisi vera.

Che cosa butta via la compilazione

Un compilatore trasforma un sorgente leggibile da una persona in istruzioni che un processore può eseguire. L'output è corretto per la macchina e dice poco delle intenzioni. Lungo la strada spariscono diverse cose:

  • Commenti e formattazione. Non arrivano mai all'output.
  • Nomi. I nomi delle variabili locali e molti nomi di funzione vengono scartati, a meno che la build non conservi i simboli di debug o che il file non abbia una tabella dei simboli.
  • Tipi. Un intero a 32 bit, un flag e un indirizzo di memoria sono tutti soltanto bit in un registro o sullo stack.
  • Struttura di controllo. Un ciclo for diventa un confronto e un salto. Un if diventa un salto condizionato verso un altro punto del file.
  • Disposizione originale. Gli ottimizzatori riordinano, uniscono e mettono inline il codice, quindi una funzione che hai scritto può non corrispondere a un solo blocco nel binario.

Ogni perdita rende meno certo il passo successivo. Chi fa reverse engineering ricostruisce alcuni di questi dettagli da indizi: le stringhe usate dal programma, le funzioni che importa dal sistema operativo e il modo in cui i dati si muovono tra i registri.

Puoi vederne una versione in piccolo con Python 3.12. L'esempio qui sotto è una funzione di tre righe con un commento. Il bytecode Python non è codice macchina nativo, quindi prendilo come un'analogia e non come un modello dell'output di C. Rende comunque l'idea.

python
# Teaching constant, not real access control.
def check_pin(user_pin):
    training_pin = 2468  # the comment never reaches the bytecode
    return user_pin == training_pin

Compilalo e disassemblalo con questi comandi:

bash
python -c "import py_compile; py_compile.compile('check_pin.py', cfile='check_pin.pyc', doraise=True)"
python -c "import marshal, dis; data = open('check_pin.pyc','rb').read()[16:]; code = marshal.loads(data); fn = [c for c in code.co_consts if hasattr(c,'co_code')][0]; print('name:', fn.co_name, '| constants:', fn.co_consts, '| locals:', fn.co_varnames); dis.dis(fn)"

Sulla macchina su cui è stato verificato questo articolo, l'output è stato:

text
name: check_pin | constants: (None, 2468) | locals: ('user_pin', 'training_pin')
  2           0 RESUME                   0

  3           2 LOAD_CONST               1 (2468)
              4 STORE_FAST               1 (training_pin)

  4           6 LOAD_FAST                0 (user_pin)
              8 LOAD_FAST                1 (training_pin)
             10 COMPARE_OP              40 (==)
             14 RETURN_VALUE

Saltano all'occhio due cose. Il commento è sparito: una ricerca del suo testo nel file compilato non trova niente. I nomi sono sopravvissuti, perché Python li tiene nel code object. Un programma C compilato spesso non conserva né l'uno né gli altri, quindi lo stesso esercizio su un binario nativo è più difficile. Usalo come modello mentale, senza aspettarti che valga per ogni linguaggio.

Analisi statica e dinamica

I metodi di reverse engineering si dividono in due grandi gruppi.

L'analisi statica esamina il file senza eseguirlo. Leggi le istruzioni, elenchi import e stringhe, mappi le funzioni e i loro riferimenti incrociati e costruisci un modello del programma. Il file non viene mai eseguito, ma i tuoi strumenti ne fanno comunque il parsing, quindi tienili aggiornati e lavora su una macchina isolata quando il campione può essere ostile. Il lavoro statico è più lento, ma è ripetibile, e la maggior parte delle persone lo fa per primo.

L'analisi dinamica esegue il programma sotto osservazione. Un debugger lo mette in pausa ai breakpoint, così puoi leggere registri e memoria. Un tracer registra le chiamate di sistema, gli accessi ai file o le connessioni di rete. Una sandbox registra che cosa cambia su disco. Il lavoro dinamico mostra il comportamento reale, compresi i valori che esistono solo a runtime, ma vuol dire eseguire codice di cui magari non ti fidi. Fallo in una macchina virtuale usa e getta senza account reali, senza cartelle condivise e senza dati sensibili, e possibilmente senza accesso alla rete.

La maggior parte del lavoro reale combina le due. Un passaggio statico trova la funzione che controlla una password. Un'esecuzione nel debugger mostra poi quali sono davvero input e output.

Disassembler e decompilatori

Un disassembler converte i byte in istruzioni assembly per una specifica architettura di processore. La traduzione è meccanica: una sequenza di byte diventa un mnemonico come mov, cmp o jne, con i suoi operandi. Il risultato corrisponde esattamente alla macchina, ma resta di basso livello. Leggere migliaia di istruzioni per capire una sola funzionalità richiede molto tempo.

Un decompilatore va oltre. Cerca di produrre pseudocodice di livello più alto, di solito simile al C, dagli stessi byte. Recupera i confini delle funzioni, il flusso di controllo come if e while, e tipi di dati approssimativi. Il suo output è un'interpretazione e non una copia del sorgente originale. Può sbagliare i tipi, unire cose che erano separate o inventare una struttura mai esistita. Una buona abitudine è leggere il codice decompilato accanto al disassemblato e fidarsi dell'assembly quando i due non concordano.

Ghidra è uno degli strumenti gratuiti più usati che fanno entrambe le cose. È un framework di reverse engineering del software creato e mantenuto dal Research Directorate della National Security Agency statunitense, con licenza Apache-2.0, e gira su Windows, macOS e Linux. Gestisce molte architetture di processore e formati di file, e si può estendere con Java o Python. Il suo README chiede JDK 25 (64-bit) per eseguire una release ufficiale. Esistono altri strumenti, commerciali e aperti, e le idee di questo articolo valgono per tutti.

A che cosa serve

Il reverse engineering ha diversi usi legittimi.

Interoperabilità. Il tuo software deve scambiare dati con un programma o un formato chiuso, e il vendor non pubblica i dettagli. L'Unione europea tratta direttamente questo caso, come spiega la sezione legale.

Ricerca di vulnerabilità. Esamini software che hai il permesso di testare, trovi falle e le segnali perché il vendor le corregga. Di solito si segue la coordinated disclosure: una segnalazione privata, una finestra per la correzione, poi i dettagli pubblici.

Analisi di malware. Chi difende smonta un file sospetto per capire che cosa fa, quali sistemi tocca e come rilevarlo. Buona parte delle conoscenze pubbliche sulle minacce viene da questo lavoro.

Studio. Leggere un programma compilato è uno dei modi migliori per capire come i computer eseguono il codice: disposizione della memoria, convenzioni di chiamata e il divario tra ciò che scrivi e ciò che viene eseguito.

Verifica di ciò che viene dichiarato. Potresti voler controllare che cosa invia in rete un programma o che cosa salva in locale, soprattutto quando c'è di mezzo la privacy. Si pongono le stesse questioni legali di qualsiasi altra analisi.

Il quadro legale, a grandi linee

Questa è una panoramica e non una consulenza legale. Le leggi cambiano da paese a paese, e i contratti possono aggiungere condizioni che valgono ovunque. Contano soprattutto tre livelli.

1. Termini di licenza. La maggior parte delle licenze software commerciali vieta il reverse engineering salvo dove la legge lo consente. Le licenze open source funzionano diversamente e spesso permettono di leggere e modificare il codice, ma hanno comunque delle condizioni. Leggere un sorgente che ti è stato dato non equivale a fare reverse engineering di un binario, e nessuna delle due cose equivale a introdursi in un servizio. Prima di analizzare un prodotto che non hai scritto tu, leggine la licenza.

2. Unione europea. La direttiva 2009/24/CE relativa alla tutela giuridica dei programmi per elaboratore include un'eccezione per l'interoperabilità all'articolo 6. In breve, un utente legittimo, o chi agisce per suo conto, può decompilare un programma quando è indispensabile per ottenere le informazioni necessarie a far funzionare con altri un programma creato autonomamente. Ci sono delle condizioni. Le informazioni non devono essere già facilmente disponibili, il lavoro deve limitarsi alle parti del programma necessarie per l'interoperabilità e le informazioni non devono essere usate per altri scopi né condivise oltre quanto richiede l'interoperabilità. Non devono servire a sviluppare un programma sostanzialmente simile, e l'eccezione non può recare un pregiudizio irragionevole ai legittimi interessi del titolare dei diritti. L'articolo 6 riguarda l'interoperabilità e non la curiosità in generale.

3. Stati Uniti. La sezione 1201 del Digital Millennium Copyright Act vieta di aggirare le misure tecnologiche che controllano l'accesso alle opere protette da copyright. Quel divieto è separato dalla violazione del copyright, quindi si può infrangere la regola senza copiare niente. La legge prevede delle esenzioni, tra cui la sezione 1201(j), che copre determinate attività di test di sicurezza a condizioni stabilite. Il Copyright Office tiene anche un procedimento ogni tre anni che può aggiungere esenzioni temporanee. La sua pagina sulla sezione 1201, citata nelle fonti, descrive il processo. Al momento della verifica era iniziato il decimo procedimento, e le esenzioni rinnovate al suo interno restano in vigore per tre anni, da ottobre 2027 a ottobre 2030. Leggi la legge e i regolamenti vigenti prima di fare affidamento su qualsiasi esenzione. Fa fede il testo, e un riassunto non lo sostituisce.

Altri paesi hanno le proprie regole. Prendi i tre livelli qui sopra come esempi di come si pongono le domande, e non come una mappa della tua giurisdizione.

Ne seguono tre indicazioni pratiche. Analizza software tuo, build tue o materiale che hai il permesso di testare. Tieni il lavoro di test dentro sistemi che controlli o per cui hai un permesso scritto. Se un progetto comporta un rischio legale, chiedi a un avvocato prima di cominciare, non dopo aver pubblicato.

Comincia dai binari che hai scritto tu

Il modo più sicuro per imparare è lavorare sul tuo codice. Scrivi un piccolo programma, compilalo e analizza il risultato. Conosci il sorgente, quindi puoi confrontare ogni ipotesi con la realtà. Eviti anche le questioni legali legate al software di terzi e il rischio di eseguire un campione sconosciuto.

Per fare pratica in modo pulito segui due regole. Non eseguire mai binari sconosciuti sulla tua macchina di tutti i giorni. Non puntare mai strumenti di analisi o scansioni su sistemi che non possiedi o che non hai il permesso scritto di testare, compresi i siti che sembrano innocui. Il prossimo articolo, una prima analisi con Ghidra su un binario che compili tu, ti guida in un primo progetto su un programma costruito da te.

Errori comuni

  • Trattare l'output decompilato come il sorgente originale. È un'interpretazione. Confrontalo con il disassemblato.
  • Credere che in un binario stripped non resti niente. Di solito restano stringhe, import, costanti e comportamento. Lo stripping rimuove i nomi, non tutto.
  • Cominciare con un campione sconosciuto su un computer di uso quotidiano. Usa una macchina virtuale isolata con snapshot.
  • Applicare ovunque la legge di un solo paese. Contano la giurisdizione e anche i contratti.
  • Confondere analisi e accesso. Esaminare un file che hai è diverso dall'usare un servizio attivo che non controlli.
  • Pubblicare i finding prima della disclosure. Condividi i dettagli attraverso il processo del vendor prima di scriverne pubblicamente.

Per andare oltre

Studia il processore che stai analizzando, di solito x86-64 o ARM64 sui desktop moderni, e come il sistema operativo passa gli argomenti alle funzioni. Compila due volte lo stesso piccolo file C con un compilatore C di cui ti fidi, una volta con le informazioni di debug e una volta stripped, e confronta che cosa resta. Leggi la documentazione di Ghidra e il README del progetto. Poi prosegui con l'articolo pratico che segue.

Fonti

Fonti

  1. Ghidra project site
  2. Directive 2009/24/EC on the legal protection of computer programs (EUR-Lex)
  3. U.S. Copyright Office: section 1201 rulemaking
  4. NSA Ghidra repository on GitHub

Altro su Reverse engineering

Reverse engineering

Perché usare NotHoly per il reverse engineering

Tieni Ghidra, radare2 o IDA. Incolla l'output del decompilatore e il modello lo spiega e scrive lo script, senza rifiutare. Triage statico per iniziare.

Intermedio3 min di lettura