Cracking WinZip Self-Extractor General Key
How a seemingly strong protection falls apart

Target WinZip Self-Extractor (v2.1) - not the personal version which is included with WinZip
Protection Serial
Tools used SoftIce 4.0 and IDA 3.75
Level (X) Beginner (X) Intermediate ( )Advanced ( ) Expert

Written by The+Q

Winzip Self-Extractor has a name/serial algorithem , but as we'll see it also has a generic key (serial) - one that will register our software with any name we input. I can't see why someone would put such a scheme in his software , but the fact is its there. Here we'll study and reverse this nice scheme.

Finding the generic-key check
Fill Organization with 'Name' and Registration # with 'Serial123'.
In SoftIce
Bpx GetDlgItemTextA , and click ok.
Once i
n SI , pret (F12) and we're at the beginning of the protection. We are at 408DE4 . Time for IDA.

IDA catches the VC runtime library signature (vc32rtf) , that means more easy analyzing =)
Importent: IDA is a powerfull tool, when working with it be effective; that means add comments, change addresses from byte_42C720 to NameEntered , and sub_408BE6 to MainCheckProc.
Working like this will not only make your disassembly look more intelligent , but you can also use these
comments and names inside SoftIce! (more on this later).

Ok, after the program gets the name and serial it changes the input serial to UPCASE, and verifies its length is not equal to 8, if it is - error.
Next the main checking procedure is called (408BE6) :

00408C01 Begin_Checks:	 movzx	 eax, NameEntered
00408C08		 test	 eax, eax	   ; Zero length?
00408C0A		 jnz	 NAMEeSERIAL?
00408C10		 xor	 eax, eax
00408C12		 jmp	 Error_End

00408C17 NAMEeSERIAL?:   push	 offset	SerialEntered
00408C1C		 push	 offset	NameEntered
00408C21		 call	 __strcmpi	   ; Name==Serial ?
00408C26		 add	 esp, 8
00408C29		 test	 eax, eax
00408C2B		 jnz	 GenKeyCheck
00408C31		 xor	 eax, eax	   ; If they're equal - exit
00408C33		 jmp	 Error_End

00408C38 GenKeyCheck:	 push	 offset	SerialEntered
00408C3D		 call	 General_Key?
00408C42		 add	 esp, 4
00408C45		 test	 eax, eax          ; If SerialEntered==General_Key then registered
00408C47		 jz	 NameSerialAlgo	   ; else try Name/Serial scheme
00408C4D		 mov	 GeneralKeyFlag, 1 ; Registration flags!
00408C57		 mov	 RegisteredFlag, 1
00408C61		 jmp	 loc_408CD6

00408C66 NameSerialAlgo: lea	 eax, [ebp+var_104]
00408C6C		 push	 eax
00408C6D		 push	 offset	NameEntered
00408C72		 call	 Create_V_Key      ; Create valid serial from NameEntered
00408C77		 push	 offset	SerialEntered
00408C7C		 lea	 eax, [ebp+var_104]
00408C82		 push	 eax
00408C83		 call	 __strcmpi         ; and compare with SerialEntered
00408C88		 add	 esp, 8
00408C8B		 test	 eax, eax
00408C8D		 jnz	 loc_408CA2        ; If equal then registered
00408C93		 mov	 RegisteredFlag, 1 ; Registration Flag!
00408C9D		 jmp	 loc_408CAC

Well , well .. there's a GeneralKey procedure along with Name/Serial procedure. We'll crack the general key scheme.

Analyzing the General-Key scheme

00412D60 General_Key?:   push	 ebp
00412D61		 mov	 ebp, esp
00412D63		 sub	 esp, 20h
00412D66		 push	 ebx
00412D67		 push	 esi
00412D68		 push	 edi
00412D69		 mov	 dword ptr [ebp-1Ch], offset Valid_Key_Chars
00412D70		 push	 dword ptr [ebp+8]
00412D73		 call	 __strupr
00412D78		 add	 esp, 4
00412D7B		 mov	 dword ptr [ebp-18h], 0
00412D82		 mov	 dword ptr [ebp-10h], 0
00412D89		 mov	 eax, [ebp-18h]
00412D8C		 mov	 ecx, [ebp+8]
00412D8F		 movzx	 eax, byte ptr [eax+ecx]
00412D93		 cmp	 eax, 59h	 ; Serial[0]=='Y' ?
00412D96		 jz	 loc_412DA3
00412D9C		 xor	 eax, eax
00412D9E		 jmp	 loc_412E8F

