In breve
La seconda lezione è un manuale di orientamento. Non risolve un singolo caso, ma insegna come non perdersi quando si analizza un programma sconosciuto: leggere la memoria, scegliere un debugger adatto, usare breakpoint mirati invece di procedere istruzione per istruzione, distinguere codice e dati, cercare i messaggi mostrati all’utente, osservare quali interruzioni vengono intercettate. Verso la fine mostra un esempio di protezione basata sulla risposta a una domanda del manuale.
La pagina spiega i concetti e il ragionamento. Non ricopia procedure operative per modificare un programma specifico e non incoraggia l’uso illecito delle tecniche descritte.
1. «Perso nei boschi di codice»
L’autore parte da un’esperienza reale: quando si interrompe un programma con un debugger, spesso ci si ritrova in un punto che non si riconosce, magari fuori dal programma stesso, in un driver o in un programma residente. Prima di analizzare occorre capire dove si è.
Gli strumenti utili devono mostrare, secondo il testo:
- I vettori di interruzione: la tabella che dice quale routine risponde a ciascuna interruzione.
- L’area dati del BIOS, che raccoglie informazioni sullo stato dell’hardware.
- Le strutture interne di DOS: catena dei blocchi di memoria, tabella dei file aperti, driver installati, programmi residenti e relativa memoria.
- La memoria estesa ed espansa, cioè le tecniche con cui i PC DOS superavano il limite della memoria convenzionale.
L’idea è quella di una mappa: senza sapere quali regioni appartengono al programma e quali al sistema, ogni osservazione può essere interpretata male.
Profiler e gerarchia delle chiamate
Prima di studiare in dettaglio il codice, il testo consiglia due strumenti di orientamento. Un profiler indica quali parti vengono davvero eseguite: se una funzione non partecipa al comportamento che interessa, è inutile analizzarla. Un grafico della gerarchia delle chiamate mostra quali funzioni ne richiamano altre, dando una visione d’insieme.
2. Il debugger come strumento principale
La lezione sostiene che il debugger è «l’unico strumento di cui si ha davvero bisogno». Deve poter operare con moduli residenti e interruzioni; per questo il semplice debug.com di DOS è giudicato troppo limitato per casi complessi. L’autore elenca alcuni debugger professionali dell’epoca.
Il testo aggiunge che molte copie dei programmi citati circolavano senza autorizzazione sul Web. Questo è un punto da separare dal valore storico: la presenza di copie diffuse non le rende lecite. Per una lettura didattica, interessa la categoria degli strumenti, non il suggerimento dell’autore di procurarsene copie senza autorizzazione.
La nota su SoftICE ha invece un interesse storico concreto: nasceva come debugger per DOS alla fine degli anni Ottanta, sviluppato da NuMega, e poi evolse verso Windows. Nota storica su SoftICE.
3. Il metodo «zen»: pensare prima di tracciare
Il tono è colorito, ma l’indicazione metodologica è chiara: quando si sospetta una protezione, non buttarsi subito in tecniche invasive. Se non si ha già un’idea, si rischia di perdere dettagli importanti e di restare sepolti da migliaia di istruzioni.
L’esecuzione a passo singolo è «costosa»: richiede tempo e produce troppi dettagli. Conviene invece procedere per approssimazioni successive:
- formulare un’ipotesi su quale parte del programma sia coinvolta;
- piazzare punti di arresto che riducano il problema;
- raccogliere una cronologia dell’esecuzione;
- usare il passo singolo soltanto quando si è vicini all’obiettivo.
Le domande da farsi prima di ogni breakpoint
Il testo ne propone tre: che cosa mi dirà questa interruzione?, che cosa dovrò sapere quando si fermerà?, il mio approccio è il più semplice e diretto? Sono domande valide per qualsiasi attività di debugging, anche su software proprio.
Le «briciole di pane»
Se non si registra una cronologia fin dall’inizio, può essere necessario ricominciare. È l’equivalente di annotare il percorso durante una passeggiata in un bosco.
4. Interruzioni software come punti di osservazione
Fermare il programma quando richiama certe interruzioni dà una vista d’insieme di ciò che sta facendo. Nel DOS, l’interruzione 21h raccoglie moltissimi servizi di sistema: file, memoria, tastiera, orologio. Altre interruzioni del BIOS gestiscono disco e tastiera.
Un breakpoint troppo generico fornisce rumore. Per questo il testo parla di breakpoint valutativi: ad esempio fermarsi su ogni chiamata della 21h, escludendo alcune funzioni frequenti e poco interessanti, come la lettura dell’ora o il controllo dello stato dell’ingresso. È un uso intelligente del filtro: non osservare tutto, ma tutto ciò che è informativo.
5. Funzioni, dati e «tentacoli»
Capire un programma significa spesso seguire una catena. Per comprendere una funzione bisogna capire quelle che chiama, poi quelle richiamate da queste, fino alle foglie. L’autore la chiama inseguire i «tentacoli». Per le funzioni esiste una lista implicita da seguire; per i dati no.
Per una variabile, occorre chiedersi quali funzioni la leggono e quali la scrivono. Si tratta dell’analisi dei riferimenti incrociati, che oggi i disassemblatori moderni producono in modo molto più comodo. Il testo cita strumenti dell’epoca, ma la logica resta la stessa.
6. Dove si trovano le chiamate di sistema
Se un programma è scritto in un linguaggio di alto livello, le chiamate al sistema passano di solito da funzioni di libreria comuni: tutte le chiamate della 21h partono dallo stesso punto. Se si individua quel punto, si può interrompere l’esecuzione al ritorno e risalire alla funzione chiamante.
L’autore propone anche un’euristica: una zona scritta in assembler dentro un programma altrimenti prodotto da un compilatore potrebbe essere stata aggiunta in un secondo tempo, e le protezioni vengono spesso inserite alla fine dello sviluppo. È una buona intuizione da usare come indizio, non come regola. Un’ottimizzazione prestazionale in assembler non è necessariamente una protezione.
7. Cercare i messaggi: stringhe ASCIIZ
Molti programmi mostrano un messaggio quando negano un’operazione. Il testo suggerisce di cercare le sequenze di caratteri leggibili nei file binari. Una stringa terminata da zero, come quelle usate dal C, è un punto di partenza utile. Non tutte le stringhe DOS sono però terminate da zero: alcuni servizi usano altri delimitatori o una lunghezza esplicita. Individuata una stringa, trovato dove viene richiamata, si risale al codice che decide di mostrarla.
La ricerca dipende da come il programma scrive sullo schermo: tramite servizi DOS, tramite il BIOS video oppure scrivendo direttamente nella memoria video. Nell’ultimo caso, un controllo sugli accessi alla zona corrispondente può individuare chi sta scrivendo il testo. Il concetto ricorda un problema più generale: partire da un effetto visibile e risalire alla causa.
8. Quando dati e codice sono mescolati
Alcune interruzioni vengono usate con dati inseriti subito dopo l’istruzione. Se il disassemblatore continua a leggere come codice ciò che in realtà sono dati, i risultati diventano senza senso. L’autore suggerisce di sospettare quando le istruzioni successive a una chiamata di interruzione non hanno logica e di esaminare come la routine modifica l’indirizzo di ritorno.
È un esempio di mescolanza di codice e dati: una sequenza che il disassemblatore tenta di interpretare come istruzioni può in realtà contenere parametri. Non va confusa automaticamente con codice auto-modificante o autoreferenziale.
9. I vettori agganciati
Confrontare i vettori di interruzione prima e dopo l’avvio di un programma rivela quali intercettazioni ha installato. Se, ad esempio, un programma aggancia la tastiera poco prima di una verifica, le zone sospette si riducono.
Il testo discute tre approcci: guardare le chiamate che leggono e scrivono i vettori, controllare direttamente le modifiche alla tabella e sospettare un gestore hardware quando una modifica avviene senza passare per i servizi di sistema.
Piccoli calcoli utili
Ogni vettore occupa 4 byte e la tabella parte all’indirizzo 0000:0000. Per trovare l’indirizzo del vettore si moltiplica il numero dell’interruzione per 4: per la 21h, 33 × 4 = 132, cioè 84 in esadecimale, quindi 0000:0084. Per la 63h, 99 × 4 = 396, cioè 018C, quindi 0000:018C.
Anche la conversione segmento:offset è spiegata bene: l’indirizzo fisico si ottiene moltiplicando il segmento per 16 e sommando l’offset. Nell’esempio del testo, 6049:891C e 6109:7D1C corrispondono entrambi all’indirizzo fisico 68DAC. Il calcolo è corretto: due coppie diverse possono indicare la stessa cella di memoria.
10. Strumenti storici citati
La lezione elenca utility per mappare la memoria, scaricarne il contenuto, analizzare file eseguibili e osservare le interruzioni. Il suo valore storico è il quadro della cultura tecnica dell’epoca: strumenti piccolissimi, spesso di pochi centinaia di byte, scritti da appassionati.
Per trovarli suggerisce Archie, un servizio nato nel 1990 per indicizzare i nomi dei file presenti sui server FTP pubblici, interrogabile anche per posta elettronica; fu considerato il primo motore di ricerca di Internet, antecedente al Web. Approfondimento storico su Archie. Poi consiglia di imparare a usare Yahoo, il tipo di directory web che all’epoca ordinava i siti per categoria.
Un’altra indicazione è però problematica: l’invito a «rubare» il codice dei migliori programmi e a modificarli senza riguardo. Una cosa è imparare dalle idee di un software, un’altra è appropriarsi del codice o delle opere altrui senza i diritti necessari.
11. L’esempio finale: la domanda del manuale
L’esempio riguarda un gioco automobilistico del 1989 che chiedeva, all’avvio, una risposta presa dal manuale cartaceo. Era una forma di protezione per copia detta manual protection o «paper protection»: il programma si poteva copiare, ma senza il manuale non si conosceva la risposta. Scheda storica del gioco.
L’autore contrappone due approcci:
- Il metodo normale: registrare la memoria prima e dopo aver digitato una risposta inventata, cercare dove compaiono i caratteri inseriti, osservare quale parte del programma li legge e arrivare alla routine che li confronta.
- Il metodo «zen»: restare nel punto in cui compare la richiesta e individuare le istruzioni che leggono un carattere alla volta dalla risposta, nell’area di codice vicina.
La differenza è tra partire dai dati inseriti e partire dal comportamento atteso della routine. Il secondo approccio riduce lo spazio di ricerca quando si riconosce già il tipo di schema.
La logica della verifica
Il programma non conserva la risposta in chiaro, ma una forma codificata: per ogni cifra applica una trasformazione al dato memorizzato, la confronta con ciò che l’utente ha digitato e si interrompe se c’è una differenza. Dal punto di vista della sicurezza è fragile perché la risposta si può ricavare da dati che il programma custodisce e dalla stessa regola usata per verificarla.
Il testo mostra anche come neutralizzare il confronto. Qui preferisco non riportare la sequenza dei passaggi: il valore didattico sta nel capire la debolezza concettuale, non nel diffondere una modifica per un prodotto specifico.
12. «Perché si crackano i programmi»: il passaggio ideologico
L’ultimo paragrafo smette di essere tecnico. L’autore afferma di agire contro la società e contro le leggi, di considerare liberi tutti i programmi protetti e di disprezzare denaro, governi, televisioni e opinione pubblica. Sono posizioni dichiarate, non spiegazioni neutrali.
Vanno notate due cose. La prima è la tensione interna: dice che i cracker non agiscono per denaro, ma poche righe prima ammette che a volte accettano incarichi e sono costosi. La seconda è che il passaggio confonde ragioni diverse: la curiosità intellettuale per i meccanismi, la critica al modello commerciale del software e la violazione di licenze o norme sono questioni distinte.
Una persona può studiare un meccanismo di protezione senza ritenere lecito rimuoverlo da programmi altrui o distribuire copie. Il documento ha interesse come testimonianza di una cultura giovanile e conflittuale di metà anni Novanta, non come giustificazione giuridica o etica.
13. Che cosa si può imparare oggi
- Orientamento prima dell’analisi. Sapere dove ci si trova evita conclusioni errate.
- Ridurre l’informazione. Un buon strumento di debug filtra, non mostra tutto.
- Partire dagli effetti. Messaggi, uscite e accessi ai dati sono punti d’ingresso verso la logica.
- Usare le euristiche con prudenza. Un’anomalia nel codice è un indizio, non una prova.
- Documentare il percorso. Una cronologia dell’analisi permette di tornare indietro.
Glossario
- Breakpoint
- Punto di arresto: il debugger ferma il programma quando viene raggiunta una condizione.
- MCB
- Blocco di controllo della memoria usato da DOS per descrivere le aree allocate.
- PSP
- Area che DOS crea all’inizio di ogni programma, con informazioni sul suo avvio.
- TSR
- Programma residente che rimane in memoria dopo la conclusione apparente.
- Segmento:offset
- Modo di indicare un indirizzo nei processori x86 in modalità reale.
- ASCIIZ
- Stringa di caratteri terminata da un byte nullo.
Fonte e verifiche
Fonte primaria: ORC_2.DOC, copia fornita da Luigi Loreti. Le osservazioni critiche sono commenti aggiunti; i calcoli sugli indirizzi sono stati ricontrollati. Non sono stati eseguiti né modificati i programmi citati.