Analyst's/Seifer's 3rd keygenme solution by _Nordic_/TMG
:----------------------------------------------------------------------------

This 3rd keygenme is considerably more difficult than the previous two, maybe
not  quite "newbie-level".  Still, not *very* difficult :-) Anyway, let's see
what we've got. 
(Note:  all offset references,  such as  :004011D1, are from w32dasm, and the
respective code is asm-only, no hex bytes)

First, there's a check for name length - preceding this code snippet, there's
a call to GetWindowTextA, for the user name, and after the call, eax contains
the name length:

:004011D1
        mov esi, eax		; move name length to esi
        test esi, esi		; is it (the length) 0?
        je 00401326		; yes, jump away, no more checking
        cmp esi, 00000040	; more than 40h (64dec) letters?
        ja 00401326		; yes, jump off, name too long

Then comes a check for the serial length.  This snippet is preceded by another
GetWindowTextA, for the serial, and its length again is in eax

:004011F5
        imul eax, 00000003			; ser len * 3
        shl eax, 02				; * 4
        add eax, 000000CD			; + 0CDh
        mov dword ptr [ebp-04], eax 	; move the result to [ebp-04]
        cmp dword ptr [ebp-04], 000001A5	; compare with 01A5h	 
        jne 004012CC				; if not, wrong ser. length
	
i.e. serial length must be = 12h digits

Then, some more checks about the serial's digits:

:00401212
        mov al, byte ptr [ebp-6C]
        test al, al			; is the first byte a zero?
        je 0040122C			; if yes, jump off, can't be
        lea ecx, dword ptr [ebp-6C]

and a small loop that checks:

:0040121C
        cmp al, 30			; is the serial digit less than 30hex?
        jb 004012EA			; if yes, jump off, wrong serial
        mov al, byte ptr [ecx+01] 	; is the next digit a zero?
        inc ecx				; if it is not, loop to the next digit,
        test al, al			; if yes, all digits have been checked
        jne 0040121C

After these preliminary tests, we move to the "main" keycheck routines.

First, there's a subroutine that converts a string (=4 bytes of it) into a hex
number. The first string to convert is "eheh", and the routine itself is like:

:0040104D
        mov al, byte ptr [ecx+esi+03]
        mov dl, byte ptr [ecx+esi+02]
        shl eax, 08
        add eax, edx
        xor edx, edx
        mov dl, byte ptr [ecx+esi+01]
        shl eax, 08
        add eax, edx
        xor edx, edx
        mov dl, byte ptr [ecx+esi]
        pop esi
        shl eax, 08
        add eax, edx
        ret

The necessity of converting strings this way is not quite clear to me - moving
4 string digits  as a dword  to a register,  will do just the same.  Maybe the
idea is  to confuse a newbie,  or maybe this is  a slight bug in the keycheck,
and it should in fact invert the byte order - I can't say.

Anyway, then  follows a small routine  that appends (concatenates)  the string
" is a whore.",  at the end  of the username.  This  routine starts  at offset
:0040109D, but I won't copy it here,  because I've "translated" it into a call
lstrcat,  offset username,  offset fxstr, in my source.  The end result is the
same, of course.

Next follows a hash loop between the username and the string "eheh":

:004010CC
        mov eax, dword ptr [ebp+08]		; prepare 4 bytes from
        push eax				; the appended name string
        push esi				; into a dword number
        call 00401040				; in this call
        mov ecx, dword ptr [esi+00405030]	; *1: ecx = value from a table
        add esp, 00000008			; adjust stack pointer, 
        xor ecx, edi				; after the call.
        add eax, ecx				; calculate a temporary value
        mov dword ptr [ebp-04], eax		; and store it into [ebp-04]
        rol dword ptr [ebp-04], 07		; rotate it left by 7
        mov eax, dword ptr [ebp-04]		; move it back to eax
        add esi, 00000004			; pointer to next table value
        xor ebx, eax				; xor the value with "eheh"
        inc edi					; increase the loop counter
        cmp esi, 00000040			; and check if it's 40h
        jl 004010CC				; if it's less, 
						; do another iteration

