Dirtbike:
A cute protection scheme
student
Not Assigned
15 July 1998
by Prophecy
Courtesy of Fravia's page of reverse engineering
slightly edited
by fravia+
fra_00xx
98xxxx
handle
1100
NA
PC
Well, I hope Prophecy will send a more 'generic' essay next time: I believe that this kind of reversing approach may be very useful if the cracker demonstrates a little more its 'generical' (i.e. not target-specific) validity. Anyway this is a nice essay and the keygen in C at the bottom can be easily re-used for your own probes. Enjoy!
There is a crack, a crack in everything That's how the light gets in
Rating
( )Beginner (x)Intermediate ( )Advanced ( )Expert

This essay illustrates the concept of using a magic buffer to validate a code and to generate your name.
Dirtbike:
A cute protection scheme
Written by Prophecy


  

INTRODUCTION:  

  

Howdy guys, it's me again.  If you're still a newbie i suggest you read my  

beginner tut and the other beginner essays on this site before attempting  

this tut.  

  

A while ago this dude called theKrow popped into #cracking4newbies and asked  

someone to crack Dirt Bike for him,  stating that all it required was a single  

registration number.  I thought, heh, this'll be a quick crack, probably only  

has one valid serial hard coded into the .exe.  Anyway, being the nice guy I  

am, I downloaded Dirt Bike and set to work.  

  

As it turned out the protection scheme was actually a lot more complicated than  

it should've been for a US $15 shareware, and  I thought I'd share it with ya  

because it's a cute little scheme which I haven't seen or read about before.  

  

I'll step ya thru the code, explaining the steps and at the end show you how to  

write the keygen for it using C.  

  

A note to good beginners/intermediate crackers:  I suggest you download the  

target and have a go at cracking it yerself before reading this tut as it is a  

good academic exercise :)  

  

TARGET:  

  

Well the target is called Dirt Bike (v4.4).  It's quite an addictive/cool little  

motocross side-on-view simulator which you can download here:  

  

http://members.aol.com/bradquick/homepage.html  

  

TOOLS:  

  

The only tool you'll need is the kick arse debugger SoftIce by NuMega.  

  

Optionally, if you want to compile the C code for the keygen you'll also need  

a C compiler surprisingly enough.  

  

Also SoftDump written by the legendary +Quine is handy too!  

  

LET'S GET STARTED:  

  

First have a quick read of the docs... it might give you some handy tips  

regarding the protection and what to expect.  In this case it doesn't.  

  

Now, let's load up the target and have a look round there... hrmm the first  

thing that pops up is a box asking for a registration number... click on  

'Not Yet'.  You'll see the Dirt Bike splash screen and in the bottom right  

corner it says 'This copy is registered to Unregistered'.  Well we can deduce  

from this that the single code we enter must somehow contain our name.  Not  

the quick crack I had originally anticipated :)  

  

Part 0:  Breaking into the target  

  

Ok, enough pissing round, let's reverse this mudda.  You'll find bpx hmemcpy  

works here.  Ok bang we're in SoftIce, hit <F11> once and then search for the  

bogus code you entered.  I found mine at a1fbe8, set a bpr on it and hit  

<F5>... you'll end up here:  

  

(Note:  lines of code in bold are important and a '.' means unimportant code  

        has been removed)  

  

00417395  INC     ECX  

00417396  MOV     EAX,EDX  

00417398  INC     EDX  

00417399  CMP     BYTE PTR [EAX],00 <- softice breaks here  

0041739C  JNZ     00417395  

0041739E  MOV     EAX,ECX  

004173A0  RET  

  

Well, this is just a simple routine to get the length of your code.  The length  

is stored in ECX, which is then moved to EAX.  Ok leave that call, and you'll  

pop out at here:  

  

004106B0  CALL    0041738C <trace down until you hit the JB>  

004106B5  ADD     ESP,04  

.  

.  

004106BD  JB      0041068E  

^ this takes us to the start of Part 1:  

  

  

Part 1: the checksum  

  

0041068E  CMP     DWORD PTR [EBP-59],03  

