Hvordan jeg løste en simpel CrackMe-udfordring med NSA's Ghidra

Hej!

Jeg har spillet lidt for nylig med Ghidra, som er et reverse engineering-værktøj, der for nylig blev åbnet fra NSA. Den officielle hjemmeside beskriver værktøjet som:

En software reverse engineering (SRE) pakke med værktøjer udviklet af NSAs Research Directorate til støtte for Cybersecurity-missionen.

Jeg er i begyndelsen af ​​min reverse engineering-karriere, så jeg gjorde ikke noget avanceret. Jeg ved ikke, hvilke funktioner der kan forventes af et professionelt værktøj som dette, hvis du ønsker at læse om avancerede Ghidra-funktioner, er dette sandsynligvis ikke artiklen for dig.

I denne artikel vil jeg forsøge at løse en simpel CrackMe-udfordring, som jeg har fundet på hjemmesiden root-me. Den udfordring, jeg løser, hedder ELF - CrackPass. Hvis du vil prøve det selv, bør du overveje ikke at læse denne artikel, fordi det vil ødelægge udfordringen fra dig.

Lad os komme igang! Jeg åbner Ghidra og opretter et nyt projekt, som jeg kalder RootMe.

Derefter importerer jeg udfordringsfilen ved at trække den til projektmappen. Jeg går med standardindstillingerne.

Efter at have fået nogle oplysninger om den binære fil, trykker jeg på OK, vælger filen og dobbeltklikker på den. Dette åbner Ghidras kode browser-værktøj og spørger, om jeg vil analysere filen, så jeg trykker på Ja og fortsætter med standardindstillingerne.

Når vi har importeret filen, får vi nogle oplysninger om den binære fil. Hvis vi trykker på OK og afviser dette vindue og derefter dobbeltklikker på den fil, vi importerede, åbner dette Ghidras kode browser-værktøj. Jeg vælger Ja, når jeg bliver bedt om at analysere binærprogrammet og fortsætte med standardindstillingerne.

Kodebrowseren er ret praktisk. I venstre panel ser vi demonteringsvisningen og i højre panel dekompileringsvisningen.

Ghidra viser os direkte ELF-headerinfoen og binærens startpunkt. Efter dobbeltklik på startpunktet springer dissemblervisningen til indtastningsfunktionen.

Nu kan vi med succes identificere hovedfunktionen, som jeg omdøber til hoved. Det ville være rart, hvis værktøjet automatisk forsøgte at registrere hovedfunktionen og omdøbe den i overensstemmelse hermed.

Før jeg analyserede hovedfunktionen, ønskede jeg at ændre dens signatur. Jeg ændrede returtypen til int og korrigerede parametrernes type og navn. Denne ændring er trådt i kraft i dekompilering, som er sejt! ?

Fremhævning af en linje i dekompileringsvisningen fremhæver den også i samlingsvisningen.

Lad os udforske FUN_080485a5-funktionen, som jeg omdøber til CheckPassword.

Indholdet af CheckPassword-funktionen kan findes nedenfor. Jeg har kopieret koden direkte fra Ghidras dekompilering, hvilket er en pæn funktion, som mange værktøjer af denne type mangler! At kunne kopiere samling og kode er en god funktion.

void CheckPassword(char *param_1) { ushort **ppuVar1; int iVar2; char *pcVar3; char cVar4; char local_108c [128]; char local_100c [4096]; cVar4 = param_1; if (cVar4 != 0) { ppuVar1 = __ctype_b_loc(); pcVar3 = param_1; do { if (((byte )(ppuVar1 + (int)cVar4) & 8) == 0) { puts("Bad password !"); /* WARNING: Subroutine does not return */ abort(); } cVar4 = pcVar3[1]; pcVar3 = pcVar3 + 1; } while (cVar4 != 0); } FUN_080484f4(local_100c,param_1); FUN_0804851c(s_THEPASSWORDISEASYTOCRACK_08049960,local_108c); iVar2 = strcmp(local_108c,local_100c); if (iVar2 == 0) { printf("Good work, the password is : \n\n%s\n",local_108c); } else { puts("Is not the good password !"); } return; }

