Crackme ZebraOne by QUASAR

1 Introduction

Ostatnio przegldajc synnego site'a http://www.crackmes.de zajrzaem do dziau o intrygujcym tytule 'Hall of Fame'. Widniay tam tylko dwa crackmesy w tym jeden o znamiennym tytule 'ZebraOne'. Opis wskazywa, e ma ono co wsplnego z matematyk. Jako, e nie miaem nic ciekawego do roboty po chwili crackmes lea ju na moim dysku.

2 Disassemblacja

Po pierwsze crackmes zwrci uwag niewielkim rozmiarem. Po drugie po zaadowaniu do IDY stao si jasne, e zosta on napisany w C, konkretnie pod LCC-Win32. Skd to wiem? Wystarczy tylko lookn w entrypoint programu:

004011CB ; Attributes: bp-based frame
004011CB     global start
004011CB start:
004011CB %define var_18 (-0x18)
004011CB %define var_4 (-4)
004011CB     mov eax, large [fs:0]
004011D1     push ebp
004011D2     mov ebp, esp
004011D4     push 0xFFFFFFFF
004011D6     push dword_403014
004011DB     push SEH
004011E0     push eax
004011E1     mov large [fs:0], esp
004011E8     sub esp, 0x10
004011EB     push ebx
004011EC     push esi
004011ED     push edi
004011EE     mov [ebp+var_18], esp
004011F1     push dword_403020
004011F6     push dword_40301C
004011FB     push dword_403018
00401200     call j___GetMainArgs
00401205     push [dword_403020]
0040120B     push [dword_40301C]
00401211     push [dword_403018]
00401217     mov [dword_40300C], esp
0040121D     call WinMain
00401222     add esp, 0x18
00401225     xor ecx, ecx
00401227     mov [ebp+var_4], ecx
0040122A     push eax
0040122B     call j_exit
00401230     leave
00401231     retn

Warto pamita takie sekwencje, charakterystyczne stringi itp. W kocu od dawna wiadomo, e najlepszym analizerem progw i detektorem kompilatorw jest hexedytor ewentualnie magiczny klawisz F3 w Windows (przepraszam 'Total' - pewnie 'Windows' si le kojarzy) Commanderze. Progi skompilowane Borland C++ maj na pocztku stringa 'fbC++Hook', ewentualnie co o Borlandzie pniej, te z Delphi stringi typu 'Boolean' na pocztku tu za nagwkiem, te z MSVC++ charakterystyczne dla MFC manglowane name'y pod koniec pliku. No ale nie o tym miaem mwi. Zagbiamy si w WinMain i dochodzimy do kodu tworzcego dialoga:

0040123C sub_40123C:                   ; CODE XREF: WinMain+36 p
0040123C %define arg_0 ( 8)
0040123C     push ebp
0040123D     mov ebp, esp
0040123F     push 0
00401241     push DialogProc
00401246     push 0
00401248     push 0x64
0040124A     push [ebp+arg_0]
0040124D     call j_DialogBoxParamA
00401252     pop ebp
00401253     retn 0x10
00401253 ; End of function sub_40123C

Nie ma co wchodzimy w DialogProc. W obsudze WM_COMMAND wida jak na doni:

004012AA loc_4012AA:                   ; CODE XREF: DialogProc+3A j
004012AA     push 0xA
004012AC     lea eax, [ebp+var_14]
004012AF     push eax
004012B0     push 0x65
004012B2     push [ebp+hWnd]
004012B5     call j_GetDlgItemTextA
004012BA     push 0xA
004012BC     lea eax, [ebp+var_A]
004012BF     push eax
004012C0     push 0x66
004012C2     push [ebp+hWnd]
004012C5     call j_GetDlgItemTextA
004012CA     lea eax, [ebp+var_A]
004012CD     push eax
004012CE     lea eax, [ebp+var_14]
004012D1     push eax
004012D2     call CheckSerial
004012D7     add esp, 8
004012DA     or  eax, eax
004012DC     jz  short loc_4012F4
004012DE     push 0
004012E0     push szGreat!!!           ; "Great !!!"
004012E5     push szCongratulations,Yo ; "Congratulations, you have
                                       ; cracked the     Z"...
004012EA     push [ebp+hWnd]
004012ED     call j_MessageBoxA
004012F2     jmp short loc_401308
004012F4 loc_4012F4:                   ; CODE XREF: DialogProc+86 j
004012F4     push 0
004012F6     push szHmmmm:p            ; "Hmmmm :P"
004012FB     push szSorry___TheSerialI ; "Sorry... The Serial isn't
                                       ; correct :"
00401300     push [ebp+hWnd]
00401303     call j_MessageBoxA

No to teraz CheckSerial. Pocztkowo nic tu nie wida, ale gdy pod koniec procki oznaczymy:

0040142C .bad.boy:                     ; CODE XREF: CheckSerial+DB j
0040142C     xor eax, eax
0040142E .return:                      ; CODE XREF: CheckSerial+49 j
0040142E                               ; CheckSerial+5B j
0040142E                               ; CheckSerial+A2 j
0040142E                               ; CheckSerial+E0 j
0040142E     leave
0040142F     retn

