Intro and Purpose

lena151's KeygenMe Solution

=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=

~ lena151's KeygenMe 1 Solution ~

=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=

Intended for legal purposes only, don't abuse !

Copyright © 2006 lena151


Tutorial

lena151's KeygenMe 1 Solution

BRIEF DISPLAY
-------------------------------------

This brief display is built on and is a continuation of part36 in the newbie series ("A reversing tutorial for newbies Part36" by me). I will assume for this short piece that you have read and understood it (or have at least that level). This is also aimed at reversers without coding capabilities. Indeed, it is possible to keygen using the templates (for the general code) mentioned above and by implementing in these the algorithm found in and ripped from the program you want to keygen. If you know how to code, you can write your own code to achieve the same as the serial calculation algorithm does. I will make it short in order not to get on your nerves...

1. In this case, BP GetDlgItemTextA brings us to the right places in no time. Study this better by stepping F8 and we immediately understand what goes on. Here are three GetDlgItemTextA. The first grabs the ID, the second the name and the third the serial.

However, notice the difference in the picture above: the second GetDlgItemTextA will copy the name in a buffer at 0041ECA0, the others copy ID/serial in a register for immediate use. Let's look it up in Win32.hlp:

[START QUOTE from Win32.hlp]
The GetDlgItemText function retrieves the title or text associated with a control in a dialog box.

UINT GetDlgItemText(
HWND hDlg, // handle of dialog box
int nIDDlgItem, // identifier of control
LPTSTR lpString, // address of buffer for text
int nMaxCount // maximum size of string
);

Parameters
hDlg Identifies the dialog box that contains the control.
nIDDlgItem Specifies the identifier of the control whose title or text is to be retrieved.
lpString Points to the buffer to receive the title or text.
nMaxCount Specifies the maximum length, of the string to be copied to the buffer pointed to by lpString.

Return Values
If the function succeeds, the return value specifies the number of characters copied to the buffer, not including the terminating null character.
If the function fails, the return value is zero.
[END QUOTE from Win32.hlp]

2. Looking closer at what happens for the name in the 2nd GetDlgItemTextA, we understand from Win32.hlp that the name is copied in a buffer at 0041ECA0 (most probably for later conversion) (see line at VA 00401665 in above picture). Put a HW BP on access at 0041ECA0 (in dump window) to easily find where the (probable) later conversion is achieved (in an algo that will need ripping). Remember that for ID/serial, these are copied into a register for immediate use, hence, the algo can't be far away. Notice both call 00411BA8's in above picture. In our case, these are holding the algo for the ID and the serial. So, the algorithm comes immediately after grabbing ID and serial respectively. We dive in the first at VA 00401658 and find ourselves back in below routine.

3. Study this better and you'll notice that it is a conversion algorithm decimal --> hex. It is a first procedure from our KeygenMe in the process of calculating a serial from the input. We will rip this code (I usually use Ziggy's Coderipper plugin for OllyDbg) because we will need to implement it in our keygen. Often, it is easier to code a line of assembler in the keygenning algo to do exactly the same as what a long ripped piece of code from a compiled high-level-language achieves. An example is in TemplateB from Part36 for the conversion of lowercase --> uppercase (invoke CharUpper,addr NameBuffer). But often, we can also use our own snippets of code. An example is this algorithm that can be replaced by a much shorter algorithm achieving exactly the same (see asm code).

See here for the result from rip1

4. See the call in the algo on line 00411BBA. Usually, it's easiest to implement it in this algo above (by replacing the line with the call, by the rip made in the call).

5. We land here

6. In our particular case, ripping this is in fact not necessary (take a better look: it never "influences" the result, hence we can ignore it) but I have ripped the code anyway, just for completeness. In case the call had held important code, we rip it and replace the call by the ripped code (always minding the useless pieces of code that don't need implementing. For better understanding, see what I effectively "use" from the rip's in the keygen code).

See here for the result from rip2

7. Let me resume : after the keygenme has grabbed all data with GetDlgItemTextA and has converted ID and Serial into hex, there will probably come a conversion of the name? Press run and we immediately break in the HW BP that we set on our name in the buffer. We have found the algo for the name. (Of course there are more ways to find this, like always). Important is that you understand that we will also need to rip this algorithm to implement it in the keygen's code (see 2nd Procedure in Asm code). REMARK: in keygenning, it is imperative that you learn to make the difference between the real serial calculation algorithm code and the code that possibly converts the input serial and "useless" code and comparing code. Also make sure that there are no other algo's somewhere, still achieving more conversions (there are none in this example).