Efter at have kigget på koden er jeg kommet til følgende konklusioner. Blokken ifkontrollerer, om brugeren har angivet en adgangskode og inspicerer den angivne adgangskode for at kontrollere, om det er et gyldigt tegn eller noget. Jeg er ikke helt sikker på, hvad den kontrollerer, men her er hvad __ctype_b_loc () s dokumentation siger:

Funktionen __ctype_b_loc () skal returnere en markør i en række tegn i det aktuelle landestandard, der indeholder karakteristika for hvert tegn i det aktuelle tegnsæt. Arrayet skal indeholde i alt 384 tegn og kan indekseres med en hvilken som helst underskrevet eller usigneret tegn (dvs. med en indeksværdi mellem 128 og 255). Hvis applikationen er multitrådet, skal matrixen være lokal i forhold til den aktuelle tråd.

Under alle omstændigheder er den kodeblok ikke rigtig værd at bruge tiden på, fordi den ikke ændrer vores adgangskode på nogen måde, det bekræfter det bare. Så vi kan springe denne form for kontrol over.

Den næste kaldte funktion er FUN_080484f4. Når vi ser på dens kode, kan vi fortælle, at det bare er en brugerdefineret memcopy-implementering. I stedet for at kopiere C-koden fra dekompilatorvisningen kopierede jeg samlingskoden - ja, det er sjovt.

