Swimming upstream...

an essay by:



Published by Tsehp


zipcrack 2.0 by Paul Kocher.
It is a very very old stuff designed in 1992 by a famous cryptographer (author of Differential Power Analysis attacks) to demonstrates weaknesses in the encryption algorithm used by PKZIP 1.0.

The program is crippled and can only search for passwords beginning with the letter 'z' (seems simple..). Moreover the 'optimized search feature' which was using undocumented properties of the encryption header doesn't work anymore with the new version of the pkzip algorithm. So you can't use the optimized search feature.

But it doesn't matter, nowadays you can find a lot of efficient tools to crack zip passwords. These tools allow you dictionnary attacks, brute force , known plaintext attacks...try zipkey or advanced zip password recovery if you want to play with some of them..

My goal is not to provide you the latest keygens or patches for the latest commercial releases. My goal is to learn and to share what i've learned!!.

If you follow this tut, you should at least learn something about the pkzip stream cipher (i did!).

Tools used:

Turbo Debugger for DOS,
Hedit 2.11,
a crypted archive (password: reverse)
Windows calculator

Let's go now!

Launch turbo debugger, open the program and in the run menu choose the 'arguments...' entry to enter the full path of the crypted zip archive.
As you can read in the zipcrack documentation, the program can only search for passwords beginning with the letter 'z'. Without being a wizard i can feel it...a closer look at my ascii table inform me that the code for the letter 'z' is 0x7A so let's search into the program source for the value '7A'.

you should find something like this:

cs:0B21 80BEFEFE7A      cmp byte ptr [bp-0102],7A
cs:0B26 7507            jne 0B2F
cs:0B28 80BEFEFD7A      cmp byte ptr [bp-0202],7A
cs:0B2D 7415            je 0B44
cs:0B2F 833E423300      cmp word ptr [3342],0000
cs:0B34 750E            jne 0B44
cs:0B36 B8EC3A          mov ax,3AEC
cs:0B39 50              push ax
cs:0B3A E81511          call 1C52
cs:0B3D 59              pop cx
cs:0B3E C70642330100    mov word ptr [3342],0001

cs:0B44 C686FEFE7A      mov byte ptr [bp-0102],7A
cs:0B49 C686FEFD7A      mov byte ptr [bp-0202],7A
cs:0B4E C6047A          mov byte ptr [si],7A
cs:0B51 837E0600        cmp word ptr [bp+06],0000
cs:0B55 7417            je 0B6E
cs:0B57 B8703F          mov ax,3F70
cs:0B5A 50              push ax
cs:0B5B 8BC6            mov ax,si
cs:0B5D 40              inc ax
cs:0B5E 50              push ax
cs:0B5F 8D86FFFD        lea ax,[bp-0201]
cs:0B63 50              push ax
cs:0B64 8D86FFFE        lea ax,[bp-0101]
cs:0B68 50              push ax
cs:0B69 E8EF05          call 115B
cs:0B6C EB15            jmp 0B83

cs:0B6E B8723F          mov ax,3F72
cs:0B71 50              push ax
cs:0B72 8BC6            mov ax,si
cs:0B74 40              inc ax
cs:0B75 50              push ax
cs:0B76 8D86FFFD        lea ax,[bp-0201]
cs:0B7A 50              push ax
cs:0B7B 8D86FFFE        lea ax,[bp-0101]
cs:0B7F 50              push ax
cs:0B80 E83900          call 0BBC
cs:0B83 83C408          add sp,0008
cs:0B86 5F              pop di
cs:0B87 5E              pop si
cs:0B88 8BE5            mov sp,bp
cs:0B8A 5D              pop bp
cs:0B8B C3              ret

Ok, it looks like we have found the trick!, put a breakpoint on the first cmp instruction and run the program (do not forget to provide it the zip file as an argument).
Don't use the optimized search.
Type *reverse and press enter.

The program breaks. Dump memory at [bp-102]...you see the same thing as me? this is the password we typed!
Wonderful, the logic here is quite simple:

The program tests if the string entered begins with the letter 'z', if not the program jumps and tests cmp word ptr [3342],0000 if it has already displayed the advertisement saying that it can only find passwords beginning with the letter 'z'.
After this, it forces the first letter to 'z', tests if you want optimized search or not and branches according to your choice.

