redCourtesy of Fravia's page of reverse engineering
A complete, nake protection code: Cracking WinZip32

CapedCrusader is slicing here all "the meat" out of a fat protection scheme, yummm, quite interesting reading IMHO... I get hundred of letters by people that ask me "Where can I learn assembly?" or "How can I understand and feel this code?" or "Could you please explain me the real meaning of this code..."
Stupid lamer questions? I don't think so: go in your local bookstores and have a look at the shelves: count the poor half-forgotten books about assembly! You'll be lucky if you find any at all! Compare with the infinite (and useless) books about, say, "How to understand MSIE during 12 dinner meals" or "Visual C++ for fat readers"
And yet it's so easy to learn assembly... It's so easy to get the power you want! Study commented ASM! Take a small windozed program and reverse it to the bones! Study the old DOS viruses, THEY used at times code tricks you can only dream of in these dire days of software programming decadence! Produce your own c programs and THEN have a look at them through winice and through wdasm, for Godzilla's sake!
And, right now, read the comments to the keygen by CapedCrusader below. Have a good reading, there is a lot to learn and understand for beginners and interemediate crackers and protectors alike in here!

~
31 march 1998, slightly edited by your truly+
student
Not Assigned


Cracking WinZip32 v6.3, by CapedCrusader

Back in the DOS days, I thought I was quite the cracker.  My friend ran
a BBS and I got him all the reg codes for his games.  Back then all a
rookie like myself had access to was Turbo Debugger for DOS.  But things
change.  Now that Windoze is around (probably to stay :( and now that
I've finally learned how to crack Windoze stuff, it seems I may be able
to catch up and relearn how to crack programs successfully.

So for my first (what I thought would be) difficult program, I decided
to develop a crack for WinZip.  After all, unless you want to spend time
opening and closing DOS windows, everybody will want a decent Windoze
zip program, right?  Most people I know have WinZip, so it seemed the
perfect target.  I even thought it would be a challenge.  Not quite, so
I guess this will have to be considered a basic crack.

Where do you start with a program like WinZip?  At first glance, it
looks like all you can do with it is remove the nag screen, because it
never openly asks you for a registration code.  The programmer obviously
must have been quite proud of this "protection" scheme.  Of course, for
any moron who uses WDasm, you quickly find that there are several
occurences of the word "register":

Name:       DialogID_0C82, # of Controls=009, Caption:"Register WinZip"
     008 - ControlID:0C84, Control Class:"" Control Text:"Continue
Unregistered"

and if that wasn't enough to convince you, along with many other
listings, I found this:

* Possible StringData Ref from Data Obj ->"Registered users, please enter "
                                        ->"your name and registration number "
                                        ->"EXACTLY as they appear on the"
                                        ->"instructions."

So I looked around thinking, "Maybe they're just trying to confuse
crackers?  After all, who'd actually try to hide something as obvious as
a registration command?"  For the sake of the programmers as well as
humanity, I pray that they simply couldn't find a better place to put
the registration information... yet the fact that their help files don't
give you any information about the instant registration makes me more
than a little skeptical.

Well, after a whopping 5 minutes I checked the help menu and "about".
Lo and behold, there it was, the "Register" command.  Jackpot!!  As you
would expect of me, I decided to try out a name and registration code.
To make things easier, it allows you to enter a maximum of 8 digits into
the reg code line.  Guess how many characters the reg code is?

Anyway, after entering the incorrect info, it yelled at me, saying the
following, "Incomplete or incorrect information.  Please press F1 for
help."  What a joke!  Not even a generic or hard to find message, but a
one-of-a-kind:

* Reference to String Resource ID=00654: "Incomplete or incorrect information"
                                  |
:00409D97 688E020000              push 0000028E
:00409D9C E82EB10100              call 00424ECF

well, let's not make this too hard.  Let's see what's right before this
code.  Maybe I'll even comment it for you... not that it needs it...

* Reference To: USER32.GetDlgItemTextA, Ord:00F5h
                                  |
:00409D58 FF15C86A4700            Call dword ptr [00476AC8]
:00409D5E 6A0A                    push 0000000A
:00409D60 6878F54600              push 0046F578

* Possible Reference to Dialog: DialogID_0C82, CONTROL_ID:0C81, ""
                                  |
:00409D65 68810C0000              push 00000C81
:00409D6A FF7508                  push [ebp+08]

* Reference To: USER32.GetDlgItemTextA, Ord:00F5h
                                  |
:00409D6D FF15C86A4700            Call dword ptr [00476AC8]

let me guess, we just got the name and reg code dialog boxes and saved
them into 471258 and 46F578, right?  In fact, 471258 is your name and
46F578 is your reg code.

:00409D73 0FB60558124700          movzx eax, byte ptr [00471258]
:00409D7A 85C0                    test eax, eax
:00409D7C 7414                    je 00409D92

if first letter of your name doesn't exist, goto "incomplete..." message
box, else pass to next check

:00409D7E 0FB60578F54600          movzx eax, byte ptr [0046F578]
:00409D85 85C0                    test eax, eax
:00409D87 7409                    je 00409D92

if first letter of reg code doesn't exist, goto "incomplete..." message
box,  else pass to next check

:00409D89 E85CF9FFFF              call 004096EA
:00409D8E 85C0                    test eax, eax
:00409D90 7541                    jne 00409DD3

and if eax is zero after a single, lone call, we go down to the bad
message.  Too easy.

* Referenced by a Jump at Addresses:00409D7C(C), :00409D87(C)
|
:00409D92 E805020000              call 00409F9C

* Reference to String Resource ID=00654: "Incomplete or incorrect information"
                                  |
:00409D97 688E020000              push 0000028E
:00409D9C E82EB10100              call 00424ECF

So here we are again, and all we can do is wonder, what's in that
function that makes eax be 0 or non-zero??  Well, if you want to waste
time reading through the code, go ahead.  I just ran softice, setting a
break point at the above function, and traced through it watching for
suspicious memory reading/writing.  I noticed lots of weird stuff going
on, but one really interesting thing caught my eye:

:004097DE 8D85F8FDFFFF            lea eax, dword ptr [ebp+FFFFFDF8]
:004097E4 50                      push eax
:004097E5 6858124700              push 00471258
:004097EA E8D4000000              call 004098C3

No, this wasn't the only function to send our friend 471258 (your name,
remember?) off to another call.  However, the eax that was pushed held a
memory address, and being the semi-intelligent person that I am, I
always observe memory addresses before and after a call, and I noticed
that this memory address suddenly contained a null-terminated 8-digit
string.  Gee, I wonder what this could be?  I punched in the code along
with my name and bingo, it worked.  How stupid could we get?  I spent
probably an hour to this point and I already had my very own reg code.
It wasn't even satisfying.  My string was 8 digits, of course, and had
numbers and a few letters: "B97719CA".  Looks much like a hex string,
no?

That's when I decided to get myself a reg code generator... stupid protection 
schemes deserve it, besides there are already many ready-made keygen on the web 
for the lemurs-lamers that want to steal this target, and so this one here, 
that I will explain you for studying purposes, won't damage anybody.

Let's look at the code that last function called.

:004098C3 55                      push ebp
:004098C4 8BEC                    mov ebp, esp
:004098C6 83EC10                  sub esp, 00000010
:004098C9 668365F400              and word ptr [ebp-0C], 0000
:004098CE 668365F800              and word ptr [ebp-08], 0000
:004098D3 8B4508                  mov eax, dword ptr [ebp+08]   ; get your name
:004098D6 8945FC                  mov dword ptr [ebp-04], eax   ; and save it locally
:004098D9 668365F000              and word ptr [ebp-10], 0000   ; set counter = 0
:004098DE EB13                    jmp 004098F3                  ; skip increment code

* Referenced by a Jump at Address:00409915(U)
|
:004098E0 668B45F0                mov ax, word ptr [ebp-10]
:004098E4 66050100                add ax, 0001                  ; add one to the
:004098E8 668945F0                mov word ptr [ebp-10], ax     ; counter
:004098EC 8B45FC                  mov eax, dword ptr [ebp-04]
:004098EF 40                      inc eax                       ; go to next letter
:004098F0 8945FC                  mov dword ptr [ebp-04], eax   ; of your name

* Referenced by a  at Address:004098DE(U)
|
:004098F3 8B45FC                  mov eax, dword ptr [ebp-04]   ; get your name
:004098F6 0FB600                  movzx eax, byte ptr [eax]     ; get one letter
:004098F9 85C0                    test eax, eax                 ; are we done?
:004098FB 741A                    je 00409917                   ; jump to end if so
:004098FD 8B45FC                  mov eax, dword ptr [ebp-04]   ; get your name again
:00409900 0FB600                  movzx eax, byte ptr [eax]     ; get letter again
:00409903 0FB74DF0                movzx ecx, word ptr [ebp-10]  ; and get counter
:00409907 0FAFC1                  imul eax, ecx                 ; letter
* counter
:0040990A 668B4DF4                mov cx, word ptr [ebp-0C]     ; and save result
:0040990E 6603C8                  add cx, ax                    ; into ongoing
:00409911 66894DF4                mov word ptr [ebp-0C], cx     ; secondary counter
:00409915 EBC9                    jmp 004098E0                  ; repeat until done

once we're done with that last step, we can look ahead and see that the
number we just got (ebp-0C) is combined with another (ebp-08) in a
function that has a printf string as a variable... an 8-digit
hexadecimal string.  Very clever of the WinZip team, converting a number
into hex and calling it a registration code.  The next few lines simply
prepare your name for the next function, which produces our last 4
digits.

* Referenced by a  at Address:004098FB(C)
|
:00409917 C7055C1C470001000000    mov dword ptr [00471C5C], 00000001
:00409921 8B4508                  mov eax, dword ptr [ebp+08]   ; get name again
:00409924 8945FC                  mov dword ptr [ebp-04], eax   ; save it again
:00409927 EB07                    jmp 00409930                  ; skip increment code

* Referenced by a  at Address:00409956(U)
|
:00409929 8B45FC                  mov eax, dword ptr [ebp-04]   ; get name
:0040992C 40                      inc eax                       ; increase letter
:0040992D 8945FC                  mov dword ptr [ebp-04], eax   ; and save

* Referenced by a  at Address:00409927(U)
|
:00409930 8B45FC                  mov eax, dword ptr [ebp-04]   ; get name
:00409933 0FB600                  movzx eax, byte ptr [eax]     ; get a letter
:00409936 85C0                    test eax, eax                 ; no letter left?
:00409938 741E                    je 00409958                   ; jump to end if none
:0040993A 6821100000              push 00001021                 ; ???
:0040993F 8B45FC                  mov eax, dword ptr [ebp-04]   ; get name
:00409942 660FB600                movzx ax, byte ptr [eax]      ; get a letter
:00409946 50                      push eax                      ; push the letter
:00409947 FF75F8                  push [ebp-08]                 ; push new ongoing
:0040994A E831000000              call 00409980                 ; counter and call...
:0040994F 83C40C                  add esp, 0000000C
:00409952 668945F8                mov word ptr [ebp-08], ax     ; save counter
:00409956 EBD1                    jmp 00409929                  ; jump to top again

* Referenced by a  at Address:00409938(C)
|
:00409958 668B45F8                mov ax, word ptr [ebp-08]     ; get counter
:0040995C 66056300                add ax, 0063                  ; add 0x0063
:00409960 668945F8                mov word ptr [ebp-08], ax     ; save again
:00409964 0FB745F4                movzx eax, word ptr [ebp-0C]  ; get last result,
:00409968 50                      push eax                      ; push it,
:00409969 0FB745F8                movzx eax, word ptr [ebp-08]  ; get this result,
:0040996D 50                      push eax                      ; push it,

* Possible StringData Ref from Data Obj ->"%04X%04X"
                                  |
:0040996E 6854394600              push 00463954                 ; push a printf code,
:00409973 FF750C                  push [ebp+0C]
:00409976 E865B90300              call 004452E0                 ; and call function
:0040997B 83C410                  add esp, 00000010
:0040997E C9                      leave
:0040997F C3                      ret

So we have the first result being used as the last four digits of the
reg code, and some second result as the first four.  After looking at
the function this will make more sense, but for now take my word.  The
function uses an ongoing counter (ebp-08) to play with the letters of
your name, and also this strange 1021.  Why they would push a constant
number for a function call when this number is never changed, either in
or out of the function is beyond me, unless they were REALLY stupid and
didn't even use their own function.  My guess is this is some coding
function, written in assembly by somebody semi intelligent, given to the
winzip people.  1021 must be some sort of key for their reg code
generator.  It hurts my head to think about the morons who programmed
this, so let's look at that function...

:00409980 55                      push ebp
:00409981 8BEC                    mov ebp, esp
:00409983 51                      push ecx
:00409984 668B450C                mov ax, word ptr [ebp+0C]     ; get letter (00xx)
:00409988 66C1E008                shl ax, 08                    ; move it over (xx00)
:0040998C 6689450C                mov word ptr [ebp+0C], ax     ; and save it
:00409990 8365FC00                and dword ptr [ebp-04], 0     ; counter = 0
:00409994 EB07                    jmp 0040999D                  ; skip counter++

* Referenced by a  at Address:004099DE(U)
|
:00409996 8B45FC                  mov eax, dword ptr [ebp-04]
:00409999 40                      inc eax                       ; counter++
:0040999A 8945FC                  mov dword ptr [ebp-04], eax

* Referenced by a  at Address:00409994(U)
|
:0040999D 837DFC08                cmp dword ptr [ebp-04], 8     ; counter>=8?
:004099A1 7D3D                    jge 004099E0                  ; if so, go to end
:004099A3 0FB74508                movzx eax, word ptr [ebp+08]  ; get ongoing counter
:004099A7 0FB74D0C                movzx ecx, word ptr [ebp+0C]  ; get letter
:004099AB 33C1                    xor eax, ecx                  ; xor them
:004099AD 2500800000              and eax, 00008000
:004099B2 85C0                    test eax, eax                 ; is eax >= 8000?
:004099B4 7412                    je 004099C8
:004099B6 0FB74508                movzx eax, word ptr [ebp+08]  ; if so, take the
:004099BA D1E0                    shl eax, 1                    ; ongoing counter*2
:004099BC 0FB74D10                movzx ecx, word ptr [ebp+10]  ; get 1021
:004099C0 33C1                    xor eax, ecx                  ; xor them
:004099C2 66894508                mov word ptr [ebp+08], ax     ; save into ongoing
:004099C6 EB0B                    jmp 004099D3                  ; counter & go down

* Referenced by a  at Address:004099B4(C)
|
:004099C8 668B4508                mov ax, word ptr [ebp+08]     ; eax was 0, so let's
:004099CC 66D1E0                  shl ax, 1                     ; just mult. ongoing
:004099CF 66894508                mov word ptr [ebp+08], ax     ; counter * 2, no xor

* Referenced by a  at Address:004099C6(U)
|
:004099D3 668B450C                mov ax, word ptr [ebp+0C]     ; get letter and
:004099D7 66D1E0                  shl ax, 1                     ; multiply by 2
:004099DA 6689450C                mov word ptr [ebp+0C], ax
:004099DE EBB6                    jmp 00409996                  ; go to top

* Referenced by a  at Address:004099A1(C)
|
:004099E0 668B4508                mov ax, word ptr [ebp+08]     ; return ongoing
:004099E4 C9                      leave                         ; counter and leave
:004099E5 C3                      ret

WOW!!  Yeah, that definately wasn't coded by the losers at WinZip.  Too
complicated.

When I made code generators for dos bbs games, I didn't know the first
thing about writing, compiling, or doing much of anything with my own
assembly routines.  I translated the assembly into C and wrote code
generators that way.  With the first function that wasn't too
tough, but with this one, we'll have to copy the assembly over, make a
few modifications, and either import it into a C program (as I did) or
include the first function and write the whole thing in assembly (much
more stylish, but I'm obsessive about coloring my regcode generators,
which is much easier in C than in assembly... to be honest I don't even
know how in assembly).

The first function goes like this: get a name, letter by letter,
multiply the letter by its subscript number and add to an ongoing
counter.  Convert the counter into a hex string and voila!  You have the
last 4 digits of your reg code!

The second function is much more involved, so take the following
assembly listing from me, or skip this and make your own.  You might
learn something that way...

--------------------------------------------------------------------------------

.MODEL MEDIUM

.CODE

.386

.RADIX 16

PUBLIC _function

_function PROC

ARG OldNumber:WORD, NewLetter:WORD, KeyCode:WORD

        push            bp
        mov             bp, sp
        push            ecx     ; get space for bp-04 in a stylish way

        mov             ax, NewLetter
        shl             ax, 08
        mov             NewLetter, ax
        and             dword ptr [bp-04], 00000000
        jmp             @POS1

@LOOP1: mov             eax, dword ptr [bp-04]
        inc             eax
        mov             dword ptr [bp-04], eax

@POS1:  cmp             dword ptr [bp-04], 00000008
        jge             @END
        movzx           ax, OldNumber
        movzx           cx, NewLetter
        xor             ax, cx
        and             ax, 00008000
        test            ax, ax
        je              @NO8000
        movzx           ax, OldNumber
        shl             ax, 1
        movzx           cx, KeyCode
        xor             ax, cx
        mov             OldNumber, ax
        jmp             @YES8000

@NO8000:mov             ax, OldNumber
        shl             ax, 1
        mov             OldNumber, ax

@YES8000:mov            ax, NewLetter
        shl             ax, 1
        mov             NewLetter, ax
        jmp             @LOOP1
@END:
        mov             ax, OldNumber

        pop             ecx
        pop             bp
        ret

_function endp
END

--------------------------------------------------------------------------------

This function can then be imported into a C program:

--------------------------------------------------------------------------------
#include <STDIO.h> #include <CONIO.h> #include <STRING.h> extern "C" int function(int OldNumber, int NewLetter, int KeyWord); void main() { char Name[50]; short Counter; short Return1 = 0, Return2 = 0; short KeyWrd = 0x1021; // We could make our own reg code generator // for our own lame programs just by changing // this number!! clrscr(); textcolor(4); cprintf("THIS PROGRAM IS ILLEGAL. HIT ENTER TO ABORT IF YOU HAVE ANY\r\n"); cprintf("PROBLEM WITH THAT FACT.\r\n\n"); textcolor(2); cprintf("WinZip32 Registration Generator by the Caped Crusader!\r\n"); textcolor(3); cprintf("Please enter your name: "); gets(Name); if (Name[0] == '\0') return; for (Counter = 1; Counter <INT)strlen(Name); Counter++) { Return2 += Name[Counter] * Counter; } for (Counter = 0; Counter < (int)strlen(Name); Counter++) { Return1 = function(Return1, Name[Counter], KeyWrd); } Return1 += 0x0063; textcolor(14); printf("Your serial number is: %04X%04X", Return1, Return2); } --------------------------------------------------------------------------------
And there you have it, a fully explained registration code
generator!  This being my first published crack for the +HCU, I refuse 
to have my name, coding style, obsessive use of color in a reg code 
generator, or lack of proper cracking terminology criticized without 
my prior consent.  

Enjoy,

                        --CapedCrusader--


redhomepage redlinks redanonymity red+ORC redstudents' essays redacademy database
redtools redcocktails redantismut CGI-scripts redsearch_forms redmail_fravia
redIs reverse engineering legal?