2009-05-20:

CONFidence 2009 ESET crackme - rozwi膮zanie

re:easy:confidence:crackme:c++
W ko艅cu jaki艣 techniczny post! A post po艣wi臋cony b臋dzie crackme ESET'u, kt贸re to by艂o do po艂amania na tegorocznej edycji CONFidence z numerkiem 2009. Crackme (przygotowanym specjalnie na confidence) pozwoli艂em sobie udost臋pni膰 (@Marcin/Jakub w razie czego dajcie zna膰, to zdejm臋 ;>), tak aby osoby nieobecne na confi r贸wnie偶 mog艂y si臋 pobawi膰:

UPDATE: Marcin podes艂a艂 mi now膮 binark臋 bez pewnych u艂atwie艅 kt贸rych tam nie mia艂o by膰:
confidence_eset_crackme.zip (binarka bez u艂atwie艅, crackme)
Poniewa偶 adresy w nowej si臋 nie zgadzaj膮 z tym o czym pisze, to na samym dole postu jest do 艣ci膮gni臋cia r贸wnie偶 stara binarka, kt贸rej adresy s膮 zgodne z moim poni偶szym tutorialem :)

Drogi czytelniku, je偶eli chcesz spr贸bowa膰 w艂asnymi si艂ami po艂ama膰 powy偶sze crackme, TO NATYCHMIAST WY艁膭CZ T膭 STRON臉!!! :) Poni偶ej zamieszczam rozwi膮zanie do 贸w crackme.



A

N

T

Y




S

P

O

I

L

E

R



Najpierw par臋 s艂贸w o rozwi膮zaniu: jak pisa艂em w poprzednim po艣cie, mia艂em 3 podej艣cia do tego crackme - za dwoma pierwszymi razami okazywa艂o si臋 偶e "nie t臋dy droga". W rozwi膮zaniu kt贸re przedstawiam poni偶ej pomin臋 etapy pomy艂ek, i przejd臋 od razu do prawid艂owego rozwi膮zania.



Celem crackme jest uzyskanie/odzyskanie has艂a, kt贸re wpisane w pole tekstowe spowoduje wy艣wietlenie czego艣 w stylu "congratz!" (patrz screen powy偶ej). Zaczniemy jak zwykle od rekonesansu - czyli w ruch id膮 narz臋dzia typu PEiD (kt贸rego ofc nie mia艂em przy sobie na konferencji - wzi膮艂em nieskonfigurowanego lapka na confi, prawie bez tools贸w, a ju偶 na pewno bez moich kod贸w ;/) czy Ent (to mia艂em przy sobie). Poni偶ej wrzucam wykres z Enta (osoby zainteresowane ale niezorientowane wtf ten wykres przedstawia odsy艂am do tego postu):



Jak wida膰, nie mamy do czynienia z 偶adnym nietrywialnym szyfrowaniem kodu - good news - wi臋c, nie zwlekaj膮c dalej mo偶emy wrzuci膰 crackme w nasz ulubiony disassembler (czytaj: IDA Pro). Patrz膮c w list臋 Export贸w dowiadujemy si臋 偶e mamy trzy miejsca startowe - 2x TLS callback oraz EP. W tym miejscu na confi zrobi艂em b艂膮d i od razu poszed艂em do EP ignoruj膮c TLSy - 偶eby troch臋 spot臋gowa膰 napi臋cie i teraz tak zrobi臋 :)

Zacznijmy od znalezienia jaki艣 procedurek obs艂uguj膮cych pobieranie tekstu z pola tekstowego - GetWindowTextA, GetDlgItemTextA, etc - czyli przegl膮damy okienko Imports. Jak si臋 okazuje w IAT wyst臋puje jedynie GetDlgItemTextA:

.text:00405B1A GetDlgItemTextA proc near               ; CODE XREF: DialogFunc+42
.text:00405B1A                 jmp     ds:__imp_GetDlgItemTextA
.text:00405B1A GetDlgItemTextA endp


Dodatkowo wida膰 偶e GetDlgItemTextA jest u偶yty jedynie raz - w DialogFunc+42:

