Tutorial - Basic Keygenning : Hang2000 1.31

Target : Hang2000 1.31 http://www.mv.com/ipusers/spacetime/
Tools : SoftICE 3.2x, W32Dasm 8.9x

I am bringing here a very easy target (hopefully) to give a good introduction to any new crackers who cannot keygen yet and those who still have blurred visions on the subject of keygenning. This kind of (easy) essay may not be produced anymore in the future (at least not by me), so take this very good chance and learn as much as possible. Protection is a Name/Serial.

Copyright information : This tutorial is copyright © ManKind.

Contact me at mankind001@bigfoot.com

The Process

Allow me to start by explaining a few basics. A keygen is an automated program written by cracker to generate valid registration information for a specific shareware/commercial program (a generic keygen is sometimes possible). How does it generate valid registration information? Well, the cracker first has to have the program he wants to keygen and using some typical crackers' tools especially a debugger (SoftICE) as his weapon, he will TRY to understand how valid registration information is generated in that program (usually the program takes in a name and a serial, and to test whether the serial is valid or not, it itself has to generate a valid serial before it can compare the valid serial with the one entered by user).

If you succeed in understanding how valid registration information is generated, you can re-code the routine in any programming language that suits you. If you aren't successful, you still have the option to brutally crack (patch) the program or you might try ripping the assembly code from the dead-listing and try to re-compile in an assembler without actually understanding how the valid registration information is being generated, you could also sniff out a serial or build an internal keygen in the program itself.

