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?