00412DA3 loc_412DA3:	 inc	 dword ptr [ebp-18h]
00412DA6		 mov	 eax, [ebp-18h]
00412DA9		 mov	 ecx, [ebp+8]
00412DAC		 movzx	 eax, byte ptr [eax+ecx]
00412DB0		 test	 eax, eax
00412DB2		 jz	 loc_412E1E
00412DB8		 shl	 dword ptr [ebp-10h], 4
00412DBC		 mov	 dword ptr [ebp-8], 0
00412DC3		 jmp	 loc_412DCB

00412DC8 loc_412DC8:	 inc	 dword ptr [ebp-8]

00412DCB loc_412DCB:	 cmp	 dword ptr [ebp-8], 10h
00412DCF		 jge	 loc_412E01
00412DD5		 mov	 eax, [ebp-18h]
00412DD8		 mov	 ecx, [ebp+8]
00412DDB		 movzx	 eax, byte ptr [eax+ecx]
00412DDF		 mov	 ecx, [ebp-8]
00412DE2		 mov	 edx, [ebp-1Ch]
00412DE5		 movzx	 ecx, byte ptr [ecx+edx]
00412DE9		 cmp	 eax, ecx
00412DEB		 jnz	 loc_412DFC
00412DF1		 mov	 al, [ebp-8]
00412DF4		 mov	 [ebp-14h], al
00412DF7		 jmp	 loc_412E01

00412DFC loc_412DFC:	 jmp	 loc_412DC8

00412E01 loc_412E01:	 cmp	 dword ptr [ebp-8], 10h
00412E05		 jnz	 loc_412E12
00412E0B		 xor	 eax, eax
00412E0D		 jmp	 loc_412E8F

00412E12 loc_412E12:	 movzx	 eax, byte ptr [ebp-14h]
00412E16		 or	 [ebp-10h], eax
00412E19		 jmp	 loc_412DA3

00412E1E loc_412E1E:	 mov	 al, [ebp-10h]
00412E21		 mov	 [ebp-0Ch], al
00412E24		 and	 byte ptr [ebp-10h], 0
00412E28		 mov	 ecx, 18h
00412E2D		 movzx	 eax, byte ptr [ebp-0Ch]
00412E31		 cdq
00412E32		 idiv	 ecx
00412E34		 mov	 [ebp-18h], edx

00412E37 loc_412E37:	 mov	 eax, [ebp-18h]
00412E3A		 mov	 [ebp-20h], eax
00412E3D		 dec	 dword ptr [ebp-18h]
00412E40		 cmp	 dword ptr [ebp-20h], 0
00412E44		 jz	 loc_412E60
00412E4A		 test	 byte ptr [ebp-0Dh], 80h

00412E4E loc_412E4E:     jz	 loc_412E58
00412E54		 or	 byte ptr [ebp-10h], 80h

00412E58 loc_412E58:	 shl	 dword ptr [ebp-10h], 1
00412E5B		 jmp	 loc_412E37

00412E60 loc_412E60:     int	 3
00412E61		 jnz	 short near ptr	loc_412E4E+5
00412E63		 call	 Hash_Proc
00412E68		 add	 esp, 4
00412E6B		 mov	 [ebp-4], al
00412E6E		 movzx	 eax, byte ptr [ebp-0Ch]
00412E72		 movzx	 ecx, byte ptr [ebp-4]
00412E76		 cmp	 eax, ecx	 ; Last	Check
00412E78		 jnz	 loc_412E88
00412E7E		 mov	 eax, 1
00412E83		 jmp	 loc_412E8A

00412E88 loc_412E88:	 xor	 eax, eax


00412E94 Hash_Proc:	 push	 ebp
00412E95		 mov	 ebp, esp
00412E97		 sub	 esp, 0Ch
00412E9A		 push	 ebx
00412E9B		 push	 esi
00412E9C		 push	 edi
00412E9D		 and	 [ebp+arg_0], 0
00412EA1		 mov	 eax, dword ptr	[ebp+arg_0]
00412EA4		 and	 eax, 0FFF00h
00412EA9		 shr	 eax, 8
00412EAC		 mov	 [ebp+var_8], eax
00412EAF		 mov	 eax, [ebp+var_8]
00412EB2		 shl	 eax, 4
00412EB5		 xor	 eax, [ebp+var_8]
00412EB8		 xor	 al, 0Fh
00412EBA		 mov	 [ebp+var_4], al
00412EBD		 mov	 [ebp+var_C], 1Eh
00412EC4		 jmp	 loc_412ECD

