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 in 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 ;
Ex.
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).
Ex.
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.
Ex. 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!