Home

 
 
 
 
 



 
 
 
 

 
 
 

 
 
 
 
 









Blog2theMax
Il blog del team di Code Architects

Premessa. Non so se questa piccola scoperta che ho fatto di recente sia già documentata da qualche parte su Internet. Non ne ho mai sentito parlare, ma mi sembra strano che nessun’altro ci abbia pensato prima. Comunque, ci sono arrivato da solo e questo è sufficiente per scriverne sul blog.


Mi sembra inutile rimarcare il fatto che uno dei problemi che assilla chi scrive software per professione è la protezione dalle copie. In più, se programmate con .NET avete anche il problema di proteggere le applicazioni dalla decompilazione. Ovviamente i due problemi sono collegati, perchè se volete proteggere una applicazione con qualche meccanismo software (ad es. la lettura delle caratteristiche della macchina su cui il cliente installa la sua copia) occorre evitare che una semplice decompilazione + ricompilazione possa permettere ad un hacker neanche troppo esperto di bypassare la vostra protezione.

Non si tratta certo di problemi nati con .NET. Una quindicina di anni fa campavo sviluppando tool per programmatori con la mia azienda personale – la SoftWhale, dove “Whale” = “Balena” per chi non lo sapesse – e uno dei miei prodotti più gettonati era NOWAY, un programma che crittografava gli eseguibili MS-DOS proteggendoli quindi dalla decompilazione (un problema molto sentito da chi lavorava in Clipper) e permetteva di lanciarli solo sui computer per i quali la software house forniva al cliente dei codici di sblocco. NOWAY era scritto in puro Assembly 8086 e non è sopravvissuto al passaggio da MS-DOS a Windows.

In commercio esistono molti tool per .NET che servono a risolvere questi problemi, incluso obfuscator, compilatori “nativi” e chiavi hardware. Se volete proteggere la vostra IP (Intellectual Property) dovreste darci una occhiata approfondita.Per nostra fortuna, in Code Architects scriviamo soprattutto applicazioni Enterprise che non richiedono questo tipo di protezione, quindi non ho mai approfondito la questione. Immagino che questi tool facciano bene il loro lavoro, e li testerò se e quando avremo la necessità di farlo.

Se però (a) scrivete perlopiù applicazioni Windows Forms, (b) non vendete software pacchettizzato ed eseguite direttamente voi le installazioni presso il cliente, e (c) fate anche assistenza post-vendita, allora è possibile ottenere la protezione dalla decompilazione e dalle copie illegali con un meccanismo davvero molto semplice.

Cominciamo scrivendo una semplice applicazione Windows Forms. In questo esempio uso VB2005 ma ovviamente la tecnica si applica a qualsiasi linguaggio .NET.

Imports System.Reflection

Friend Module Module1

 

   <STAThread()> _

   Public Sub Main()

      ' in this demo we call the secret routine both directly and via reflection

      ' just to prove that either method works

       MySecretCode("Direct call")

       GetType(Module1).InvokeMember("MySecretCode", BindingFlags.InvokeMethod, _

          Nothing, Nothing, New Object() {"Reflection call"})

   End Sub

 

   Public Sub MySecretCode(ByVal title As String)

#If Not COPYPROTECT Then

      ' here goes all the code that you want to protect from decompilation

      MessageBox.Show("Running secret code!", title)

      ' ...

#End If

   End Sub

End Module

Poichè la variabile di compilazione COPYPROTECT non è definita, il codice nel metodo MySecretCode è incluso nell’eseguibile come se il blocco #IF non esistesse. In questo esempio proteggo un solo metodo, ma in una applicazione reale dovreste ripetere il procedimento con tutti i metodi che contengono del codice critico, magari dove implementate gli algoritmi più interessanti oppure dove controllate che l’utente abbia la licenza di eseguire il programma sul computer in questione. È importante che tutti i blocchi #IF... #ENDIF siano completamente contenuti in singoli metodi, ovvero che non capiti mai che uno di questi blocchi contenga le keyword Sub o Function (o le corrispettive End Sub o End Function). Per lo stesso motivo, potete utilizzare il meccanismo anche con le Property ma solo se utilizzate i blocchi #IF all’interno del blocco Get o Set. I blocchi #IF non devono assolutamente includere field, definizioni di eventi, o altro. 

Per completare la preparazione è opportuno definire una nuova configurazione per la soluzione. Dal menù Build selezionate il comando Configuration Manager, dalla combobox in alto a sinistra selezionate <New> e create una configurazione chiamata CopyProtected, che copia le impostazioni iniziali dalla configurazione Release.

