.NET dekompilace
Programy v .NET jsou kompilovány na cílový procesor až za běhu a jsou na disku uloženy v předkompilované formě v bytecode, který se dá zobrazovat pro člověka jako CIL. Tento formát je získán jako výsledek práce kompilátoru .NET jazyků (třeba C#). Každý z rodiny .NET jazyků je překládán do CIL (no existují i kompilátory C# přímo pro cílový procesor, ale moc se to nepoužívá). Tento formát je standardizován a tím je umožněn vznik dekompilátorů.
Hello world v CIL
.method public static void Main() cil managed
{
.entrypoint
.maxstack 1
ldstr "Hello, world!"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
Výsledkem je, že nikdo (ani dekompilátor) nedokáže (bez přídavných souborů) určit originální programovací jazyk a z toho plynou určitá omezení a pro autora originálního programu výhody. Dekompilátor např. nezná jména lokálních proměnných (prostě nějaké vygeneruje) a některé speciální konstrukce z jiných jazyků nedokáže zobrazit pokud zvolíte jiný výsledný jazyk, nebo je převede na jiné konstrukce. Např. příkaz switch je někdy převeden na sekvenci příkazů if.
Naopak ve výsledném souboru (exe, assembly) jsou uvedeny jména funkcí, tříd, texty atd. Právě těchto informací využívají dekompilátory a to je základem jak se jim bránit.
Obfuscator
Obfuscator je program, který z výsledného kódu odebere informace, které nejsou nutné pro běh programy, přejmenuje třídy, metody a vůbec veškeré informace používané dekompilátory. Některé umí i mnohem složitější věci.
Ten pravý pro vaše použití najdete na internetu. Součástí instalace VS je sice základní verze jednoho z nich, která ale umí jen přejmenování. Ostatní vlastnosti jsou již jen v placené verzi.
Reflector
Nejlepší volně dostupný dekompilátor je Reflector. Alternativou je mu Anakrino, již nevyvíjený, ale se zdrojáky.
Reflector umí dekompilovat do C#, VB, Delphi.NET, Chrome, MC++ a CIL.
Ukážeme si kde jsou slabá místa takové dekompilace. Principiálně dekompilátor určité postupy v CIL nahrazuje příkazy vyššího jazyka. Uvedeme si příklad:
Originální program
[STAThread]
static void Main()
{
const int ciTestSwitch1 = 5;
const int ciTestSwitch2 = 6;
const string csTest = "test2";
for (int iLoop = 0; iLoop < 10; iLoop++)
{
System.Console.WriteLine(iLoop.ToString());
switch (iLoop)
{
case ciTestSwitch1:
System.Console.WriteLine("Test1");
break;
case ciTestSwitch2:
System.Console.WriteLine(csTest);
break;
}
}
}
C# (bez ladících informací .pdb), zmiznou názvy lokálních proměnných a konstanty.
[STAThread]
private static void Main()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i.ToString());
switch (i)
{
case 5:
Console.WriteLine("Test1");
break;
case 6:
Console.WriteLine("test2");
break;
}
}
}
Delphi.NET - evidentně by to takto nikdy programátor nenapsal (použil by for a vypustil příkazy break). Je vidět, že for v C# je to samé co while, což pro Delphi v žádném případě neplatí (zde FOR může iterovat nad výčtem a pod.), takže proto dekompilátor zvolit variantu s while.
procedure Class1.Main;
begin
i := 0;
while ((i < 10)) do
begin
Console.WriteLine(i.ToString);
case i of
5:
begin
Console.WriteLine('Test1');
break;
end;
6:
begin
Console.WriteLine('test2');
break;
end;
end;
inc(i)
end
end;
Raději uvedu jak by to napsal programátor:
procedure Class1.Main;
var
i: Integer;
begin
for i:= 0 to 9 do
begin
Console.WriteLine(i.ToString);
case i of
5: Console.WriteLine('Test1');
6: Console.WriteLine('test2');
end;
end
end;
VB.NET - také zajímavé
<STAThread> _
Private Shared Sub Main()
Dim i As Integer
For i = 0 To 10 - 1
Console.WriteLine(i.ToString)
Select Case i
Case 5
Console.WriteLine("Test1")
Exit Select
Case 6
Console.WriteLine("test2")
Exit Select
End Select
Next i
End Sub
A nakonec CIL
.method private hidebysig static void Main() cil managed
{
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor()
.entrypoint
.maxstack 2
.locals init (
[0] int32 num,
[1] int32 num2)
L_0000: ldc.i4.0
L_0001: stloc.0
L_0002: br.s L_0040
L_0004: ldloca.s num
L_0006: call instance string [mscorlib]System.Int32::ToString()
L_000b: call void [mscorlib]System.Console::WriteLine(string)
L_0010: ldloc.0
L_0011: stloc.1
L_0012: ldloc.1
L_0013: ldc.i4.5
L_0014: sub
L_0015: switch (L_0024, L_0030)
L_0022: br.s L_003c
L_0024: ldstr "Test1"
L_0029: call void [mscorlib]System.Console::WriteLine(string)
L_002e: br.s L_003c
L_0030: ldstr "test2"
L_0035: call void [mscorlib]System.Console::WriteLine(string)
L_003a: br.s L_003c
L_003c: ldloc.0
L_003d: ldc.i4.1
L_003e: add
L_003f: stloc.0
L_0040: ldloc.0
L_0041: ldc.i4.s 10
L_0043: blt.s L_0004
L_0045: ret
}
Dekompilátor má mimochodem i dobré využití k získávání informací o samotném .NET frameworku.