Dekompilering

Med dekompilering avses den process där ett program som tidigare kompilerats till maskinkod eller bytekod översätts till ett högnivåspråk med hjälp av en dekompilator.

Användning 
Dekompilering är en aktivitet som ligger i en juridisk gråzon. Det är till exempel ofta uttryckligen förbjudet att dekompilera upphovsrättsskyddad mjukvara. Samtidigt finns det andra mer legitima användningsområden.

 Återställning av förlorad källkod. Det är tyvärr allt vanligare att källkoden till programvara försvinner. Om programvaran behöver uppdateras blir det angeläget att ha tillgång till kompilerbar källkod.
 Automatiserad analys av malware.
 Säkerhetsundersökningar.
 Portering från en datortyp till en annan.

Begränsningar 
Den användare som förväntar sig kunna återställa källkod från maskinkod helt automatiskt blir snabbt besviken. Förutom att dekompilatortekniken fortfarande är väldigt omogen, är det bevisligen matematiskt omöjligt att dekompilera ett maskinkodsprogram till sin ursprungliga källkodsform. Detta följer från Alan Turings berömda stopproblem.

Dessutom försvinner mycket information när källkod kompileras av exempelvis en C-kompilator. Bland annat försvinner funktionsnamn, och i många fall är det svårt att avgöra om ett givet värde är en programadress eller ett vanligt heltal. Att felaktigt betrakta ett heltal som en programadress leder dekompilatorn att försöka dekompilera datavärden, vilket ger fel resultat.

De vanliga bytekodformaten, som till exempel Javaklasser och .NET Framework-assemblies, är något enklare att dekompilera eftersom användbar metadata (som funktions- och variabelnamn) medföljer i programfilerna. För att försvåra dekompilering finns det särskilda så kallad obfuscator-program, som genom förändringar i metadatan försöker att göra den dekompilerade koden obegriplig.

I praktiken betyder informationsförlusten vid kompilering att en dekompilatoranvändare måste bistå dekompilatorn med kompletterande information för att underlätta dess arbete. Exempel på sådan information är användarvänliga namn för funktioner, adresser för kända funktioner, och adressområden som dekompilatorn bör undvika eftersom det är känt att de enbart innehåller data och ingen programkod. Dekompilering blir på detta sätt en dialog mellan användare istället för en automatiserad process som kompilering.

Förlopp 
Den första dekompileringsfasen består av inläsning av maskinkodsprogrammet. Ofta är det lagrat i ett binärformat som är OS-specifikt, och som ger värdefull information om vilka adresser startfunktionerna har. Exempelvis måste headern i en programfil som kompilerats från ett C-program innehålla adressen till funktionen main(), som är startfunktionen i de flesta C-program.

Disassemblering 
Nu följer en disassembleringsfas, där startfunktionernas maskinkod undersöks, disassembleras, och översätts till ett internt intermediärt (och maskinneutralt) format. Maskinspecifika sekvenser översätts till maskinneutrala sådana. Exempelvis kan den stereotypa 80386-sekvensen:
    cdq    eax
    xor    eax,edx
    sub    eax,edx
översättas till det mindre maskinspecifika:
    eax = abs(eax)
När hoppinstruktioner påträffas, byggs det undersökta programmets kontrollgraf upp. När funktionsanrop till en viss adress påträffas, "vet" dekompilatorn att en ny funktion kan disassembleras vid den adressen.

Programanalys 
Efter disassemblering börjar programanalysen. Den liknar kompilatorers motsvarande analysfas i stor del, fast den utförs "baklänges". Programuttryck byggs ihop ur de enklare maskininstruktionerna. Exempelvis blir instruktionerna:
    mov   eax,[ebx+0x04]
    add   eax,[ebx+0x08]
    sub   [ebx+0x0C],eax
hopfogade till uttrycket:
    mem[ebx+0x0C] -= mem[ebx+0x4] + mem[ebx+0x08]

Typhärledning 
Typhärledning är nästa fas: dekompilatorn försöker att härleda vilka datatyper programvariablerna hade i källkoden genom att studera hur de används i programmet. Från programsnutten ovan kan dekompilatorn härleda att ebx måste vara en pekare till en post med åtminstone tre fält. Dessvärre vet inte dekompilatorn mycket om datatypen för dessa tre fält; ytterligare  information krävs för att bestämma detta. Uttryckt i C blir typerna:
    struct T1 * ebx;
    struct T1 {
        int v0004; 
        int v0008;
        int v000C;
    };
Med hjälp av de härledda typerna kan programkoden omformuleras ytterligare. Sålunda blir uttrycket ovan omgjort till:
    ebx->v000C -= ebx->v0004 + ebx->v0008

Strukturering 
Struktureringsfasen försöker att bygga kontrollstrukturer (som while-slingor och if-sater) ur de primitiva hoppinstruktionerna. Exempelvis blir följande maskinkod:
    xor eax,eax
 l0002:
    or  ebx,ebx
    jge l0003
    add eax,[ebx]
    mov ebx,[ebx+0x4]
    jmp l0002    
 l0003:
    mov [0x10040000],eax
översatt till det mer begripliga:
    eax = 0;
    while (ebx < 0) {
        eax += ebx->v0000;
        ebx = ebx->v0004;
    }
    globals.v10040000 = eax;

Utmatning 
I slutskedet matas den dekompilerade programtexten ut, möjligtvis baserad på användarangivna funktion- och variabelnamn. Användaren kan då undersöka programtexten, hitta eventuella fel, och genom mer utförliga processinstruktioner hjälpa dekompilatorn att göra färre fel i nästa iteration.

Referenser 
 C. Cifuentes, Reverse compilation techniques (1994). Doktorsavhandling, Queensland University of Technology.
 R. Falke, Entwicklung eines Typanalysesystem für einen Decompiler (2004). Diplomarbete, Technische Universität Dresden

Programmering
Datavetenskap