Dopo esservi accertati che la nuova configurazione è quella attiva, create una costante condizionale di compilazione chiamata COPYPROTECT e impostata al valore True. In C# questa costante si crea dalla pagina Build delle proprietà di progetto, mentre in VB2005 dovete cliccare sul pulsante Advanced Compile Options che trovate nella pagina Compile:

Grazie a questa nuova configurazione, potete passare facilmente dalla versione normale (ossia Release) a quella copy-protected. Compilando l’applicazione in entrambe le configurazioni, avrete due eseguibili con lo stesso nome (ad es. Project1.exe) e contenenti gli stessi metadati (perchè le #IF sono sempre all’interno dei metodi e non includono campi, proprietà, o altro). L’esempio che segue presume che abbiate compilato una applicazione che si chiama Project1, che NON è firmata con uno strong name.

Preparate il setup, se necessario, utilizzando la versione Release, ma ricordatevi anche di memorizzare separatamente (ad es. su una chiavetta USB) l’eseguibile ottenuto in configurazione copy-protected e l’utility NGEN fornita con .NET Framework. Infine, recatevi presso il vostro cliente e seguite accuratamente la semplice procedura:

1) Lanciate il setup della applicazione, o più banalmente utilizzate XCOPY per copiare tutti i file sul disco rigido del cliente. In questo esempio supporremo che l’installazione sia avvenuta nella directory c:\myapp e che quindi il file eseguibile sia c:\myapp\Project1.exe.

2) Aprite una finestra di comandi, navigate nella directory che contiene l’esegubile e lanciate NGEN per creare una immagine in codice nativo della applicazione. Il comportamento di default di NGEN è OK, quindi non preoccupatevi troppo delle varie opzioni. (Lasciate aperta questa finestra, perchè vi servirà presto.)

3) Aprite una seconda finestra di comandi, navigate nella directory c:\windows\assembly\NativeImages_v2.0.50727_32\nomeassembly, dove nomeassembly è il nome dell’EXE ma senza l’estensione (Project1 in questo esempio). Noterete che in questa directory contiene una sottodirectory il cui nome è una stringa di caratteri esadecimali (nel mio esempio ho ottenuto d37032afe4f6f44588d52ca99d7bb1e5). Questa directory contiene un eseguibile dal nome nomeassembly.ni.exe (Project1.ni.exe nel nostro esempio), che contiene la versione compilata (senza IL e quindi non decompilabile) dell’eseguibile completo.

4) Dal prompt della seconda finestra dei comandi, con un comando COPY o MOVE spostate il file <nomeassembly>.ni.exe su qualche altra directory del disco rigido, ad esempio:
     MOVE d37032afe4f6f44588d52ca99d7bb1e5\Project1.ni.exe c:\
Questa operazione serve ad evitare che questo eseguibile sia cancellato dalla successiva invocazione di NGEN. Lasciate aperta anche questa seconda finestra dei comandi.

5) Sovrascrivete il file c:\myapp\Project1.exe con la versione copy-protected che avete sulla chiavetta USB.

6) Dalla prima finestra di comandi lanciate nuovamente NGEN, questa volta sulla versione copy-protected. Se questo programma venisse eseguito ora probabilmente andrebbe in errore o comunque non funzionerebbe bene, perchè alcune parti del codice non sono presenti, quindi NON lo mandate in esecuzione.

7) Dalla seconda finestra dei comandi, eseguite un comando DIR e noterete che la directory creata precedentemente (d37032afe4f6f44588d52ca99d7bb1e5, in questo esempio) è stata sostituita oppure è stata affiancata da un’altra directory dal nome random. (Nell’esempio che sto eseguendo mentre scrivo queste note è stata creata una directory di nome 9990426218ec334e9d3d62f41cb9a255.) Questa nuova directory contiene una nuova versione di Project1.ni.exe, che corrisponde alla versione in codice nativo dell'’eseguibile incompleto.