00410692  JNZ     0041069D  

00410694  MOV     DWORD PTR [EBP-59],00000000 ;set counter=0  

0041069B  JMP     004106AA  

0041069D  MOV     ECX,[EBP-5D]  

^ move counter (initally set at 4) into ECX  

004106A0  MOV     EAX,[EBP+08] ;move address of bogus code into eax  

004106A3  MOVSX   EDX,BYTE PTR [ECX+EAX]  

^ move nth char of bogus code into edx, where n is 5,6,7...  

004106A7  ADD     [EBP-55],EDX  

^ add the char to [EBP-55] which has an initial value of 0x231  

004106AA  INC     DWORD PTR [EBP-5D] ;increment counter  

004106AD  PUSH    DWORD PTR [EBP+08]  

004106B0  CALL    0041738C ;get length of code again  

004106B5  ADD     ESP,04 ;stack correction i do believe :)  

004106B8  MOV     EDX,EAX ;move length into edx  

004106BA  CMP     [EBP-5D],EDX ;reached end of code yet?  

004106BD  JB      0041068E ;no/yes  

  

--------------------------------------------------------------------------------  

So this snippet of code is adding the ascii values of the 5th, 6th etc... chars  

of your registration number to the initial value of 0x231... i'm calling this  

a checksum.  

--------------------------------------------------------------------------------  

  

004106BF  JMP     004106C8  

004106C1  SUB     DWORD PTR [EBP-55],00000141  

004106C8  CMP     DWORD PTR [EBP-55],0000270F  

004106CF  JG      004106C1  

^Is checksum > 0x270f?  If so, subtract 0x141 until it ain't.  

004106D1  JMP     004106DA  

004106D3  ADD     DWORD PTR [EBP-55],00001984  

004106DA  CMP     DWORD PTR [EBP-55],000003E8  

004106E1  JL      004106D3  

^Is checksum < 0x3e8?  If so, add 0x1984 until it ain't.  

  

  

Part 2: backtracking  

  

Now a bit later on this prog uses a 'magic number' to check if your code is  

valid.  This magic number is displayed in memory.  I used a technique which I  

call backtracking to find the place the magic number was generated. Backtracking  

is simple, yet powerful:  you know that after you've stepped over a call, the  

magic number has been generated as you can see it in the data window.  So you  

know you had to step into that call.  So <F5> out of SoftIce and start again,  

this time making sure you actually step into that call.  You repeat this  

procedure until you reach the code generating routine.  

  

004106E3  LEA     EAX,[EBP-4D]  

004106E6  PUSH    EAX  

^this is actually the location the magic number (m) is stored, as you can  

quickly determine by typing d eax and stepping over the next call.  

004106E7  PUSH    DWORD PTR [EBP-55]  

004106EA  CALL    004050D2 ;step into here to goto magic number routine  

  

If you did the above correctly, you should be somewhere around BFF796A6.   

Anyway, although we don't know it yet, this snippet actually generates the  