See here for the result from rip3

8. We have now collected all necessary material to make our keygen. Of course, the ripped code will still need some adaptation to our needs. It is often the most difficult part in keygenning : some thinking is necessary to find what exactly comes in the keygen and what can (must) be left out. A good understanding of what exactly the serial algorithm is (and achieves), is always imperative.

9. Step back and look at each particular case from a distance to find an overview and resume in short for yourself what exactly happens to compare the serial (with what and how). In this particular case, the keygenme grabs input from ID, name and serial. First it converts the ID into hex. Next, the input fake serial is converted in hex. Next, the name gets converted in another algo in a hex value that is added with the ID value into a final hex value. Finally, the into hex converted fake serial value is compared with the calculated correct final hex value. Notice that when returning from the name algo, we land in that compare.

We find the values that are compared in the pane window

Is it clear that in fact, two hex values are compared? The input serial was converted to hex in the same algorithm as the ID (see above). Instead of implementing a rip again to convert hex --> decimal, we use invoke wsprintf,addr SerialBuffer,addr EndFormatToDec,eax and invoke SetDlgItemText,hWnd,IDC_SERIAL,addr SerialBuffer to display the correct serial in the keygen's box (see Asm code)

Remark : this KeygenMe can easily be forced into selfkeygenning. Simply manually editing the pointer's value in the pane window (representing a fake input value in this case) into the value from eax (which holds the correct value) writes the correct value in the keygen's box and turns the KeygenMe into permanently registered. Or even easier : assemble the following and save changes for a permanent SelfKeygen.

9. See the asm code page to study better how to implement the rips into the keygen. See also and find some more basic files and the templates in "A reversing tutorial for newbies Part36". Keygenning also means to reflect for yourself if you need to make certain assumptions. An example for this : the keygenme holds a simple conversion for decimal value --> hex value for the ID input. Is it clear that the keygenner needs to understand that he needs to restrict input to a decimal input for ID (no letters)? Indeed, an author not always gives you these hints either! Another example: the ID input can only be 9 digits long due to a restriction of the input box. The keygenner needs to remark these things!

10. Thanks for your patience in reading this. Please, accept my best regards whilst my greetz fly out to so many lovely people.

lena151.


Asm code

lena151's KeygenMe Solution

Commented asm code :
-----------------------------------

;This is part of a tutorial in Keygenning lena151's KeygenMe 1
;This asm code is built on TemplateB from part36 in the newbie
;series "A reversing tutorial for newbies Part36 by lena151".
;However, here ONLY THE RIPPED CODE IS IMPORTANT. Hence,
;scroll down for green and purple text. This is not intended to
;learn to code in assembler but of this may be changed to your
;own needs. Intended for legal purposes only, don't abuse !

.386
.model flat,stdcall
option casemap:none

include windows.inc
include user32.inc
include kernel32.inc
include ufmodapi.inc
include gdi32.inc
includelib user32.lib
includelib kernel32.lib
includelib gdi32.lib
includelib winmm.lib
includelib ufmod.lib

Main proto :DWORD,:DWORD,:DWORD,:DWORD
SerialCalc proto :DWORD

.data
;=============Editing XM-A to another included name changes the music.
include XM-I.inc ;I changed this time to "include XM-I.inc" for another sound
XMSize equ $ - table
FormatToDec db "%d-",0
EndFormatToDec db "%d",0
MoreInputTxt db "Thanks, but I need more chars for the name !",0
MoreIDInputTxt db "Input ID-number also (digits 0-9 only)",0
TooLongTxt db "Input for Name or ID too high : lower input",0

;=============The about box text and caption : edit as you like.
AboutTxt db "lena151's KeygenMe#1.",10,13
db " Created by an old lady",10,13
db " for all good people.",0
AboutCaption db "ABOUT",0

.data?
hInstance HINSTANCE ?
ResInf dd ?
ResDat dd ?
ResSize dd ?
RGNRes dd ?
Brush dd ?
Transparency dd ?
Music dd ?
NameBuffer db 50 dup(?)
IDBuffer db 50 dup(?)
TempBuffer db 300 dup(?)
SerialBuffer db 300 dup(?)