************************************************************* * FUNCTION ************************************************************* undefined FUN_080484f4 (undefined4 param_1 , undefined4 p undefined AL:1  undefined4 Stack[0x4]:4 param_1 XREF[1]: 080484f8 (R) undefined4 Stack[0x8]:4 param_2 XREF[1]: 080484fb (R) FUN_080484f4 XREF[1]: CheckPassword:080485f5 (c) 080484f4 55 PUSH EBP 080484f5 89 e5 MOV EBP ,ESP 080484f7 53 PUSH EBX 080484f8 8b 5d 08 MOV EBX ,dword ptr [EBP + param_1 ] 080484fb 8b 4d 0c MOV ECX ,dword ptr [EBP + param_2 ] 080484fe 0f b6 11 MOVZX EDX ,byte ptr [ECX ] 08048501 84 d2 TEST DL,DL 08048503 74 14 JZ LAB_08048519 08048505 b8 00 00 MOV EAX ,0x0 00 00 LAB_0804850a XREF[1]: 08048517 (j) 0804850a 88 14 03 MOV byte ptr [EBX + EAX *0x1 ],DL 0804850d 0f b6 54 MOVZX EDX ,byte ptr [ECX + EAX *0x1 + 0x1 ] 01 01 08048512 83 c0 01 ADD EAX ,0x1 08048515 84 d2 TEST DL,DL 08048517 75 f1 JNZ LAB_0804850a LAB_08048519 XREF[1]: 08048503 (j) 08048519 5b POP EBX 0804851a 5d POP EBP 0804851b c3 RETComment: param_1 is dest, param_2 is src. 08048501 checks if src is null and if it is it returns else it initializes EAX (index, current_character) with 0. The next instructions move bytes into EBX (dest) from EDX (src).The loop stops when EDX is null.

Og den anden funktion FUN_0804851c genererer adgangskoden fra "THEPASSWORDISEASYTOCRACK" -strengen. Ser på den dekompilerede udsigt. vi kan groft se, hvordan denne funktion fungerer. Hvis vi ikke havde det, skulle vi manuelt analysere hver monteringsinstruktion fra funktionen for at forstå, hvad den gør.

Derefter sammenligner vi den tidligere genererede adgangskode med den adgangskode, som vi fik fra brugeren (det første argument, argv [1]). Hvis det stemmer overens, siger programmet et godt stykke arbejde og udskriver det, ellers udskriver det en fejlmeddelelse.

Fra denne grundlæggende analyse kan vi konkludere, at hvis vi lapper programmet forskellige steder, kan vi få det til at spytte adgangskoden uden at vi behøver at vende nogen C-funktion og skrive kode. Patching af programmet betyder at ændre nogle af dets instruktioner.

Lad os se, hvad vi skal lappe:

På adresse 0x0804868c lapper vi JNS-instruktionen i en JMP. Og voilà, ændringen afspejles i dekompilatorvisningen. Ptrace-resultatkontrollen er omgået.

{ ptrace(PTRACE_TRACEME,0,1,0); if (argc != 2) { puts("You must give a password for use this program !"); /* WARNING: Subroutine does not return */ abort(); } CheckPassword(argv[1]); return 0;}

På adresse 0x080485b8 lapper vi JZ-instruktionen i en JMP. Vi omgår den adgangskodebekræftelsesblok, vi så tidligere.

void CheckPassword(undefined4 param_1) { int iVar1; char local_108c [128]; char local_100c [4096]; CustomCopy(local_100c,param_1); GeneratePassword(s_THEPASSWORDISEASYTOCRACK_08049960,local_108c); iVar1 = strcmp(local_108c,local_100c); if (iVar1 == 0) { printf("Good work, the password is : \n\n%s\n",local_108c); } else { puts("Is not the good password !"); } return; }

På adresse 0x0804861e lapper vi JNZ til JZ. Dette inverterer if / else-tilstanden. Da vi ikke kender adgangskoden, sender vi en tilfældig adgangskode, der ikke er lig med den genererede, og udfører således printf på den anden blok.

void CheckPassword(undefined4 param_1) { int iVar1; char local_108c [128]; char local_100c [4096]; CustomCopy(local_100c,param_1); // constructs the password from the strings and stores it in // local_108c GeneratePassword(s_THEPASSWORDISEASYTOCRACK_08049960,local_108c); iVar1 = strcmp(local_108c,local_100c); if (iVar1 == 0) { // passwords are equal puts("Is not the good password !"); } else { printf("Good work, the password is : \n\n%s\n",local_108c); } return; }

Det er alt!

Nu kører vi programmet. I andre værktøjer gemmer vi bare filen, og den fungerer, men i Ghidra ser det ud til, at vi har brug for at eksportere den.

For at eksportere programmet går vi til File -> Export Program (O). Vi ændrer formatet til binært og klikker på OK.

Jeg får det eksporterede program på mit skrivebord, men det virker ikke - det lykkedes mig ikke at køre det eksporterede program. Efter at have forsøgt at læse dens header med readelf -h-programmet får jeg følgende output:

[email protected]:/mnt/c/users/denis/Desktop# readelf -h Crack.bin ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x8048440 Start of program headers: 52 (bytes into file) Start of section headers: 2848 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 7 Size of section headers: 40 (bytes) Number of section headers: 27 Section header string table index: 26 readelf: Error: Reading 1080 bytes extends past end of file for section headers

Skam. Det ser ud til, at Ghidra har ødelagt filoverskriften ... og lige nu vil jeg ikke rette headere manuelt. Så jeg fyrede et andet værktøj op og anvendte de samme programrettelser på filen, gemte det, kørte det med et tilfældigt argument og validerede flag.

Konklusioner

Ghidra er et godt værktøj med meget potentiale. I sin nuværende tilstand er det ikke så godt, men det fungerer. Jeg har også stødt på en underlig rullefejl, mens jeg kører den på min bærbare computer.

Alternativerne ville være at betale $$ for andre værktøjer af denne art, lave dine egne værktøjer eller arbejde med gratis, men ikke så brugervenlige værktøjer.

Lad os håbe, at når koden er frigivet, vil samfundet begynde at lave rettelser og forbedre Ghidra.

Tak for læsningen!