Subject: Cracking (keygenning)
Target: Password keeper v5.6
Author: BlackB
Date: 2000-02-27
Tools used: SoftICE, Borland C++ 5.02
Difficulty (scale 1-5): 3
Requirements: profound knowledge of assembler and c++, general cracking knowledge and experience
Important note: all asm code in this essay comes from w32dasm and not from SoftICE (to avoid confused newbies :P)

Before starting!
This essay is for knowledge purposes only!!
Software developers spend much time in making their programs. They live from the money we give them!
Please buy good software!!
I. Introduction

At last! A new lesson! ;-) I bet that many beginners wonder how they can keygen programs. As there isn't much good and
usable beginner info on the subject I decided to write a clear and simple (well, it's not that simple) tutorial on it.
Now, for all newbies reading this: if you don't have some cracking experience, didn't crack any programs yourself, don't
know assembler or c++, don't know how to work with SoftICE, .... in other words: if you don't have the minimum required
knowledge -> don't start keygenning! Keygenning requires a good/ deep insight into assembler and into a programming
language in which you convert the assembler keygen routine from the original program. People that don't have the minimum
requirements may understand this tutorial but won't be able to make a keygen you've been warned enough!

II. This lesson
I didn't wrote this lesson as a cracking tutorial for Password Keeper, but as a general tutorial on keygenning, i.e. you don't
need the program to get on with it.
III. Keygenning

Now what exactly is keygenning (you might wonder?). Well it's isolating the serial-creation-routine from a program,
understanding it and reproducing it in a programming language you know (such as c++). That way, the user can enter
any name he wants to register the program.

The advantage of a keygen is that the program remains in its original state because you don't have to patch it. Secondly,
there's a big chance that you can update to a newer version of the program without having to search for a new crack, which
would be almost impossible with a patch. The disadvantages are that it's more difficult than patching and that you don't learn
about new protections: you won't have to remove nagscreens, trials, crc checks, double checks, won't have to unpack
the program, etc.... . Some people like keygenning more than patching, others don''s up to you to decide.
How do we start if we want to keygen a program: at first, run the program and find out where you can enter a username
and/or serial. Then fill in your name (if necessary), fill in a bogus (=random) serial, set the usual breakpoints in SoftICE to
catch the username/serial reads (bpx getdlgitemtexta, bpx getwindowtexta). Press "OK" or "Register" or anything like that
and start tracing through the code. There's probably a lot of unimportant code that just reads and places some strings. The
code snippet we've gotta find is the one that makes the calculation. So don't start tracing into all the calls you come across!
If you step over a call, and a messagebox with the message: "Invalid registration", it's obvious that you traced a bit too far.
In that case set a breakpoint on the call that made appear the message or set a breakpoint on the previous call. Then re-run
the program, fill in username/ serial, click OK and SoftICE will break on the call. Then trace into the call and there you
may find the calculation routine.
Please note that there may be two (or even more) calculation routines. As an example: a program needs a serial with following pattern: xxx-xxxxxx-xx. There could be three calculation calls: one for the "xxx"-part, one for the "xxxxxx"-part and on for the "xx" part.

Another good method is searching (in SoftICE) for the serial you entered and setting a memory breakpoint on it. Example: a program copies your serial to some memory location. What do you do? You set the usual breakpoints, blablabla.....then when SoftICE breaks you press F12 (execute until 'ret') so the serial is read. Then when you're sure it's copied in memory,
search for it by using the "s 30:00 lffffffff 'typeyourserialorusernamehere' " SoftICE will search the memory for the string and if found it will appear in the data window (where you can see the memory location). Then set a bpr on that string. Search for the next string (there may be more copies of the serial in memory) by typing "s" in SoftICE. Don't type the "s 30:00 lfffffff ......" again because SoftICE will restart the search and it will find the string you already have found. It's like the Findfirst/ findnext method in assembler and pascal.
Okay if you set all breakpoints, SoftICE will break on all instructions that read and write to the memory locations where the serial and or username is located. That way you can find where the calculation routine is.
Note that it's impossible to try and explain all different possibilities to find a calculation routine and how
that routine will look like.....that's your job and is part of the basic cracking knowledge you should have.

Okay, when you found the calculation routine your assembler knowledge will have to do the rest: trace into the calculation ro utine and try to understand what it does. Make notes! Lots of notes! Make a summary on what it does i.e. what values are moved into what registers. Common used calculation instructions are IMUL, SHL and SHR in combination with MOV, LEA and INC (they function as counters). Also look for registers with the length of your serial in it.
Once you understand how it works, write it down on paper and start writing the routine in your favorite programming language
If this is all some confusing for you, I've written a tutorial on how to keygen Password Keeper v5.6

IV. Password Keeper v5.6

I don't know if it's a good program or not, but in my opinion the safest place to store a password is in your head. If you use it,
I hope the password routine isn't the same as the serial calc routine :)

Okay, let's start. First let me tell you that I won't explain in detail how to find the serial calculation routine, because that is basic cracking knowledge, and is not intended to be explained in this lesson!
Run program, fill in username, company, serial, bpx getdlgitemtexta in SoftICE, click "OK", SoftICE breaks, press CTRL-D twice then press F12 twice to leave the call and to get into the main program.
You should see this:

:00412812 E8A97BFFFF              call 0040A3C0
:00412817 8D4C2440                lea ecx, dword ptr [esp+40]
:0041281B 51                      push ecx
:0041281C E83E6F0000              call 0041975F
:00412821 56                      push esi
:00412822 8BD8                    mov ebx, eax
:00412824 E8273F0000              call 00416750
:00412829 83C438                  add esp, 00000038
:0041282C 3D92A71901              cmp eax, 0119A792
:00412831 7518                    jne 0041284B

* Reference To: KERNEL32.lstrcpyA, Ord:0302h
:00412833 8B1DD0114200            mov ebx, dword ptr [004211D0]

* Possible StringData Ref from Data Obj ->"Gregory Braun"
:00412839 6824594200              push 00425924
:0041283E 56                      push esi
:0041283F FFD3                    call ebx

* Possible StringData Ref from Data Obj ->"Software Design"
:00412841 6814594200              push 00425914
:00412846 57                      push edi
:00412847 FFD3                    call ebx
:00412849 EB07                    jmp 00412852

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
:0041284B 3D3CCE5F0D              cmp eax, 0D5FCE3C
:00412850 750C                    jne 0041285E

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
:00412852 57                      push edi
:00412853 56                      push esi
:00412854 E827530000              call 00417B80
:00412859 83C408                  add esp, 00000008
:0041285C 8BD8                    mov ebx, eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
:0041285E 57                      push edi
:0041285F 56                      push esi
:00412860 E81B530000              call 00417B80 [<- Serial calculation routine]
:00412865 83C408                  add esp, 00000008
:00412868 3BD8                    cmp ebx, eax [<- Compare your serial to real serial]
:0041286A 5F                      pop edi
:0041286B 741D                    je 0041288A [<- Jump if correct serial]

How did I know where the serial calculation was? Well, after it executes the jump after the call, it displays the "Invalid serial" message. So the previous call became very supicious. Clear all breakpoints and set one on the calc serial routine. Re-enter serial, SoftICE breaks, now trace into the call (F8). You should see this:

      * Referenced by a CALL at Addresses:
|:0040F61D   , :004126C2   , :00412854   , :00412860   
:00417B80 8B442404                mov eax, dword ptr [esp+04]
:00417B84 56                      push esi
:00417B85 8B35DCCA4200            mov esi, dword ptr [0042CADC]
:00417B8B 50                      push eax
:00417B8C 81CE78030000            or esi, 00000378
:00417B92 E8B9EBFFFF              call 00416750 [<- Calculations with username]
:00417B97 8B4C2410                mov ecx, dword ptr [esp+10]
:00417B9B 03F0                    add esi, eax
:00417B9D 51                      push ecx
:00417B9E E8ADEBFFFF              call 00416750 [<- Calculations with company]
:00417BA3 83C408                  add esp, 00000008
:00417BA6 03C6                    add eax, esi
:00417BA8 5E                      pop esi
:00417BA9 C3                      ret

Let's trace into the first call:

:00416750 51                      push ecx
:00416751 53                      push ebx
:00416752 8B5C240C                mov ebx, dword ptr [esp+0C]
:00416756 56                      push esi
:00416757 33F6                    xor esi, esi
:00416759 53                      push ebx
:0041675A 8974240C                mov dword ptr [esp+0C], esi

* Reference To: KERNEL32.lstrlenA, Ord:0308h
:0041675E FF15A8104200            Call dword ptr [004210A8]
:00416764 85DB                    test ebx, ebx
:00416766 744F                    je 004167B7
:00416768 85C0                    test eax, eax
:0041676A 744B                    je 004167B7
:0041676C 33D2                    xor edx, edx
:0041676E 85C0                    test eax, eax
:00416770 7E45                    jle 004167B7 [<- Length username < 1?]
:00416772 55                      push ebp
:00416773 57                      push edi

* Possible StringData Ref from Data Obj ->"|b!pz*ls;rn|lf$vi^Axpe)rx5aic&9/2m5lsi4@0dmZw9"
                                        ->"4cmqpfhw" [<- Interesting, a calculation table]
:00416774 BE9C624200              mov esi, 0042629C
:00416779 BF01000000              mov edi, 00000001
:0041677E 2BF3                    sub esi, ebx
:00416780 8BCB                    mov ecx, ebx
:00416782 2BFB                    sub edi, ebx

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


:00416784 0FBE1C0E                movsx ebx, byte ptr [esi+ecx] 1.
:00416788 0FBEAC1064624200        movsx ebp, byte ptr [eax+edx+00426264] 2.
:00416790 0FAFDD                  imul ebx, ebp 3.
:00416793 8D2C0F                  lea ebp, dword ptr [edi+ecx] 4.
:00416796 0FAFDD                  imul ebx, ebp 5.
:00416799 0FBE29                  movsx ebp, byte ptr [ecx] 6.
:0041679C 0FAFDD                  imul ebx, ebp 7.
:0041679F 8B6C2410                mov ebp, dword ptr [esp+10] 8.
:004167A3 03EB                    add ebp, ebx 9.
:004167A5 42                      inc edx 10.
:004167A6 41                      inc ecx
:004167A7 3BD0                    cmp edx, eax 11.
:004167A9 896C2410                mov dword ptr [esp+10], ebp 12.
:004167AD 7CD5                    jl 00416784 13.
:004167AF 8BC5                    mov eax, ebp 14.
:004167B1 5F                      pop edi
:004167B2 5D                      pop ebp
:004167B3 5E                      pop esi
:004167B4 5B                      pop ebx
:004167B5 59                      pop ecx
:004167B6 C3                      ret  

Okay, read on and try to follow, I will explain the calc routine:
1. move first char of table1 into ebx
2. move n-th char of table2 into ebp (n=length of username)
3. multiply ebx with ebp and store result in ebx
4. load how many times the calc routine is executed into ebp
5. multiply ebx with ebp and store in ebx
6. move first char of username into ebp
7. multiply ebx with ebp and store in ebx
8. load previous ebp (stored in esp+10) into ebp (first time this will be zero)
9. add ebx to ebp
10. increase counter edx (used to get the next char of table2)
increase counter ecx (used to get the next char of table1)
11. compare counter value to length of username
12. store the value in ebp at esp+10
13. if the username isn't processed completely, re-exec the calc routine

Both tables can be easily found by looking into the memory when the calc routine is executed and by finding them in a hex editor. They are:

  • table1: |b!pz*ls;rn|lf$vi^Axpe)rx5aic&9/2m5lsi4@0dmZw94cmqpfhw
  • table2: serB&nz|mfM1/5(!sd$Mq.{s]+sFjtKpzSdtzoXqmb^Al@dv:s?x/

To clear up your mind I'll give an example:

1. ebx=|
2. ebp=n
3. ebx=decimal value of '|' * decimal value of 'n'
4. ebp=1
5. ebx=ebx*ebp
6. ebp=B
7. ebx=ebx*ebp
8. ebp=previousebp (in the first loop=0)
9. ebp=ebp+ebx
10. edx=1
11. eax=6 > edx=1
12. previousebp=ebp
13. goto 1 because eax > edx

If whole the username is processed, the total result located in ebp is stored for later usage in eax. Our first call is finished. Then eax is added to esi. That esi is a constant. It will contain the following hex value: "ABCDFF8". The eax isn't needed anymore now
The second call is identically the same, it only processes the company. Again, the result will be in ebp, and will be stored in eax again. After the second call is executed, esi (containing result of the first call + constant) is added to eax (which is the result of the second call).
The remaining decimal value in eax is the real serial. You can view it by typing "? eax" in SoftICE. The serial you entered is stored in ebx and can be viewed with "? ebx".

Now, the last thing to do is to convert all this information to a c++ program, which can be quite hard. I did it too and here is my result (maybe you can do a lot better?)


readln(char *s)
 int i=0;
 char ch;
 cin >> resetiosflags(ios::skipws);
 while (cin >> ch, ch != '\n') s[i++] = ch;

void main(void)
 const char table1[]="#serB&nz|mfM1/5(!sd$Mq.{s]+sFjtKpzSdtzoXqmb^Al@dv:s?x/";
 const char table2[]="#|b!pz*ls;rn|lf$vi^Axpe)rx5aic&9/2m5lsi4@0dmZw94cmqpfhw";
 const unsigned long constant = 0xABCDFF8;
 char username[26], company[26];
 long ebx, ebp, previous, serial, esi;
 int count, count2, strlengte1, strlengte2;
 cout << "{--------------------------}" << endl;
 cout << "Ebola Virus Crew presents: " << endl;
 cout << "Password Keeper v5.6 KeyGen" << endl;
 cout << "Author: BlackB" << endl;
 cout << "Date: 27-02-2000" << endl;
 cout << "{--------------------------}" << endl;

 cout << "Enter username: ";
 while (strlen(username)==0 || strlen(username) > 26)
   cout << "Username must have at least 1 char and can not be bigger than 26 chars!" << endl;
   cout << "Enter username: ";
 cout << "Enter company: ";
 while (strlen(company)==0 || strlen(company) > 26)
   cout << "Company must have at least 1 char and can not be bigger than 26 chars!" << endl;
   cout << "Enter company: ";
 for (count=1;count <= strlen(username);count++)
   ebx=table1[count+strlengte1-1] * table2[count];
  for (count=1;count <= strlen(company);count++)
    ebx=table1[count+strlengte2-1] * table2[count];
 cout << "Serial: " << serial << endl;
 cin >> serial;
 cout << endl;
 cout << "Enjoy!";



V. In the end

I hope this lesson could help you keygenning. I worked a lot on it, and I hope you won't be disappointed. If you are, ask yourself if you have the required knowledge. If you think you should be able to handle this, and you think there's something that's really unclear or explained in a bad way or if there's something missing -> don't hesitate to tell me:

Sincere greets,


Essay written by The Blackbird © 1999-2000
This essay can be freely distributed/ published/ printed etc... as long as no modifications are made.