de Denis Nuțiu

Cum am rezolvat o simplă provocare CrackMe cu Ghidra NSA

Cum am rezolvat o simpla provocare CrackMe cu Ghidra NSA

Salut!

M-am jucat recent cu un pic Ghidra, care este un instrument de inginerie inversă care a fost deschis recent de către ANS. Site-ul oficial descrie instrumentul ca:

O suită de instrumente de inginerie inversă software (SRE) dezvoltată de Direcția de cercetare a NSA în sprijinul misiunii de securitate cibernetică.

Sunt la începutul carierei mele de inginerie inversă, așa că nu am făcut nimic avansat. Nu știu ce caracteristici să vă așteptați de la un instrument profesionist ca acesta, dacă doriți să citiți despre caracteristicile avansate Ghidra, probabil că nu este articolul pentru dvs.

În acest articol voi încerca să rezolv o provocare simplă CrackMe pe care am găsit-o pe site rădăcină-mă. Provocarea pe care o rezolv se numește ELF – CrackPass. Dacă doriți să încercați singur, atunci ar trebui să luați în considerare să nu citiți acest articol, deoarece vă va strica provocarea.

Să începem! Deschid Ghidra și creez un nou proiect pe care îl numesc RootMe.

1611177546 662 Cum am rezolvat o simpla provocare CrackMe cu Ghidra NSA

Apoi, import fișierul provocare trăgându-l în dosarul proiectului. Voi merge cu valorile implicite.

1611177547 985 Cum am rezolvat o simpla provocare CrackMe cu Ghidra NSA

După ce mi s-au prezentat câteva informații despre fișierul binar, apăs OK, selectez fișierul și dau dublu clic pe el. Aceasta deschide utilitarul browserului de cod Ghidra și mă întreabă dacă vreau să analizez fișierul, apoi apăs Da și continuu cu valorile implicite.

După ce importăm fișierul, primim câteva informații despre fișierul binar. Dacă apăsăm OK și închidem această fereastră, apoi facem dublu clic pe fișierul pe care l-am importat, acesta deschide utilitarul browserului de cod Ghidra. Selectez Da când mi se solicită să analizez binele și să continui cu valorile implicite.

1611177547 667 Cum am rezolvat o simpla provocare CrackMe cu Ghidra NSA

Browserul de coduri este destul de convenabil. În panoul din stânga vom vedea vizualizarea dezasamblării și în panoul din dreapta vizualizarea decompilare.

Ghidra ne arată direct informațiile antetului ELF și punctul de intrare al binarului. După ce ați făcut dublu clic pe punctul de intrare, vizualizarea disimulator trece la funcția de intrare.

Acum putem identifica cu succes funcția principală, pe care o redenumesc în main. Ar fi frumos dacă instrumentul ar încerca să detecteze automat funcția principală și să o redenumească în consecință.

1611177548 766 Cum am rezolvat o simpla provocare CrackMe cu Ghidra NSA

Înainte de a analiza funcția principală, am vrut să-i schimb semnătura. Am schimbat tipul de returnare în int și am corectat tipul și numele parametrilor. Această modificare a intrat în vigoare în modul de decompilare, care este minunat! ?

Evidențierea unei linii în vizualizarea decompilare o evidențiază și în vizualizarea asamblării.

1611177548 665 Cum am rezolvat o simpla provocare CrackMe cu Ghidra NSA

Să explorăm funcția FUN_080485a5, pe care o voi redenumi în CheckPassword.

Conținutul funcției CheckPassword poate fi găsit mai jos. Am copiat codul direct din vizualizarea decompilării lui Ghidra, care este o caracteristică îngrijită de care lipsesc multe instrumente de acest tip! Capacitatea de a copia asamblarea și codul este o caracteristică plăcută.

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 : nn%sn",local_108c);   }   else {     puts("Is not the good password !");   }   return; }

După ce am analizat codul, am ajuns la următoarele concluzii. Blocul cu if verifică dacă utilizatorul a furnizat o parolă și inspectează parola furnizată pentru a verifica dacă este un caracter valid sau ceva similar. Nu știu exact ce verifică, dar iată ce spune documentația __ctype_b_loc ():

