|
|
Training session 34: Keygen Creation #2
Difficulty: Medium Learn how to create a keygen for Winzip
Creator: m101
Target: Winzip 7.*
URL: http://www.winzip.com
For this tutorial you will need both Softice and either Win32DASM or IDA. If you plan on compiling the Keygen provided herein, you will need atleast TASM v2.0. You should have a basic knowledge of assembly to attempt this tutorial. Although at the time of writing, Winzip 7 wasnt the latest release, the serial algorithm hasnt changed. The aim of this tutorial is not to teach you how to defeat Winzip, but however how to identify an algorithm in assembly, and then create a key generator for it.
The first thing to do is ofcourse, to find the serial generation algorithm. Open up Winzip and feed it some garbage. Now hit ok and a message box will popup with the message "Incomplete or incorrect information". Crack open Win32DASM and goto 'Refs > String Data References'. Search until you find our message and youll find yourself here:
:004080B0 EB31 jmp 004080E3
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00408051(C), :0040805A(C), :00408063(C)
|
:004080B2 E8E5010000 call 0040829C
* Possible Reference to String Resource ID=00654: "Incomplete or incorrect information"
|
:004080B7 688E020000 push 0000028E
:004080BC E89B050200 call 0042865C
:004080C1 59 pop ecx
:004080C2 50 push eax
:004080C3 57 push edi
Weve found our error dialog, so lets check out those conditional jumps. Notice they are all close together? This is generally due to input checking, and ofcourse our serial. So lets trace those jumps back a bit:
:00408030 68810C0000 push 00000C81
:00408035 57 push edi
* Reference To: USER32.GetDlgItemTextA, Ord:00F5h
|
:00408036 FF150C844600 Call dword ptr [0046840C]
:0040803C 56 push esi
:0040803D E857160200 call 00429699
:00408042 59 pop ecx
:00408043 56 push esi
:00408044 E879160200 call 004296C2
:00408049 803D28D9470000 cmp byte ptr [0047D928], 00 <is username longer than 0?
:00408050 59 pop ecx
:00408051 745F je 004080B2 <bad cracker
:00408053 803D58D9470000 cmp byte ptr [0047D958], 00 <is password longer than 0?
:0040805A 7456 je 004080B2 <bad cracker
:0040805C E8EAFAFFFF call 00407B4B <create serials
:00408061 85C0 test eax, eax <must return eax = 1
:00408063 744D je 004080B2 <bad cracker
:00408065 53 push ebx
A very nice GetDlgItemTextA right before our checking, this is an excellent opportunity to load up softice and just check what the each register points to before our tests/calls. As you can see, it checks the to make sure we gave it any input, performs a call, and finally returns a value that either tells us if our serial is correct or not. Trace the call down and you will find this:
* Referenced by a CALL at Addresses:
|:0040108C , :00401228 , :0040805C , :0042D0EC
|
:00407B4B 55 push ebp
:00407B4C 8BEC mov ebp, esp
:00407B4E 81EC08020000 sub esp, 00000208
:00407B54 53 push ebx
:00407B55 56 push esi
:00407B56 33F6 xor esi, esi
:00407B58 803D28D9470000 cmp byte ptr [0047D928], 00 <check for username
:00407B5F 57 push edi
:00407B60 0F84A1000000 je 00407C07 <bad cracker
:00407B66 8D45EC lea eax, dword ptr [ebp-14]
:00407B69 50 push eax
* Possible StringData Ref from Data Obj ->"c"
|
:00407B6A 6860F44600 push 0046F460
Yet again it checks for our input, and depending on the result, returns a bad vulue in eax from the call. Trace the code further down and you will find this:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407BF9(C)
|
:00407C0E 8D85C0FEFFFF lea eax, dword ptr [ebp+FFFFFEC0]
:00407C14 50 push eax
:00407C15 57 push edi <push username
:00407C16 E8AB000000 call 00407CC6 <calculate serial
:00407C1B 59 pop ecx <pop username
:00407C1C BE58D94700 mov esi, 0047D958
:00407C21 59 pop ecx <pop serial
:00407C22 8D85C0FEFFFF lea eax, dword ptr [ebp+FFFFFEC0]
:00407C28 56 push esi
:00407C29 50 push eax
:00407C2A E8D1FC0400 call 00457900
:00407C2F F7D8 neg eax
:00407C31 1BC0 sbb eax, eax
:00407C33 59 pop ecx
:00407C34 40 inc eax
:00407C35 59 pop ecx
:00407C36 A37CB04700 mov dword ptr [0047B07C], eax
From a little debuggin we find that the call at 00407C16 returns a correct serial according to our username. Follow the call through and you will find this:
:00407CD3 8A11 mov dl, byte ptr [ecx] <set dl to first character of username
:00407CD5 57 push edi
:00407CD6 33C0 xor eax, eax <clear eax
:00407CD8 8BF1 mov esi, ecx <set esi to point to username
:00407CDA 33FF xor edi, edi <clear edi
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407CF1(U)
|
:00407CDC 84D2 test dl, dl <is byte = 0?
:00407CDE 7413 je 00407CF3 <yes, then jump
:00407CE0 660FB6D2 movzx dx, dl <clear dh and leave dl alone
:00407CE4 8BDF mov ebx, edi <set ebx = edi
:00407CE6 0FAFDA imul ebx, edx <multiply char by position in username
:00407CE9 015DFC add dword ptr [ebp-04], ebx <accumulate ebx
:00407CEC 8A5601 mov dl, byte ptr [esi+01] <move next char into dl
:00407CEF 47 inc edi <edi = edi + 1
:00407CF0 46 inc esi <next character
:00407CF1 EBE9 jmp 00407CDC <loop
This little bit of code does a nice little thing to our serial. It returns the last four characters of our serial. Each hex value of the username is multiplied by its position relative to the first, then adds the results up leaving us with our value. This is illustrated below:
m 6D * 0 = 0
1 31 * 1 = 31
0 30 * 2 = 60
1 31 * 3 = 93
0 + 31 + 60 + 93 = 0124 <second half of serial
After calculating the second half of our serial, it obviously has to calculate the first half. This is done below:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407CDE(C)
|
:00407CF3 C705ECD3470001000000 mov dword ptr [0047D3EC], 00000001
:00407CFD 8BF1 mov esi, ecx <reset esi to start of username
:00407CFF 8A09 mov cl, byte ptr [ecx] <set cl = first character of username
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D1C(U)
|
:00407D01 84C9 test cl, cl <is byte = 0?
:00407D03 7419 je 00407D1E <yes, then jump
:00407D05 660FB6C9 movzx cx, cl <clear ch and leave cl alone
:00407D09 6821100000 push 00001021
:00407D0E 51 push ecx
:00407D0F 50 push eax
:00407D10 E82A000000 call 00407D3F
:00407D15 8A4E01 mov cl, byte ptr [esi+01] <set cl to next byte
:00407D18 83C40C add esp, 0000000C
:00407D1B 46 inc esi <next character
:00407D1C EBE3 jmp 00407D01 <loop
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D03(C)
|
:00407D1E 0FB74DFC movzx ecx, word ptr [ebp-04]
:00407D22 83C063 add eax, 00000063 <add 63 to the result
:00407D25 51 push ecx
:00407D26 0FB7C0 movzx eax, ax <leave first part of serial in ax
The mass of the routine relies on that call, so lets follow that through and see what it does:
* Referenced by a CALL at Addresses:
|:00407D10 , :00407DFB
|
:00407D3F 55 push ebp
:00407D40 8BEC mov ebp, esp
:00407D42 8B4508 mov eax, dword ptr [ebp+08]
:00407D45 56 push esi
:00407D46 33C9 xor ecx, ecx <reset ecx
* Possible Reference to String Resource ID=00008: "Delete files from %s"
|
:00407D48 6A08 push 00000008 <place 08 on stack
:00407D4A 8A6D0C mov ch, byte ptr [ebp+0C] <move character to ch
:00407D4D 5A pop edx <edx = 08
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D65(C)
|
:00407D4E 8BF1 mov esi, ecx <esi = ecx
:00407D50 33F0 xor esi, eax <xor esi with current sum
:00407D52 66F7C60080 test si, 8000 <is si greater than 8000?
:00407D57 7407 je 00407D60 <yes, then jump
:00407D59 03C0 add eax, eax <eax = eax * 2
:00407D5B 334510 xor eax, dword ptr [ebp+10] <xor eax with 1021
:00407D5E EB02 jmp 00407D62
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D57(C)
|
:00407D60 D1E0 shl eax, 1 <eax = eax * 2
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D5E(U)
|
:00407D62 D1E1 shl ecx, 1 <ecx = ecx * 2
:00407D64 4A dec edx <loop count = loop count - 1
:00407D65 75E7 jne 00407D4E <jump after 8th loop
:00407D67 5E pop esi
:00407D68 5D pop ebp
:00407D69 C3 ret
As the code is above, it is not in an easy to read form, even with commenting. This is because the code has been pepered either by the creators, or by the compiler. To make this a little less confusing, I have removed the junk code as shown below:
-----------------------------------------------------------------------------------------------------------------
Second half of serial
-----------------------------------------------------------------------------------------------------------------
:00407CD3 8A11 mov dl, byte ptr [ecx] <set dl to first character of username
:00407CD6 33C0 xor eax, eax <clear eax
:00407CD8 8BF1 mov esi, ecx <set esi to point to username
:00407CDA 33FF xor edi, edi <clear edi
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407CF1(U)
|
:00407CDC 84D2 test dl, dl <is byte = 0?
:00407CDE 7413 je 00407CF3 <yes, then jump
:00407CE0 660FB6D2 movzx dx, dl <clear dh and leave dl alone
:00407CE4 8BDF mov ebx, edi <set ebx to loop number
:00407CE6 0FAFDA imul ebx, edx <multiply character by loop number
:00407CE9 015DFC add dword ptr [ebp-04], ebx <accumulate ebx
:00407CEC 8A5601 mov dl, byte ptr [esi+01] <move next char into dl
:00407CEF 47 inc edi <edi = edi + 1
:00407CF0 46 inc esi <next character
:00407CF1 EBE9 jmp 00407CDC <loop
-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------
First half of serial
-----------------------------------------------------------------------------------------------------------------
:00407CFD 8BF1 mov esi, ecx <reset esi to start of username
:00407CFF 8A09 mov cl, byte ptr [ecx] <set cl = 1st char of username
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D1C(U)
|
:00407D01 84C9 test cl, cl <is byte = 0?
:00407D03 7419 jz 00407D1E <yes, then jump
:00407D05 660FB6C9 movzx cx, cl <clear ch and leave cl alone
:00407D46 33C9 xor ecx, ecx <reset ecx
:00407D4A 8A6D0C mov ch, byte ptr [ebp+0C] <move character to ch
:00407D4D 5A mov edx, 08 <edx = 08
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D65(C)
|
:00407D4E 8BF1 mov esi, ecx <esi = ecx
:00407D50 33F0 xor esi, eax <xor esi with current sum
:00407D52 66F7C60080 test si, 8000 <is si greater than 8000?
:00407D57 7407 jz 00407D60 <yes, then jump
:00407D59 03C0 add eax, eax <eax = eax * 2
:00407D5B 334510 xor eax, 1021 <xor eax with 1021
:00407D5E EB02 jmp 00407D62
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D57(C)
|
:00407D60 D1E0 shl eax, 1 <eax = eax * 2
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D5E(U)
|
:00407D62 D1E1 shl ecx, 1 <ecx = ecx * 2
:00407D64 4A dec edx <loop count = loop count - 1
:00407D65 75E7 jnz 00407D4E <jump until 8th loop
:00407D15 8A4E01 mov cl, byte ptr [esi+01] <set cl to next byte
:00407D1B 46 inc esi <next character
:00407D1C EBE3 jmp 00407D01 <loop
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407D03(C)
|
:00407D22 83C063 add eax, 00000063 <add 63 to the result
:00407D26 0FB7C0 movzx eax, ax <leave 1st part of serial in ax
-----------------------------------------------------------------------------------------------------------------
Follow this code through in Softice if you have to, and you will get a good idea of what is happening. Now try coding up a keygen for yourself. For an input of 'm101' you should arrive with something like AX = B189 BX = 0124, a total result of 'B1890124' as your serial.
Here is the keygen i coded:
-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------
MAIN SEGMENT
ASSUME DS:MAIN,ES:MAIN,CS:MAIN,SS:MAIN
ORG 100h
START:
MOV AH,09h
MOV DX,OFFSET TEXTB
INT 21h
MOV AH,02h
MOV DL,0Ah
INT 21h
MOV AH,09h
MOV DX,OFFSET TEXTC
INT 21h
MOV AH,02h
MOV DL,0Ah
INT 21h
MOV AH,09h
MOV DX,OFFSET TEXT
INT 21h
MOV DI,OFFSET INPUTTER - 1
TAKENEXTINPUT:
STOSB
XOR AX,AX
INT 16h
MOV AH,02h
MOV DL,AL
INT 21h
CMP AL,0dh
JNZ TAKENEXTINPUT
MOV AL,0h
STOSB
MOV AH,02h
MOV DL,0Ah
INT 21h
XOR DX,DX
XOR CX,CX
XOR AX,AX
MOV SI,OFFSET INPUTTER
NEXTCHAR:
LODSB
TEST AL,AL
JZ LASTCHAR
XOR AH,AH
PUSH DX
MUL DX
ADD CX,AX
XOR AX,AX
POP DX
INC DX
JMP NEXTCHAR
LASTCHAR:
PUSH CX
XOR AX,AX
XOR BX,BX
XOR CX,CX
XOR DX,DX
MOV SI,OFFSET INPUTTER
SUMNATION:
LODSB
TEST AL,AL
JZ FINISHED
MOV AH,AL
XOR AL,AL
MOV DI,08h
ITTERATE:
MOV CX,AX
XOR CX,BX
TEST CX,8000h
JZ JUMPER
ADD BX,BX
XOR BX,1021h
JMP JUMPY
JUMPER:
SHL BX,1h
JUMPY:
SHL AX,1h
DEC DI
JNZ ITTERATE
JMP SUMNATION
FINISHED:
ADD BX,63h
MOV AX,BX
POP BX
XOR CX,CX
MOV BYTE PTR [OFFSET HEXA],AH
MOV BYTE PTR [OFFSET HEXA+1],AL
MOV BYTE PTR [OFFSET HEXA+2],BH
MOV BYTE PTR [OFFSET HEXA+3],BL
XOR AX,AX
XOR BX,BX
XOR DX,DX
MOV SI,OFFSET HEXA
MOV AH,09h
MOV DX,OFFSET TEXTD
INT 21h
XOR AX,AX
ALPHACHARLOOP:
LODSB
SHL AX,1h
SHL AX,1h
SHL AX,1h
SHL AX,1h
SHR AL,1h
SHR AL,1h
SHR AL,1h
SHR AL,1h
CMP AL,0Ah
JL ALPHA
ADD AL,37h
JMP ALPHANUMERIC
ALPHA:
ADD AL,30h
ALPHANUMERIC:
CMP AH,0Ah
JL ALPHAB
ADD AH,37h
JMP ALPHANUMERICB
ALPHAB:
ADD AH,30h
ALPHANUMERICB:
MOV BX,AX
MOV AH,02h
MOV DL,BH
INT 21h
MOV AH,02h
MOV DL,BL
INT 21h
XOR AX,AX
CMP SI, OFFSET HEXA+4
JNZ ALPHACHARLOOP
MOV AH,02h
MOV DL,0Ah
INT 21h
MOV AH,09h
MOV DX,OFFSET TEXTE
INT 21h
XOR AX,AX
INT 16h
INT 20h
TEXT DB 'Username: $'
TEXTB DB 'Winzip 7.* Keygen$'
TEXTC DB 'Cracked by m101 for Phrozen Crew$'
TEXTD DB 'Your Serial is: $'
TEXTE DB 'Press any key to quit...$'
INPUTTER DB 'aaa$'
HEXA DB 'aaaa$'
MAIN ENDS
END START
-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------
Although the keygen isnt as small as it could be, for a tiny 376 bytes after being compiled i wouldnt complain. I encourage you to just continuously keep going through the code until it makes more sense to you. After reading this, you hopefully will have a better understanding of how programs generate and check serials.
|
|