visita il sito del nostro sponsor





Tecniche di protezione del software




Il presente articolo è stato pubblicato per la prima volta sul numero 118 (Febbraio 2001) della rivista:


Potete trovare gli articoli indediti di Enigma Amiga Life al seguente indirizzo:
http://www.amigalife.info


I latini dicevano "ripetita iuvant", quindi riprendiamo il discorso interrotto ribadendo, ancora una volta, che in tutte le le tecniche di protezione esiste un punto debole e questo è costituito dalla porzione di codice che deve essere caricata ed eseguita per prima, sia essa il main o il loader, che riconosciuta la protezione ha il compito di lanciare il programma vero e proprio dopo averlo decrittato. Tale codice deve necessariamente essere in chiaro: non protetto, non crittato, non compattato, e facilmente individuabile dalle routine del Sistema
Operativo e quindi, anche dall'eventuale cracker. Il compito di chi progetta un nuovo schema di protezione non è un lavoro semplice, ed esperienze di cracking non fanno sicuramente male, anche perché queste aiutano a comprendere, per poi eliminarli, quali siano gli appigli che si stanno offrendo al cracker, sebbene quasi tutte le norme internazionali di tutela del software vietino espressamente questo genere di attività didattica.


L'errore più comune è di concentrare i controlli in poche subroutine e farle richiamare in punti prestabiliti, se non addirittura in un'unica iper-routine, o lasciare in chiaro i messaggi che verranno mostrati nel caso i controlli falliscano. Nella scorsa puntata abbiamo accennato a come sia possibile nascondere da occhi indiscreti le frasi sensibili, quindi la soluzione di una parte del problema è già stata trattata anche se in modo superficiale, vediamo ora come sia possibile complicare la vita al cracker.


Una buona soluzione consiste nel disseminare qua e là, tra il codice del programma, le routine di protezione, magari duplicandole e richiamandole occasionalmente, in modo da sviare il cracker, costringendolo così, a studiare tutto il programma o quasi per inseguire il flusso della protezione, in poche parole è fortemente sconsigliato l'uso si subroutine per eseguire i controlli, quindi i metodi di un eventuale classe devono essere dichiarati inline, proprio per forzare il compilatore a creare codice ridondante. Per esempio, in un videogioco, si potrebbe pensare di eseguire un controllo antipirateria, non nel main, ma nel codice di ogni livello (duplicandolo ogni volta: un controllo diverso per ogni livello), anche se la soluzione più diabolica sarebbe quella di effettuare i controlli a partire da un certo livello in poi, per esempio dal terzo, in questo modo il cracker, che difficilemente ha il tempo o la voglia di terminare il gioco, crede che questo sia divenuto copiabile e procede con la distribuzione delle copie pirata, che però sono inservibili dato che qualsiasi giocatore incallito, prima o poi, giungerà ad un livello protetto. Questa tecnica non è mutuabile per gli applicativi, ma con un po' di fantasia...


Il cracker per sproteggere un siffatto programma deve arrivare a conoscerlo quasi come il suo creatore spendendo un mucchio di energie. E' certamente per questo motivo, che quando l'Amiga regnava nel settore videoludico, i videogiochi sprotetti erano corredati di Trainer, ovvero di particolari menù, con i quali eludere i controlli sulla vitalità del personaggio o sul fine della partita, cosa che spesso li rendeva più interessanti degli originali, e spinse qualche softwarehouse a lasciar trapelare i cheat, ovvero delle combinazioni segrete per attivare funzioni simili a quelle dei trainer, che servono in fase di debug del programma, e che difficilmente sono rimosse nella versione finale, per non rischiare di comprometterne l'affidabiltà. Oggi questa usanza di aggiungere i trainer si è persa, forse perché è scaduto il livello dei cracker, ciò può essere una conseguenza delle attuali protezioni ipertecnologiche, ma poi tradite da una cattiva implementazione; cosa che accade spesso nelle soluzioni preconfezionate basate l'uso di dll o loader (sempre identici) contenenti le routine anticopia.


Di solito ci si dimentica che più è piccolo il codice più è facile seguirne il flusso e comprenderne la funzione. In generale, la mente umana non riesce a focalizzarsi su più di un paio di schermate di codice, ed il cracker opera su codice mnemonico prodotto da un compilatore durante la traduzione da linguaggio ad alto livello, caratterizzato da istruzioni potenti e versatili, a linguaggio macchina, per definizione questo è il livello più basso, e necessariamente un'istruzione ad alto livello da origine ad una sequenza, più o meno lunga, di istruzioni a basso livello, tutto ciò è un vantaggio per chi protegge in quanto rende più tediosa la fase di crack, quindi la logica, nonché l'esperienza, suggerirebbe di innestare le routine anticopia direttamente nel codice da proteggere, rendendole parte integrante, ma questo è frequentemente ignorato nei kit commerciali; e quando questi sono ben progettati e realizzati, sono talvolta usati in modo improprio o tremendamente stupido. Tutto ciò accade perché non è raro che si decida di aggiungere una qualche forma di protezione poco prima della distribuzione commerciale del prodotto, ed il team di sviluppo, per mancaza di tempo o per sbadataggine, si limiti a scrivere una funzione wrapper, dal pittoresco nome IsProtectionOn(), nella quale incapsula tutto il codice di protezione che, come abbiamo più volte ribadito, andrebbe sparpagliato e camuffato per bene tra le altre funzioni del codice, per tanto l'innesto di una protezione anticopia andrebbe pianificato nelle fasi iniziali del progetto.