00412EC9 loc_412EC9:	 sub	 [ebp+var_C], 4
00412ECD loc_412ECD:	 cmp	 [ebp+var_C], 8
00412ED1		 jle	 loc_412F1A
00412ED7		 mov	 eax, 0Fh
00412EDC		 mov	 cl, byte ptr [ebp+var_C]
00412EDF		 shl	 eax, cl
00412EE1		 and	 eax, dword ptr	[ebp+arg_0]
00412EE4		 mov	 cl, byte ptr [ebp+var_C]
00412EE7		 shr	 eax, cl
00412EE9		 movzx	 eax, al
00412EEC		 xor	 al, [ebp+var_4]
00412EEF		 mov	 [ebp+var_4], al
00412EF2		 movzx	 eax, [ebp+var_4]
00412EF6		 test	 al, 0F0h
00412EF8		 jz	 loc_412F12
00412EFE		 movzx	 eax, byte ptr [ebp+var_8]
00412F02		 movzx	 ecx, [ebp+var_4]
00412F06		 xor	 eax, ecx
00412F08		 not	 eax
00412F0A		 mov	 [ebp+var_4], al
00412F0D		 jmp	 loc_412F15

00412F12 loc_412F12:	 shl	 [ebp+var_4], 1
00412F15 loc_412F15:	 jmp	 loc_412EC9

00412F1A loc_412F1A:	 mov	 al, [ebp+var_4]

I bet this looks pretty cryptic right now :) Debugging this procedure will surely help you follow the scheme.
Here's what the code does:

1) 1st char of SerialEntered must be 'Y'. If its not -> error-exit.

2) Create a 32-bit Key (KeyA) from the rest of SerialEntered chars, using the following table:

Char in serial 2 3 4 7 8 9 A B C D E F H J K M
Char in KeyA 0 1 2 3 4 5 6 7 8 9 A B C D E F

So if we enter 'Y234BM' as serial , KeyA will be : 0x0000127F
SerialEntered='Y42JKBA' ,
KeyA = 0x0020DE76 , and so on.
If serial has a char that is not on the list ('G' for example) then error-exit.

3) From KeyA define 2 numbers: A1= KeyA && 0xFF ; A2= KeyA && 0xFFFFFF00 ;
KeyA=0x0020DE76 ; A1=0x76 ; A2=0x0020DE00;

4) Define KeyB = A2 R-O-L (A1 mod (0x18));
R-O-L is a variation of the normal rotate left operator (rol).
A1=0x76 ; 0x76 mod 0x18 = 0x16 ; A2=0x0020DE00 ; KeyB= 0x0020DE00 R-O-L 0x16 = 0x80083700;

5) KeyC = Hash_Proc (KeyB);
Hash_Proc is the function at offset 412E94.
KeyB=0x80083700 ; KeyC=0x45;

6) If KeyC == A1 then registered!!
In our example 0x45 is not 0x76, so 'Y42JKBA' is not a correct serial.

Looks pretty tough eh? 8-)

Reversing the scheme
Lets understand this a little better. A1 is ofcourse the last 2 digist of KeyA (thanks to the && 0xFF) , which means it corresponds to the last 2 chars in the serial. In the above example A1=0x76 , and by the table we see it corresponds to 'BA' - the last 2 chars in the serial. Now A1 tells how much to rotate A2 (all the other chars in the serial) to the left. The result is an input for a hash function (one-way function). The result from the hash function is compared with A1; if its equal , then registered.

So, what do you say ? Brute-force on KeyA??
NO! Lets think abit more. If A2 = 0 , then KeyB = 0 (no matter how much you rotate zero , you'll get zero:),
now what about KeyC? KeyC = Hash_Proc( 0x00000000 ) . If we put THIS KeyC in A1 , we will pass
the protection becouse nomatter how much A1 will rotate zero , we'll get zero!!
Lets see, with SI we input zero to Hash_Proc:
KeyC = Hash_Proc(0x00000000) = 0xE1
So we know A1 should be 0xE1, and A2 should be zero, how does it add up with our serial?
Easy.. A1 is the last 2 chars in the serial , so they should be 'K3' (check the table)
and A2 is all the other chars in the serial . You can eigther put '2' chars in the middle , or no chars at all!
So Winzip-SE general serial is : 'YK3'

Last Words
What can we conclude from this target?
We can have faith that even protections that apear strong , can fall apart becouse thier authors tried too hard.

Cheers to all the crackers in the world! =)
The+Q (02.12.00)


Appendix: IDA , map files and SoftIce
Here we'll see how to debug properly. That means using the names and comments (and all other symbols) from IDA , in SoftIce.

The trick is producing a .MAP file from IDA . Than using Msym.exe (inside winice\Util16 directory) convert the map file to .SYM file. Next step load this file with SI Symbol loader (loader32.exe) to softice. Load the target , and relocate the symbol table to the target offset (SYMLOC command in SI).
It takes some getting used to (SI with full symbol support) , but it sure worths it!Once more , thank you NuMega!