+HCU papers
mIRC 5.* A KEY GENERATOR EXPLAINED by +Malattia
~ version July 1998 ~
by +Malattia
Courtesy of Fravia's page of reverse engineering
mIRC 5.* KEY GENERATOR
by .+MaLaTTiA.
In this essay we'll study mIRC 5.* keygenerator. I'm talking about version 5.* because
keygens (differently from patches which are related to an offset which always change)
can be often used for different versions of a program: in this case, i made the keygen
for mIRC v5.11 and now it still works with 5.31, and who knows for how many other
versions it'll work... (hey, I've tried it with 5.4 too... and it works!)
This essay is intended for newbies: I'll try to explain my ideas and techniques in the
_easiest_ way I can. You just have to know how to use ONE tool, Wdasm (choose
whichever version you like, I used 8.5 but there's 8.9 outside there too!), and if you
want to build a keygen you just have to know some programming (in whatever language:
I'll just help you to build an algorithm, then you can choose what programming language
you want to use!).
... Ok, let's start.
The first thing you have to do is to find where the serial check is located: I've
found it quite quickly using the dead listing approach (in most of the password
protected programs this is the quickest way... Wdasm-SoftIce 1-0):
:00437DE7 68CC074C00 push 004C07CC
:00437DEC 6810054C00 push 004C0510
:00437DF1 E8DAA40400 call 004822D0
:00437DF6 83C408 add esp, 00000008
:00437DF9 85C0 test eax, eax
:00437DFB 746A je 00437E67
How did I reach it? Easy, I just searched for the string "Sorry, your registration
name and number don't match!" that appears when you enter a wrong serial, and then I
"backtraced" in the dead listing... Also, you'll see that the jump
:00437DFB 746A je 00437E67
takes you to the "bad" routine, so you can easily guess that eax value should be
DIFFERENT FROM ZERO.
Now you can guess also that the call is to a procedure which checks the name and the
serial you entered, so the two PUSHes before the call should be the name and the pw:
well, you'll have to check this with S-Ice if you want to be sure (SoftIce-Wdasm 1-1).
Remember that you can guess if you're "just" cracking, but you can't if you have to
study an algorithm for some time... it would become all lost time if you've failed
your guess! Anyway, if you check with S-Ice you'll find this:
:00437DE7 68CC074C00 push 004C07CC --> Name
:00437DEC 6810054C00 push 004C0510 --> Pass
(As you can see, I'm ALWAYS right... ;))
Now, we should give a look to that 4822d0 call:
**************
CALL 4822D0:
**************
* Referenced by a CALL at Addresses:
|:00437DF1 , :00482482
|
First of all, notice that the check of the password is called in two different parts of
the program: this is particularly useful when you have to patch! In fact, if you just
patch the jump at 437dfb, ie. from:
:00437DFB 746A je 00437E67
to:
:00437DFB 7400 je 00437DFD
or (less elegantly):
:00437DFB 90 nop
:00437DFC 90 nop
... erm... I was writing... if you just patch that address, you won't crack the program:
the next time you will execute it, it will delete your info and restart unregistered. So,
if you plan to patch a password protected program using the dead-listing technique, you
have to FOLLOW the call which you think checks for the pw, then check WHERE it is called,
then go and patch all those places, or if you want to make a more "professional" crack
you can just change the called procedure so that it always returns the value you want.
Now, let's see what is contained inside that call... A good way to start is to remember
what value you want and look at the END of the procedure (before the RET command) the
different ways you can return from that call.
Look here:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00482358(C)
|
:00482361 33C0 xor eax, eax --> BAD_FLAG!
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004822EA(U), :004822FF(U), :0048234D(U), :0048235F(U)
|
:00482363 5F pop edi
:00482364 5E pop esi
:00482365 5B pop ebx
:00482366 5D pop ebp
:00482367 C3 ret
Here it is: we know that eax=0 is the bad flag, so we can find in which places there is
a bad or good choice... Another important thing is that 482363 address called FOUR times
(all with UNCONDITIONAL jumps!) and that 482358 called one time with a conditional jump:
does this mean there's only ONE way to make the wrong choice? Of course... NO!!! Look at
other pieces of code like this:
:004822E8 33C0 xor eax, eax --> EAX=0 BAD FLAG!
:004822EA EB77 jmp 00482363 --> END_PROC
Here the bad flag is created before the jump to the end sequence... and you can
understand that xor eax,eax isn't anything else than a CREATE_BAD_FLAG only from that
jump! There are many xors in a program... and not all of them do the same thing of
course: this is the way you can understand which ones seem interesting for you!
Now... another suggestion: COMMENT the disassembled file! If you do it, in a few minutes
it will be like reading an Aesop's fable: you'll understand every little thing
immediately!
First of all, let's call that 482363 address END_PROC, so you can see in the code when
you exit the call... and you are also able to call the xor eax, eax CREATE_BAD_FLAG or
with a similar name. Use your fantasy, call that "YOU_DAMNED_BASTARD" or whatever you
like, the important is that you understand it :) Also, follow the addresses of the
parameters passed to the call, in this case the PW and the NAME: since you've passed
the name AND THEN the pw, you'll have the first one in ebp+08, and the second one in
ebp+0C, as you can see from the code:
:004822D6 8B750C mov esi, dword ptr [ebp+0C] --> PW
:004822D9 8B5D08 mov ebx, dword ptr [ebp+08] --> NAME
:004822DC 53 push ebx
:004822DD E87E1D0200 call 004A4060
:004822E2 59 pop ecx
:004822E3 83F805 cmp eax, 00000005
:004822E6 7304 jnb 004822EC
:004822E8 33C0 xor eax, eax --> EAX=0 BAD FLAG!
:004822EA EB77 jmp 00482363 --> END_PROC
Now you can follow it quite easily: put pw address in esi, put name address in ebx, do
something with the name (you see there's that PUSH EBX, don't you?), then check that the
returned value is at least 5. If eax is less than 5 return an error flag. You want me to
comment it? So you're lazier than me! :)
:004822D6 8B750C mov esi, dword ptr [ebp+0C] --> PW
:004822D9 8B5D08 mov ebx, dword ptr [ebp+08] --> NAME
:004822DC 53 push ebx
:004822DD E87E1D0200 call 004A4060 --> CHECK LENGTH
:004822E2 59 pop ecx
:004822E3 83F805 cmp eax, 00000005 --> Name must be at least 5
chars long
:004822E6 7304 jnb 004822EC
:004822E8 33C0 xor eax, eax --> EAX=0 BAD FLAG!
:004822EA EB77 jmp 00482363 --> END_PROC
Ta daaa... (I correct myself: TA-CHAAAAN! ;)) I bet you haven't guessed it! :) As you
can see, it's just a matter of comments and all becomes easily readable! Learn this
technique and your cracking will become more effective, also you will rest your eyes on
white paper (or whatever color you like) instead of a 14'' monitor at a 1280x1024
resolution 8-|
Now let's see what else this call does with our beloved parameters...
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004822E6(C) (the password is >=5 char)
|
:004822EC 56 push esi --> again PW
:004822ED 53 push ebx --> again Name
:004822EE E8FDFEFFFF call 004821F0 --> do something with them
:004822F3 83C408 add esp, 00000008
:004822F6 85C0 test eax, eax
:004822F8 7407 je 00482301 --> IF eax=0 THEN do some other things...
:004822FA B801000000 mov eax, 00000001 --> GOOD_FLAG!
:004822FF EB62 jmp 00482363 --> END_PROC
Hey!!! We have reached a VERY interesting point! You see that test eax, eax and the jump
after that? Well, we DON'T WANT to make it jump, so we will have our GOOD_FLAG and we
will register immediately! So from a "long" procedure like this (well if you call this
long you haven't seen some M$ calls :-|) you can cut away more than half of it and start
to consider just that "call 4821f0".
Ok let's cut it! ...zic-zac-zic-zac...
(you don't know but I have all the commented procedure in my notepad and I'm really
cutting it :)
And now... let's give a look to that 4821f0 call:
**************
CALL 4821f0:
**************
First of all, remember that we want eax DIFFERENT FROM 0. So, every time we find something
like xor eax,eax and then a jump to the end of the call we know the procedure is setting a
BAD flag. So, first of all go and give a look to the end of the call: is there a xor in
that piece of code? Is there a conditional jump near the end? And so on...
Here it is:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004822BB(C)
|
:004822C1 B801000000 mov eax, 00000001 --> GOOD_FLAG!
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0048220F(U), :0048222C(U), :0048227F(U), :004822BF(U)
|
:004822C6 5F pop edi
:004822C7 5E pop esi
:004822C8 5B pop ebx
:004822C9 8BE5 mov esp, ebp
:004822CB 5D pop ebp
:004822CC C3 ret
LOOK AND _UNDERSTAND_!!! It's really plain text! I correct myself, it's easier than plain
text! It's like an illustrated book for children! :)
We have FOUR UNCONDITIONAL jumps to the end of the procedure, please take a look at them:
***JUMP 1:***
:0048220D 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG!
:0048220F E9B2000000 jmp 004822C6 --> END_PROC
***JUMP 2:***
:0048222A 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG!
:0048222C E995000000 jmp 004822C6 --> END_PROC
***JUMP 3:***
:0048227D 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG!
:0048227F EB45 jmp 004822C6 --> END_PROC
***JUMP 4:***
:004822BD 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG!
:004822BF EB05 jmp 004822C6 --> END_PROC
They are _absolutely_identical_!!! So now you know that whenever there is a jump to
END_PROC, it's because something went wrong (in fact, every time there is an xor before
the jump). Otherwise, if you come to that instruction from the right address (4822C1),
it's because the jump at 4822BB took you there. But let's go straight on with the call
and let's see what happens:
:004821F0 some junk here...
:004821F9 8B750C mov esi, dword ptr [ebp+0C] --> PW
:004821FC 6A2D push 0000002D
You know what 2D is? NO? NOOO??? :)
Well I pardon you only because you're newbies, but now you have to learn it by heart
and if I find you forgot it I'll show no mercy! >:)
2D is the hex value of the '-' char, which is often used as a separator inside the
passwords. Sometimes the '-' char is not used just as a separator, but is part of the
pw itself: if it's not found or if it's in the wrong position, the password is wrong.
In this case, you don't have to put the '-' in a particular place, but you do have
to put it somewhere inside the pw: in fact, it is used as a separator to check two
different parts of the pw in two different ways. Look here:
:004821FE 56 push esi --> PW
:004821FF E8081E0200 call 004A400C --> Finds the "-" and returns its position
:00482204 83C408 add esp, 00000008
:00482207 8BD8 mov ebx, eax
:00482209 85DB test ebx, ebx
:0048220B 7507 jne 00482214
:0048220D 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG! (no "-")
:0048220F E9B2000000 jmp 004822C6 --> END_PROC
This is the piece of code which checks for the presence of the 2D value inside the password.
If there isn't any '-' (or if the char is the first char of the password) the program exits
the call with 0 value in eax, which is, as you know, a BAD flag. Now here's what happens if
2D value is present:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048220B(C) (the password has a "-" inside it)
|
:00482214 C60300 mov byte ptr [ebx], 00 --> zeroes the "-"
:00482217 56 push esi --> PW
:00482218 E89F710200 call 004A93BC --> Creates first checksum: IMPORTANT!!!
:0048221D 59 pop ecx --> PW
:0048221E 8945FC mov dword ptr [ebp-04], eax --> SAVES FIRST CHECKSUM!
The first line of code zeroes the '-' symbol. This often happens during serial checks, because
most of the times 2D values are not to be included in the algorithm which builds the password
from the name or (as in this case) builds a checksum from the password and compares it with
another checksum built from the name. That CALL 004A93BC is nothing else than a "_atol"
function, which returns a number from a string which represents a number: for instance, the
string "12345" returns the value 12345, which is a NUMBER and not a sequence of chars. After
the checksum is created in eax, its value is saved at the location pointed by [ebp-04].
:00482221 C6032D mov byte ptr [ebx], 2D --> puts the "-" again
:00482224 43 inc ebx
:00482225 803B00 cmp byte ptr [ebx], 00 --> must have something after the "-"
:00482228 7507 jne 00482231
:0048222A 33C0 xor eax, eax --> eax=0 <-> BAD_FLAG!
:0048222C E995000000 jmp 004822C6 --> END_PROC
Now there's another check: the 2D character must be INSIDE the password, not at its end.
From this we can understand that '-' is just a separator that splits the password in two
pieces, which generate two different checksums (and we can suppose that the password will
be used to make two values too).
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00482228(C) (the "-" is NOT the last character!)
|
:00482231 53 push ebx --> second section of the password
:00482232 E885710200 call 004A93BC --> Creates second checksum: IMPORTANT!!!
:00482237 59 pop ecx
:00482238 8945F8 mov dword ptr [ebp-08], eax --> SAVES SECOND CHECKSUM
Here, with the same method as in the piece of code above, the second checksum is created
and then it is saved in the address pointed by [ebp-08]. Now the beautiful part of the
algorithm starts... first it checks for the length of the name, and lets you go on only
if your name is at least 3 chars long:
:0048223B 8B4508 mov eax, dword ptr [ebp+08]
:0048223E 50 push eax --> Name
:0048223F E81C1E0200 call 004A4060 --> Strlen
:00482244 59 pop ecx
:00482245 8945F4 mov dword ptr [ebp-0C], eax --> Saves name's length
:00482248 33C0 xor eax, eax
:0048224A 33DB xor ebx, ebx
:0048224C BA03000000 mov edx, 00000003
:00482251 8B4D08 mov ecx, dword ptr [ebp+08] --> name's address
:00482254 83C103 add ecx, 00000003
:00482257 3B55F4 cmp edx, dword ptr [ebp-0C] --> Cmp (3, namelength)
:0048225A 7D1C jge 00482278
Now here is the funny piece of code: remember, you don't have to clone the assembly
routine, you have to UNDERSTAND what's goin' on and then try to replicate it... maybe
in an easier way!
:0048225C 0FB631 movzx esi, byte ptr [ecx] --> put in esi the 4th letter
of the name
:0048225F 0FAF348544BB4B00 imul esi, dword ptr [4*eax + 004BBB44] --> CHECK THE TABLE!
;esi=esi*table value
:00482267 03DE add ebx, esi --> ebx=ebx+esi
:00482269 40 inc eax --> eax=0, 1, 2, 3...
:0048226A 83F826 cmp eax, 00000026
:0048226D 7E02 jle 00482271
:0048226F 33C0 xor eax, eax --> translated: "if eax>26 then
eax=0"
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048226D(C)
|
:00482271 42 inc edx --> edx=4, 5, 6...
:00482272 41 inc ecx --> ecx=name's address+4, 5, 6...
:00482273 3B55F4 cmp edx, dword ptr [ebp-0C] --> cmp edx, namelength
:00482276 7CE4 jl 0048225C --> continue while edx cmp ebx, first_checksum
NOTE: ebx, which is built from the NAME, must be equal to the first checksum, which is
built from the PASSWORD. This is the relation between the two strings you inserted.
The second checksum is compared with another value, which is created with this algorithm:
:00482295 0FB631 movzx esi, byte ptr [ecx] --> move 4th letter in esi
:00482298 0FB679FF movzx edi, byte ptr [ecx-01] --> move 3rd letter in edi
:0048229C 0FAFF7 imul esi, edi --> esi=esi*edi
:0048229F 0FAF348544BB4B00 imul esi, dword ptr [4*eax + 004BBB44] --> esi=esi*table value
:004822A7 03DE add ebx, esi --> ebx=ebx+esi
...
:004822B8 3B5DF8 cmp ebx, dword ptr [ebp-08] --> cmp ebx, second_checksum
:004822BB 7404 je 004822C1 --> JMP_GOOD_FLAG!
Et voila'! This is the last part of the protection! The value is created in a different way:
this time we load two letters (the 4th and the 3rd, then the 5th and the 4th, and so on), we
multiply them together and then with the table value, finally we save the value obtained in
ebx. At the end this is compared with the second checksum, and if this is ok too we jump to
GOOD_FLAG and, then, the end of the procedure.
Phew... the job is done: now you can code a keygen for mIRC in whatever language you like!
I think the algo is quite simple (that's why I used to give this "exercise" to some pupils)
but here's a little piece of my c code:
---------------------------MIRC v5.* KEYGEN by .+MaLaTTiA.---------------------begins
#include
#include
long vals[40]={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
long value;
unsigned char string[40];
int i,j;
void main (){
printf ("\n...//\\Oo/\\\\ MIRC v5.* KeyGen - by .+MaLaTTiA. //\\Oo/\\\\...\
n\n");
printf ("\n User name: ");
gets (string);
if (strlen (string)<5) {
printf ("\nName must be at least 5 chars long!");
exit ();
}
for (i=4;i<=strlen(string);i++){
j=(i-4)%26;
value=value+string[i-1]*vals[j];
}
... and so on :)
---------------------------MIRC v5.* KEYGEN by .+MaLaTTiA.-----------------------ends
I think this is really enough, so please DON'T MAIL ME asking the other piece of the
program: if you are not able to code "it.class" tppabs="http://Fravia.org/it.class" yourselves you don't deserve my attention >:)
Well, I think I told you almost everything... at the end of this tute you should be able
not just to make mIRC keygen, but also to recognize and understand a key algo from some
of its characteristics: for instance the "2d" value, the loop, the strlen funcion and
so on... also, you should have learned some useful techniques such as "code commenting"
(which is especially useful together with the dead listing technique) and some kind of
"algorithm abstraction" that should help you to make keygens in whatever language you
like, once you've understood how the coding algorithm works: this last technique is not
very easy to apply, but you'll learn to master it after just a few keygens... and
consider yourselves lucky, you didn't even have to reverse any function! ;)
For any comments please mail me at malattia(at)usa(dot)net...
byez,
.+MaLaTTiA.
homepage
links
anonymity
+ORC
students' essays
academy database
tools
counter measures
cocktails
antismut
bots wars
search_forms
mail_Fravia
Is reverse engineering legal?