Though there exist quite some ways to keygen, I still consider by understanding the valid registration information generation routine and re-code it is the most original, unique and best way. It is also worthwhile to note that in the WAREZ or cracking scene, a keygen is considered the best crack compared to patching and sniffing a serial. One last thing I want you to know is that there are actually a variety of different keygens available, some of them which are key filemakers (that's a variation of keygen!), single serial keygen and hmm, say generic keygen for several programs...

Now, let's get specific. I will show you from the beginning till the end how we can keygen this (easy) target of ours. Run Hang2000 and go to the registration screen (File -> Register). As I have told you, it will take in a name and a serial. Just in case you do not know yet, whenever we try to register a program, we have to enter some registration information even though we know that the information is not valid. The FAKE registration information is used to pass the very first check of the program (a basic check to prevent users from easily registering the program).

The check is usually done by checking the length of user's name and/or serial. Only name and/or serial with certain length will pass the check. If we do not pass the check, it is likely that we will immediately be confronted by message like "Sorry, your registration information is not valid" which I like to call the bad_boy message, and when so, the valid registration information will not be generated at all to be compared with our fake registration information and to conclude, if that happens we will not be able to understand how the valid registration information is generated.

Until we dive into the code, we will not know the check looks like, but one thing we are sure of is that we must not have empty (0 in length) name and seria l(I am sure out of my experience :D ). So, in order to not have empty name and serial, we fill in the following temporary (not fake, as we are gonna validate a registration based on the following name) registration information in the appropriate text fields (you can of course change the following information but my explanation in this essay will be based on the MY own registration information) :-

Name : ManKind
Serial# : 23199981

(NOTE : selection of 23199981 as serial is not adhered to any rules, it is just my lucky and usual fake serial from the very beginning of my cracking career :D).

Set a breakpoint on GetDlgItemTextA in SoftICE which is a very common API function to get text from text fields (another usual one is GetWindowTextA) with the following command :-

bpx GetDlgItemTextA

With this breakpoint, we will be able to break into our target's code when it tries to get our name and serial (that is after we press the Register button) and that will land us quite near to where valid registration information, in this case, the valid serial will be generated and later compared with our temporary serial (actually you don't need to break in here, you can break in from the program's entry point with Symbol Loader but that will keep you tracing with F10 (step over) for a long long time and still that does not promise you an exact place where the real serial is generated, so in fact, breaking into the program when you have entered your temporary registration information is the best and most recommended :D ).

After setting the breakpoint, come out of SoftICE and press the Register button. You will be thrown into SoftICE and you are now in the middle of the GetDlgItemTextA API function and this is not what we are interested in, so, press F11 once to return to the caller of the API function that will land inside the process of Hang2000 (disable your breakpoint on GetDlgItemTextA) :-

* Reference To: USER32.GetDlgItemTextA, Ord:0104h

:00406A02 mov esi, dword ptr [00411224] <-- esi contains the GetDlgItemTextA API function
:00406A08 push ebx
:00406A09 mov ebx, dword ptr [esp+00000210]
:00406A10 lea ecx, dword ptr [esp+0000008C]
:00406A17 push 28
:00406A19 push ecx

* Dialog: DialogID_008A, CONTROL_ID:03EC, "&6" <-- text field for Name

:00406A1A push 3EC
:00406A1F push ebx
:00406A20 call esi <-- call GetDlgItemTextA API to get name
:00406A22 lea edx, dword ptr [esp+0C]
:00406A26 push 1E
:00406A28 push edx

* Dialog: DialogID_008A, CONTROL_ID:03ED, "&8" <-- text field for Serial #

:00406A29 push 3ED
:00406A2E push ebx
:00406A2F call esi <-- text field for Serial# to get serial
:00406A31 lea eax, dword ptr [esp+0C]
:00406A35 lea ecx, dword ptr [esp+8C]
:00406A3C push eax
:00406A3D push ecx
:00406A3E call 00406CD0 <-- ***
:00406A43 add esp, 8
:00406A46 cmp ax, 0001 <-- compare ax with one
:00406A4A jne 00406AF7 <-- jump to bad_boy if ax not equal to 1

There is not much to be said about the above code. The important code is the call at address 00406A3E which I have marked with three asterisks (*). After returning from the call, a compare will be done with the ax register to see if we have entered valid registration information. How do I know that the call marked with 3 asterisks is important and how do I know that the cmp and jne instructions at address 00406A46 and 00406A4A are the codes to verify whether we have entered valid registration information? Well, all the calls before the one marked with 3 asterisks do not look suspicious as they are only there to get our name and serial while immediately after returning from the call marked with 3 asterisks, verification is done to see if we have entered valid registration information (try to enter a fake name/serial and follow the jump to address 00406AF7 and you will see how I know that the cmp and jne instructions are the verification codes) and that makes the call very suspicious. I am very sure that the whole key generation routine (routine to generate valid registration information) is in that call, so let's trace into it by pressing F8 (trace into) in SoftICE when the white line of indicator is on address 00406A3E :-

* Referenced by a CALL at Addresses:
|:00401687   , :00406A3E

:00406CD0 mov ecx, dword ptr [esp+04]
:00406CD4 sub esp, 80
:00406CDA lea eax, dword ptr [esp]
:00406CDE push eax
:00406CDF push ecx
:00406CE0 call 00406BB0 <-- ****
:00406CE5 add esp, 8
:00406CE8 test ax, ax
:00406CEB jne 00406CF4 <-- this will always jump unless there is no name entered
:00406CED add esp, 80
:00406CF3 ret

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

:00406CF4 push ebx
:00406CF5 push esi
:00406CF6 mov esi, dword ptr [esp+90] <-- move fake/temporary serial to esi
:00406CFD lea eax, dword ptr [esp+08] <-- move the valid serial to eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

:00406D01 mov dl, byte ptr [eax] <-- move current byte of valid serial to dl
:00406D03 mov bl, byte ptr [esi] <-- move current byte of fake serial to bl
:00406D05 mov cl, dl <-- move dl to cl
:00406D07 cmp dl, bl <-- compare dl with bl
:00406D09 jne 00406D3A <-- jump to bad_boy if not equal
:00406D0B test cl, cl <-- see if cl = null
:00406D0D je 00406D25 <-- if so, jump to good_boy because all the bytes have been compared
:00406D0F mov dl, byte ptr [eax+01] <-- move current+1 byte of valid serial to dl
:00406D12 mov bl, byte ptr [esi+01] <-- move current+1 byte of fake serial to bl
:00406D15 mov cl, dl <-- move dl to cl
:00406D17 cmp dl, bl <-- compare dl with bl
:00406D19 jne 00406D3A <-- jump to bad_boy if not equal
:00406D1B add eax, 2 <-- update position of eax as 2 bytes have been compared
:00406D1E add esi, 2 <-- do the same to esi
:00406D21 test cl, cl <-- see if cl = null
:00406D23 jne 00406D01 <-- jump if not because maybe there are still bytes to be compared

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

:00406D25 xor eax, eax <-- start of good_boy
:00406D27 xor ecx, ecx
:00406D29 test eax, eax
:00406D2B sete cl    \
:00406D2E pop esi     > important instructions
:00406D2F mov ax, cx /
:00406D32 pop ebx
:00406D33 add esp, 80
:00406D39 ret

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00406D09(C), :00406D19(C)

:00406D3A sbb eax, eax <-- start of bad_boy
:00406D3C pop esi
:00406D3D sbb eax, FFFFFFFF
:00406D40 xor ecx, ecx
:00406D42 test eax, eax \
:00406D44 sete cl        > important instructions
:00406D47 mov ax, cx    /
:00406D4A pop ebx
:00406D4B add esp, 80
:00406D51 ret

See the call I marked with 4 asterisks (*) at address 00406CE0?. I suspect that the key generation routine is all inside that call because immediately after returning from the call there is no other call and the fake serial is then compared to the valid serial. Where else can the valid serial be generated other than that call? However, before stepping into that call, I will like to ask you to not to pay attention to the part where the fake and valid serial are compared first, it is just a loop through all the chars of the two serials (never mind if you can't understand the codes of the loop, you just don't have to).

Instead, look at address 00406D2B and 00406D42 which I have marked as important instructions. Why important instructions? That is because the (sete cl) and later (mov ax, cx) instructions will determine whether we are registered or not depending on the value of ax register once we return from this call (we are in a call, remember?) back to address 00406A43. By the way, take a fast look at the jne instruction at address 00406CEB, this is the first check I previously mentioned about that we must not have empty name and serial. We want to make a keygen, don't we? Yes, we do and so let's forget about sniffing the valid serial, and step into the call marked with 4 asterisks by pressing F8 :-

* Referenced by a CALL at Address:

:00406BB0 mov edx, dword ptr [esp+04] <-- move name to edx
:00406BB4 sub esp, 40
:00406BB7 or ecx, FFFFFFFF
:00406BBA xor eax, eax
:00406BBC push ebx
:00406BBD push ebp
:00406BBE push esi
:00406BBF push edi
:00406BC0 mov edi, edx
:00406BC2 xor ebp, ebp
:00406BC4 repnz
:00406BC5 scasb
:00406BC6 not ecx <-- contains length_of_name+1
:00406BC8 dec ecx <-- decrement ecx to get length_of_name
:00406BC9 mov ebx, ecx <-- move ecx to ebx
:00406BCB test bx, bx <-- test if name actually exists
:00406BCE jg 00406BFD <-- jump if it does to good_boy

Since we do enter a name, we will always jump to address 00406BFD. Note that this is another check whether we have entered a name or not (confirming the facts that fake registration information is really important for us :D ). Continue tracing with F10 (step over) :-

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

:00406BFD mov edi, edx
:00406BFF or ecx, FFFFFFFF
:00406C02 xor eax, eax
:00406C04 lea esi, dword ptr [esp+10]
:00406C08 repnz
:00406C09 scasb
:00406C0A not ecx
:00406C0C sub edi, ecx
:00406C0E mov eax, esi
:00406C10 mov edx, ecx
:00406C12 mov esi, edi
:00406C14 mov edi, eax
:00406C16 shr ecx, 02
:00406C19 repz
:00406C1A movsd
:00406C1B mov ecx, edx
:00406C1D and ecx, 3
:00406C20 repz
:00406C21 movsb
:00406C22 lea ecx, dword ptr [esp+10]
:00406C26 push ecx
:00406C27 call 00407560
:00406C2C add esp, 4

Lots of code but the above does not do a lot, what it does is uppercase all the characters of our name. How do I know? Do "d 0075F484" command in SoftICE without the quotes just before the call instruction at address 00406C27 and immediately after stepping over the call, the characters of your name will change to uppercase in the data window (and 1 thing I want you to note here, though here is quite an inappropriate place, is that whatever you see in data window with the d command is considered string while whatever you see in the code window with the ? command is considered number). Continue tracing and pay attention now because the following is the start of the keygenneration routine :-

:00406C2F test bx, bx <-- test if name exist
:00406C32 jle 00406C5A <-- this will always not jump when we entered a name
:00406C34 lea ecx, dword ptr [esp+10] <-- move name to ecx
:00406C38 movsx edx, bx

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

:00406C3B mov al, byte ptr [ecx] <-- move current byte of name to al
:00406C3D cmp al, 41 <-- compare it with 65 decimal ("A")
:00406C3F jl 00406C56 <-- jump to 406C56, get next byte of name without calculating anything
:00406C41 cmp al, 5A <-- compare it with 90 decimal ("Z")
:00406C43 jg 00406C56 <-- refer to comment for address 00406C3F
:00406C45 movsx esi, al <-- move al to esi
:00406C48 add ebp, esi <-- add esi to ebp
:00406C4A cmp al, 45 <-- compare it with 69 decimal ("E")
:00406C4C jne 00406C53 <-- jump if al !="69" decimal to 00406C53 to add 3 to ebp
:00406C4E add ebp, 5 <-- add ebp with 5
:00406C51 jmp 00406C56 <-- fixed jump to 00406C56

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

:00406C53 add ebp, 3 <-- add ebp with 3

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00406C3F(C), :00406C43(C), :00406C51(U)

:00406C56 inc ecx <-- go to next byte of name
:00406C57 dec edx <-- decrease the counter by one
:00406C58 jne 00406C3B <-- loop it as long as edx !=0 (edx=0 means name is done)

Basically the above is a loop for as many times as the length of our name (but one thing to note is that calculation will only be done to characters of name that are in the range of A-Z) to calculate a temporary value (ebp). Pay extra attention to the jumps(jmp, jne, jg, jl) and after some time, I guess you will be able to understand the loop. Anyway the following example of a loop in C++ may help to illustrate the loop better :-

for(i=0;i 64 && temp1 < 91) 
        ebp = ebp + temp1 ; 
        if (temp1 !=69) 
            ebp= ebp + 3 ;
            ebp= ebp + 5 ;

When the loop has finished, you will end up here which will add 13131 decimal to our temporary value, convert the temporary value into a string and concatenate it with "LJBEPC-" string (NOTE : the valid serial for my name is LJBEPC-13666). After concatenation, the new string is actually our valid serial which will be compared to our fake serial as soon as you return from this call (we are in a call of a call now! :D ) :-

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

* Possible StringData Ref from Data Obj ->"LJBEPC-" <-- the string

:00406C5A mov edi, 00413478
:00406C5F or ecx, FFFFFFFF
:00406C62 xor eax, eax
:00406C64 mov ebx, dword ptr [esp+58]
:00406C68 repnz
:00406C69 scasb
:00406C6A not ecx
:00406C6C sub edi, ecx
:00406C6E add ebp, 0000334B <-- add 13131 decimal to ebp
:00406C74 mov edx, ecx
:00406C76 mov esi, edi
:00406C78 mov edi, ebx
:00406C7A push ebp
:00406C7B shr ecx, 02
:00406C7E repz
:00406C7F movsd
:00406C80 mov ecx, edx
:00406C82 lea eax, dword ptr [esp+14]
:00406C86 and ecx, 3

* Possible StringData Ref from Data Obj ->"%ld"

:00406C89 push 004133C0
:00406C8E repz
:00406C8F movsb
:00406C90 push eax
:00406C91 call 00409857
:00406C96 lea edi, dword ptr [esp+1C]
:00406C9A or ecx, FFFFFFFF
:00406C9D xor eax, eax
:00406C9F add esp, 0C
:00406CA2 repnz
:00406CA3 scasb
:00406CA4 not ecx
:00406CA6 sub edi, ecx
:00406CA8 mov esi, edi
:00406CAA mov edx, ecx
:00406CAC mov edi, ebx
:00406CAE or ecx, FFFFFFFF
:00406CB1 repnz
:00406CB2 scasb
:00406CB3 mov ecx, edx
:00406CB5 dec edi
:00406CB6 shr ecx, 02
:00406CB9 repz
:00406CBA movsd
:00406CBB mov ecx, edx
:00406CBD mov ax, 0001
:00406CC1 and ecx, 3
:00406CC4 repz
:00406CC5 movsb
:00406CC6 pop edi
:00406CC7 pop esi
:00406CC8 pop ebp
:00406CC9 pop ebx
:00406CCA add esp, 40
:00406CCD ret

After pressing F10 when the white line of indicator of SoftICE is on address 00406CCD (the ret instruction), you will be returned to (this is what happens when you come to a ret instruction when you are in a call) address 00406CE5. Then, from there, verification of the validity of serials will be done and depending on the results a value (1 = serials equal and registered, 0 = serials not equal and not registered) will be placed on ax. Since in there we are still in a call (from address 00406A3E), we will soon return to address 00406A43 where immediately a final check to see if we are registered or not (depending on the value of ax) will be done.

Guess what? We have successfully gone through this program's key generation routine (I aplogize for my bad explanation and writing skills if you still do not understand everything yet, my only advice is re-read please. Not much effort is needed to actually build a keygen for this program as the key generation routine is really short and simple and with the core of the keygen (the heart of the keygen) half completed (remember the C++ example loop to calculate the temporary value? that is important part in our keygen for this program), our work is just like a walk in the park. Below is my source of the keygen which I compiled using Microsoft Visual C++ 6.0 (other compilers should also work).

#include <conio.h> // getche()
#include <string.h> // strlen, strupr, etc.
#include <stdio.h> // printf, gets, etc.
#include <iostream.h> // cin, cout, etc.

void main() {
    char name[255] ; // variable for name
    int temp1=0, i, name2, ebp=0 ; // some variables
    printf("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n") ;
    printf("+ C++ KeyGen for Hang2000 v1.31 +\n") ; // intro
    printf("+     by ManKind on 5 Dec 2K    +\n") ; // intro
    printf("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n") ;
    printf("Name: ") ; // wait for input
    gets(name) ; // store user's name in variable name
    name2 = strlen(name) ; // get the length of name
    strupr(name) ; // uppercase all the chars of name

    for(i=0;i<name2;i++) { // this is the core loop
        temp1 = name[i] ; // get current byte of name
        if (temp1 > 64 && temp1 < 91) {
            ebp = ebp + temp1 ; // add current byte to ebp if it's in the A-Z range
            if (temp1 != 69)
	       ebp = ebp + 3 ; // if not equal to 69, add 3 to ebp
	       ebp = ebp + 5 ; // if equal to 69, add 5 to ebp
 // boy, the programmer must have some special likings for 'E'

    ebp = ebp + 13131 ; // add 13131 decimal to ebp
    printf("Serial#: LJBEPC-") ; // output the valid registration serial!
    cout<<ebp<<endl ;
    getche() ; // wait for user to press Enter before exit

As you probably can see, I am not a good coder and that means the above source is not optimized and some things have to be added to make it a complete keygen (like check if user entered name or not, besides I know the keygen has some problem with name that has space in between the characters). Though I can improve it, I have decided not to do so as it is only meant as an example and I don't expect the source to be distributed (for bad purpose, that is to register the program, eww!) without this essay.

One last thing I want to tell you is that the registration information is kept in a file named wh2k.dat in your windows directory. Finally we have come to the end of this essay. Hope to see you soon on my next tutorial. As usual, contact me if I make any mistakes, give me your feedback, comments, suggestions and opinions about this tutorial and my way of presenting it.

Extra Notes

Recently, I have read a few comments by tutorials' readers concerning the quality of cracking tutorials available. Quite a few of them said that there are some tutorials that are useless as they teach nothing and are just a scratch work by the author and I am greatly disturbed by those statements. I am not sure if my essays fall into that kind of category of useless essays but the truth is that I have tried my best to make every of my essays (at least those of my latest ones) resourceful and not just any essays that teach nothing. So, I will like to call out to all my essays' readers (if there are any, hehe :D ) to send me suggestions, comments, opinions and feedback so that I know whether my essay and writing style is acceptable to you (that means, you can understand what I try to present, learn something and not bored by my texts) or do they need some change. I am open to positive remarks, I will like to receive critics and am willing to change and improve my essays and writing style.

Well, whether or not we crack this program is not important at all here. I just want you to understand the approaches I have taken and the reasons for doing so and to learn from the methods I have shown you. However you must be clever enough to take other approaches and methods (based on your own logic) when dealing with other targets. Ability to do so will make you a better cracker as tutorials I believe are not used to teach you how to crack only a specific target, they show you the methods, approaches available while cracking and expect you to apply them on other targets with your own intelligence.


Thanks and greets to:
+ORC, +HCU, Sandman, HarvestR, tKC, ytc_, Punisher, Kwai_Lo, TORN@DO, CrackZ, cLUSTER, LaZaRuS, mISTER fANATIC, yes123, WhizKiD, Volatility, ACiD BuRN, Eternal Bliss, R!SC, Kwazy Webbit, +Mammon, Shadow, Falcon, ^tCM^, WaJ, egis, Borna Janes, CyberBlade, Sheep140/Reclaim, josephCo, Kathras, +tsehp, Predator, tscube, AB4DS(Death), douby, Steinowitz, Lord Soth, seifer666, Latigo, Dawai, Lucifer48, NoodleSpa, Mercution, NeuRaL_NoiSE, Fravia+, [dshadow], yAtEs, Duelist, Alpine, hutch, flag eRRatum, Nitrus, LiQUiD8, +Frog's Print, Muad`Dib, Acid_Cool_178, Iczelion, Razzia, wOODY^dRN, Warezpup, Bomber Monkey, XMen and other crackers, individuals and organisations who have helped me, either directly or indirectly.

Service for Mankind