. . . . . . . . . . . . . . . . .
.                               .
. KeyGen Tutorial on  mIRC 5.41 .
.               -               .
.     (c) 1998 by Dynamite      .
.                               .
. . . . . . . . . . . . . . . . .


( )Beginner  (X)Intermediate  ( )Advanced  ( )Expert

In this tut, we will get the information to write a keygen for the IRC client mIRC 5.41. The
first time I worked on mIRC 5.41 I wondered, because the code was so clearly written. It was
really fun to crack it :-) Because of the clearly written code (Thanks to Khaled Mardam-Bey) it
is a nice target for a tut. Only one thing is very bad on mIRC. One time registered, you can't
un-register it. I tried to delete it, but the registration was allready there. Maybe it keeps
the registration info in the registry or in a file in the win95 dir. I don't know...

For this tut you will need Soft-Ice 3.0+ and W32Dasm (I'm using v8.9).

Run mIRC and select Register in the Help menu. Enter anything you want and press the register
button. You're noting a msgbox poping up, which tells you, that your registration info was
wrong. So let's set a BPX at MessageBoxA. Repeat the whole crap and you are in SIce, where
the msgbox pops up. It is at :0043D29C. Write this address down and load MIRC32.EXE into
W32Dasm. Once in W32Dasm, goto :0043D29C and then scroll up, until you find the next reference
to a conditional jump. You'll find it at :0043D257. It comes from :0043D1C6. At :0043D1C6 you
can see the following piece of code:


:0043D1B5 68701E4D00              push 004D1E70		; the s/n is pushed
:0043D1BA 68B41B4D00              push 004D1BB4		; the name is pushed
:0043D1BF E844140500              call 0048E608		; that's the check_function
:0043D1C4 85C0                    test eax, eax		; valid s/n entered?
:0043D1C6 0F848B000000            je 0043D257		; jump, when s/n was wrong


Okay. Now we know where to search. We have to set a BPX at :0048E608. When I worked on mIRC, I
traced through the whole check_function and all its sub-functions. Here follows the whole
check_function well commented:


* Referenced by a CALL at Addresses:
|:0043D1BF   , :0048E7CA   
|
:0048E608 55                      push ebp
:0048E609 8BEC                    mov ebp, esp
:0048E60B 53                      push ebx
:0048E60C 56                      push esi
:0048E60D 57                      push edi
:0048E60E 8B750C                  mov esi, dword ptr [ebp+0C]
:0048E611 8B5D08                  mov ebx, dword ptr [ebp+08]
:0048E614 53                      push ebx			; push name
:0048E615 E8AE530200              call 004B39C8			; get length of name
:0048E61A 59                      pop ecx
:0048E61B 83F805                  cmp eax, 00000005		; the min. of chars is 5
:0048E61E 7304                    jnb 0048E624			; if name consist of 5 or more
								  chars, jump. else leave func
:0048E620 33C0                    xor eax, eax
:0048E622 EB6D                    jmp 0048E691			; jump to end of function


If you entered a name with 4 or less chars, then restart the whole process and enter a name
with minimal 5 chars.


* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048E61E(C)
|
:0048E624 56                      push esi
:0048E625 53                      push ebx
:0048E626 E8FDFEFFFF              call 0048E528


Here follows the function :0048E528. This is the main function of the whole protection, because
it validates the s/n with the username.


* Referenced by a CALL at Addresses:
|:0048E626   , :0048E66E   
|
:0048E528 55                      push ebp
:0048E529 8BEC                    mov ebp, esp
:0048E52B 83C4F4                  add esp, FFFFFFF4
:0048E52E 53                      push ebx
:0048E52F 56                      push esi
:0048E530 57                      push edi
:0048E531 8B750C                  mov esi, dword ptr [ebp+0C]	; move s/n to esi
:0048E534 6A2D                    push 0000002D
:0048E536 56                      push esi			; push s/n
:0048E537 E838540200              call 004B3974			; check, if there is a "-" in
								  the s/n
:0048E53C 83C408                  add esp, 00000008
:0048E53F 8BD8                    mov ebx, eax
:0048E541 85DB                    test ebx, ebx
:0048E543 7507                    jne 0048E54C			; jump if there is a "-" else
								  leave function 
:0048E545 33C0                    xor eax, eax
:0048E547 E9B2000000              jmp 0048E5FE			; leave function


You see, that your s/n have to contain a "-". If you didn't enter a "-" in your s/n then enter
it now and restart the whole process.


* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048E543(C)
|
:0048E54C C60300                  mov byte ptr [ebx], 00	; replace the "-" in the s/n with
								  a '00' byte
:0048E54F 56                      push esi			; push s/n
:0048E550 E807A80200              call 004B8D5C			; check if the s/n consists of
								  numbers
:0048E555 59                      pop ecx
:0048E556 8945FC                  mov dword ptr [ebp-04], eax
:0048E559 C6032D                  mov byte ptr [ebx], 2D
:0048E55C 43                      inc ebx
:0048E55D 803B00                  cmp byte ptr [ebx], 00
:0048E560 7507                    jne 0048E569
:0048E562 33C0                    xor eax, eax
:0048E564 E995000000              jmp 0048E5FE

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048E560(C)
|
:0048E569 53                      push ebx
:0048E56A E8EDA70200              call 004B8D5C
:0048E56F 59                      pop ecx
:0048E570 8945F8                  mov dword ptr [ebp-08], eax
:0048E573 8B4508                  mov eax, dword ptr [ebp+08]
:0048E576 50                      push eax			; push name
:0048E577 E84C540200              call 004B39C8			; get length of name
:0048E57C 59                      pop ecx
:0048E57D 8945F4                  mov dword ptr [ebp-0C], eax
:0048E580 33C0                    xor eax, eax
:0048E582 33DB                    xor ebx, ebx
:0048E584 BA03000000              mov edx, 00000003
:0048E589 8B4D08                  mov ecx, dword ptr [ebp+08]	; move name to ecx
:0048E58C 83C103                  add ecx, 00000003
:0048E58F 3B55F4                  cmp edx, dword ptr [ebp-0C]
:0048E592 7D1C                    jge 0048E5B0


Okay. Here follows a very interesting part of the protection. All chars of the name (excluded the
first three chars) are multiplicated with special digits out of a table. The outcome is added to
a checksum in EBX. You can look into the actual position of the table, if you enter
'D 4*eax+004CCB30' in SIce. To ease your cracking work, I'll give you the table.

Num_of_char	Digit
---------------------
4		0B
5		06
6		11
7		0C
8		0C
9		0E
10		05
11		0C
12		10
13		0A
14		0B
15		06
16		0E
17		0E
18		04
19		0B
20		06
21		0E
22		0E
23		04
24		0B
25		09
26		0C
27		0B
28		0A
29		08
30		0A
31		0A
32		10
33		08
34		04
35		06
36		0A
37		0C
38		10
39		08
40		0A
41		04
42		10


* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048E5AE(C)
|
:0048E594 0FB631                  movzx esi, byte ptr [ecx]		; move next char of name
									  to esi
:0048E597 0FAF348530CB4C00        imul esi, dword ptr [4*eax+004CCB30]	; multiplicate the char
									  with one digit out of
									  the table
:0048E59F 03DE                    add ebx, esi				; add the outcome to EBX
:0048E5A1 40                      inc eax				; next char
:0048E5A2 83F826                  cmp eax, 00000026
:0048E5A5 7E02                    jle 0048E5A9
:0048E5A7 33C0                    xor eax, eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048E5A5(C)
|
:0048E5A9 42                      inc edx
:0048E5AA 41                      inc ecx
:0048E5AB 3B55F4                  cmp edx, dword ptr [ebp-0C]		; end of string?
:0048E5AE 7CE4                    jl 0048E594				; jump if not end of str

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048E592(C)
|
:0048E5B0 3B5DFC                  cmp ebx, dword ptr [ebp-04]		; compare checksum (ebx)
									  with the first part of
									  the s/n.
:0048E5B3 7404                    je 0048E5B9				; jump when they match
:0048E5B5 33C0                    xor eax, eax
:0048E5B7 EB45                    jmp 0048E5FE				; else leave function


At :0048E5B0 the checksum (EBX) is compared with the first part of the s/n. The first part of the
s/n is the part before the "-". Here follows an example:
If you entered "Dynamite" in the name box, you have to calculate as follows:

 ----------------------------------------
| Name:	  D   y   n   a   m   i   t   e  |
| Number: 1   2   3   4   5   6   7   8  |
| Digits:             0B  06  11  0C  0C |
 ----------------------------------------

CheckSum = (61 * 0B) + (6D * 06) + (69 * 11) + (74 * 0C) + (65 * 0C)
CheckSum = 0x17DE

Now you only have to convert 0x17DE to decimal number.

0x17DE ==> 6110

So the first part of your serial is '6110-'. The second part of the serial is calculated some
lines below.


* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048E5EE(C)
|
:0048E5CD 0FB631                  movzx esi, byte ptr [ecx]		; move one char to esi
:0048E5D0 0FB679FF                movzx edi, byte ptr [ecx-01]		; move one char before to
									  edi
:0048E5D4 0FAFF7                  imul esi, edi				; multiplicate the 2 chrs
:0048E5D7 0FAF348530CB4C00        imul esi, dword ptr [4*eax+004CCB30]	; multiplicate the
									  outcome with the
									  numbers out of the
									  table
:0048E5DF 03DE                    add ebx, esi				; add outcome to checksum
									  in EBX
:0048E5E1 40                      inc eax
:0048E5E2 83F826                  cmp eax, 00000026
:0048E5E5 7E02                    jle 0048E5E9
:0048E5E7 33C0                    xor eax, eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048E5E5(C)
|
:0048E5E9 42                      inc edx
:0048E5EA 41                      inc ecx
:0048E5EB 3B55F4                  cmp edx, dword ptr [ebp-0C]		; end of string?
:0048E5EE 7CDD                    jl 0048E5CD				; jump if not end of str

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048E5CB(C)
|
:0048E5F0 3B5DF8                  cmp ebx, dword ptr [ebp-08]		; compare checksum with
									  second part of the s/n
:0048E5F3 7404                    je 0048E5F9				; jump when they match
:0048E5F5 33C0                    xor eax, eax
:0048E5F7 EB05                    jmp 0048E5FE				; else leave


This second calculation multiplicates every char of the name with the char before the char. The
outcoming of this multiplication is multiplicated with a number out of the tabel. To get the
second part of the s/n you have to calculate as follows:

 ----------------------------------------
| Name:	  D   y   n   a   m   i   t   e  |
| Number: 1   2   3   4   5   6   7   8  |
| Digits:             0B  06  11  0C  0C |
 ----------------------------------------

CheckSum = (('a' * 'n') * 0B) + (('m' * 'a') * 06) + (('i' * 'm') * 11) +
	   (('t' * 'i') * 0C) + (('e' * 't') * 0C)

CheckSum = 0xA1A6D

Just convert it to a decimal number and you get 662125. Now you have to put the two parts
together and you got the valid s/n for 'Dynamite'.

name: Dynamite
s/n: 6110-662125



I think you got all information to write a keygen for mIRC 5.41. I hope you enjoyed this tut by
me. Sorry for my bad english :]

Greetz are going to he +HCU and to NiKai ;-)


(c) 1998 by Dynamite