Home    Training    Downloads    Tutorials    Arbitary    Get Fate    Proxy Info
 
Training session 32: Keygen Creation
Difficulty: Medium
Learn how to create a keygen for mIRC v5.7
Creator: m101


The basic purpose of this tutorial is to teach you how to find an algorithm in assembly, and create a keygen to create working keys for it. The target of this tutorial is mIRC 5.7. I have chosen this program for its simplicity in key creation. For this tutorial you require Win32DASM at a minimum, and for extra verification, softice.

First open up mIRC and try to register it. "Sorry, your registration name and number don't match blah blah blah". Kill mIRC and open up Win32DASM and decompile mirc32.exe. Open up the dead listing in Refs > String Data References and go through till you find the message you got from a failed registration. Double click it and you will find yourself in the following code:
:00498B79 6A00                    push 00000000

* Possible Reference to String Resource ID=01912: "mIRC Registration!"
                                  |
:00498B7B 6878070000              push 00000778
:00498B80 E8F365F8FF              call 0041F178
:00498B85 50                      push eax
:00498B86 6A00                    push 00000000

* Possible Reference to String Resource ID=01913: "Sorry, your registration name and number don't match! Pleas"
                                  |
:00498B88 6879070000              push 00000779
:00498B8D E8E665F8FF              call 0041F178
:00498B92 50                      push eax
:00498B93 8B4508                  mov eax, dword ptr [ebp+08]
:00498B96 50                      push eax

Trace this back to its call at the following code:
:00498A8A 52                      push edx

* Reference To: USER32.SendDlgItemMessageA, Ord:0000h
                                  |
:00498A8B E841800500              Call 004F0AD1
:00498A90 68334A5000              push 00504A33
:00498A95 684C465000              push 0050464C
:00498A9A E8E5FBFFFF              call 00498684
:00498A9F 85C0                    test eax, eax
:00498AA1 0F849B000000            je 00498B42
:00498AA7 BE3C9D4F00              mov esi, 004F9D3C
:00498AAC BF4C465000              mov edi, 0050464C

Notice the SendDlgItemMessageA? Well if you open up softice, you can set a breakpoint on it and you will find yourself at this code after the second call. To make this all really easy for you, you should comment the code, but because you are probably really slack, ive done it for you:
:00498A90 68334A5000              push 00504A33		<serial
:00498A95 684C465000              push 0050464C		<username
:00498A9A E8E5FBFFFF              call 00498684		<username and serial operations
:00498A9F 85C0                    test eax, eax		<test eax
:00498AA1 0F849B000000            je 00498B42			<bad cracker if eax=1
:00498AA7 BE3C9D4F00              mov esi, 004F9D3C

As you can see, the username and serial are pushed then a call is made to some operations on the pushed values. The result must result in eax being 0 or the routine will fail and we will get an 'unregistered' result. At this point you could patch the code to trick mIRC into being registered, but only until you restard, which ofcourse is totally useless to us. Follow the code through the call and you will get this:
* Referenced by a CALL at Addresses:
|:004987DF   , :004988B2   , :00498A9A   
|
:00498684 55                      push ebp
:00498685 8BEC                    mov ebp, esp
:00498687 53                      push ebx

*CUT*

:004986D6 83E103                  and ecx, 00000003
:004986D9 F3                      repz
:004986DA A4                      movsb
:004986DB 5E                      pop esi			<serial
:004986DC 68204A5100              push 00514A20		<serial
:004986E1 6820495100              push 00514920		<username
:004986E6 E8A1FEFFFF              call 0049858C		<must return eax=1  <<<first check!!!
:004986EB 85C0                    test eax, eax
:004986ED 7407                    je 004986F6			<eax must be true to perform second check!!!
:004986EF B801000000              mov eax, 00000001		<bad cracker
:004986F4 EB74                    jmp 0049876A