.text:00401175                 mov     ebx, offset String
.text:0040117A                 push    20
.text:0040117C                 push    0
.text:0040117E                 push    ebx
.text:0040117F                 call    memset
.text:00401184                 push    20              ; cchMax
.text:00401186                 push    ebx             ; lpString
.text:00401187                 push    67h             ; nIDDlgItem
.text:00401189                 push    [ebp+hDlg]      ; hDlg
.text:0040118C                 call    GetDlgItemTextA
.text:00401191                 test    eax, eax
.text:00401193                 jz      short loc_4011AE
.text:00401195                 push    ebx
.text:00401196                 call    sub_403055
.text:0040119B                 test    eax, eax
.text:0040119D                 jz      short loc_4011AE
.text:0040119F ; "Congratulations, your password is corre"...
.text:0040119F                 push    offset aCongratulation
.text:004011A4                 push    [ebp+hDlg]      ; hDlg
.text:004011A7                 call    sub_401132
.text:004011AC                 jmp     short locret_4011C9
.text:004011AE ; ---------------------------------------------------------------------------
.text:004011AE
.text:004011AE loc_4011AE:                             ; CODE XREF: DialogFunc+49
.text:004011AE                                         ; DialogFunc+53
.text:004011AE ; "Sorry, your password is wrong"
.text:004011AE                 push    offset aSorryYourPassw
.text:004011B3                 push    [ebp+hDlg]      ; hDlg
.text:004011B6                 call    sub_401132
.text:004011BB                 jmp     short locret_4011C9


Pod adresem 00401196 jest call sub_403055, do kt贸rej jest przekazywany string, a nast臋pnie, w zale偶no艣ci od tego co zwr贸ci sub_403055 wy艣wietlany jest napis "Contratulations..." albo "Sorry...". Tak wi臋c sercem ca艂o艣ci jest sub_403055.

Po zej艣ciu do tej funkcji okazuje si臋 ze jest ona d艂uga. Bardzo d艂uga. Baaaaaaaaardzo bardzo d艂uga. A konkretniej, ma jakie艣 3000 linii kodu asma, z czego wi臋kszo艣膰 to instrukcje typu sub, xor, lea, ror, rol, czy add. Pozosta艂a cz臋艣膰 wygl膮da nast臋puj膮co:

.text:00403055 sub_403055      proc near               ; CODE XREF: DialogFunc+4C
.text:00403055
.text:00403055 arg_0           = dword ptr  8
.text:00403055
.text:00403055                 push    ebp
.text:00403056                 mov     ebp, esp
.text:00403058                 push    5
.text:0040305A                 pop     ecx
.text:0040305B                 mov     esi, [ebp+arg_0]
.text:0040305E                 mov     edi, offset go
.text:00403063                 pushf
.text:00403064                 xor     dword ptr ds:[esp], 100h
.text:0040306C                 popf

.text:0040306D                 nop
.text:0040306E
.text:0040306E loc_40306E:                             ; CODE XREF: sub_403055+2A94
.text:0040306E                 lodsd
.text:0040306F                 sub     eax, 8A14F2F5h
.text:00403074                 xor     eax, 7418FCC5h
.text:00403079                 lea     eax, [eax+3A61C552h]
.text:0040307F                 sub     eax, 0D101638Ch

...

.text:00405AE4                 ror     eax, 17h
.text:00405AE7                 stosd
.text:00405AE8                 dec     ecx
.text:00405AE9                 jnz     loc_40306E
.text:00405AEF                 nop
.text:00405AF0                 nop
.text:00405AF1                 nop
.text:00405AF2                 nop
.text:00405AF3                 mov     edi, offset go
.text:00405AF8                 mov     esi, offset dword_4070C2
.text:00405AFD                 push    5
.text:00405AFF                 pop     ecx
.text:00405B00                 repe cmpsd
.text:00405B02                 setz    al
.text:00405B05                 and     eax, 0FFh
.text:00405B0A                 leave
.text:00405B0B                 retn    4
.text:00405B0B sub_403055      endp


W skr贸cie, do ESI wrzucany jest adres stringu, do EDI wrzucany jest adres buforu na zakodowany string, a nast臋pnie po 4ry bajty (lodsd) string jest kodowany (max 5*4 czyli 20 bajt贸w), i zapisywany w buforze wyj艣ciowym (stosd). Na samym ko艅cu nast臋puje por贸wnanie zakodowanego stringu z oryginalnym zakodowanym has艂em znajduj膮cym si臋 pod adresem 4070C2:

0FBE0BB50h, 0D16C80CCh, 716786EDh, 3B77A739h, 493A8A5Ah

A nast臋pnie w zale偶no艣ci czy has艂o jest OK czy nie, zwracana jest pewna warto艣膰.

Wszystko wygl膮da prosto i klarownie na pierwszy rzut oka, zastanowi膰 mog膮 tylko dwie rzeczy:

.text:00403063                 pushf
.text:00403064                 xor     dword ptr ds:[esp], 100h
.text:0040306C                 popf


oraz

.text:00405AEF                 nop
.text:00405AF0                 nop
.text:00405AF1                 nop
.text:00405AF2                 nop


Pierwszy zastanawiaj膮cy kod to jest w艂膮czenie Trap Flag - czyli trybu krokowego procesora. Na pocz膮tku uzna艂em 偶e to jaki艣 anti-debug, i zignorowa艂em, ale jak si臋 za chwil臋 oka偶e, nic bardziej mylnego!

Drug膮 spraw膮 s膮 4ry NOPy kt贸re ni st膮d ni zow膮d le偶膮 sobie pod koniec funkcji - czy偶by runtime co艣 by艂o tam wrzucane? Jaki艣 dodatkowy kod?

I w tym momencie wr贸cimy do TLS callback贸w, a konkretniej TlsCallback_0 pod adresem 40233A. Od razu rzuci膰 si臋 w oczy mo偶e debug string "loading imports" - jak si臋 okazuje takich string贸w jest wi臋cej, i bardzo dobrze t艂umacz膮 co si臋 dzieje w kodzie. Callback jest kr贸tki, i skupia si臋 na wywo艂aniu paru funkcji, z kt贸rych najciekawsz膮 jest sub_40120E.

W tej偶e funkcji widzimy m.in. CreateProcsss z flag膮 DEBUG_PROCESS, a nast臋pnie p臋tl臋 debuggera kt贸ra odbiera m.in. event EXCEPTION_SINGLE_STEP kt贸ry generowany jest przez w艂膮czenie Trap Flag w kodzie kt贸ry analizowali艣my par臋 linii wy偶ej! No i uk艂adanka zaczyna pasowa膰! Podsumujmy co do tej pory wiemy:

- gdy odpalamy crackme wykonanie nigdy nie dociera do wy艣wietlenia okna - zamiast tego proces odpala ponownie swojego exeka jako debugger!
- czyli mamy dwa procesy
- proces dziecko - kt贸ry wy艣wietla okno, sprawdza has艂o, i w pewnym momencie Trap Flag uaktywnia
- proces rodzica - kt贸ry pozostaje w p臋tli debuggera i czeka a偶 si臋 Trap Flag uaktywni

Oczywistym nast臋pstwem powy偶szego jest fakt i偶 procesu dziecka nie mo偶emy debugowa膰 za pomoc膮 debugger API (stealth debuggery typu Obsidian i debuggery ring 0 ofc dadz膮 rad臋, ale nie s膮 potrzebne tak na prawd臋).

Rzu膰my okiem na obs艂ug臋 eventu SINGLE_STEP:

.text:00401302                 mov     eax, [CONTEXT.EIP]
.text:00401308                 mov     edx, [eax]
.text:0040130A                 cmp     edx, 90909090h
.text:00401310                 jz      short koniec
.text:00401312                 or      [CONTEXT.EFLAGS], 100h
.text:0040131C                 cmp     dl, 35h         ; XOR EAX, ...
.text:0040131F                 jz      short action_xor
.text:00401321                 cmp     dl, 2Dh         ; SUB EAX, ...
.text:00401324                 jz      short action_sub
.text:00401326                 cmp     dl, 5           ; ADD EAX, ...
.text:00401329                 jz      short action_add
.text:0040132B                 jmp     short koniec
.text:0040132D ; ---------------------------------------------------------------------------
.text:0040132D
.text:0040132D action_xor:                             ; CODE XREF: sub_40120E+111
.text:0040132D                 sub     dword ptr [CONTEXT.EAX], 2
.text:00401334                 jmp     short koniec
.text:00401336 ; ---------------------------------------------------------------------------
.text:00401336
.text:00401336 action_sub:                             ; CODE XREF: sub_40120E+116
.text:00401336                 add     dword ptr [CONTEXT.EAX], 1
.text:0040133D                 jmp     short koniec
.text:0040133F ; ---------------------------------------------------------------------------
.text:0040133F
.text:0040133F action_add:                             ; CODE XREF: sub_40120E+11B
.text:0040133F                 xor     dword ptr [CONTEXT.EAX], 10101010h
.text:00401349
.text:00401349 koniec:                                 ; CODE XREF: sub_40120E+102
.text:00401349                                         ; sub_40120E+11D ...