at *1:, we have a thingy  that gets  a dword value from a table.  Instead of a
dword, I've used a byte pointer in the source - the table dword values are all
less than 0FFh (=byte values) anyway. The table is like
	012h,05Ch,034h,022h,0ABh,09Dh,054h,00h,
	0DDh,084h,0AEh,066h,031h,078h,073h,0CFh

Then comes another loop, at

:00401257
        mov eax, dword ptr [ebp-04] 		; eax=the dword value from
        xor edx, edx				; the previous hash
        mov esi, 0000001A			; modulo 01Ah 
        div esi					; dl=a pointer to a table 
        mov dl, byte ptr [ebp+edx-000000F0]	; *2: Table, A -> Z
        mov byte ptr [ebp+ecx-38], dl		; move the byte into a temp. string
        mov eax, dword ptr [ebp-04]		; take the hash dword
        shl eax, 03				; divide it by 8
        mov edx, 00012345			; multiply it with 012345h
        imul eax				; 	
        add eax, edx				; add edx to it
        mov dword ptr [ebp-04], eax		; and store it as a new 
        inc ecx					; hash value
        cmp ecx, 00000012			; check if all 12h digits
        jb 00401257				; have been converted	

*2: A table consisting of capital letters, from A to Z, in alphabetical order.
	
After all this,  we have  a string of  12 capital letters  (referred  below as
tmpstr). This string is only dependent of the user name, as we saw. It will be
further  manipulated in the keycheck,  but for a keygen, it's the first of two
"important" strings.

As we saw, the (input) serial has not  come into play yet, but now is the time
to check it. The serial is compared with tmpstr, and this is how it works:

;-----------------------------------------------------------------------------
In  [ebp+6C],  we have the  (input) serial,  and in [ebp+38], the  tmpstr from
above. The keycheck does a bytewise xor with these two strings:

:0040128D
        mov cl, byte ptr [ebp+eax-6C]		; a digit from the serial
        mov dl, byte ptr [ebp+eax-38]		; a digit from tmpstr
        sub cl, 30			 	; serial digit-30h 
        xor dl, cl				; xor'ed with tmpstr digit
        mov byte ptr [ebp+eax-38], dl		; is moved to tempstr
        inc eax					; and the loop is done
        cmp eax, 00000012			; 12hex times
        jb 0040128D

after that, we have another temporary string, and it gets some more treatment,
like this;	

:00401116
        mov dl, byte ptr [eax+ecx]		; get a digit from tmpstr
        xor dl, al				; xor it with its position
        mov byte ptr [eax+ecx], dl		; in the string
        inc eax					; and repeat for all 12 digits.
        cmp eax, 00000012
        jb 00401116

Again, we have  a new temporary string,  and this string is compared,  byte by
byte, to "KEYGENNING4NEWBIES".  If these two strings are equal, "good guy, now
write a keygen", if not, back to the drawing-board.

Since xor is  a reversible operation, let's reverse  :-)  Taking the last code
snippet (:00401116 down),  and running "KEYGENNING4NEWBIES" through it, we get
a string "KD[DAKHNFN>EIZLFUB".
And again, reversing a xor,  if we run the second last code snippet (:0040128D
down), but giving it as inputs, the "original" tmpstr that was calculated from
the name, and instead of the fake serial, the "KD[DAKHNFN>EIZLFUB" string, and
instead of  subtracting 30h, adding 30h to the xor'ed bytes,  we get a working
serial for the input user name.

To ease a bit in  the writing of a keygen,  we  can enter "KD[DAKHNFN>EIZLFUB"
as  a fixed string,  since it is  always the same,  for any  input  user name.
The string "eheh" is of course dd 068656865h, and can be entered as such.

Attached is the source of my keygen. I didn't write it in a hi-level language,
though, although  Seifer so wished - sorry.  I hope that my explanations about
the workings of the algo are sufficient, however :-)
The algo is mostly  ripped & "streamlined" from the k4n3.exe.  I also wouldn't
recommend trying to translate this into VB - VB just isn't too good with dword
xor's or imul's, for instance. Delphi or Cpp would certainly do fine, although
it is  difficult to do a *small* (=below 30 kb) Delphi .exe - a very desirable
feature in  every keygen.  Anyway, for almost every keygen, it's best to rip &
reverse  the original as asm, IMHO - provided that you  understand  what EVERY
code snippet actually does :-) 

Greets,

_Nordic_/TMG