It seems that all we have to do here, is to change the jne 0B2F by a jmp 0B2F.
So launch you favorite hex editor, open zipcrack.com, search for
750780BEFEFD7A and replace 7507 by EB29.

Let's verify: open a dos window and launch zipcrack with the good argumen t. I say yes, you say no, i say yes, you say no la la lala la la lala ooppss it's not time for the Beatles yet! go on and type *reverse and smash the enter key to see the result of your hard work...!!
NOTHING HAPPENS....maybe this program doesn't work well...but no, it works, if you test it upon an archive crypted with a password beginning by 'z' it prints 'MATCH'.

So, where is the problem?

To catch it, we have to trace deeper in the program and have a look into the pkzip stream cipher implementation.
Re-load the program in turbo debugger, but a breakpoint on the cmp and run it!
When the program breaks, go on tracing step by step into the code until you reach the
call 0BBC.
Have you noticed that the ax register is loaded and pushed with the address of the string you entered + 1 ? seems odd!
Go on tracing and enter into the call function until you r each these lines:

cs:0BFA 66C706783F7A55+mov dword ptr [3F78],EE1C557A
cs:0C03 66C7067C3F1094+mov dword ptr [3F7C],
cs:0C0C 66C706803FC476+mov dword ptr [3F80],

Do you feel it? it looks like crypto stuff initialization or something like that. At this point it is time to have a closer look into the pkzip stream cipher specification.
I suggest you a paper published by +Tsehp called '
ZIP Attacks with Reduced Known-Plaintext', the paragraph 1.1 of this paper descibes the cipher and say that:

'...The internal state of the cipher consists of three 32-bit words: key0, key1, key2. thes values are initialized to 0x12345678, 0x23456789, 0x34567890, respectively...'

It doesn't look like the instructions we found in the program!!!!

'..The cipher is keyed by encrypting the user password and throwing away the corresponding stream bytes. The stream bytes produced after this point are XORed with the plaintext to produce the ciphertext...'

The important thing to understand here is that a stream cipher has an internal state defined by key0, key1 and key2 and in order to decrypt something we have to update this state with the password bytes.
Are you curious ? i think so ! what is the state of the cipher after it processed the caracter 'z' ? good question, isn't it ?
Answer? let's compute it together..first the initialization.
Key(0) = 0x12345678
Key(1) = 0x23456789
Key(2) = 0x34567890

compute PKZIP_stream_byte('z') like this:

key(0) = crc32(key(0) , 0x7A) where
crc32(crc,b)=((crc >> 8) ^ crctab[(crc & 0xFF) ^ b])

i've searched some pkzip source code and found the crc32 table used
, find it by yourself or believe the value i give you here after!

key(0) = ((0x00123456) ^ crctab[78 ^ 7A]) = 0x00123456 ^ crctab[2] = 0x00123456 ^ 0xEE0E612C =
0xEE1C557A looks pretty good no?

key(1) = (key(1)+(key(0) & 0xFF)) * 0x08088405 + 1 = (0x23456789 + 7A) * 0x08088405 + 1 =
0xD2149410 good! let's going on...

key(2) = crc32 (key(2), key(1) >> 24) = 0x00345678 ^ crctab[90 ^ D2] = 0x00345678 ^ crctab[0x42] = 0x00345678 ^ 0x98D220BC =

That's it!! Paul Kocher has 'forwarded' the cipher to the next state. All we have to do is to move the cipher state backward.
To do this, we have to modify the three mov instructions to put the correct initialization values, but that's not all, we have to correct the address loaded in ax before the call, to point to the beginning of the password string, that means
nop the inc ax instruction, change lea ax,[bp-0201] with lea ax,[bp-0202] and change lea ax,[bp-0101] with lea ax,[bp-0102].

I let you do this with an hex editor, don't forget that intel computers are little-endian ;-) so you have to search 0x7A551CEE and replace it by 0x78563412 and so on for key1 and key2 and all things should work well after that.

That's all folks!

Additional readings:

APPNOTE.TXT, the pkzip format official specification, find the latest on the pkware site.
To learn more about stream ciphers i suggest you this

Errors, comments, suggestions ?

write me!