Dzia艂anie tego mechanizmu jest nast臋puj膮ce (je偶eli kto艣 czyta艂 m贸j art z Xploit 3/2008 o user opcodes to dostrze偶e podobie艅stwo :>):
- spod EIP procesu dziecka pobierane s膮 4ry bajty instrukcji
- je偶eli te 4 bajty to 90909090 (czyli 4x NOP - brzmi znajomo ?) to nic wi臋cej nie jest robione
- w innym wypadku flaga TF jest odnawiana (TF jest gaszone po pierwszym 'uruchomieniu')
- a nast臋pnie analizowany jest pierwszy bajt instrukcji spod EIP:
-- je偶eli jest to 35 (czyli XOR EAX, imm32), to dodatkowo od EAX odejmowane jest 2 (to si臋 wykonuje PRZED prawdziw膮 instrukcj膮)
-- je偶eli jest to 2D (czyli SUB EAX, imm32), to dodatkowo do EAX dodawane jest 1
-- je偶eli jest to 05 (czyli ADD EAX, imm32), to dodatkowo EAX jest xorowane z 10101010h
- wykonanie procesu jest kontynuowane

Czyli - metoda szyfruj膮ca has艂o zawiera dodatkowe dodawania/odejmowania/xorowania kt贸rych nie wida膰 w listingu (bo wykonuje je zew debugger)! Sprryyytne :)

OK. Teraz mamy ju偶 wszystkie klocki potrzebne do rozwi膮zania zagadki! Metod na odzyskanie has艂a jest kilka - np. mo偶na odwr贸ci膰 listing, albo zrobi膰 brute force - ja zdecydowa艂em si臋 na to ostatnie.

W takim wypadku nale偶y zacz膮膰 od skopiowania ca艂ej procedury zawieraj膮cej kodowanie has艂a, a nast臋pnie kilkoma regexpami wklei膰 dodatkowe dec+dec, inc czy xor (najlepiej robi膰 to w艂a艣nie w tej kolejno艣ci). Potem mo偶na usun膮膰 niepotrzebne ju偶 w艂膮czanie TF, i skompilowa膰.

Ostateczna posta膰 procedury: esetcode.nasm

Teraz klepiemy w C/C++ prosty bruteforce kt贸ry ma:
- wczyta膰 do pami臋ci skompilowan膮 wersj臋 procedury
- poprawi膰 w niej adresy
- a nast臋pnie, korzystaj膮c z faktu 偶e jest tylko 4GB kombinacji minus kombinacje zawieraj膮ce przynajmniej jeden znak niedrukowalny, odpala膰 procedur臋 szyfruj膮c膮 has艂o

Poniewa偶 has艂o szyfrowane jest DWORDami kt贸re s膮 dodatkowo niezale偶ne od siebie, tak wi臋c mo偶emy w jednej p臋tli szuka膰 prawid艂owego rozwi膮zania dla wszystkich pi臋ciu DWORD贸w z has艂a. Taki brute force wygl膮da nast臋puj膮co (kod pisany na kolanie na konkursie, wi臋c nie spodziewajcie sie miss code 2009 ;p):

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char buf[1024 * 1024];

DWORD a[5];
DWORD b[5];