Un errore come questo, in un sol colpo, vanifica tutti gli sforzi di chi ha progettato le API dei kit commerciali, ed il codice dietro di esse, spesso e volentieri un vero gioiellino di tipo automodificante (rende più arduo l'uso di debugger) ed autocontrollante (impedisce la realizzazione di bypass) per scoraggiare gli attacchi da parte dei cracker, i quali preferiscono, piuttosto che aver a che fare con il codice automodificante, spulciarsi il codice dell'applicativo alla ricerca della falla nella protezione, e nel caso precedente anche una talpa riuscirebbe a scovarla. Questo è il tipico errore delle softwarehouse che ricorrono alle dongle, molto facili da usare, specialmente in modo errato.


Un altro frequente errore è quello di legare l'esito di un controllo ad un flag che diventa TRUE o FALSE dopo un controllo anticopia, questo perché il cracker non deve fare altro che forzare il valore corretto in tale flag, per vedere funzionare il software anche in caso di fallimento della verifica. Si può ovviare a questo inconveniente passando tra le subroutine non dei flag, ma delle informazioni più complesse, magari usando un protocollo preso in prestito dalle tecniche per rendere sicure le transazioni, come il Zero Knowledge Protocol, ampiamente utilizzato anche nel protocollo di riconoscimento della password dell'utente quando ci si collega con il proprio provider internet, noto con il nome di CHAP (challenge acknowledgedment protocol), nel quale non viene trasmessa la password, che entrambi le parti già conoscono, ma grazie ad una sequenza di domande mirate ed a risposte calcolabili sono se si possiede la passoword, il sistema decide se siamo autorizzati o meno. Per maggiori informazioni sul ZKP si rimanda all'apposito riquadro di approfondimento.


Si può sfruttare l'idea del challenge per costruire il grado di fiducia sull'integrità del sistema anticopia, vediamo come. In punti diversi del programma devono essere eseguiti diversi controlli, non solo usando codice ridondante, ma anche verificando cose diverse come marcatori, serial number, presenza di dongle,... In questo modo si riduce il rischio che il cracker abbia trovato e neutralizzato tutti i nostri espedienti, ciascun controllo fornirà il suo esito che andrà memorizzato opportunamente. All'avvio, il programma comincia ad eseguire i controlli, i quali dovranno continuare durante tutto il ciclo di esecuzione, in questo modo durante il funzionamento cresce il grado fiducia, amenoché anche uno solo dei controlli fallisca, nel qual caso si ha certezza che qualche tentativo di manomissione del nostro codice c'è stato, e si possono far scattare le procedure di blocco del pacchetto, anche se è buona norma ritardare il più possibile il momento della rappresaglia, che in alcun modo deve provocare perdita dei dati o il blocco del computer dell'utente, infatti il blocco di sistema informatico è considerato reato in molti stati, e si rischiano pene piuttosto severe.


Si conclude qui questa breve trattazione sulle tecniche di protezione anche se ci sarebbe ancora tanto da dire, chissà un giorno potremmo riprendere questo discorso, magari approfondendo aspetti da voi proposti.


Francesco De Napoli

I trucchi del mestiere
1) Mai usare nomi significativi nella dichiarazione delle procedure e soprattutto dei file usati per memorizzare le infomazioni, anche se non sensibili.
2) Usare il più possibile codice ridondante, automodificante, ed autocontrollante (uso di checksum ovunque possible).
3) Realizzare procedure di autoriparazione del codice (p.e. Reed Solomon per memorizzare plug-in o livelli dei giochi).
4) Memorizzare le informazioni sensibili (serial number, password,...) in posti insoliti e più volte, e crittate con algoritmi o per lo meno con chiavi diverse.
5) Non fidarsi di informazioni come la data di sistema che possono essere facilmente modificate dall'utente, eventualmente confrontarle con quella prelevate per altra via.
6) Attendere qualche secondo prima di validare eventuali password, in modo da rallentare attacchi basati sulla forza bruta.
7) Non usare mai frasi in chiaro per segnalare le violazioni del sistema, o il termine del periodo di prova.
8) Depistare il cracker con chiamate a funzioni di validazione fasulle.




Zero Knowledgement ProtocolIl protocollo Zero Knowledgement Protocol è stato messo appunto per risolvere diversi problemi, tutti legati alla pericolosità di divulgare conoscenze, ed in in particolare password, ogni volta che si deve dimostrare ad una terza parte di esserne in possesso, sia perché qualcuno possa carpirle in modo fraudolento, e sia per impedire comportamenti scorretti della terza parte, ovvero che si spacci per noi in seguito. L'esempio tipico che viene usato per spiegarne il funzionamento è la Caverna di Alibabà.


Supponiamo che la caverna abbia la forma rappresentata in figura e che ci sia richiesto di dimostrare di conoscere la parola magica con cui aprire la porta. Se il nostro interlocutore è presente mentre la pronunciamo anch'egli sarà inevitabilmente partecipe della nostra conoscenza. Possiamo però fornirgli una prova indiretta: imbocchiamo, da soli ed a caso, un ramo della caverna; a questo punto chiediamo di dire da quale ramo dobbiamo riapparire. La probabilità che siamo nel ramo giusto, e quindi non sia necessario attraversare la porta, è pari ad 1/2. Ripetendo una seconda volta l'esperienza, la probabilità di essere nel ramo indicato è ancora 1/2, ma la probabilità che per ben 2 volte ci sia stato un simile evento è di 1/4. E' facile rendersi conto che la probabilità che ciò accada per ben n volte di seguito è pari a 1/(2^n). Dopo solo cinque tentativi la probabilità che non conosciamo la parola magica è di 1/32, e quindi l'osservatore può ragionevolmente ritenere che conosciamo il segreto per n sufficientemente grande se siamo sempre emersi dal lato corretto, in caso contrario sarà sicuro che non conosciamo la parola magica.







visita il sito del nostro sponsor