|
Dirtbike:
A cute protection scheme
|
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.
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:
homepage
links
search_forms
+ORC
students' essays
academy database
reality cracking
how to search
javascript wars
tools
anonymity academy
cocktails
antismut CGI-scripts
mail_Fravia
Is reverse engineering legal?