italy 22-1o-2oo2
Tutorial for TabMail v2.4
a travel to the center of the MD5 + keygening
written by ZaiRoN

this is a little program that uses MD5 and other simple operations in his routine protection. I'll use this program to write something about what is and how MD5 works.
Maybe I am not the right person to write something about crypto because my knowledge is limited but I think this might help someone out there;)
many of the information you'll find in this tutorial is taken from:
The MD5 Message-Digest Algoritm [RFC 1321]
You can find it at
TabMail v2.4
Program description:
indeed I really don't know what TabMail is for. The name suggest me that is a mail related program but don't know...
Here is a little description from the help file:
"TabMail is a mail user agent (MUA) for sending, receiving and managing your email messages. It's small and fast, conforms Internet Standards and supports Unicode characters.Some other properties: Automatic attachments compression, Warn when open (or save) dangerous attachments, Simple view of compound messages, Message-thread can span many mailboxes, HTML messages views as plain text, Easy PGP message encryption/decryption, SMTP and POP3 authentication, Support for SSL/TLS connections, Scan for viruses in attachments, Multi-user and multi-account."
- softice or ollydbg for studying the algo
- windasm (it's not really necessary indeed)
- lcc for the keygen
music: The Who - Tommy

Preamble: two words on MD5
MD5 was developed by Ronald L. Rivest and is an hashing algorithm that belongs to the category called 'digest algorithm'.
The algorithm takes as input a message of arbitrary length and produces as output a 128 bit message digest (or fingerprints) of the input. The message digest has mainly three important properties:
- fixed length (128 bits)
- it is computationally impossible to produce two messages m1 and m2 having the same message digest
- it is not possible to find the original message starting from the message digest
Here is how the algorithm works through these 5 steps:
1) Append padding bits
The message to encrypt is been extended with a series of bits. The first bit is set to '1' and the others to '0'. The number of bits that are added in this way plus the number of bits in the message must be congruent to 448 modulo 512.
2) Append length
8 bytes are appended to the bits that have been added in the previous step. These 64 bits contain a number that represent the length of the message without the padding bits.
3) Initialize MD buffer
A 4 word buffer is initialized in this way:
    word 1: 01 23 45 67
    word 2: 89 AB CD EF
    word 3: FE DC BA 98
    word 4: 76 54 32 10
