Skip to Content
Nuova versione 12 disponibile 🎉

Codice dinamico

Passare dal codice demo al codice retail con la cifratura del codice.

La cifratura del codice permette di cifrare metodi specifici di un’applicazione, che possono poi essere sostituiti in base alle funzionalità a cui l’utente ha accesso. Per esempio, se un’applicazione ha funzionalità di base e funzionalità avanzate, i metodi cifrati delle funzionalità avanzate possono essere conservati separatamente dall’eseguibile principale dell’applicazione e caricati in fase di esecuzione solo se l’utente ha una licenza che gli dà accesso a quelle funzionalità. Questa selezione del codice dinamico permette agli sviluppatori di creare una versione demo o di prova della propria applicazione con funzionalità limitate e di sbloccare le funzionalità aggiuntive fornendo agli utenti una chiave di licenza. Con la cifratura del codice gli sviluppatori proteggono la propria proprietà intellettuale e offrono agli utenti opzioni di licenza flessibili.

Il codice dinamico offerto dalla cifratura del codice permette di scambiare in fase di esecuzione versioni diverse di un metodo in base a condizioni esterne. Per esempio, un’applicazione può avere un insieme limitato di funzionalità in modalità demo, mentre in presenza di un file di licenza valido vengono abilitate funzionalità più avanzate. Per ottenere questo risultato si cifra il codice sia della versione limitata sia della versione avanzata delle funzionalità, e si usa un meccanismo di selezione del codice dinamico per passare dall’una all’altra in base alla presenza o all’assenza del file di licenza.

Applicazione demo

Nel modo classico di realizzare un’applicazione demo, il codice retail è già presente nella versione demo e il file di licenza permette l’esecuzione delle funzionalità avanzate. Questo approccio ha però un inconveniente rilevante: il codice eseguito quando il file di licenza è presente non è protetto, e un attaccante potrebbe aggirare la convalida della licenza per sbloccare le funzionalità con licenza.

Con la selezione del codice dinamico, invece, il codice che sblocca le funzionalità avanzate è cifrato e non può essere eseguito in alcun modo senza il file di licenza, che contiene la chiave di cifratura necessaria per decifrare ed eseguire il codice. Questo offre una protezione migliore contro il reverse engineering e l’accesso non autorizzato alle funzionalità avanzate. Il file di licenza funge da chiave univoca per sbloccare sezioni specifiche del codice, e rende difficile agli attaccanti aggirare il meccanismo di licenza e accedere alle funzionalità avanzate.

Considera il seguente progetto di esempio disponibile su GitHub. Puoi ottenere il codice sorgente dell’esempio con Git, eseguendo dalla riga di comando il seguente comando:

git clone https://github.com/babelfornet/dynamic-code-example.git

L’esempio mostra come usare il codice dinamico per cambiare il comportamento dell’applicazione in base alla presenza di file specifici.

[Obfuscation(Feature = "msil encryption:id=feature1;source=demo;internal=true;password=1234", Exclude = false)] public void Feature1Demo() { Console.WriteLine("Feature1 Demo"); Feature2Demo(); } [Obfuscation(Feature = "msil encryption:id=feature2;source=demo;internal=true;password=1234", Exclude = false)] public static void Feature2Demo() { Console.WriteLine("Feature2 Demo"); }

Questi sono due metodi demo contrassegnati per essere sostituiti in fase di esecuzione con versioni diverse dei metodi tramite la selezione del codice dinamico. I metodi Feature1Demo() e Feature2Demo() sono cifrati con la cifratura del codice, rispettivamente con ID “feature1” e “feature2”. L’origine (source) del codice cifrato è indicata come “demo”, e la proprietà internal è impostata su true per mantenere il codice cifrato all’interno dell’assembly. La password di cifratura è “1234”.

Quando un’applicazione viene eseguita senza un file di licenza valido, è considerata in modalità demo. In questo caso l’assembly chiama i due metodi precedenti, contrassegnati per essere sostituiti in fase di esecuzione dal codice dinamico. Questi metodi implementano le funzionalità di base dell’applicazione, che possono essere limitate rispetto alla versione con licenza completa.

static void Main(string[] args) { var app = new App(); app.Feature1Demo(); }

Considera ora i due metodi seguenti, che sostituiranno i metodi demo quando l’applicazione ha una licenza. Nota che l’origine è impostata su “retail”: se viene trovato un file di licenza, il metodo con ID “feature1” Feature1Retail() sostituirà Feature1Demo(), mentre quello con ID “feature2” Feature2Retail() sostituirà Feature2Demo().

[Obfuscation(Feature = "msil encryption:id=feature1;source=retail;internal=true;password=4567", Exclude = false)] public static void Feature1Retail() { Console.WriteLine("Feature1 Retail"); Feature2Retail(); } [Obfuscation(Feature = "msil encryption:id=feature2;source=retail;internal=true;password=4567", Exclude = false)] public static void Feature2Retail() { Console.WriteLine("Feature2 Retail"); }

Questi due metodi non vengono chiamati in fase di esecuzione dall’assembly, che funziona in modalità demo, e sono cifrati con una password diversa da quella usata per i metodi demo. Questa password non è disponibile al codice dell’applicazione, perché Babel rimuove l’informazione durante l’offuscamento, dopo aver cifrato i metodi.

Ti serve un modo per passare dinamicamente da “demo” a “retail” con la cifratura del codice. Poiché i metodi precedenti sono stati cifrati con una password, devi definire il punto di ingresso della cifratura del codice di Babel che recupera la password in fase di esecuzione.

