Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Reverse engineering di applicazioni .NET con dnSpy

Perché l'interoperabilità tra i linguaggi del framework .NET rende più semplice il reverse engineering di tali applicazioni, e come effettuarlo con dnSpy.
Perché l'interoperabilità tra i linguaggi del framework .NET rende più semplice il reverse engineering di tali applicazioni, e come effettuarlo con dnSpy.
Link copiato negli appunti

Come molti già sapranno, il reverse engineering (dall'inglese ingegneria inversa) è quel processo che, tramite un'analisi dettagliata, mira ad ottenere tutte le specifiche di funzionamento, sviluppo e produzione di un determinato prodotto. In informatica, le tecniche di reverse engineering vengono impiegate per carpire tutti i dettagli sul funzionamento di un software, pur non disponendo dei relativi codici sorgenti. Gli scopi sono molteplici: dall'aggiramento delle protezioni (ad esempio, i controlli di licenza) al vero e proprio spionaggio industriale.

Sebbene (in via del tutto teorica) sia sempre possibile effettuare un completo reverse engineering di un software, le tecnologie, i linguaggi e le scelte progettuali con cui esso è stato realizzato possono rendere questo compito più o meno complesso. Ad esempio, tecnologie come Java e .NET offrono ad un attaccante molteplici possibilità di analisi, permettendo addirittura di ricostruire il codice sorgente a partire dai soli eseguibili.

Caratteristiche di un'applicazione .NET

I principali motivi per cui il reverse engineering di un'applicazione .NET risulta spesso un'operazione banale, risiedono nelle caratteristiche architetturali su cui si basa la piattaforma stessa: distribuzione in linguaggio intermedio e capacità di reflection.

Common Intermediate Language

Un'applicazione .NET viene tipicamente distribuita sotto forma di file eseguibile .exe e/o uno o più file di libreria .dll. Per garantire la piena interoperabilità tra i vari linguaggi supportati da .NET (C#, VB.NET, eccetera), i codici sorgenti vengono tradotti in un linguaggio intermedio comune detto Common Intermediate Language o CIL (precedentemente noto come MSIL). Ciò permette ad esempio ad un programma scritto in linguaggio C# di richiamare funzioni di una libreria .NET scritta in Visual Basic, e viceversa.

Durante il processo di compilazione, il codice CIL viene memorizzato all'interno del file eseguibile in una forma binaria nota come bytecode. Quando l'eseguibile viene lanciato, il loader del sistema operativo lo riconosce come eseguibile .NET, e provvede ad invocare il Common Language Runtime o CLR di .NET. Il CLR provvede a caricare il bytecode CIL, compilarlo in codice macchina e quindi eseguirlo.

Quindi, a differenza di un'applicazione sviluppata in C++, il cui processo di compilazione produce un eseguibile contenente solo codice macchina, un'applicazione .NET viene "compilata" producendo un eseguibile che contiene codice intermedio. Ciò rende il processo di reverse engineering molto più semplice, poiché il linguaggio intermedio CIL è una rappresentazione di livello più alto rispetto al codice macchina, molto più vicina ai sorgenti del programma e quindi più facile da invertire.

Reflection

Un'altra caratteristica delle applicazioni .NET è la capacità di reflection. In breve, in un linguaggio che supporti reflection i programmi possono accedere durante l'esecuzione alle informazioni relative alla propria struttura. Ad esempio, in .NET è possibile scrivere un programma che elenchi i nomi dei metodi di una sua classe.

Si evince quindi che affinché la reflection funzioni è necessario inserire all'interno dei file eseguibili le tabelle con i nomi di tutti i simboli, le descrizioni dettagliate dei tipi, le signature delle funzioni e così via. Tutta informazioni preziose per chi si appresta ad analizzare il programma in dettaglio.

Dall'eseguibile ai sorgenti: il processo di decompilazione

Come già detto, le caratteristiche sopra citate permettono in molti casi di ricostruire i codici sorgenti a partire dai soli file eseguibili. Esistono diversi strumenti sia commerciali che gratuiti che permettono di effettuare questa operazione in tutta semplicità. Uno di questi è l'ottimo dnSpy.

Esempio pratico

Si supponga di aver sviluppato in linguaggio C# un semplice programma che richiede in input un numero e ne restituisca un altro frutto di alcune operazioni aritmetiche effettuate su di esso. Il codice del programma è riportato di seguito.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyApp
{
	class Program
	{
		/// <summary>
		/// Mio algoritmo segreto
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		static int SecretAlgoritm(int value)
		{
			if (value < 100)
				return (value * 3) + 1;
			else
				return (value + (5 * 2)) + 4;
		}
		/// <summary>
		/// Main
		/// </summary>
		/// <param name="args"></param>
		static void Main(string[] args)
		{
			Console.Write("Digita un numero: ");
			int numero = 0;
			string input = Console.ReadLine();
			if(int.TryParse(input, out numero))
			{
				Console.WriteLine("Risultato: {0}", SecretAlgoritm(numero));
			}
			else
			{
				Console.WriteLine("ERRORE: valore non valido!");
			}
			Console.Read();
		}
	}
}

Le operazioni in questione, effettuate nella funzione SecretAlgoritm, sono il cuore del programma e rappresentano la proprietà intellettuale da proteggere. Pertanto, i codice sorgenti vengono tenuti al sicuro mentre l'eseguibile viene distribuito agli utenti.

Un attaccante che voglia effettuare il reverse engineering dell'applicazione, al fine di scoprire quali calcoli essa effettui, deve semplicemente aprire il file .exe con il programma dnSpy, liberamente scaricabile da GitHub.

dnSpy è in grado di decompilare il programma e restituire il codice sorgente.

Figura 1. Output del programma dnSpy (click per ingrandire)

Output del programma dnSpy

Come si può notare, il codice restituito non è identico a quello di partenza, ma è del tutto equivalente. Il codice della funzione SecretAlgoritm ricostruito da dnSpy è:

private static int SecretAlgoritm(int value)
	{
		if (value < 100)
		{
			return value * 3 + 1;
		}
		return value + 10 + 4;
	}

La clausola else è sparita poiché irrilevante (il primo ramo dello if contiene un return, interrompendo l'esecuzione del codice). Inoltre l'espressione 5*2 è stata sostituita dal valore 10 poiché costante (risparmiando così una moltiplicazione).

Queste modifiche sono apportate dall'ottimizzatore del compilatore durante il processo di traduzione da C# a CIL.

dnSpy permette inoltre di ottenere i sorgenti in un linguaggio diverso da quello utilizzato per lo sviluppo (purché compatibile con CLR). Ad esempio, è possibile ottenere il codice VB.NET sebbene il programma sia stato scritto in C#.

Infine, una comoda funzione permette di esportare il progetto in una soluzione di Visual Studio, disponendo così di una copia integrale dei sorgenti ricostruiti a partire dall'eseguibile.

Contromisure

Esistono diverse tecniche che permettono di ostacolare il processo di reverse engineering. Le più comuni si basano sull'offuscamento del codice: prima di essere compilato, il codice viene trasformato rinominando tutti i simboli con stringhe casuali. Ciò rende più difficoltosa (ma non impossibile) la comprensione dei sorgenti ottenuti decompilando. Tecniche più complesse prevedono la cifratura delle costanti, delle risorse binarie o addirittura di alcune porzioni del codice.

Tuttavia, tecniche di protezione troppo aggressive rischiano di compromettere le performance del software, o addirittura di introdurre bug causati dalle trasformazioni effettuate sul codice. Pertanto, si consiglia sempre di agire con cautela e valutare l'implementazione di opportune protezioni solo laddove strettamente necessario.

Conclusioni

La comodità e la semplicità d'uso di tecnologie come .NET si pagano al prezzo di una maggiore esposizione al reverse engineering. Sebbene tali rischi possano essere mitigati con l'adozione di strumenti appositi, essi rappresentano un ulteriore costo sia durante le fasi di sviluppo che di manutenzione del progetto.

Ti consigliamo anche