- italy 22-1o-2oo2
- Tutorial for TabMail v2.4
- a travel to the center of the MD5
+ keygening
-
- written by ZaiRoN
- Introduction:
- 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;)
-
- Literature:
- 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 http://www.rfc-editor.org
-
- Target:
- TabMail v2.4
- http://dlg.krakow.pl/tabmail
-
- 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."
-
- Tools:
- - 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
-
- hmmm...am 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: http://www.damn.to/software/hashcalc.html
- 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) */
- MD5Init(&md5context);
- MD5Update(&md5context,
message, strlen(name)+11);
- MD5Final(digest,&md5context);
- for(i=0;i<16;i++)
- {
- 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;
- break;
- }
- 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;
- memset(&wc,0,sizeof(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";
- RegisterClass(&wc);
- 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);
- else
- genSerial(hwndDlg,
name); // find the serial!!!
- }
- break;
- case
WM_CLOSE:
- EndDialog(hwndDlg,0);
- 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 !!!
-
- ciao,
-
- ZaiRoN
- zaironcrk(at)hotmail.com
- Disclaimer
- All the information given are for
educational purposes only. I would like to remember that you have to
buy software after the trial period.
-