Zebrone v1.1

by

{Cronos}

Intro

Once again this isn't a tutorial for newbies, this crackme is interesting and requires a reasonable understanding of assembly coding.

Target

The crackme appears to have the name of Zebra and to be written by aLoNg3x (I wish they would write nfo's for their crackmes). The crackme is what I would call a floating point crackme, making heavy use of the maths coprocessor for calculations and requiring us to reverse and understand a number of fpu opcodes in order to determine what is happening.

Analysis

OK, we are presented with a dialog box with two text boxes into which we can type and a register button. So we need to put in two things and get the correct registration message. The disassembly of the program shows some entry code, easily traced through and we are led to the following piece of code at the real beginning to the program.
1000:0040123c 55                   push   ebp
1000:0040123d 89e5                 mov    ebp, esp
1000:0040123f 6a00                 push   00h
1000:00401241 6856124000           push   offset 401256h
1000:00401246 6a00                 push   00h
1000:00401248 6a64                 push   64h
1000:0040124a ff7508               push   dword ptr [ebp+08h]
1000:0040124d e842020000           call   _DialogBoxParamA
1000:00401252 5d                   pop    ebp
1000:00401253 c21000               ret    10h
;                                                 XREFS First: 1000:00401241 Number : 1
1000:00401256 55                   push   ebp			; dialog box routine
1000:00401257 89e5                 mov    ebp, esp
1000:00401259 83ec14               sub    esp, 14h
1000:0040125c 8b450c               mov    eax, [ebp+0ch]
1000:0040125f 83f810               cmp    eax, 10h
1000:00401262 0f84cd000000         jz     401335h		; check the message
1000:00401268 0f8cd6000000         jl     401344h
1000:0040126e 3d10010000           cmp    eax, 110h
1000:00401273 740c                 jz     401281h
1000:00401275 3d11010000           cmp    eax, 111h
1000:0040127a 740d                 jz     401289h
1000:0040127c e9c3000000           jmp    401344h
;                                                 XREFS First: 1000:00401273 Number : 1
1000:00401281 31c0                 xor    eax, eax
1000:00401283 40                   inc    eax
1000:00401284 e9bd000000           jmp    401346h
;                                                 XREFS First: 1000:0040127a Number : 1
1000:00401289 0fb74510             movzx  eax, [ebp+10h]
1000:0040128d 83f801               cmp    eax, 01h
1000:00401290 7418                 jz     4012aah
1000:00401292 83f802               cmp    eax, 02h
1000:00401295 7476                 jz     40130dh
1000:00401297 83f801               cmp    eax, 01h
1000:0040129a 0f8ca4000000         jl     401344h
1000:004012a0 83f867               cmp    eax, 67h
1000:004012a3 7477                 jz     40131ch
1000:004012a5 e99a000000           jmp    401344h
;                                                 XREFS First: 1000:00401290 Number : 1
1000:004012aa 6a0a                 push   0ah			; get 10 bytes max
1000:004012ac 8d45ec               lea    eax, [ebp-14h]	; to here
1000:004012af 50                   push   eax
1000:004012b0 6a65                 push   65h
1000:004012b2 ff7508               push   dword ptr [ebp+08h]
1000:004012b5 e8f2010000           call   _GetDlgItemTextA	; now!
1000:004012ba 6a0a                 push   0ah			; another 10 bytes
1000:004012bc 8d45f6               lea    eax, [ebp-0ah]	; to here
1000:004012bf 50                   push   eax
1000:004012c0 6a66                 push   66h
1000:004012c2 ff7508               push   dword ptr [ebp+08h]
1000:004012c5 e8e2010000           call   _GetDlgItemTextA	; now!
1000:004012ca 8d45f6               lea    eax, [ebp-0ah]	; string1
1000:004012cd 50                   push   eax
1000:004012ce 8d45ec               lea    eax, [ebp-14h]	; string2
1000:004012d1 50                   push   eax
1000:004012d2 e873000000           call   40134ah		; ***check them***
1000:004012d7 83c408               add    esp, 08h
1000:004012da 09c0                 or     eax, eax		; did they pass ?
1000:004012dc 7416                 jz     4012f4h		;if (eax=0) then no
1000:004012de 6a00                 push   00h
1000:004012e0 6826324000           push   offset s_Great____
1000:004012e5 6830324000           push   offset s_Congratulations__you_have_cracked_th
1000:004012ea ff7508               push   dword ptr [ebp+08h]
1000:004012ed e8c6010000           call   _MessageBoxA
1000:004012f2 eb14                 jmp    401308h
;                                                 XREFS First: 1000:004012dc Number : 1
1000:004012f4 6a00                 push   00h
1000:004012f6 68f8314000           push   offset s_Hmmmm__P
1000:004012fb 6801324000           push   offset s_Sorry____The_Serial_isn_t_correct__Þ
1000:00401300 ff7508               push   dword ptr [ebp+08h]
1000:00401303 e8b0010000           call   _MessageBoxA
;                                                 XREFS First: 1000:004012f2 Number : 1
1000:00401308 31c0                 xor    eax, eax
1000:0040130a 40                   inc    eax
1000:0040130b eb39                 jmp    401346h
;                                                 XREFS First: 1000:00401295 Number : 1
1000:0040130d 6a00                 push   00h
1000:0040130f ff7508               push   dword ptr [ebp+08h]
1000:00401312 e889010000           call   _EndDialog
1000:00401317 31c0                 xor    eax, eax
1000:00401319 40                   inc    eax
1000:0040131a eb2a                 jmp    401346h
;                                                 XREFS First: 1000:004012a3 Number : 1
1000:0040131c 6a00                 push   00h
1000:0040131e 6840304000           push   offset s_Zebra_ver__1_1
1000:00401323 684f304000           push   offset s_This_is_the_1_1_Zebra_Crackme__Thank
1000:00401328 ff7508               push   dword ptr [ebp+08h]
1000:0040132b e888010000           call   _MessageBoxA
1000:00401330 31c0                 xor    eax, eax
1000:00401332 40                   inc    eax
1000:00401333 eb11                 jmp    401346h
;                                                 XREFS First: 1000:00401262 Number : 1
1000:00401335 6a00                 push   00h
1000:00401337 ff7508               push   dword ptr [ebp+08h]
1000:0040133a e861010000           call   _EndDialog
1000:0040133f 31c0                 xor    eax, eax
1000:00401341 40                   inc    eax
1000:00401342 eb02                 jmp    401346h
;                                                 XREFS First: 1000:00401268 Number : 4
1000:00401344 31c0                 xor    eax, eax
;                                                 XREFS First: 1000:00401284 Number : 5
1000:00401346 c9                   leave  
1000:00401347 c21000               ret    10h
I have added some comments to the code above but I'm really not going to dwell on it. It's just the standard setting up of a dialog box and the dialog box message processing routine. The code which becomes of more interest is when the register button has been pressed and then we arrive at 4012aa. We see that two strings are passed to a checking routine and the rest of it is about printing well done, or bad luck you failed.

The checking routine is shown in entirety below:


;                                                 XREFS First: 1000:004012d2 Number : 1
;****************************************************************************************
;* This is our checking routine, which we need to crack.
;* enter with string1 and string2 addresses on the stack
;* exit with result in eax (=0 for fail, <>0 for success)
;****************************************************************************************
1000:0040134a 55                   push   ebp
1000:0040134b 89e5                 mov    ebp, esp
1000:0040134d 83ec68               sub    esp, 68h			; reserve space, quite a bit!
1000:00401350 ff7508               push   dword ptr [ebp+08h]		; this is arg1
1000:00401353 e878010000           call   _atof
1000:00401358 dd55e8               fst    double-real ptr [ebp-18h]	; [ebp-18]=atof(arg1)
1000:0040135b 83ec08               sub    esp, 08h
1000:0040135e dd1c24               fstp   double-real ptr [esp]
1000:00401361 e882010000           call   _floor
1000:00401366 dd5df8               fstp   double-real ptr [ebp-08h]	; [ebp-8]=floor(arg1)
1000:00401369 ff750c               push   dword ptr [ebp+0ch]		; this is arg2
1000:0040136c e85f010000           call   _atof	
1000:00401371 dd55d8               fst    double-real ptr [ebp-28h]	; [ebp-28]=atof(arg2)
1000:00401374 83ec08               sub    esp, 08h
1000:00401377 dd1c24               fstp   double-real ptr [esp]
1000:0040137a e869010000           call   _floor
1000:0040137f 83c418               add    esp, 18h
1000:00401382 dd55f0               fst    double-real ptr [ebp-10h]	; [ebp-10]=floor(arg2)
1000:00401385 dc4df8               fmul   double-real ptr [ebp-08h]	; floor(arg1)*floor(arg2)
1000:00401388 d9ee                 fldz   
1000:0040138a ded9                 fcompp 				; does it equal 0 ?
1000:0040138c dfe0                 fstsw  ax
1000:0040138e 9e                   sahf   				; check result
1000:0040138f 7507                 jnz    401398h
1000:00401391 31c0                 xor    eax, eax			; if yes, then we failed
1000:00401393 e996000000           jmp    40142eh
;                                                 XREFS First: 1000:0040138f Number : 1
1000:00401398 dd45f8               fld    double-real ptr [ebp-08h]
1000:0040139b dc5df0               fcomp  double-real ptr [ebp-10h]	; floor(arg1)=floor(arg2) ?
1000:0040139e dfe0                 fstsw  ax
1000:004013a0 9e                   sahf   				; check result
1000:004013a1 7507                 jnz    4013aah
1000:004013a3 31c0                 xor    eax, eax			; if yes, then we failed
1000:004013a5 e984000000           jmp    40142eh
;                                                 XREFS First: 1000:004013a1 Number : 1
1000:004013aa dd45f8               fld    double-real ptr [ebp-08h]
1000:004013ad dd5dc8               fstp   double-real ptr [ebp-38h]	; [ebp-38]=floor(arg1)
1000:004013b0 d9e8                 fld1   
1000:004013b2 dd55c0               fst    double-real ptr [ebp-40h]	; [ebp-40]=1
1000:004013b5 dc5dc8               fcomp  double-real ptr [ebp-38h]	; floor(arg1)<1 ?
1000:004013b8 dfe0                 fstsw  ax
1000:004013ba 9e                   sahf   				; check result
1000:004013bb 772d                 ja     4013eah			; if yes, then failed
1000:004013bd df2d38304000         fild   long-int ptr [403038h]	; 2540be400h(=39062500*256=10^10 decimal)
1000:004013c3 dd55b8               fst    double-real ptr [ebp-48h]	; [ebp-48]=10^10
1000:004013c6 dc5dc8               fcomp  double-real ptr [ebp-38h]	; floor(arg1)>10^10 ?
1000:004013c9 dfe0                 fstsw  ax
1000:004013cb 9e                   sahf   
1000:004013cc 721c                 jc     4013eah			; if yes, then failed
1000:004013ce dd45f0               fld    double-real ptr [ebp-10h]	; floor(arg2)
1000:004013d1 dd5db0               fstp   double-real ptr [ebp-50h]	; [ebp-50]=floor(arg2)
1000:004013d4 dd45c0               fld    double-real ptr [ebp-40h]
1000:004013d7 dc5db0               fcomp  double-real ptr [ebp-50h]	; floor(arg2)<1 ?
1000:004013da dfe0                 fstsw  ax
1000:004013dc 9e                   sahf   
1000:004013dd 770b                 ja     4013eah			; if yes, then failed
1000:004013df dd45b8               fld    double-real ptr [ebp-48h]
1000:004013e2 dc5db0               fcomp  double-real ptr [ebp-50h]	; floor(arg2)>10^10 ?
1000:004013e5 dfe0                 fstsw  ax
1000:004013e7 9e                   sahf   
1000:004013e8 7304                 jnc    4013eeh			; if yes, then failed
;                                                 XREFS First: 1000:004013bb Number : 3
1000:004013ea 31c0                 xor    eax, eax
1000:004013ec eb40                 jmp    40142eh
;                                                 XREFS First: 1000:004013e8 Number : 1
1000:004013ee dd45f8               fld    double-real ptr [ebp-08h]
1000:004013f1 d9fe                 fsin   				; sin(floor(arg1))
1000:004013f3 dd5da8               fstp   double-real ptr [ebp-58h]
1000:004013f6 dd45f0               fld    double-real ptr [ebp-10h]
1000:004013f9 d9fe                 fsin   				; sin(floor(arg2))
1000:004013fb dd5da0               fstp   double-real ptr [ebp-60h]
1000:004013fe dd45a8               fld    double-real ptr [ebp-58h]
1000:00401401 dc4da0               fmul   double-real ptr [ebp-60h]	; sin(floor(arg1))*sin(floor(arg2))
1000:00401404 df2d30304000         fild   long-int ptr [403030h]	; 2386f26fc10000(=3.90625*10^13*256=10^16)
1000:0040140a dec9                 fmulp  st(1), st(0)			; 10^16*sin(floor(arg1))*sin(floor(arg2))
1000:0040140c 83ec08               sub    esp, 08h
1000:0040140f dd1c24               fstp   double-real ptr [esp]
1000:00401412 e8d1000000           call   _floor			; floor(10^16*sin(floor(arg1))*sin(floor(arg2)))
1000:00401417 83c408               add    esp, 08h
1000:0040141a dd5d98               fstp   double-real ptr [ebp-68h]
1000:0040141d d9ee                 fldz   
1000:0040141f dc5d98               fcomp  double-real ptr [ebp-68h]	; compare it to zero
1000:00401422 dfe0                 fstsw  ax				; check fp status word
1000:00401424 9e                   sahf   
1000:00401425 7505                 jnz    40142ch
1000:00401427 31c0                 xor    eax, eax			; we passed!
1000:00401429 40                   inc    eax
1000:0040142a eb02                 jmp    40142eh
;                                                 XREFS First: 1000:00401425 Number : 1
1000:0040142c 31c0                 xor    eax, eax			; we failed!
;                                                 XREFS First: 1000:00401393 Number : 4
1000:0040142e c9                   leave  
1000:0040142f c3                   ret    
;***************************************************************************************
;* Checking routine ended now, phew!
;***************************************************************************************
As you can see I have added a lot of comments to this code. So what is it all about ? Well, first of all the two strings are converted into floating point numbers. These are converted into integer floats with the floor() call. We now have two numbers, call them x and y. First we check that these are within a certain range: 1 < x,y < 10^10, as well as checking that they are non-zero and not equal. Finally we calculate sin(x) and sin(y) and check to see if sin(x)*sin(y)*10^16 < 1. This is it, looks easy doesn't it ?

So having worked out what the checking routine is doing it only remains to find two integers x and y such that sin(x)*sin(y)*10^16<1. And this will solve the crackme. Now this isn't quite as simple as it sounds. What we want is that sin(x) and sin(y) are very close to zero, or in other words that x is close to n*Pi for some integer n. What this amounts to is that x/n is a rational approximation to Pi. Let me give you an example. If you calculate 22/7 or 355/113 then you will find that they are very close to Pi. This means that sin(22) and sin(355) are very close to zero. In fact sin(22)=-.008851309290 and sin(355)=-.00003014435336. We want some much better approximations to Pi than this though.

The description in the paragraph above is enough for me to solve the problem. I will use continued fractions in order to approximate Pi with fractions, to a closer and closer degree, and so obtain a series of numbers with smaller and smaller sines. It basically relies on expressing Pi in the form Pi=a+1/(b+1/(c+1/(d+.... We then chop this series off at a point and recalculate it back again to obtain a fraction which is our approximation to Pi. In fact it's easier than it looks to find the series, simply by subtracting the integer part of your number and then finding the reciprocal, over and over again.

Having said the above I will use Maple to do it for me :)
> restart;
> with(numtheory):
Warning, new definition for order
> for i from 1 to 18 do
> cfrac(Pi,i):
> print(i,cfrac(%),evalf(sin(numer(cfrac(%)))));
> od:

My short Maple session, using the number theory package to calculate continued fractions is above. The results from Maple are below.

                       1, 22/7, -.008851309290

                           333
                        2, ---, -.008821166114
                           106


                          355
                       3, ---, -.00003014435336
                          113


                        103993
                     4, ------, -.00001912933578
                        33102


                        104348
                     5, ------, -.00001101501758
                        33215


                        208341                -5
                     6, ------, .8114318195 10
                        66317


                        312689                -5
                     7, ------, .2900699389 10
                        99532


                        833719                -5
                     8, ------, .2312919416 10
                        265381


                       1146408                 -6
                    9, -------, -.5877799729 10
                       364913


                        4272943                 -6
                    10, -------, -.5495794978 10
                        1360120


                        5419351                 -7
                    11, -------, -.3820047507 10
                        1725033


                       80143857                 -7
                   12, --------, -.1477284682 10
                       25510582


                       165707065                 -8
                   13, ---------, -.8654781435 10
                       52746197


                       245850922                -8
                   14, ---------, .6118065383 10
                       78256779


                       411557987                -8
                   15, ---------, .2536716052 10
                       131002976


                       1068966896                -8
                   16, ----------, .1044633279 10
                       340262731


                       2549491779                -9
                   17, ----------, .4474494938 10
                       811528438


                       6167950454                -9
                   18, ----------, .1497342914 10
                       1963319607

Now, we seek the two numerators which are less than 10^10, and we'll just take the largest. Hence we will use 411557987 and 245850922. So enter these two numbers into the dialog box and hit register!

Conclusions

This was a fairly interesting crackme, I must admit I have been thinking about making a crackme based on solving some mathematical problem for a while now. I have actually always shied away from messing with fpu instructions and so I did learn quite a bit from this. It was definitely worth doing for the experience and I would rate it 6/10 for difficulty, although I think some people would really struggle with this.

{Cronos}