.const
IDD_KEYGEN equ 1001
IDC_EXIT equ 1002
IDC_COPY equ 1003
IDC_ABOUT equ 1004
IDC_NAME equ 1005
IDC_SERIAL equ 1006
IDC_MUSIC equ 1011
IDC_ID equ 1012
ICON equ 2001
LAYER equ 80000h
TRANSPAR equ 2
SLEEPTIME equ 20

;==============It can be necessary to change the minimum and/or maximum input before
;==============starting calculation of a serial. MaxNameRead should always be at
;==============least as big as MaxNameLength. Same for MaxIDRead.
MinNameLength equ 0
MaxNameLength equ 40
MaxNameRead equ 50
MinIDLength equ 0
MaxIDLength equ 9
MaxIDRead equ 50

;==============Unless you know what you are doing : don't change anything in
;==============the following till the next commented lines below.
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,1001,NULL,addr Main,NULL
invoke ExitProcess,eax

Main proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.if uMsg == WM_INITDIALOG
.elseif uMsg==WM_LBUTTONDOWN
invoke SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0
.elseif uMsg==WM_CTLCOLORDLG
mov eax, Brush
ret
.elseif uMsg==WM_COMMAND
mov eax,wParam
.if ax==IDC_NAME
shr eax,16
.if ax==EN_CHANGE
invoke GetDlgItemText,hWnd,IDC_NAME,addr NameBuffer,MaxNameRead
.if eax == 0
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr MoreInputTxt
.elseif eax < MinNameLength
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr MoreInputTxt
.elseif eax > MaxNameLength
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr TooLongTxt
.elseif
invoke GetDlgItemText,hWnd,IDC_ID,addr IDBuffer,MaxIDRead
.if eax == 0
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr MoreIDInputTxt
.elseif eax < MinIDLength
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr MoreIDInputTxt
.elseif eax > MaxIDLength
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr TooLongTxt
.elseif
invoke RtlZeroMemory,addr SerialBuffer,SerialBuffer
invoke SerialCalc,hWnd
.endif
.endif
.endif
.elseif ax==IDC_ID
shr eax,16
.if ax==EN_CHANGE
invoke GetDlgItemText,hWnd,IDC_ID,addr IDBuffer,MaxIDRead
.if eax == 0
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr MoreIDInputTxt
.elseif eax < MinIDLength
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr MoreIDInputTxt
.elseif eax > MaxIDLength
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr TooLongTxt
.elseif
invoke GetDlgItemText,hWnd,IDC_NAME,addr NameBuffer,MaxNameRead
.if eax ==0
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr MoreInputTxt
.elseif eax < MinNameLength
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr MoreInputTxt
.elseif eax > MaxNameLength
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr TooLongTxt
.elseif
invoke RtlZeroMemory,addr SerialBuffer,SerialBuffer
invoke SerialCalc,hWnd
.endif
.endif
.endif
.elseif eax==IDC_ABOUT
invoke MessageBox,hWnd,addr AboutTxt,addr AboutCaption,MB_OK
.elseif eax==IDC_COPY
invoke SendDlgItemMessage,hWnd,IDC_SERIAL,EM_SETSEL,0,-1
invoke SendDlgItemMessage,hWnd,IDC_SERIAL,WM_COPY,0,0
.elseif eax==IDC_MUSIC
.if Music==1
mov Music,0
invoke uFMOD_PlaySong,0,0,0
.elseif Music==0
mov Music,1
invoke uFMOD_PlaySong,addr table,XMSize,XM_MEMORY
.endif
.elseif eax==IDC_EXIT
invoke SendMessage,hWnd,WM_CLOSE,0,0
.endif
.elseif uMsg==WM_CLOSE
invoke uFMOD_PlaySong,0,0,0
mov Transparency,255
@@:
invoke Sleep,SLEEPTIME
sub Transparency,3
invoke SetLayeredWindowAttributes,hWnd,0,Transparency,TRANSPAR
cmp Transparency,0
jne @b
invoke DeleteObject,Brush
invoke EndDialog,hWnd,0
.endif
xor eax,eax
ret

Main endp

SerialCalc proc hWnd:HWND