aforementioned magic number in reverse (i'll call it k)...:  

  

  

Part 3: manipulating the checksum  

  

BFF796A6  MOV     EDI,[ESP+18] ;move our checksum into EDI  

BFF796AA  MOV     EBX,[ESP+20] ;move a constant 0xa into EBX  

BFF796AE  MOV     EBP,[ESP+14] ;move address of k into EBP  

BFF796B2  MOV     EAX,EDI ;move checksum into EAX  

BFF796B4  SUB     EDX,EDX ;set edx=0  

BFF796B6  DIV     EBX  

^aha, the crucial step.  This divides the contents of EAX by the constant 0xa,  

storing the quotient in EAX and the modulus (remainder) in EDX.  

BFF796B8  MOV     ECX,EDX ;move modulus into ECX  

BFF796BA  MOV     EAX,EDI ;move checksum back into EAX  

BFF796BC  SUB     EDX,EDX ;set ecx=0  

BFF796BE  ADD     ECX,30  

^adds 0x30 to the modulus (interesting eh?)  

BFF796C1  DIV     EBX ;same as above  

BFF796C3  MOV     EDI,EAX ;store new quotient in EDI  

.  

BFF796CE  INC     ESI ;increase counter  

BFF796CF  MOV     [EBP+00],CL ;stored first number of k into EBP  

BFF796D2  INC     EBP ;ready EBP to store next char of k  

BFF796D3  CMP     [ESP+1C],ESI ;compare counter with 4  

BFF796D7  JL      BFF796DD ;if less, repeat procedure  

.  

  

--------------------------------------------------------------------------------  

So quite a neat little process:  

  

The checksum is divided by 0xa, and the result (a) and modulus (b) are stored in  

the eax and edx registers.  then 0x30 is added to b to give the first digit of  

k.  The process is repeated to obtain the 2nd, 3rd and 4th numbers of k.  

  

Later on, this number is reversed, we'll call it 'm'. So that if we obtained a  

number 2307, we'd now have 7032.  

  

BTW, Natzgul pointed out to me that all that happend was the checksum was  

converted from hex to decimal... .  

  

Okay, hightail outta here, and press <F12> a  

few times till your back in the dirt bike proggie...:  

--------------------------------------------------------------------------------  

  

  

Part 4: verifying the code and introducing the magic buffer  

  

004106EF  ADD     ESP,08 ;correct the ol' stack  

004106F2  MOV     DWORD PTR [EBP-5D],00000000 ;set counter=0  

004106F9  JMP     0041071F  

004106FB  MOV     EAX,[EBP-5D] ;set EAX=counter  

004106FE  MOV     ECX,[EBP-5D] ;set ECX=counter  

00410701  MOVSX   EDX,BYTE PTR [EAX+EBP-4D]  

^[EBP-4D] = m remember?  And EAX=counter, so this line moves the nth char of m  

into EDX, where n=1,2,3 and 4. (loops around)  

00410706  ADD     EDX,-24 ;subtract 0x24 from ascii value of nth char of m  

00410709  MOV     EAX,[EBP+08] ;move address of bogus code into EAX  

0041070C  MOV     BL,[EDX+EBP-43]  

  

--------------------------------------------------------------------------------  

Hrmm by now you've probably noticed the following hairy beast:  

  

XcgjH3uKTawSL6CrGRUJvFyAfkNBQsMEzPoDxtqZ78einYpWdhmVb4... intrigueing, isn't it?  

  

EBP-43 is the starting address of that big long thing which i'm calling the  

"magic buffer".  So basically, the target is is getting the ascii val of the  

number of k, subracting 0x24 from it (i'll call this number n).  Then it  

takes the (n+1)th char of the magic buffer and compares it to the 1st, 2nd etc  

char of your bogus code... you dig?  

--------------------------------------------------------------------------------  

  

00410710  CMP     BL,[ECX+EAX] ;compares as described above  

00410713  JZ      0041071C ;jump good guy, otherwise continue  

00410715  MOV     DWORD PTR [EBP-51],00000000 ;set bad_guy flag  

0041071C  INC     DWORD PTR [EBP-5D] ;increase counter  

0041071F  CMP     DWORD PTR [EBP-5D],04 ;are we done yet?  

00410723  JL      004106FB ;y/n (if not repeat procedure using next digit of k)  

00410725  CMP     DWORD PTR [EBP-51],00 ;is the bad_guy flag set?  

00410729  JNZ     00410756 ;the infamous jump hehe (jump to good guy if not 0)  

  

--------------------------------------------------------------------------------  

At this point i simply reversed the jump (r fl z) and hit <F5> out of softice  

and sure enuf, it said registered but displayed garbage in the 'registered  

to' section.  Naturally my next inclination was to get da prog to display  

Prophecy [tNO] or something like that instead so....:  

--------------------------------------------------------------------------------  

  

  

Part 5:  How the prog works out who it's registered to  

  

This is another kinda cool part of this protection- it uses the magic buffer to  

determine what's gonna get displayed in the 'registered to' section. So keep  

tracing down the code until you reach:  

  

00410768  MOV     ECX,[EBP-5D] ;[ebp-5d] is a counter, initially set at 4  

0041076B  MOV     EAX,[EBP+08] ;put address of code into eax  

0041076E  MOV     DL,[ECX+EAX] ;put (n+1)th char of code into DL, where n=value  

.                                                                 of [ebp-5d]  

00410787  MOV     EAX,[EBP-59] ;set eax=counter2 (initially 0)  

0041078A  MOV     ECX,[EBP-5D] ;set ecx=counter  

0041078D  MOV     BL,[EAX+EBP-43] ;take (counter2)th char of magic buffer  

00410791  MOV     EAX,[EBP+08] ;move address of code into EAX  

00410794  CMP     BL,[ECX+EAX] ;cmp (counter)th char of our code with bl  

00410797  JNZ     00410809  

^ if chars not the same, repeat procedure until end of buffer is reached  

00410799  CMP     DWORD PTR [EBP-59],1A  

^ compares count with 0x1a (which is 26 in decimal!)  

0041079D  JGE     004107B4 ;jump if >= 0x1a  

0041079F  MOV     DL,[EBP-59]  

004107A2  ADD     DL,41  

  

--------------------------------------------------------------------------------  

adds 0x41 to the count.  The count is the number of iterations required to  

find the (counter)th char of our code in the magic buffer.  As the length of  

the buffer is 0x36, the values of counter range from 0x0 to 0x35.  So, the  

minimum amount of counts required is 0, this means DL would be 0x41, which  

corresponds to the ascii value 'A'... interesting.  

--------------------------------------------------------------------------------  

.  

004107B4  CMP     DWORD PTR [EBP-59],34 ;cmp count with 0x34  

004107B8  JGE     004107D2 ;jump if >= 0x34  

.  

004107C0  ADD     DL,47  

^so if the minimum count possible to reach this section is 0x1a, which procures  

an ascii value of 'a' when 0x47 is added to it.  

.  

004107D2  CMP     DWORD PTR [EBP-59],34  

.  

004107E1  MOV     BYTE PTR [EDX+EAX+00000380],2E  

^if count=0x34, then name of our char gets mapped to a '.' (ascii val of 0x2e)  

.  

004107EB  CMP     DWORD PTR [EBP-59],35  

.  

004107FA  MOV     BYTE PTR [EDX+EAX+00000380],20  

^if count=0x34, then name of our char gets mapped to a ' ' (ascii val of 0x20)  

  

the rest of the code basically goes back and compares the char of code with  

next char of magic buffer, then goes onto the next letter of code when it has  

decided whether or not the code was found in the magic buffer.  

  

also, if the char is not found in the magic buffer, then the prog will just  

use that char (you can confirm this by entering a code whose char ain't in the  

magic buffer... you'll see it displayed in the 'registered to' part)  

  

--------------------------------------------------------------------------------  

So, if you studied the above code carefully, you would've figured out by  

now how the prog works out what chars it's gonna stick in the 'Registered to'  

section...  

  

If you want: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.  

             ||||||||||||||||||||||||||||||||||||||||||||||||||||||  

then enter : XcgjH3uKTawSL6CrGRUJvFyAfkNBQsMEzPoDxtqZ78einYpWdhmVb4  

  

If the letter ain't found in the magic buffer, then don't modify it in any  

way... so that's the protection unveiled! Simple eh?  

--------------------------------------------------------------------------------  

  

  

Part 6: Example - calculating a valid code for 'bob'  

  

Well, by now you should know that the code is of the form 'xxxxyyy', where  

'xxxx' are the 4 digits which are compared with modified checksum. So, follow  

these simple steps:  

  

(1) from table above, we know we have to enter xxxxB7B.  We also know that  

'xxxx' depends on B7B, so let's work that out now.  

  

(2) ascii values of B,7 and B are added to initial val of 0x213:  

   0x42+0x37+0x42+0x213=0x2ce (call this sum i)  

  

(3) is i > 0x270f? if so, subtract 0x141 until it isn't  

  

(4) then, is i < 0x3e8?  if so, add 0x1984 until it isn't  

   hence, j=i+0x1984=0x2ce+0x1984=0x1c52  

  

(5) j is now divided by 0xa, and the result (a) and modulus (b) are  

stored in the eax and edx registers.  then 0x30 is added to b to give the  

first digit of the new code, let's call it k.  The process is then repeated  

again 3 times so that you are left with a 4 digit code k:  

  

0x1c52/0xa=0x2d5 modulus 0x0, hence char 1 = 0x30 (0)  

 0x2d5/0xa=0x48  modulus 0x5, hence char 2 = 0x35 (5)  

  0x48/0xa=0x7   modulus 0x2, hence char 3 = 0x32 (2)  

   0x7/0xa=0x0   modulus 0x7, hence char 4 = 0x37 (7)  

  

This gives a value of 0527 for k.  

  

(6) This number is reversed, to give our magic number m: 7250  

  

(7) Now, it takes the ascii value of the first char (0x37) and subtracts 0x24:  

   0x37-0x24=0x13 (call this n)  

  

(8) It now checks the first char of our code with the (n+1)th char of the magic  

buffer:  

  

XcgjH3uKTawSL6CrGRUJvFyAfkNBQsMEzPoDxtqZ78einYpWdhmVb4 (magic buffer)  

  

Hence:  char1: 0x37-0x24=0x13, 0x14th char of buffer: J  

        char2: 0x32-0x24=0xe , 0xfth  char of buffer: C  

        char3: 0x35-0x24=0x11, 0x12th char of buffer: R  

        char4: 0x30-0x24=0xc , 0xdth  char of buffer: L  

  

As the J,C,R and L are compared to the first 4 chars of our registration  

number, this means that these are the valid values for the 'xxxx' component  

of our code...  

  

(9) So, the full code for 'bob' is:  JCRLB7B ... you dig?  Hope so :)  

  

  

Part 7: Keygen  

  

I've written a keygen for this target in C, you shouldn't have too much trouble  

following it:  

  

---------------------------------START OF KEYGEN--------------------------------  

/* C sorce file, compiled with borland c++ 5.02  

 *  

 * this source has been included with this keygen for educational purposes, so  

 * other crackers wanting to learn can do so by examining this source file.  

 *  

 * it is quite conceivable that a later version of this software gets released  

 * which uses the same protection scheme.  in such cases my keygen be packaged  

 * with the software as long as the source is included in the keygen archive.  

 *  

 * however, modifying this source to make it look like it was written by  

 * someone else from some other group is simply lame.  

 *  

 * if the software HAS changed it's protection scheme, include the source  

 * with your keygen to prove it.  

 */  

#include <stdio.h>  

#include <string.h>  

#include <ctype.h>  

#include <conio.h>  

  

int main(){  

  

unsigned char name[500]={0}, temp[5]={0};  

unsigned char m[5]={0}, first4chars[5]={0}, lastchars[500]={0};  

unsigned char magicbuffer[0x37]="XcgjH3uKTawSL6CrGRUJvFyAfkNBQsMEzPoDxtqZ78einYpWdhmVb4";  

unsigned char      buffer[0x37]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz. ";  

unsigned int i=0,j,k=0,n,a,b;  

int y=0,z=0,length=0;  

  

tryagain: /* go back here if user stuffs up */  

length=0; /* reset length of name */  

clrscr();  

  

printf("ŽŸŸŸŸŸŸŸ ƒ ŸŸŸ Π ŸŸŸŸŸŸŸ ƒ ŸŸŸŸŸŸŸŸŸŸŸŸψ\n");  

printf("œώ€€Ύώ€€€Ύώ€€Ύώ€€ΎΠ   ΠΎ€Ύ ώΎ€€Ύώ€€€Ύώ „\n");  

printf("„  ώƒ €€€Ύ €ƒ  €€€ώΎΠ €€€Ύ  €€€Ύ €€€Ύ  „\n");  

printf("„    ƒ€€€Ύ    ƒ€€€  €€€€€Ύ ƒ€€€Ύ €€€Ύ  „\n");  

printf("„   ΠΎ€€€ΎΠ  ΠΎ€€€ΎΠ ώΎ€€ΎΠΎ€€€ΎΠΎ€€ΎΠ „\n");  

printf("ΏŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸ ώώΎ ŸŸŸŸŸŸŸŸŸŸŸŸ\n");  

printf("\nKey Generator for Dirtbike v4.4");  

printf("\nWritten by Prophecy [tNO]\n\n");  

  

printf("Please enter your name:  ");  

gets(name);  

  

/* work out length */  

  

while (name[length] != '\0'){  

	length++;  

}  

  

if(length==0){  

	printf("\n\n *** You didn't enter a name!  Try again... ***");  

	getch();  

	goto tryagain;  

}  

  

if(length>50){  

	printf("\n\n *** Your name is too long.  Try again... ***");  

	getch();  

	goto tryagain;  

}  

  

/* convert the name entered into their corresponding letters from the magic  

   buffer */  

  

for(y=0;y<length;y++){  

	while(((buffer[z]-name[y]) != 0) && (buffer[z] != '\0')){  

		z++;  

	}  

	if(magicbuffer[z]=='\0'){ /* if char not found in buffer */  

		lastchars[y]=name[y]; /* use the value from the name as is */  

	}  

  

	else{  

		lastchars[y]=magicbuffer[z]; /* else use the char from the magic buffer */  

	}  

	z=0;  

}  

  

/* work out 'i' */  

  

for(z=0;z<=length;z++){  

	i+=lastchars[z];  

}  

i+=0x213;  

  

/* go thru steps (3) and (4) */  

  

while(i>0x270f){  

	i-=0x141;  

}  

  

while(i<0x3e8){  

	i+=0x1984;  

}  

j=i; /* using same variables as in comments */  

a=j;  

  

/* work out k */  

for(z=0;z<4;z++){  

	b=a%0xa; /* modulus (remainder) */  

	a/=0xa; /* quotient */  

	k=(k*10) + b;  

}  

  

/* find out m (ie reverse k) */  

  

y=0;  

sprintf(temp, "%04d", k);  

for(z=3;z>=0;z--){  

	m[y]=temp[z];  

	y++;  

}  

  

/* work out the first 4 digits of your valid code */  

  

for(z=0;z<4;z++){  

	n=(m[z]-0x24);  

	first4chars[z]=magicbuffer[n];  

}  

  

printf("\nYour reigistration code is:  %s%s",first4chars,lastchars);  

  

return 0;  

}  

----------------------------------END OF KEYGEN---------------------------------  

  

  

Part 8:  Other miscellaneous points  

  

You may have noticed this prog stores the regcode in a file called register.dbk  

which is exactly 100 bytes long.  Hence as a safeguard in my keygen I  

specified the max length of the name as being 50 chars long... although you  

could probly get away with 85 chars or something (couldn't be bothered finding  

out)...  This is a trivial point but hey, everything counts!  

  

  

Greetz  

  

Greetz fly out to:  

  

Cali, Natzgul, ZEN-crack, eagle (see, I learned... doesn't my tut look  

beautiful? :), to^clean, dasmonkey, Twister, the #cracking4newbies crew,  

the +HCUkers and fravia!  

  

  

Conclusion:  

  

I hoped this tut has helped you in one way or another.  

  

You can send me praise and abuse at: prophecy_@usa.net  

  

Have a good one, and may the (zen) force be with you!  

  

Veni vidi vici.  



Ob Duh
I wont even bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one. Should you want to STEAL this software instead, you don't need to crack its protection scheme at all: you'll find it on most Warez sites, complete and already regged, farewell.

You are deep inside fravia's page of reverse engineering, choose your way out:

redhomepage redlinks redsearch_forms red+ORC redstudents' essays redacademy database
redreality cracking redhow to search redjavascript wars
redtools redanonymity academy redcocktails redantismut CGI-scripts redmail_fravia+
redIs reverse engineering legal?