Well as you can see, our serial is pushed, and if the result is wrong the serial will fail. So lets check out the the call there and see what it does for us:
:0049858C 55                      push ebp
:0049858D 8BEC                    mov ebp, esp
:0049858F 83C4F4                  add esp, FFFFFFF4
:00498592 53                      push ebx
:00498593 56                      push esi
:00498594 57                      push edi
:00498595 8B750C                  mov esi, dword ptr [ebp+0C]
:00498598 8B4508                  mov eax, dword ptr [ebp+08]
:0049859B 50                      push eax
:0049859C E8778DF6FF              call 00401318		<strlen on username
:004985A1 59                      pop ecx
:004985A2 83F805                  cmp eax, 00000005		<is username = 5?
:004985A5 7307                    jnb 004985AE			<if equal or larger jump to good cracker
:004985A7 33C0                    xor eax, eax			<bad cracker
:004985A9 E9CA000000              jmp 00498678
Very interesting, as you can see, it performs a strlen operation on the username and returns its length. If the username isnt atleast five characters then the the function returns false and we get an 'unregistered' message. If you follow the 'bad cracker' jump then you get the following code:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0049866D(C)
|
:00498673 B801000000              mov eax, 00000001		<good cracker

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004985A9(U), :004985C1(U), :004985DE(U), :00498631(U), :00498671(U)		<all bad crackers
|
:00498678 5F                      pop edi
:00498679 5E                      pop esi
:0049867A 5B                      pop ebx
:0049867B 8BE5                    mov esp, ebp
:0049867D 5D                      pop ebp
:0049867E C20800                  ret 0008

Now this is interesting to us, check out all those unconditional jumps. All of them return bad cracker apart from the one call. The next peice of code basically checks for the split in the serial:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004985A5(C)
|
:004985AE 6A2D                    push 0000002D		<check for "-" in serial
:004985B0 56                      push esi			<serial
:004985B1 E8C28CF6FF              call 00401278
:004985B6 83C408                  add esp, 00000008
:004985B9 8BD8                    mov ebx, eax
:004985BB 85DB                    test ebx, ebx
:004985BD 7507                    jne 004985C6
:004985BF 33C0                    xor eax, eax			<bad cracker
:004985C1 E9B2000000              jmp 00498678

See that 2D? It refers to "-". This is one thing you must remember, its all over most protections. Follow the code through and you will get this:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004985BD(C)
|
:004985C6 C60300                  mov byte ptr [ebx], 00	<remove the "-" from the string
:004985C9 56                      push esi
:004985CA E84514F7FF              call 00409A14		<check for "-" problems in string and generate checksums
:004985CF 59                      pop ecx
:004985D0 8945FC                  mov dword ptr [ebp-04], eax
:004985D3 C6032D                  mov byte ptr [ebx], 2D
:004985D6 43                      inc ebx
:004985D7 803B00                  cmp byte ptr [ebx], 00
:004985DA 7507                    jne 004985E3			<good cracker
:004985DC 33C0                    xor eax, eax			<bad cracker
:004985DE E995000000              jmp 00498678

This should all be pretty self explanitory to you, so just keep following the code:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004985DA(C)
|
:004985E3 53                      push ebx
:004985E4 E82B14F7FF              call 00409A14
:004985E9 59                      pop ecx
:004985EA 8945F8                  mov dword ptr [ebp-08], eax
:004985ED 8B5508                  mov edx, dword ptr [ebp+08]
:004985F0 52                      push edx
:004985F1 E8228DF6FF              call 00401318			<username length
:004985F6 59                      pop ecx				<username
:004985F7 8945F4                  mov dword ptr [ebp-0C], eax
:004985FA 33C0                    xor eax, eax				<eax = 0
:004985FC 33DB                    xor ebx, ebx				<ebx = 0
:004985FE BA03000000              mov edx, 00000003
:00498603 8B4D08                  mov ecx, dword ptr [ebp+08]	<ecx = username
:00498606 83C103                  add ecx, 00000003			<set pointer to 4th character
:00498609 3B55F4                  cmp edx, dword ptr [ebp-0C]
:0049860C 7D1C                    jge 0049862A
Now we are into the point where the real serial is created. As you can see more string lengths are called and it leads onto the main key creation code:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00498628(C)
|
:0049860E 0FB631                  movzx esi, byte ptr [ecx]			<set esi to the 4th byte of username
:00498611 0FAF34859C9C4F00        imul esi, dword ptr [4*eax+004F9C9C]	<multiply byte by table value
:00498619 03DE                    add ebx, esi						<ebx = ebx + (byte * table_value)
:0049861B 40                      inc eax						<next byte of table
:0049861C 83F826                  cmp eax, 00000026					<26 characters of table read read?
:0049861F 7E02                    jle 00498623						<if not, then continue
:00498621 33C0                    xor eax, eax						<else set counter to 0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0049861F(C)
|
:00498623 42                      inc edx				<edx = edx + 1
:00498624 41                      inc ecx				<next character of username
:00498625 3B55F4                  cmp edx, dword ptr [ebp-0C]	<end of username?
:00498628 7CE4                    jl 0049860E				<if more then loop

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0049860C(C)
|
:0049862A 3B5DFC                  cmp ebx, dword ptr [ebp-04]		<compare result with serial
:0049862D 7404                    je 00498633					<good cracker
:0049862F 33C0                    xor eax, eax					<bad cracker
:00498631 EB45                    jmp 00498678
Ok, so what the hell does this do? It generates the first half of the serial, something like '1234-XXXXXX'. The second half is generated next. Basically as you can see, it takes the fourth value of the username, multiplies it by a value from a table, then adds the result to ebx to create the serial, but ofcourse the serial is in hex so you will have to convert it for use. Each loop a new value is taken from the table and the next character is taken to be processed. I bet your wondering about that table? Well its like an array of all the values, if you check it in softice, you will find the following values:
0B 06 11 0C 0C 0E 05 0C 10 0A 0B 06 0E 0E 04 0B 06 0E 0E 04 0B 09 0C 0B 0A 08 0A 0A 10 08 04 06 0A 0C 10 08 0A 04 10 00