Kod robi si troch janiejszy. W procce mona zauway jeszcze kilka innych rzeczy, ale pora zaadowa crackme do debuggera. Shift-F10 wygeneruj plik map i zaaduj go do Olly'ego. Teraz Alt+F1 i w linii komend wpisz 'bp CheckSerial'. Teraz klepnij F9 i wpisz cokolwiek w dwa edity. 'Register' i ldujemy tutaj:

<zebrone.>/$  55            PUSH    EBP
0040134B  |.  89E5          MOV     EBP,ESP
0040134D  |.  83EC 68       SUB     ESP,68
00401350  |.  FF75 08       PUSH    [ARG.1]           ; /s
00401353  |.  E8 78010000   CALL    <zebrone.j_atof>  ; \atof
00401358  |.  DD55 E8       FST     [QWORD EBP-18]
0040135B  |.  83EC 08       SUB     ESP,8
0040135E  |.  DD1C24        FSTP    [QWORD ESP]
00401361  |.  E8 82010000   CALL    <zebrone.j_floor> ; JMP to
                                                      ; CRTDLL.floor
00401366  |.  DD5D F8       FSTP    [QWORD EBP-8]
00401369  |.  FF75 0C       PUSH    [ARG.2]           ; /s
0040136C  |.  E8 5F010000   CALL    <zebrone.j_atof>  ; \atof
00401371  |.  DD55 D8       FST     [QWORD EBP-28]
00401374  |.  83EC 08       SUB     ESP,8
00401377  |.  DD1C24        FSTP    [QWORD ESP]
0040137A  |.  E8 69010000   CALL    <zebrone.j_floor> ;  JMP to
                                                      ; CRTDLL.floor
0040137F  |.  83C4 18       ADD     ESP,18

Atof zamienia podanego stringa na float, a floor zwraca najblisz liczb cakowit nie wiksz od argumentu. Z tego wniosek, e w obu editach naley poda liczby (w kocu miao by matematycznie nie?). Jednak dla uszanowania tradycji pierwsz bd nazywa 'name' a drug 'serial', oki? Oczywicie moe by tak e w pierwszym edicie moe by co na ksztat '22QUASAR' bo atof koczy konwersj na pierwszym znaku, ktry nie jest cyfr dziesitn czy kropk. Nastpnie wida:

00401382  |.  DD55 F0       FST     [QWORD EBP-10]
00401385  |.  DC4D F8       FMUL    [QWORD EBP-8]
00401388  |.  D9EE          FLDZ
0040138A  |.  DED9          FCOMPP
0040138C  |.  DFE0          FSTSW   AX
0040138E  |.  9E            SAHF
0040138F  |.  75 07         JNZ     SHORT <zebrone.loc_401398>
00401391  |.  31C0          XOR     EAX,EAX
00401393  |.  E9 96000000   JMP     <zebrone..return>

Liczby wygenerowane z name i seriala s mnoone przez siebie, jeli wynik jest rwny zero to zonk. Z tego wniosek, e adna z liczb nie moe by rwna 0.

<zebrone.>|>  DD45 F8       FLD     [QWORD EBP-8]
0040139B  |.  DC5D F0       FCOMP   [QWORD EBP-10]
0040139E  |.  DFE0          FSTSW   AX
004013A0  |.  9E            SAHF
004013A1  |.  75 07         JNZ     SHORT <zebrone.loc_4013AA>
004013A3  |.  31C0          XOR     EAX,EAX
004013A5  |.  E9 84000000   JMP     <zebrone..return>

Teraz porwnanie obu liczb, jeli s rwne to te zonk.

004013B0  |.  D9E8          FLD1
004013B2  |.  DD55 C0       FST     [QWORD EBP-40]
004013B5  |.  DC5D C8       FCOMP   [QWORD EBP-38]
004013B8  |.  DFE0          FSTSW   AX
004013BA  |.  9E            SAHF
004013BB  |.  77 2D         JA      SHORT <zebrone..bad.boy1>

Tutaj pierwsza cz jest porwnywana z 1, to samo jest pniej z serialem, jeli jest mniejsza to zonk. Naley tutaj zauway jedn rzecz. Obie liczby s zaokrglane funkcj floor wic musz by rwne co najmniej 2.

004013BD  |.  DF2D 38304000 FILD    [QWORD 403038]  ;10^10
004013C3  |.  DD55 B8       FST     [QWORD EBP-48]
004013C6  |.  DC5D C8       FCOMP   [QWORD EBP-38]
004013C9  |.  DFE0          FSTSW   AX
004013CB  |.  9E            SAHF
004013CC  |.  72 1C         JB      SHORT <zebrone..bad.boy1>

A tutaj wida, e name i serial musz by mniejsze od 10^10 (czyli 10 z dziesicioma zerami, po "naszymu" 10 miliardw)

