Dekompilering

Från Wikipedia
Hoppa till: navigering, sök

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[redigera | redigera wikitext]

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[redigera | redigera wikitext]

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 Dotnet-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[redigera | redigera wikitext]

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[redigera | redigera wikitext]

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[redigera | redigera wikitext]

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[redigera | redigera wikitext]

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[redigera | redigera wikitext]

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[redigera | redigera wikitext]

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[redigera | redigera wikitext]