4) Process message in 16-Word block
This is the core of the algorithm! It works using the message obtained from step #1-#2 and the buffer initialized in step #3.
5) Output
This is the last step where the output is built as a 16 byte digest.
The first three steps belong to the initialization phase and can be executed in any order you like. The last two steps, working on the previous steps results, produce the digest.
That's all! Don't worry if you have not understood something because we will see these steps when we will study the protection routine of the program.
The 1 part of the protection routine: MD5
After this little introduction, we can start with the tutorial.
The program is compressed with upx; no problem. Make an 'upx -d tabmail.exe' or dump it and fix the ep. This is not necessary but if you want to take a look at the dead_list you have to unpack the file.
Run the target and try to find how the program can be registered. The only way in order to register the program is to use the 'register' button in the initial form.
Insert a name (ZaiRoN...) and a serial (135790) and see what will happen when you push the OK button. hmmm....a nice error message! Well, we can try to search the error message in the windasm string references.
Found! Take a look at the code that bring us to the error message and see if we can learn something about the protection routine:
:00483220 mov eax, dword ptr [ebp-0C]   <--- eax -> registration name
:00483223 pop edx   <----------------------- edx -> serial you have typed
:00483224 call 0048319C   <----------------- hmmm...
:00483229 test al, al
:0048322B je 00483240   <------------------- if al = 0 jump directly to the error message
:0048324B mov eax, 0048329C   <------------- 48329C -> "Incorrect registation inform..."
:00483250 call 00473104   <----------------- display the messagebox
Seems that we have found a good starting point! Put a bpx on the call at 483224 and begin to step:
:004831A4 call 00403DBC   <-------------- returns the length of the registration name
:004831A9 cmp eax, 00000008   <---------- and compare it with the number 8
:004831AC jle 004831C3   <--------------- the jump bring us to ERROR...
:004831AE mov edx, dword ptr [004CC220]
:004831B4 mov edx, dword ptr [edx]   <--- edx -> "Magdaleine"
:004831B6 mov ecx, esi   <--------------- ecx -> serial you have typed
:004831B8 mov eax, ebx   <--------------- eax -> registration name
:004831BA call 00480710   <-------------- we have to explore the content of this call
:004831BF test al, al
:004831C1 jne 004831C8
:004831C3 xor eax, eax   <--------------- put eax = 0 and we are fucked!
First information: a check on the registration name, in particular on his length. The name must have at least 9 characters.
Enter the call and avoid some trash_instructions. When you will arrive at 48076E, enter in the call:
:0047F32A mov [edi+04], 67452301   <--- Attention!
:0047F331 mov [edi+08], EFCDAB89   <--- Attention!
:0047F338 mov [edi+0C], 98BADCFE   <--- Attention!
:0047F33F mov [edi+10], 10325476   <--- Attention!
Says nothing this constant to you? Right! Those are the constant values used by MD5; we have found them in the step #3: Initialize MD buffer.
We can go on:
:0047F35B mov eax, esi   <---- eax -> message to be encrypted
:0047F35D call 00403DBC   <--- returns the number of characters in the message I wrong or in the message there is something more than the registration name?
The message is: "Magdaleine",00,"ZaiRoN..."
What "Magdaleine" is? The name "Magdaleine" followed by the 0x00 byte is always put before the registration name. Obviously, this is not correlated with MD5 algorithm; it is a feature of the program's protection routine. Maybe this name is referred to the girl (or the wife or the son or the turtle:p) of the coder's routine...
A little note before proceed: starting from now, I will use the word message for referring to the string "Magdaleine",00,"ZaiRoN..."
:0047F36A lea eax, dword ptr [esp+04]   <------- eax -> 0x40 bytes buffer
:0047F36E xor ecx, ecx
:0047F370 mov edx, 00000040
:0047F375 call 00402B58   <--------------------- clears the buffer
:0047F387 lea edx, dword ptr [esp+04]   <------- edx -> buffer
:0047F38B lea eax, dword ptr [esi+ebx-01]   <--- eax -> message
:0047F38F mov ecx, dword ptr [esp]
:0047F392 call 004027B4   <--------------------- the message is put in the beginning of the buffer
:0047F397 mov eax, dword ptr [esp]   <---------- numbers of characters in the message
:0047F39A mov [esp+eax+04], 80   <-------------- puts 0x80 after the message in the buffer
:0047F3C0 mov eax, esi   <---------------------- eax -> message
:0047F3C2 call 00403DBC   <--------------------- returns in eax the numbers of characters in the message
:0047F3C7 shl eax, 03   <----------------------- eax = eax * 8 (= A0h for my message)
:0047F3CA mov dword ptr [esp], eax   <---------- save the value
:0047F3CD lea edx, dword ptr [esp+3C]   <------- edx -> to a particular byte in the buffer
:0047F3D1 mov eax, esp
:0047F3D3 mov ecx, 00000004
:0047F3D8 call 004027B4   <--------------------- writes A0 in the byte pointed by edx
In these few lines of code there are two of the five steps of the algorithm, in particular the number #1 and #2.
For a better explanation of what is happened, here is *my* buffer after the execution of this few lines:
The buffer contains 64 bytes (77F218/77F257) of which many to zero.
The first part of the buffer is the message to be encrypted (77F218/77F22B).
After that, there is a 0x80 byte value. Where it come from? Do you remember the step #1 of the algorithm? This value is an immediate consequence of the step: Append padding bits. Like the algorithm says, the first bit after the message, must be 1 and the others 0. So, the 8 bits of the first byte are:
10000000 = 1 * 2^7 + 0 * 2^6 + ... + 0 * 2^0 = 128 = 0x80
The first step does not say explicitly how many 0_bits we have to add. In order to know this value, we must resolve the congruence who we have seen before...
In this case we are lucky because the message to be encrypted is short and we can find the value without carrying out the mathematical calculation. In fact, the number of bits in the message plus the number of bits in the padding (77F22C/77F24F) must be equal to 448. In this way, the congruence is easy satisfied :)
If the message had been longer you would have had to do some simple calculations to solve the congruence.
We check if it is effectively therefore:
- the number of bits in the message is 160 (20 byte * 8 :)
- the number of bits in the padding is 288 (36 byte * 8)
and so: 160 + 288 = 448
Perfect! We have given a sense to the 2/3 of the bytes in the buffer.
We have to understand only what the last 8 bytes represent. This is very simple because is the result obtained by the step 2 of the algorithm: Append length. The message -we have seen before- is composed by 160 bits; so we have to put A0 (160 in hex) in the last part of the buffer.
The initialization part of the MD5 is done; it's time to create the digest!
:0047F3DD lea edx, dword ptr [esp+04]   <--- edx -> buffer
:0047F3E1 mov eax, edi   <------------------ eax+4 -> buffer that contains the MD5 constant values
:0047F3E3 mov ecx, dword ptr [eax]
:0047F3E5 call [ecx+10]   <----------------- finally: Process Message in 16-Word Blocks!!!
Have you tried to step in the last call? Very funny indeed!
It's a very long instructions series (maybe it is better to say 'instruction's block series') without a jump (conditional and/or not-conditional). If you really want to know how exactly the function works, have a look to the original description (look inside The MD5 Message-Digest Algoritm [RFC 1321]).
This particular structure of the function is more or less a common characteristic to many hash. Useless to say that is impossible to reverse this function because is one-way function.
Before proceeding with the serial protection routine we will see what this function returns. Enter in the call and go directly at the end of it:
:480407 mov dword ptr [ebx+08], eax
:48040A add dword ptr [ebx+04], edi   <--- ebx+4 -> digest
:48040D add dword ptr [ebx+08], ebp
:480410 mov eax, dword ptr [esp]
:480413 add dword ptr [ebx+0C], eax
:480416 mov eax, dword ptr [esp+04]
:48041A add dword ptr [ebx+10], eax
The last operations to store the digest. My message generates this 16 bytes digest:
E7 1F 65 B3   32 1E BC C8   61 08 BF 7A   84 70 4D 94
This is the end of the first part of the tutorial where the MD5 algorithm is used. Now we know (or at least we should know...) how MD5 works.
Obviously in the future you don't have to find all this information to recognize this particular hash. The two things that make you to shout the word MD5 are:
- the initialization constant values
- the jumpless series of instructions
Sometimes this doesn't help you much (i.e. you can find a modified version of MD5) and so, I would like to suggest a little program. It is coded by Ivanopulo, it is called DAMN Hash Calculator and you can find it here:
There are many programs like this on the net (maybe better...don't know) but this is the first I have downloaded and ... the first love is not never forgotten ;)
The rest of the protection routine
It's time to see which damn algorithm is applied to the digest. Indeed it is very simple.
Steps until you will reach this piece of code:
:00480798 mov edx, dword ptr [ebp-0C]   <--- edx -> "E71F65B3-321EBCC8-6108BF7A-84704D94"
:0048079B mov eax, dword ptr [ebp-04]   <--- eax -> serial you have typed
:0048079E call 0047F1F4   <----------------- hmmm...
Interesting! The first time I have found these instructions, I have thought that the value pointed by edx (the digest mapped to string and extended with the 3 '-' separator) were the right serial but...i was wrong.
In this last part of the tutorial I will use the word digest to referring to the digest in this new form.
Enter in the call. There is a cycle in it that is repeated several times; the values I have put in the parenthesis -relative to my digest and the serial I have typed- are obtained from the first iteration:
:0047F21D cmp byte ptr [edi+ebx-01], 2D   <--- compare the ebx character of the digest with the separator '-'
:0047F222 jne 0047F225
:0047F224 inc ebx   <------------------------- increments ebx if and only if '-'is founded
:0047F225 lea eax, dword ptr [ebp-08]
:0047F228 push eax
:0047F229 mov ecx, 00000002
:0047F22E mov edx, ebx
:0047F230 mov eax, edi   <-------------------- eax -> a character of the digest (the first)
:0047F232 call 00403FC0   <------------------- takes two characters from the digest (the first two: E7)
:0047F237 mov eax, dword ptr [ebp-08]   <----- eax -> two characters taken
:0047F23A push eax   <------------------------ push the pointer to those characters
:0047F23B lea eax, dword ptr [ebp-0C]
:0047F23E push eax
:0047F23F mov ecx, 00000002
:0047F244 mov edx, ebx
:0047F246 mov eax, esi   <-------------------- eax -> a character of the serial (the first)
:0047F248 call 00403FC0   <------------------- takes two characters from the serial (the first two: 13)
:0047F24D mov eax, dword ptr [ebp-0C]   <----- eax -> characters from the serial (13)
:0047F250 pop edx   <------------------------- edx -> characters from the digest (E7)
:0047F251 call 0047F1B8   <------------------- check on the couple (13 and E7)
:0047F256 test al, al   <--------------------- check is ok: al = 1...otherwise al = 0
:0047F258 je 0047F26C   <--------------------- the jump bring us to the error message
:0047F25A add ebx, 00000002   <--------------- moves to the next two characters
:0047F25D mov eax, esi   <-------------------- eax -> serial
:0047F25F call 00403DBC   <------------------- returns in eax the length of the serial
:0047F264 cmp ebx, eax   <-------------------- we must continue with the check or have ended???
:0047F266 jl 0047F21D
:0047F268 mov [ebp-01], 01   <---------------- if you arrive here, the serial is valid!!!
This is the final check. The check is made taking two characters from the digest and two from the serial. If all the couple taken satisfy the property hidden in the call in 47F251 then we are registered.
The only thing to do is to reveal what this property is (based on the first iteration):
:0047F1D3 and eax, 000000FF   <--- al = E7 (first two characters from the digest)
:0047F1D8 test al, 01   <--------- al is odd or not !?!
:0047F1DA je 0047F1EC   <--------- not odd: jump directly to compare the two characters couples
:0047F1DC mov edx, ebx   <-------- ebx = 00000013 (first two characters from the serial)
:0047F1DE imul edx, ebx   <------- ebx * ebx
:0047F1E1 imul edx, ebx   <------- (ebx * ebx) * ebx = 0x1ACB
:0047F1E4 and edx, 000000FF   <--- edx and 0xFF = 1ACB and 0xFF = 0xCB
:0047F1EA mov ebx, edx   <-------- ebx = 0xCB
:0047F1EC cmp ebx, eax   <-------- compare 0xCB with 0xE7
:0047F1EE sete al   <------------- they must be the same!!!
The code speaks alone; there are only some simple operations.
This is the end of the protection routine scheme.
The last effort: write the keygen
At this point we have to understand how a valid serial is obtained starting from a name.
If you have followed the tutorial you should already know the answer; in any case, this is how we have to proceed:
1. takes the name and, using the MD5 algorithm, finds the digest (16 bytes)
2. takes one byte from the digest and checks if it's odd or not
2.1 the byte is not odd: the byte is converted in two new characters of the serial (i.e. byte 0x32 became the characters '3' and '2').
2.2 the byte is odd: writes a little brute (using instruction in 47F1DE/47F1E4) that look for the new two characters
3. repeats the point #2 16 times (remember to add the separator '-')
4. game over...
If the point #1 scares you, don't worry! On the net there are many MD5 implementations, you can use one of these. As I have said in the beginning of the tutorial, you can simply use the original code (look inside The MD5 Message-Digest Algoritm [RFC 1321]).
You have to call only some functions...nothing more.
This is my little keygen. It is written in c and compiled with lcc. I have used the original MD5 implementation. 
#include <windows.h>
#include <commctrl.h>
#include "TabMailres.h"
#include "md5.c"   // necessary for use md5
static unsigned char name[30]={0};   // registration name
/* text and caption for the messagebox */
static char caption_msgbox[]="hmmm...";
static char text_msgbox[]="You have to put a name with atleast 9 characters!!!";
/* prototypes */
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
static VOID genSerial(HWND _hWnd, unsigned char _name[]);
/* computes and writes the right serial in the serial box */
static void genSerial(HWND _hWnd, unsigned char _name[])
        MD5_CTX md5context;             // structure for MD5
        unsigned char message[42]={0};  // message
        unsigned char digest[16]={0};   // digest
        unsigned char serial[36]={0};   // right serial
        unsigned int i,k;               // simple variables
        strcpy(message, "Magdaleine");  // the first part of the message
        strcpy(&message[11], _name);    // time to append the name
        /* these are the three calls in order to use MD5 (see md5.c e .h) */
        MD5Update(&md5context, message, strlen(name)+11);
                if(digest[i]%2)                         // it's odd or not !?!
                        for(k=0;k<0xFF;k++)             // the brute begin
                                if((k*k*k & 0xFF) == digest[i])
                                        digest[i]=(char) k;
                if ((i==4) || (i==8) || (i==12))
                        serial[strlen(serial)] = '-';   // add the separator
                /* add characters to the right serial */
                wsprintf((char *)serial,"%s%02x",serial, digest[i]);
        /* write the serial */
        SetDlgItemText(_hWnd, SERIAL, serial);
int APIENTRY WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
        WNDCLASS wc;
        wc.lpfnWndProc = DefDlgProc;
        wc.cbWndExtra = DLGWINDOWEXTRA;
        wc.hInstance = hinst;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
        wc.lpszClassName = "TabMail";
        return DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINDIALOG), NULL, (DLGPROC) DialogFunc);
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
        switch (msg) {
        case WM_COMMAND:
                switch (LOWORD(wParam)) {
                        case ID_CALCOLA:                // 'find right serial' button
                                ZeroMemory(name, 30);   // clear the buffer for the name
                                /* check on the name */
                                if (GetDlgItemText(hwndDlg, NAME, name, 30) < 9)
                                        MessageBox(hwndDlg, text_msgbox, caption_msgbox, MB_OK);
                                        genSerial(hwndDlg, name);   // find the serial!!!
        case WM_CLOSE:
                return TRUE;
        return FALSE;
/* game over... */
Final thoughts
I hope that someone will learn something from this little tutorial because sometimes encryption schemes stop peoples whom want to make a keygen. As you can see the target is very easy. Now that you know what MD5 is, the use of MD5 doesn't increase the program's difficulty.
For comments, suggestions and other things, feel free to mail me.
Greetings and thanks
Greets fly out to Bengaly (the man always online:)), Ivanopulo (for the hash calculator), Kayaker, Woodmann, all friends at the RCE forum, all UIC members and of course YOU!!!
that's all !!!
All the information given are for educational purposes only. I would like to remember that you have to buy software after the trial period.