<zebrone.>|>  DD45 F8       FLD     [QWORD EBP-8]
004013F1  |.  D9FE          FSIN
004013F3  |.  DD5D A8       FSTP    [QWORD EBP-58]
004013F6  |.  DD45 F0       FLD     [QWORD EBP-10]
004013F9  |.  D9FE          FSIN
004013FB  |.  DD5D A0       FSTP    [QWORD EBP-60]
004013FE  |.  DD45 A8       FLD     [QWORD EBP-58]
00401401  |.  DC4D A0       FMUL    [QWORD EBP-60]
00401404  |.  DF2D 30304000 FILD    [QWORD 403030] ;10^16
0040140A  |.  DEC9          FMULP   ST(1),ST(0)
0040140C  |.  83EC 08       SUB     ESP,8
0040140F  |.  DD1C24        FSTP    [QWORD ESP]
00401412  |.  E8 D1000000   CALL    <zebrone.j_floor> ; JMP to
                                                      ; CRTDLL.floor
00401417  |.  83C4 08       ADD     ESP,8
0040141A  |.  DD5D 98       FSTP    [QWORD EBP-68]
0040141D  |.  D9EE          FLDZ
0040141F  |.  DC5D 98       FCOMP   [QWORD EBP-68]
00401422  |.  DFE0          FSTSW   AX
00401424  |.  9E            SAHF
00401425  |.  75 05         JNZ     SHORT <zebrone..bad.boy>

A to ju esencja i sens caego crackmesa. Z name i serial liczone s sinusy (matematyka na poziomie iie akademickim, zreszt teraz po reformie trzeba by przynajmniej licealist aby dowiedzie si co to 'sinus' i 'cosinus', ale odbiegam od tematu), nastpnie te sinusy mnoone s przez siebie a jakby tego byo mao to mnoone s jeszcze przez 10^16. Potem wynik jest obcinany funkcj floor i jeli wynosi zero to serial/name s poprawne. No to teraz powtrka z podstawwki (pardon 'szkoy redniej'). Sinus przyjmuje warto zero dla dowolnej cakowitej wielokrotnoci liczby 'pi' (taki kibel z pofalowanym daszkiem, suy do liczenia pola koa, obwodu okrgu i paru innych rzeczy). Ideaem byoby gdybymy mogli poda wartoci przyblione pi i np 2*pi ale niestety nasze liczby s zaokrglane. Z pomoc przychodzi jednak architektura koprocesora, zwizane z ni ograniczenia no i oczywicie wasnoci liczb. Jak wynika z analizy kodu najlepiej aby oba sinusy miay ten sam znak (bo np. floor(-0.1)=-1 a nie 0 jakby si mogo na pierwszy rzut oka wydawa), i byy mniejsze od 10^-8 (bo pomnoone przez siebie dadz liczb <10^-16 a oto nam chodzi). Poniewa jestem leniwy czowiek stworzyem takie cosik:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

int main(void) {
   double myarc; //kat
   double i,t;
   for(i=1;i<1000000000;i+=1)
   {
      myarc=i*3.1415926535897932384626433832795f;
      t=sin(floor(myarc));
      if(fabs(t)<0.00000001)
      {
         printf("\nsin(%u)=%s",(unsigned int)myarc,t>0?"+":"-");
      }
   }
   return 0;
}

Skompilowane Mingw daje wynik po paru minutach. Oto wyniki:

sin(245850922)=+
sin(823115974)=+
sin(903259831)=-
sin(1480524883)=-
sin(1726375805)=-
sin(2383784714)=+
sin(2715198844)=-
sin(2961049766)=+

+ i - oznacza, e warto sinusa dla podanego kta jest dodatnia lub ujemna. Jest to potrzebne bo trzeba podawa wartoci ktw parami dodatni z dodatnim i ujemny z ujemnym. No to teraz sprawdzenie. Co jest do ch~1 wafla. Okazuje si, e z listy pasuj tylko dwa pierwsze seriale. Dlaczego. Look jeszcze raz na DialogProc:

004012AA loc_4012AA:                ; CODE XREF: DialogProc+3A j
004012AA     push 0xA               ;pobierz maks. 10 znakw!!!
004012AC     lea eax, [ebp+var_14]
004012AF     push eax
004012B0     push 0x65
004012B2     push [ebp+hWnd]
004012B5     call j_GetDlgItemTextA
004012BA     push 0xA
004012BC     lea eax, [ebp+var_A]
004012BF     push eax
004012C0     push 0x66
004012C2     push [ebp+hWnd]
004012C5     call j_GetDlgItemTextA
004012CA     lea eax, [ebp+var_A]
004012CD     push eax
004012CE     lea eax, [ebp+var_14]
004012D1     push eax
004012D2     call CheckSerial

Okazuje si, e nasz serial zostaje obcity do 10 znakw. Z 2383784714 robi si 238378471 co nie spenia podanego algorytmu. Zatem z naszych liczb rozwizaniem crackmesa jest tylko jedna para 245850922 i 823115974.