Only the first twenty six values are used. Next we have the creation of the second half of the serial:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0049862D(C)
|
:00498633 33C0                    xor eax, eax				<reset eax
:00498635 33DB                    xor ebx, ebx				<reset ebx
:00498637 BA03000000              mov edx, 00000003
:0049863C 8B4D08                  mov ecx, dword ptr [ebp+08]	<set ecx to username
:0049863F 83C103                  add ecx, 00000003			<move to 4th character
:00498642 3B55F4                  cmp edx, dword ptr [ebp-0C]
:00498645 7D23                    jge 0049866A

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00498668(C)
|
:00498647 0FB631                  movzx esi, byte ptr [ecx]			<set esi to 4th byte
:0049864A 0FB679FF                movzx edi, byte ptr [ecx-01]			<set edi to 3rd byte
:0049864E 0FAFF7                  imul esi, edi					<esi = esi * edi
:00498651 0FAF34859C9C4F00        imul esi, dword ptr [4*eax+004F9C9C]	<esi = esi * byte from table
:00498659 03DE                    add ebx, esi						<ebx = ebx + esi
:0049865B 40                      inc eax						<next byte in table
:0049865C 83F826                  cmp eax, 00000026					<is table upto 26?
:0049865F 7E02                    jle 00498663						<no, then continue
:00498661 33C0                    xor eax, eax						<else set eax = 0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0049865F(C)
|
:00498663 42                      inc edx				<edx = edx+1
:00498664 41                      inc ecx				<next byte in character
:00498665 3B55F4                  cmp edx, dword ptr [ebp-0C]	<end of username?
:00498668 7CDD                    jl 00498647				<no, then loop

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00498645(C)
|
:0049866A 3B5DF8                  cmp ebx, dword ptr [ebp-08]	<compare second half of serial
:0049866D 7404                    je 00498673				<good cracker
:0049866F 33C0                    xor eax, eax				<bad cracker
:00498671 EB05                    jmp 00498678
Next the algorithm takes the third character, multiplies it by the fourth value and then multiplies the result by the values from the table. After that the fourth and fifth value are taken and so on until the checksum is entirely created. From now, i dont know if im just being lazy or not, but ive created the keygen entirely in assembly with debug to prove it can be done. To code a keygen, all you really have to do is translate the assembly code into another language. The following keygen is only able to do small five letter keys, and nothing that returns too higher a value, also the result still has to be converted into decimal.

------------------------------------------------