;=============INFO : here starts the calculation for the serial like found in the KeygenMe at
;=============00411BA8. BTW, GetDlgItemTextA brings you here soon. (See 00401658)
;=============You'll notice that I left out the unnecessary part (from the call). All that
;=============this first Procedure does is converting the ID (which must be a number) in HEX.
;=============It becomes clear from the routine that it can only be digits 0 --> 9
;=============REMARK: the label names were only changed for better readability (B1,C1, etc)

lea esi,IDBuffer ;load the offset -where the name is stored- in edi
xor edi,edi ;<= 1st Procedure Start : ID-Number conversion in HEX
@B1:
mov bl,byte ptr ds:[esi]
inc esi
movsx eax,bl
cmp eax,0ffh
xor eax,eax
jbe @C1
xor eax,eax
@C1:
test eax,eax
jnz @B1
cmp bl,02bh
je @E1
cmp bl,02dh
jnz @F1
@E1:
cmp bl,02dh
sete al
and eax,1
mov bl,byte ptr ds:[esi]
inc esi
jmp @G1
@F1:
xor eax,eax
jmp @G1
@H1:
movsx ecx,bl
mov edx,edi
mov bl,byte ptr ds:[esi]
add edx,edx
lea edx,dword ptr ds:[edx+edx*4]
add edx,ecx
add edx,-030h
inc esi
mov edi,edx
@G1:
cmp bl,030h
jl @I1
cmp bl,039h
jle @H1
@I1:
test eax,eax
je @J1
mov eax,edi
neg eax
jmp @K1
@J1:
mov eax,edi
@K1: ;<= 1st Procedure End

;=============REMARK : the above 1st Procedure is in fact rather cumbersome.
;=============I've ripped this routine from the KeygenMe (as it is in there) but
;=============it can be replaced by a short and much clearer routine as follows
;=============(it is a code snippet I keep for using when dec --> hex is required)

; xor eax,eax
; lea esi,IDBuffer
; xor ecx,ecx
; xor edx,edx
; mov al,byte ptr ds:[esi]
; inc esi
; cmp al,2
; jnz @A1
; mov al,byte ptr ds:[esi]
; not edx
; inc esi
; jmp @A1
;@B1:
; sub al,030h
; lea ecx,dword ptr ds:[ecx+ecx*4]
; lea ecx,dword ptr ds:[eax+ecx*2]
; mov al,byte ptr ds:[esi]
; inc esi
;@A1:
; or al,al
; jnz @B1
; lea eax,dword ptr ds:[ecx+edx]
; xor eax,edx

;=============For completeness and FYIO: I would use the next line instead of the ripped code

;invoke GetDlgItemInt,hWnd,IDC_ID,addr IDBuffer,MaxIDRead

;=============It achieves exactly the same as the complete rip (coding in asm is GREAT!)
;=============However,this is not about coding, hence we will continue with the rip from the keygen

;=============The second Procedure in the serial calculation executes some algo on the name
;=============(in two parts uppercase/lowercase etc, just study it a bit better on your own)
;=============and adds it to the result from the ID conversion --> final result for serial
;=============This Procedure starts at 00408EB3 in the KeygenMe

lea edx,NameBuffer ;<= 2nd Procedure Start : Name
mov ecx,0a3h
mov ebx,05bh
add eax,05eh
imul esi,eax,01b0ah
xor eax,eax
jmp @L1
@M1:
cmp byte ptr ds:[edx+eax],041h
jl @N1
cmp byte ptr ds:[edx+eax],04eh
jle @O1
@N1:
cmp byte ptr ds:[edx+eax],061h
jl @P1
cmp byte ptr ds:[edx+eax],06eh
jg @P1
@O1:
movsx edi,byte ptr ds:[edx+eax]
add ecx,edi
@P1:
cmp byte ptr ds:[edx+eax],04fh
jl @Q1
cmp byte ptr ds:[edx+eax],05ah
jle @R1
@Q1:
cmp byte ptr ds:[edx+eax],06fh
jl @S1
cmp byte ptr ds:[edx+eax],07ah
jg @S1
@R1:
movsx edi,byte ptr ds:[edx+eax]
add ebx,edi
@S1:
inc eax
@L1:
cmp byte ptr ds:[edx+eax],0
jnz @M1
mov eax,ecx
imul ebx
add eax,esi ;<= 2nd Procedure End