[Obfuscation(Feature = "msil encryption get password", Exclude = false)] internal static string GetPassword(string source) { Console.WriteLine($"> Get {source} password"); if (source == "demo") return "1234"; string pwd = File.ReadAllText(source + ".txt"); Console.WriteLine($">> {source} password: {pwd}"); return pwd; }

Il metodo GetPassword() viene usato dalla BVM (Bytecode Virtual Machine) per recuperare la password necessaria a decifrare un metodo cifrato quando viene chiamato durante l’esecuzione del programma.

Se il nome dell’origine è “demo”, il metodo restituisce la password fissa “1234”, cioè quella usata per cifrare i metodi “demo” Feature1Demo() e Feature2Demo(). Altrimenti legge la password da un file di testo con lo stesso nome dell’origine e l’estensione “.txt”. La password viene poi restituita al chiamante. Nota che la password demo è memorizzata nel codice perché l’applicazione può sempre funzionare in modalità demo, e non c’è bisogno di nasconderla.

Il passaggio tra le origini “demo” e “retail” avviene nel metodo GetSourceStream(), che la BVM chiama quando viene chiamato uno dei metodi cifrati.

[Obfuscation(Feature = "msil encryption get stream", Exclude = false)] internal static object GetSourceStream(string source) { Console.WriteLine($"> Get {source} stream"); if (source == "demo") { string[] sources = { "retail", "special" }; foreach (var src in sources) { if (File.Exists($"{src}.txt")) { Console.WriteLine($">> Switch to {src}"); return src; } } return null; } // External sources if (File.Exists(source + ".eil")) { Console.WriteLine($">>> Read {source} stream"); return File.OpenRead(source + ".eil"); } return null; }

Questo metodo ha un ruolo fondamentale nella selezione dinamica dell’origine corretta dei metodi cifrati in base a condizioni esterne, come la presenza di un file di licenza. All’avvio, infatti, l’applicazione chiama i metodi demo, che possono essere decifrati con la password restituita dal metodo GetPassword().

Nella selezione del codice dinamico, la BVM (Binary Virtual Machine) ha il compito di eseguire il codice cifrato e di passare da una versione all’altra di un metodo in base a condizioni esterne. Prima di eseguire qualsiasi metodo “demo”, la BVM chiama il metodo GetSourceStream() passando “demo” come parametro. Poiché i metodi “demo” sono interni all’assembly, non occorre restituire alcun flusso per l’origine demo. Se però viene trovato il file “retail.txt”, vuoi passare al codice dell’origine “retail”: devi quindi comunicare alla BVM che vuoi eseguire l’origine “retail”, restituendo la stringa “retail” dal metodo GetSourceStream().

Quando la BVM riceve la stringa “retail” da GetSourceStream(), sa che deve eseguire il codice “retail”. La BVM chiama quindi GetPassword() con l’argomento “retail”. GetPassword() legge la password della versione “retail” dal file retail.txt e la restituisce alla BVM, che la usa per decifrare il codice “retail”. Una volta decifrato, il codice “retail” viene eseguito al posto del codice “demo”. In questo modo l’applicazione può passare in fase di esecuzione da una versione all’altra del codice cifrato in base alla presenza o all’assenza di un file di licenza, senza che il codice debba essere ricompilato o ridistribuito.

Il codice dinamico permette di definire più origini interne ed esterne. Per esempio puoi dichiarare un’altra origine esterna, “special”, da caricare al posto del codice “retail” quando è presente il file special.txt, che contiene la password per decifrare il codice.

[Obfuscation(Feature = "msil encryption:id=feature1;source=special;internal=false;password=0000", Exclude = false)] public static void Feature1Special() { Console.WriteLine("Feature1 Special"); Feature2Special(); } [Obfuscation(Feature = "msil encryption:id=feature2;source=special;internal=false;password=0000", Exclude = false)] public static void Feature2Special() { Console.WriteLine("Feature2 Special"); }

Il codice mostra due metodi, Feature1Special() e Feature2Special(), contrassegnati con attributi Obfuscation in cui la proprietà “internal” è impostata su “false”. Questo significa che il codice offuscato di questi metodi viene generato in un file esterno con estensione “.eil” e non è incluso nell’assembly. Il file esterno “.eil” con il codice cifrato può essere caricato e decifrato in fase di esecuzione con la password specificata negli attributi Obfuscation.

Per eseguire l’esempio, compila prima la soluzione in configurazione debug. Poi apri il progetto obfuscate.babel in Babel Desktop ed eseguilo. Il progetto non imposta un percorso di output, quindi Babel Desktop scrive l’assembly offuscato in una cartella BabelOut accanto all’assembly di input. Copia quindi i file DynamicCode.exe, DynamicCode.runtimeconfig.json e DynamicCode.deps.json nella cartella BabelOut. Infine, da una finestra di PowerShell, esegui DynamicCode.exe nella cartella BabelOut: dovresti vedere questo output:

> Get demo stream > Get demo password Feature1 Demo Feature2 Demo

Per provare l’origine “retail”, copia il file retail.txt nella cartella BabelOut ed esegui DynamicCode.exe. L’output dovrebbe essere:

> Get demo stream >> Switch to retail > Get retail password >> retail password: 4567 Feature1 Retail Feature2 Retail

Per provare l’origine “special”, copia il file special.txt nella cartella BabelOut ed esegui DynamicCode.exe. L’output dovrebbe essere:

> Get demo stream >> Switch to special > Get special stream >>> Read special stream > Get special password >> special password: 0000 Feature1 Special Feature2 Special
Last updated on