If you have programmed in .NET for more than a couple of weeks you’ve probably read something about how easy it is to disassemble your .NET assembly. You may have also noticed the Dotfuscator Community Edition tool in the Visual Studio.NET 2003 tools folder and thought - ‘OK, I can use this to protect my code’.
Just how easy is it for some to read your code after disassembly? I thought I would do a simple test this week to find out. See for yourself.
Finding a simple algorithm
The first step was to find a simple, short algorithm to compile into an assembly. I needed some C# samples for the test because I knew that the Salamander on-line disassembler generated C# code. A quick search on Google turned up a quick-sort algorithm on Code Project. Before continuing, let me give full credit to the author of the code I’m using for the test - Mark Clifton. Check out his CodeProject article on Quick Sort to learn more about his code.
Tools of the trade
Assembler and disassemblers — compilers and decompilers, these are the yin/yang of the programming world. For those who may have forgotten the difference between a disassembler and a decompiler here is a short refresher. Both of these tools take an existing executable file and turn it into a more readable format.
Disassembler: Converts a compiled application into an assembler language. This is very easy to do in .NET because the code in the EXE is stored in IL (Intermediate Language). Assembler instructions are closer to the CPU instruction set than they are to higher level languages like Java or Visual Basic.
Decompiler: Converts a compiled application into a specific higher level language. In other words it changes it to the language with which you normally write your code (C#, VB.NET, Java).
Finding a decompiler
There are a number of disassembler available for .NET code including ILDASM which ships with .NET. There are also several decompilers. I heard about the Remotesoft Salamander decompiler while speaking at a software conference this year and thought I would use it for this test. Written by Dr. Huihong Luo it will frighten you with the ease that it cracks your code. I suppose that is its main purpose as Remotesoft also sells obfuscater tools.
Code Comparison
Here is Marks’ original code on the left and the decompiled version on the right. I’ve tried to format the code so that the functions line up for easier comparison.
Note that all the comments in the original code do not make it into the ‘cracked’ version.
Note: the original code was not obfuscated before compiling.
|
using System; // heavily modified from: http://www.msdnaa.net/Resources/Display.aspx?ResID=952 namespace AAL.Lib { // my own interface, allowing control over the swap process public interface ISwap { void Swap(ArrayList array, int left, int right); } // implements a default swapper and comparer public class Sort : ISwap, IComparer { public static IComparer comparer; public static ISwap swapper; /// /// The basic constructor does a quicksort using the built in comparer and swapper /// /// The array to sort. public static void QuickSort(ArrayList array) { Sort s=new Sort(); Sort.comparer=s; Sort.swapper=s; QuickSort(array, 0, array.Count-1); } /// /// Specifies my own swapper, but the default comparer /// /// The array to sort. /// The custom swapper. public static void QuickSort(ArrayList array, ISwap swapper) { Sort.comparer=new Sort(); Sort.swapper=swapper; QuickSort(array, 0, array.Count-1); } /// /// Specifies my own comparer, but the default swapper /// /// The array to sort. /// The customer comparer. public static void QuickSort(ArrayList array, IComparer comparer) { Sort.comparer=comparer; Sort.swapper=new Sort(); QuickSort(array, 0, array.Count-1); } /// /// Specifies both my comparer and my swapper /// /// The array to sort. /// The custom comparer. /// The custom swapper. public static void QuickSort(ArrayList array, IComparer comparer, ISwap swapper) { Sort.comparer=comparer; Sort.swapper=swapper; QuickSort(array, 0, array.Count-1); } private static void QuickSort(ArrayList array, int lower, int upper) { // Check for non-base case if (lower < upper) { // Split and sort partitions int split=Pivot(array, lower, upper); QuickSort(array, lower, split-1); QuickSort(array, split+1, upper); } } private static int Pivot(ArrayList array, int lower, int upper) { // Pivot with first element int left=lower+1; object pivot=array[lower]; int right=upper; // Partition array elements while (left <= right) { // Find item out of place while ( (left <= right) && (comparer.Compare(array[left], pivot) <= 0) ) { ++left; } while ( (left <= right) && (comparer.Compare(array[right], pivot) > 0) ) { –right; } // Swap values if necessary if (left < right) { swapper.Swap(array, left, right); ++left; –right; } } // Move pivot element swapper.Swap(array, lower, right); return right; } public void Swap(ArrayList array, int left, int right) { object swap=array[left]; array[left]=array[right]; array[right]=swap; } public int Compare(object a, object b) { return a.ToString().CompareTo(b.ToString()); } } } |
// Decompiled by Salamander version 1.1 Beta using System; namespace AAL.Lib { public class Sort: ISwap, IComparer { public static IComparer comparer; public static ISwap swapper; public static void QuickSort(ArrayList array) { Sort sort = new Sort(); comparer = sort; swapper = sort; QuickSort(array, 0, array.Count - 1); } public static void QuickSort(ArrayList array, ISwap swapper) { comparer = new Sort(); Sort.swapper = swapper; QuickSort(array, 0, array.Count - 1); } public static void QuickSort(ArrayList array, IComparer comparer) { Sort.comparer = comparer; swapper = new Sort(); QuickSort(array, 0, array.Count - 1); } public static void QuickSort(ArrayList array, IComparer comparer, ISwap swapper) { Sort.comparer = comparer; Sort.swapper = swapper; QuickSort(array, 0, array.Count - 1); } private static void QuickSort(ArrayList array, int lower, int upper) { if (lower < upper) { int i = Pivot(array, lower, upper); QuickSort(array, lower, i - 1); QuickSort(array, i + 1, upper); } } private static int Pivot(ArrayList array, int lower, int upper) { int i = lower + 1; object local = array[lower]; int j = upper; while (i <= j) { for (i++; i <= j && comparer.Compare(array[i], local) <= 0; i++) { } for (; i <= j && comparer.Compare(array[j], local) > 0; j–) { } if (i < j) { swapper.Swap(array, i, j); i++; j–; } } swapper.Swap(array, lower, j); return j; } public virtual void Swap(ArrayList array, int left, int right) { object local = array[left]; array[left] = array[right]; array[right] = local; } public virtual int Compare(object a, object b) { return a.ToString().CompareTo(b.ToString()); } } } |