Adventures in keygens part 1

Aren’t those tunes awesome? midi tracks of the 80’s packaged with a little app and shouts to people you’ve never heard of? I’m talking about keygens. And I want to go over how they’re made.

This is going to be a multi-parter.  This first section is going to go over the very basics.

 

For starters, most copy protection schemes today (and for the last 15 years or so) use some sort of key based authentication. The two most common bypasses for these forms of authentication are either a patch, or a generated key. A patch is when we just jump over the crap don’t like (ie; a code patch that makes consideration always true, false or not operate at all). A keygen is a program that emulates (or as will see, flat out rips off) the copy protection algorithm.

 

What I have below is something I wrote recently as a keygen for a web spider application. The program is kind of stupid(although so was the copy protection scheme in place), but it works. It builds upon the simple concept that copy protection schemes are easy to copy. I’m not allowed to disclose which one since that’s against the rules (and I’m sick of the letters from my host).

 

#include <windows.h>
#include <stdio.h>
#define LoWord(l) ((WORD)(l))
#define HiWord(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF)) // pelles doesnt support hi / lo words
// which just splits and shifts the double word. whatever.
unsigned long GenKey(DWORD);

int main(int argc, char *argv[])
{
char root[MAX_PATH] = “C:\\”;
char volname[MAX_PATH];
DWORD serial, maxname, flags;
char fsName[128];
DWORD fsNameLength;
fsNameLength = sizeof(fsName)-1;
GetVolumeInformation(root, volname, MAX_PATH,&serial, &maxname, &flags, fsName, fsNameLength);
printf(“Name of System: %s\r\n”,fsName);
if(volname[0] == ‘\0’) // lol null
{
printf(“No Volume name present\r\n”);
}
else
{
printf(“Volume Name: %s\r\n”,volname);
}
printf(“Volume Serial Number: %x\r\n”,serial);
printf(“Your Special key: %x\r\n”,GenKey(serial));
return 0;
}

unsigned long GenKey(DWORD serial)
{
DWORD seed1 = 4096;
DWORD seed2 = HiWord(serial);
DWORD retme = seed1 ^ serial * seed2;
return retme;
}

 

The first 2 values displayed are topical, and made only to look nice. The last 2 are the more interesting of values. Our volume serial number assigned by NTFS(windows) when the drive is first formatted. There are 3 values that go into generating the special key value. seed1, seed2, and the volume serial number. Value one is static at 4096, the next value is the High word (that is the first 4 bytes of the double word aka the first encountered word). What is the High word of the volume serial number? Depends on the drive, but for me its 9801H or 36993 in decimal.

We perform an XOR (exclusive OR) on our serial number against our static value 4096. The exclusive-or operation takes two inputs and returns a 1 if either one or the other of the inputs is a 1, but not if both are. That is, if both inputs are 1 or both inputs are 0, it returns 0. So 9801D8E5 in decimal is 2550257893.

xor 2550257893, 4096 equals 2550253797 (9801C8E5 in hex)

Then we multiply that value against the high word of our volume serial number 9801H / 36993 decimal. We then shown the serial as hex via our format string specifier.

 

How did I come up with this in the first place? I checked the disassembly of the protection routine. I’ll go over how to find it in the next part.

 

What about the assembly? What does it look like? According to IDA:

mov     eax, [ebp+VolumeSerialNumber]
push    eax             ; int
push    offset aVolumeSerialNu ; “Volume Serial Number: %x\r\n”
call    sub_401120
add     esp, 8
mov     eax, [ebp+VolumeSerialNumber]
push    eax
call    sub_4010D0
pop     ecx
push    eax             ; int
push    offset aYourSpecialKey ; “Your Special key: %x\r\n”
call    sub_401120
add     esp, 8
xor     eax, eax
pop     edi
mov     esp, ebp
pop     ebp
retn

The ‘protection’ sub routine is made at our  call to    sub_4010D0

arg_0= dword ptr  4
mov     eax, [esp+arg_0]
mov     edx, eax
shr     edx, 10h
movzx   edx, dx
imul    eax, edx
xor     eax, 1000h
retn

There’s our XOR, our multiplication, ignore the right shift, that’s just compiler optimization (more on this again in the next section).

Here is what our program looks like:

Next section – finding the algorithm in a sea of code. (Gold in a sea of piss).

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.