invoke wsprintf,addr SerialBuffer,addr EndFormatToDec,eax ;write the final part of the serial (decimal)
invoke SetDlgItemText,hWnd,IDC_SERIAL,addr SerialBuffer ;display calculated serial
xor eax,eax
ret

SerialCalc endp

end start


Rips

lena151's KeygenMe 1 Solution

RIPS:
------------

RIP1

RIP2

RIP3

Copyright © 2006 lena151


Rip1

lena151's KeygenMe 1 Solution

RIP1:
------------

@lena151'_00411ba8: ;<= Procedure Start
push ebp
mov ebp,esp
push ebx
push esi
push edi
xor edi,edi
mov esi,[arg.1]
@lena151'_00411bb3:
mov bl,byte ptr ds:[esi]
inc esi
movsx eax,bl
push eax
call @lena151'_00413758 ;<= Jump/Call Address Not Resolved
pop ecx
test eax,eax
jnz @lena151'_00411BB3
cmp bl,02bh
je @lena151'_00411BCE
cmp bl,02dh
jnz @lena151'_00411BDC
@lena151'_00411bce:
cmp bl,02dh
sete al
and eax,1
mov bl,byte ptr ds:[esi]
inc esi
jmp @lena151'_00411BF4
@lena151'_00411bdc:
xor eax,eax
jmp @lena151'_00411BF4
@lena151'_00411be0:
movsx ecx,bl
mov edx,edi
mov bl,byte ptr ds:[esi]
add edx,edx
lea edx,dword ptr ds:[edx+edx*4]
add edx,ecx
add edx,-030h
inc esi
mov edi,edx
@lena151'_00411bf4:
cmp bl,030h
jl @lena151'_00411BFE
cmp bl,039h
jle @lena151'_00411BE0
@lena151'_00411bfe:
test eax,eax
je @lena151'_00411C08
mov eax,edi
neg eax
jmp @lena151'_00411C0A
@lena151'_00411c08:
mov eax,edi
@lena151'_00411c0a:
pop edi
pop esi
pop ebx
pop ebp
ret ;<= Procedure End


Rip2

lena151's KeygenMe 1 Solution

RIP2:
------------

@lena151'_00413758: ;<= Procedure Start
push ebp
mov ebp,esp
mov eax,[arg.1]
cmp eax,0ffh
jbe @lena151'_00413769
xor eax,eax
pop ebp
ret
@lena151'_00413769:
movzx edx,word ptr ds:[eax*2+041a2aah]
and edx,8
mov eax,edx
pop ebp
ret ;<= Procedure End


Rip3

lena151's KeygenMe 1 Solution

RIP3:
------------

@lena151'_00408eb3: ;<= Procedure Start
push ebp
mov ebp,esp
push ebx
push esi
push edi
mov edx,[arg.2]
mov ecx,0a3h
mov ebx,05bh
mov eax,[arg.1]
add eax,05eh
imul esi,eax,01b0ah
xor eax,eax
jmp @lena151'_00408F13
@lena151'_00408ed6:
cmp byte ptr ds:[edx+eax],041h
jl @lena151'_00408EE2
cmp byte ptr ds:[edx+eax],04eh
jle @lena151'_00408EEE
@lena151'_00408ee2:
cmp byte ptr ds:[edx+eax],061h
jl @lena151'_00408EF4
cmp byte ptr ds:[edx+eax],06eh
jg @lena151'_00408EF4
@lena151'_00408eee:
movsx edi,byte ptr ds:[edx+eax]
add ecx,edi
@lena151'_00408ef4:
cmp byte ptr ds:[edx+eax],04fh
jl @lena151'_00408F00
cmp byte ptr ds:[edx+eax],05ah
jle @lena151'_00408F0C
@lena151'_00408f00:
cmp byte ptr ds:[edx+eax],06fh
jl @lena151'_00408F12
cmp byte ptr ds:[edx+eax],07ah
jg @lena151'_00408F12
@lena151'_00408f0c:
movsx edi,byte ptr ds:[edx+eax]
add ebx,edi
@lena151'_00408f12:
inc eax
@lena151'_00408f13:
cmp byte ptr ds:[edx+eax],0
jnz @lena151'_00408ED6
mov eax,ecx
imul ebx
add eax,esi
pop edi
pop esi
pop ebx
pop ebp
ret ;<= Procedure End