int
main(void)
{
 FILE *f;
 f = fopen("code", "rb");
 fread(buf, 1, sizeof(buf), f);
 fclose(f);

 int (__stdcall *func)(const char *a) = (typeof(func))&buf;

#define P1 0xd
#define P2 0x34B8
#define P3 0x34BD

 DWORD v12 = (DWORD)a;
 DWORD v3 = (DWORD)b;

 *(DWORD*)(P1 + buf) = v12;
 *(DWORD*)(P2 + buf) = v12;
 *(DWORD*)(P3 + buf) = v3;

 puts("done patching"); fflush(stdout);

 static unsigned char brute[40];

 DWORD myin;

 for(myin = 0; myin != 0xffffffff; myin++)
 {
   *(DWORD*)brute = myin;

       if((myin % 0x01000000) == 0)
     putchar('.');

   if(brute[0] < ' ' || brute[0] > '~') continue;
   if(brute[1] < ' ' || brute[1] > '~') continue;
   if(brute[2] < ' ' || brute[2] > '~') continue;
   if(brute[3] < ' ' || brute[3] > '~') continue;

   func((const char*)brute);

   if(a[0] == 0x0FBE0BB50 || a[0] == 0x0D16C80CC || a[0] == 0x716786ED || a[0] == 0x3B77A739 || a[0] == 0x493A8A5A)
   {
     char asdf[8];
     *(DWORD*)(asdf) = myin;
     asdf[4] = 0;
     printf("%.8x (%s) == %.8x\n", myin, asdf, a[0]);
   }
 }

 return 0;
}


Kompilujemy (g++, z uwagi na u偶ycie typeof()), odpalamy, i... (ilo艣膰 kropek mo偶e nie odpowiada膰 rzeczywisto艣ci ;p)

done patching
.................................20276e69 (in' ) == 716786ed
20756f59 (You ) == fbe0bb50
........................................6b6c6174 (talk) == d16c80cc
..6d206f74 (to m) == 3b77a739
......................................................


Jak wida膰 nie znalaz艂o ostatniego ci膮gu (\0 by trzeba tam w brute dorzuci膰), ale to ma艂o istotne. Uk艂adaj膮c to co mamy mo偶emy si臋 domy艣li膰 co jest ostatnie: You talkin' to m ->  You talkin' to me?, i gotow臋! :)

No i chyba tyle :)

P.S. Oryginalna binarka z CONFidence: confiesetcrackme.zip

Comments:

2009-05-20 18:20:05 = Malcom
{
Ciekawe, ciekawe... :>

BTW, Czemu ludzie od security i nie tylko pisza taki paskudny kod?
Ani to C ani C++, tylko miksy, C z klasami lub inne potworki ;p
}
2009-05-20 19:59:23 = Gynvael Coldwind
{
@Malcom
To przez ewolucj臋! -> http://www.kaila.pl/humor/program.htm ;D
}
2009-05-23 10:40:33 = Gynvael Coldwind
{
@ged_
Gratz za XSS'a (mimo 偶e trzeba klikn膮膰) ;> To pierwsza rzecz kt贸r膮 kto艣 znalaz艂 na moim blogu ;> Zaraz to spatchuje ;>

@flame
Pozwoli艂em sobie ukry膰 flamewar jaki wywi膮za艂 si臋 mi臋dzy bw a innymi czytelnikami ;>
Natomiast dla cel贸w kronikarskich zaznacz臋 o co chodzi艂o: bw stwierdzi艂 偶e crackme by艂o zbyt proste i 偶e powinno by膰 trudniejsze, na co marcin odpar艂 偶e tak mia艂o by膰 jak by艂o (i 偶e crackme by艂o przewidziane na 30 minut), a potem dyskusja zesz艂a na to co powinno by膰 dozwolone podczas crackme, a czego u偶ywania mo偶na zabroni膰, co sko艅czy艂o si臋 og贸lnymi wjazdami personalno/firmowymi :)
}
2009-05-25 18:48:45 = Leming
{
Uchylisz r膮bka tajemnicy dot. tego XSS'a ;> ?
}
2009-05-25 19:27:39 = Gynvael Coldwind
{
@Leming
Sure ;> Prosta sprawa - w URL w komentarzach wpisa膰 np. javascript:alert(666), i potem czeka膰 a偶 kto艣 kliknie ;>
}
2009-06-19 22:08:58 = no comment
{
http://www.secnews.pl/2009/06/19/eset-crackme/
}
2010-03-16 07:14:08 = timon
{
Jak dlugo mielil sie ten brute force? Bo tak na moje 'oko' to chyba pare godzin :) Ale moze mi sie tylko wydaje ;)
}
2010-03-16 12:07:32 = Gynvael Coldwind
{
@timon
2-3 minuty z tego co pami臋tam ;)
}

Add a comment:

Nick:
URL (optional):
Math captcha: 4 * 3 + 10 =