8) Sempre dalla seconda finestra dei comandi, copiate la copia precedente di Project1.ni.exe (salvata al punto #4) sulla nuova copia, ad esempio con questo comando:
     MOVE c:\project1.ni.exe 9990426218ec334e9d3d62f41cb9a255
rispondendo Yes alla domanda se intendiamo sovrascrivere il file Project1.ni.exe.

Fatto! Ora potete staccare la chiavetta USB e lasciare sul disco rigido del vostro cliente solo l'assembly "copy-protected" (in versione IL) e l'assembly completo (solo in versione compilata in modo nativo e quindi non decompilabile). Per quanto può sembrare incredibile, l'applicazione funziona correttamente e mostra le due message box :-) Ma se provate a usare ILDASM o Reflector per sbirciare dentro il metodo MySecretCode (e qualsiasi altro metodo il cui interno è racchiuso con un #IF Not COPYPROTECT) troverete che il metodo è vuoto!

Ovviamente neanche Reflector riesce a decompilare alcunchè, visto che proprio manca il codice IL. Nè aiuta usare ILDASM sull’eseguibile Project1.ni.exe, perchè l’unica cosa che si ottiene è visualizzare un manifest senza codice IL:

Come può funzionare questa piccola magia? La spiegazione è semplice: quando lanciate un qualsiasi eseguibile .NET, per prima cosa il CLR va a controllare se vi è una “native image” che corrisponde esattamente a quella applicazione, e la corrispondenza viene cercata confrontando il nome della applicazione e la sua firma (la stringona esadecimale). Se esiste una immagine nativa, quella viene lanciata al posto dell’eseguibile. La condizione però perchè tutto il codice funzioni correttamente è che i metadati coincidano anch’essi, altrimenti troppe funzioni del CLR non funzionerebbero a dovere (il garbage collector, tanto per citarne il più importante). Nel nostro caso noi abbiamo creato due eseguibili con gli stessi metadati (ecco perchè è importante che i blocchi #IF non devono eliminare alcun metodo, campo, o proprietà), e siamo riusciti a mantenere sul disco rigido la versione IL “incompleta” mentre mandiamo in esecuzione la versione nativa “completa”.

Oltre a proteggere dalla decompilazione, molto simpaticamente questo meccanismo protegge anche dalle copie illegali. Infatti, se provate a spostare tutti i file (compreso l’eseguibile Project1.ni.exe) su un’altro computer, è praticamente certo che l'applicazione NON funzionerà correttamente. Da quel che ho potuto capire, questo accade perchè il nome della directory creata da NGEN – ovvero la “firma” di un particolare eseguibile – dipende anche da qualche informazione di sistema e quindi varia da macchina a macchina.

Per quanto interessante, questa tecnica ha alcune limitazioni abbastanza serie:

A) Se si intende proteggere una DLL peer Windows Forms l'assembly non deve essere segnato con uno strong name. Il motivo è che nel manifest degli assembly che referenziano una DLL con strong-name viene memorizzato anche l'hash dell'assembly in questione, e questo è un valore che cambia quando si compila la versione copy-protected. Quindi, visto che la DLL non può avere uno strong-name, neanche gli EXE che usano la DLL possono averlo, il che rappresenta una limitazione notevole. Questo limite non esiste per le applicazioni ASP.NET.

B) Se avviene un errore in una applicazione ASP.NET protetta oppure si modifica il Web config in qualsiasi modo, l'applicazione smette di funzionare. Il problema si risolve con un IISRESET.

C) Se avviene qualche modifica sostanziale all’hardware o al software di sistema – ad es. la sostituzione della CPU, l’aggiunta di memoria, l'upgrade del sistema operativo, ecc. – il .NET Framework non può più usare l’immagine nativa e lancerà l’eseguibile normale, che ovviamente non funzionerà bene perchè alcune porzioni di codice mancano. In quel caso occorre ripetere l’installazione e tutto il procedimento descritto sopra. È importante quindi mettere in guardia il vostro cliente dai rischi che corre facendo questi upgrade durante la notte, il fine settimana, o mentre siete in vacanza alle Bahamas.

Per mitigare il problema potete almeno fare in modo che l’applicazione mostri un chiaro messaggio di errore quando l’immagine nativa non è aggiornata e il CLR torna ad usare l’eseguibile “incompleto”. Per fare questo è sufficiente avere una istruzione MessageBox.Show in un blocco #IF COPYPROTECT (senza il Not), ad es:

Friend Module Module1
    Public Sub Main()     

#If COPYPROTECT Then

        MessageBox.Show("This app requires reinstallation. Please contact tech support.")

        Exit Sub

#End If

        ' Here the real application begins

        ' ...

    End Sub

End Module

Per quanto ne so, Microsoft non ha mai documentato quali modifiche al sistema disabilitano l’immagine nativa creata con NGEN, quindi non posso essere più preciso su questo punto. Se qualcuno conosce qualche articolo che spiega questi casi, lo menzioni nei commenti.

Per il resto, ho fatto un po’ di prove, sembra che tutto quanto fili liscio e che la tecnica non abbia altre controindicazioni. Se vi imbattete in qualche problema, fatemelo sapere.

 
Feed di Blog2theMax
RSS 2.0 | Atom 0.2
Cerca nel blog
Archivio
<March 2010>
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910
Categorie

Powered by: newtelligence dasBlog 1.7.5016.2

 ©2004-2005 Code Architects S.r.l. - All rights reserved  Sign In