Funcția __ctype_b_loc () va returna un pointer într-o matrice de caractere din localitatea curentă care conține caracteristici pentru fiecare caracter din setul de caractere curent. Matricea trebuie să conțină un total de 384 de caractere și poate fi indexată cu orice caracter semnat sau nesemnat (adică cu o valoare a indexului între 128 și 255). Dacă aplicația are mai multe fire, matricea trebuie să fie locală la firul curent.

Oricum, acel bloc de cod nu merită cu adevărat timpul, deoarece nu modifică parola în niciun fel, ci doar o verifică. Deci, putem sări peste acest tip de verificare.

Următoarea funcție numită este FUN_080484f4. Privind codul său, putem spune că este doar o implementare personalizată memcopy. În loc să copiez codul C din vizualizarea decompilatorului, am copiat codul de asamblare – da, este distractiv.

*************************************************************                     *                           FUNCTION                                               *************************************************************                     undefined  FUN_080484f4 (undefined4  param_1 , undefined4  p     undefined         AL:1           <RETURN>     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.

Iar cealaltă funcție FUN_0804851c generează parola din șirul „THEPASSWORDISEASYTOCRACK”. Privind vederea descompilată. putem vedea aproximativ cum funcționează această funcție. Dacă nu am avea asta, ar trebui să analizăm manual fiecare instrucțiune de asamblare din funcție pentru a înțelege ce face.

Apoi, comparăm parola generată anterior cu parola pe care am primit-o de la utilizator (primul argument, argv[1]). Dacă se potrivește, programul spune treaba bună și îl imprimă, altfel imprimă un mesaj de eroare.

Din această analiză de bază, putem concluziona că, dacă punem programul în diferite locuri, îl putem face să scuipe parola fără ca noi să trebuiască să inversăm orice funcție C și să scriem cod. Corectarea programului înseamnă schimbarea unora dintre instrucțiunile sale.

Să vedem ce trebuie să reparăm:

La adresa 0x0804868c patchem instrucțiunea JNS într-un JMP. Și voilà, schimbarea se reflectă în vizualizarea decompilatorului. Verificarea rezultatului ptrace este ocolită.

{   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;}

La adresa 0x080485b8, punem instrucțiunile JZ într-un JMP. Ocolim blocul de verificare a parolei pe care l-am văzut mai devreme.

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 : nn%sn",local_108c);   }   else {     puts("Is not the good password !");   }   return; }

La adresa 0x0804861e corecționăm JNZ cu JZ. Aceasta inversează condiția if / else. Deoarece nu cunoaștem parola, vom trimite o parolă aleatorie care nu este egală cu cea generată, executând astfel printf pe blocul else.

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 : nn%sn",local_108c);   }   return; }

Asta e tot!

Acum rulăm programul. În alte instrumente, salvăm fișierul și funcționează, dar în Ghidra se pare că trebuie să-l exportăm.

Pentru a exporta programul, mergem la Fișier -> Export program (O). Schimbăm formatul în binar și facem clic pe OK.

1611177549 555 Cum am rezolvat o simpla provocare CrackMe cu Ghidra NSA

Primesc programul exportat pe desktop, dar nu funcționează – nu am reușit să rulez programul exportat. După ce am încercat să citesc antetul cu programul readelf -h, obțin următoarea ieșire:

root@DESKTOP:/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

Rușine. Se pare că Ghidra a dat peste cap antetul fișierului… Și, chiar acum, nu vreau să repar manual anteturile. Așa că am tras un alt instrument și am aplicat aceleași patch-uri la fișier, l-am salvat, l-am rulat cu un argument aleatoriu și am validat semnalizatorul.

Concluzii

Ghidra este un instrument frumos cu mult potențial. În starea sa actuală, nu este atât de grozav, dar funcționează. De asemenea, am întâlnit o eroare de derulare ciudată în timp ce o rulam pe laptopul meu.

Alternativele ar fi să plătiți $ $ pentru alte instrumente de acest fel, să vă creați propriile instrumente sau să lucrați cu instrumente gratuite, dar nu atât de ușor de utilizat.

Să sperăm că odată lansat codul, comunitatea va începe să facă remedieri și să îmbunătățească Ghidra.

Mulțumesc pentru lectură!