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}