debug
a 100
mov byte ptr [500],4e	;N
mov byte ptr [501],61	;a
mov byte ptr [502],6d	;m
mov byte ptr [503],65	;e
mov byte ptr [504],3a	;:
mov byte ptr [505],24	;$
mov ah,09		;string mode
lea dx,[500]		;set string start [0500]
int 21			;print string
lea di,[5ff]		;set first input
;loop
stosb			;save input
xor ax,ax		;clear for loop
int 16			;input user to al
cmp al,0d		;check for enter
jnz 12a			;if enter loop
mov al,24		;set last char $
stosb			;save $
;setup table values at [0800]
mov word ptr [800],0b
mov word ptr [802],06
mov word ptr [804],11
mov word ptr [806],0c
mov word ptr [808],0c
mov word ptr [80a],0e
mov word ptr [80c],05
mov word ptr [80e],0c
mov word ptr [810],10
mov word ptr [812],0a
mov word ptr [814],0b
mov word ptr [816],06
mov word ptr [818],0e
mov word ptr [81a],0e
mov word ptr [81c],04
mov word ptr [81e],0b
mov word ptr [820],06
mov word ptr [822],0e
mov word ptr [824],0e
mov word ptr [826],04
mov word ptr [828],0b
mov word ptr [82a],09
mov word ptr [82c],0c
mov word ptr [82e],0b
mov word ptr [830],0a
mov word ptr [832],08
mov word ptr [834],0a
mov word ptr [836],0a
mov word ptr [838],10
mov word ptr [83a],08
mov word ptr [83c],04
mov word ptr [83e],06
mov word ptr [840],0a
mov word ptr [842],0c
mov word ptr [844],10
mov word ptr [846],08
mov word ptr [848],0a
mov word ptr [84a],04
mov word ptr [84c],10
mov word ptr [84e],00
;continue with first checksum
sub di,1		;calculate user length
mov word ptr [900],di	;place length in [0900]
xor bx,bx		;clear bx
xor cx,cx		;clear cx
lea si,[603]		;set si to 3rd character
lea di,[800]		;set di to 1st value
;loop
xor ax,ax		;clear ax
lodsb			;load username from si
mov cx,ax		;set cx = input from si
xor ax,ax		;clear ax
xchg di,si		;switch di and si values
lodsb			;load table from si
inc si			;skip space in table
xchg si,di		;switch values back
mul cx			;multiply char by table
add bx, ax		;accumulate values
mov dx,[900]		;set dx = user length
cmp si,dx		;last character?
jl 239			;if so loop
push bx			;add 1st value to stack
xor ax,ax		;clear ax
xor bx,bx		;clear bx
xor cx,cx		;clear cx
xor dx,dx		;clear dx
xor si,si		;clear si
xor di,di		;clear di
lea si,[602]		;point si to 3rd char
lea di,[800]		;point di to table
xor ax,ax		;clear ax
;loop
lodsb			;load unername from si
mov cx,ax		;set cx = character
xor ax,ax		;clear ax
lodsb			;load next char from si
mov dx,ax		;set dx = next character
dec si			;si = si - 1
xor ax,ax		;clear ax
xchg di,si		;switch di and si
lodsb			;load table from si
inc si			;skip space in table
xchg si,di		;switch si and di back
push ax			;store value temporarily
mov ax,cx		;set ax = character
mul dx			;multiply two characters
mov cx,ax		;set result to cx
pop ax			;restore ax from stack
mul cx			;multiply by table value
add bx, ax		;accumulate values
xor ax,ax		;clear ax
xor cx,cx		;clear cx
mov dx,[900]		;set dx to user length
dec dx			;dx = dx - 1
cmp si,dx		;last character?
jl 269			;if so loop
xor cx,cx		;clear cx
xor dx,dx		;clear dx
pop ax			;restore 1st checksum
int 20			;quit

g =100 298

------------------------------------------------

AX will contain the first half of the string, and BX the second half, convert them and you will be given something like '1234-56789'. Just to you you how easy it is to do it in other languages, here is the first checksum in C:

------------------------------------------------

for (i=4;i<=strlen(username);i++){
    j=(i-4)%26;
    value=value+username[i-1]*table_values[j];
}

------------------------------------------------

When you first create a keygen, it can be a real bitch to understand what the code is doing, and how to implement it. You however will find that after a few tries, you will get used to it and be able to create keygens without too much problems. Just remember to comment the code and you wont have much problems at all ;)
Name

URL or Email

Message