The Zip Magic Protection Scheme


© February 1999
  by duffy


Introduction. The breeze was gentle, but a storm was coming. -Unknown

This protection scheme may appear difficult to understand and reverse
but, while advanced in some aspects, is very well suited to the
newby/intermediate reverser. There are new concepts here. You will become
familiar with the Portable Executable (PE) file format. (See Section VII below for additional resources and for related materials). While this task may seem daunting you will find it is simple to understand, interesting and very powerful knowledge. You should also be reasonably familiar with SoftICE and Hacker's View (Hiew). Although, I have several hex editors, Hiew is such an extraordinary tool it is the only one I really use. All file changes will be explained from the Hiew perspective. Finally, I use Windows 95 and, therefore, cannot speak for other operating systems.

The target file is one of three primary executables each similarly
protected in the file zm98eval.zip (1,639,565 bytes). Download it at
http://www.mijenix.com. I selected the Wizard file (zmwiz.exe) because of its small size. The Wizard helps the user either prepare or decompress zip
archives.

Tools:
SoftICE
W32dasm
Hiew
SoftDump for Windows 95 (sdump95.exe)
Dumppe.exe
hexadecimal calculator (for example, bcalc1.zip is excellent)

Tools can be located through a simple FTP Search or at your preferred
Reverse Engineering tool page.

The point of this paper is to instruct and to educate, nothing more. If you
wish to use the target for any purpose other than Reverse Engineering, or
you keep it beyond its evaluation period, register it.

I. And the earth was without form and void. -Genesis

Disassemble the file with W32dasm and save the output. The program is
protected with a 30-day cinderella time-out. You will find, though, that after the 30 days has passed a 10-day grace period is granted.

Set your system calendar ahead 31 days and run the program to reveal the grace period dialog box. Take note of relevant strings. Set the system calendar ahead 15 more days to reveal the expiration notification box, again taking note of important strings. As usual, the dialog box will act as a portal to the protection scheme.

Set a BPX DialogBoxParamA in SoftICE. Run zmwiz.exe to activate the breakpoint and <F12> back to the target. We will start simply: trace backward from the call and look for a conditional jump that, if taken, would avoid the call. You will not find it--<F12> out of this routine. Again, trace backward from this call.
 
Note the conditional jump (jz):

 
:0040A743 F644241802              test [esp+18], 02 
:0040A748 7428                    jz 0040A772 
:0040A74A 800514C44000FB          add byte ptr [0040C414], FB 
:0040A751 E84AFBFFFF              call 0040A2A0   **Leads to Nag Dialog
:0040A756 C60515C4400007          mov byte ptr [0040C415], 07
BPX :0040A748 and rerun the program. You will find that the program does not break. Using the SoftICE loader rerun zmwiz. This will cause the file to break at its entry point. Step <F10> to the call at :0040449A. Trace <F8> into the call and step until you reach the call at :00401C3E. Entering the call places you at :00403B50. Dump the address :0040A748 to the data window and note that no data is present. (This is why SoftICE did not recognize the BPX). Continue stepping while watching the data window.
:00403D19 50                      push eax 
:00403D1A 51                      push ecx 
:00403D1B 55                      push ebp
:00403D1C 8B6C2420                mov ebp, dword ptr [esp+20] 
:00403D20 55                      push ebp 
:00403D21 FF15F0124100            Call KERNEL32.WriteProcessMemory
After executing the above call the data window suddenly has code.
This is important and is the heart of the protection scheme. Open the file in the decode window of Hiew and GOTO our conditional jump address. To do this enter <F5>, then .0040A748. (If using an early version of Hiew it may be necessary to use the relative virtual address rather than the actual address.
 
Change .0040A748 to .0000A748).

The absence of code leads us to believe that the program must be writing its own code once it is executed. Obviously, patching is not possible. But we have not wasted our time. Push these notes aside, we will return to them later.

What roles do the Windows API functions WriteProcessMemory and its cohort
ReadProcessMemory play in the protection scheme?

II. "It's a friend of mine--a Cheshire Cat," said Alice: "allow me to introduce it." -Carroll

The WriteProcessMemory function writes memory in a specified process. The
entire area to be written to must be accessible, or the operation fails.

BOOL WriteProcessMemory( 

  1. HANDLE  hProcess,             // handle of process whose memory is 
written
  2. LPVOID lpBaseAddress,         // address to start writing to
  3. LPVOID  lpBuffer,             // address of buffer to write data to
  4. DWORD   cbWrite,              // number of bytes write
  5. LPDWORD lpNumberofBytesWritten// actual number of bytes written
   );
The terms "process", and "image" refer to a program once it has loaded to RAM and become active. Its code is located at a "virtual" address. This is in contrast to the disk file which is referred to as "raw" or "executable" data.

Its data is located at an "offset". Note that there are 5 parameters for the WriteProcessMemory function (only 4 are used by zmwiz). Of these, we are most interested in 3: the base address to which data is written (param #2), the buffer supplying data to be written into the base address (param #3), and the number of bytes written to this address (param #4).

BPX WriteProcessMemory in SoftICE and run zmwiz. <F12> back to zmwiz, <F10> once to load the code to SoftICE. Clear this BPX and set a new BPX at :00403D19 and rerun the program. Take note of the bytes-written value at eax, the buffer at ecx, and the base address at ebp. These are important and should be written down. The values are 1D14, CC1D9C (for me) and 409000, respectively. Your buffer addresses may differ from those listed here.
 
(Optional: BoundsChecker may also be used to examine API function parameters if you have this useful tool).

It is appropriate, at this point, to examine the parameter requirements of
ReadProcessMemory:

The ReadProcessMemory function reads memory in a specified process.
The entire area to be read must be accessible, or the operation fails.

BOOL ReadProcessMemory( 

  1. HANDLE  hProcess,        // handle of process whose memory is read
  2. LPCVOID lpBaseAddress,   // address to start reading to
  3. LPVOID  lpBuffer,        // address of buffer to place read  data
  4. DWORD   cbRead,               // number of bytes read
  5. LPDWORD lpNumberofBytesRead   // address of number of bytes read
   );
Now, BPX ReadProcessMemory and run the target. The function is called four
times; the fourth call is the one that interests us. (Actually, the address
is loaded to esi and esi is called).
:00403C8F 8B442424                mov eax, dword ptr [esp+24]
:00403C93 8B0D28F04000            mov ecx, dword ptr [0040F028]
:00403C99 50                      push eax
:00403C9A 51                      push ecx
:00403C9B 55                      push ebp
:00403C9C 53                      push ebx
:00403C9D FFD6                    call esi ; **This is ReadProcessMemory
:00403C9F A128F04000              mov eax, dword ptr [0040F028]
Clear the BPX ReadProcessMemory (but not the one you set earlier at
:00403D19). Set a BPX for :00403C99. Leave SoftICE and run the target.
Again, take careful note of parameters #2, #3, and #4. ReadProcessMemory
reads 1D00 bytes starting at :00409000, storing them at :00CC0078. Stop and
start again. At the break dump :00CC0078 to the data window. When the call at
 
:00403C9D is executed the data window will fill with the encrypted data
:00409000. Continue stepping: the call at :00403CF0 decrypts the data at
:00CC0078 writing it to :00CC1D9C.

It is from here that the decrypted code is finally copied to :00409000
by WriteProcessMemory. We will say no more about the mechanics of the
decryption, however the reader is encouraged to examine this interesting
code.

The reading and writing functions start at :00409000. Dump this address
to the data window, leave SoftICE, run zmwiz and note, as the program
breaks, the data changes as the code is read then written to the window.

Encrypted:
.00409000:AF DE 00 00-92 0F 00 00-BE 00 0F 00-12 03 24 15  ¯Þ  ’   ¾$   
.00409010: 36 27 38 39-6A 7B 4C 9D-6E 1F 09 06-01 13 34 E5  6'89j{Ln4å  
.00409020: F6 96 F7 17-13 25 89 30-30 7B 4C C9-3E 18 BD 40  ö–÷%‰00{LÉ> ½@  
.00409030: AB 0F 09 F3-4B F6 96 0E-03 10 A7 FC-23 65 47 FA  «  óKö–§ü#eGú  
.00409040: B0 B0 BA 72-25 4C 12 95-B1 9B ED 81-7D 5D 0D CC  °°ºr%L•±›í}] Ì  

Decrypted:

.00409000:  8B 44 24 08-81 EC 98 00-00 00 83 E8-02 56 0F 84  ‹D$ ì˜ƒè V „  
.00409010:  FC 00 00 00-2D 0E 01 00-00 0F 84 8F-00 00 00 48  ü   - „   H  
.00409020:  0F 85 EA 00-00 00 8B 84-24 A8 00 00-00 66 3D 01   …ê   ‹„$¨f=   
.00409030:  00 75 1A 8B-84 24 A0 00-00 00 50 FF-15 54 14 41   u ‹„$ Pÿ T A  
.00409040:  00 33 C0 5E-81 C4 98 00-00 00 C2 10-00 66 3D 02   3À^Ä˜   Âf=
III. I will study and prepare and someday my chance will come. -Lincoln

Let's continue studying our target. Using dumppe.exe dump the target's PE
header information to a text file. You can use the following batch file to
facilitate this: dumppe.exe %1 > c:\windows\desktop\dumppe.txt. Place this
batch file in the same directory as dumppe.exe, then place a shortcut to it
in your "send to" directory. Now, just select the file to dump, right click, and select the bat shortcut. Look on your desktop for the PE dump.

Open the file in notepad. There is a lot of information here but we are only concerned with a small portion of it. Find the area titled "Section Table".
 
There are six sections with names like .text, .etext and so on.

Section Table
-------------
01  .text       Virtual Address         00001000
                Virtual Size            00007FB0
                Raw Data Offset         00000400
                Raw Data Size           00008000
                Relocation Offset       00000000
                Relocation Count        0000
                Line Number Offset      00000000
                Line Number Count       0000
                Characteristics         60000020
                        Code
                        Execuatble
                        Readable

02  .etext      Virtual Address         00009000
                Virtual Size            00001D00
                Raw Data Offset         00008400
                Raw Data Size           00001E00
                Relocation Offset       00000000
                Relocation Count        0000
                Line Number Offset      00000000
                Line Number Count       0000
                Characteristics         60000020
                        Code
                        Execuatble
                        Readable

     .
     .
     .

06  .rsrc       Virtual Address         00012000
                Virtual Size            00001EC0
                Raw Data Offset         0000E400
                Raw Data Size           00002000
                Relocation Offset       00000000
                Relocation Count        0000
                Line Number Offset      00000000
                Line Number Count       0000
                Characteristics         40000040
                        Initialized Data
                        Readable
The .text and .etext sections contain code while the others are data sections.
 
Remember, "Virtual" refers to an active process, and "raw" to a disk file.

The addresses given are relative to the image base (listed earlier) which is 400000. This is simply the preferred address at which code begins to load. In the .text section the relative virtual address (RVA) is 00001000 (all values are in hexadecimal notation) while the actual address is 400000+1000=401000. Look at the RVA in the .etext section and convert it to the actual address. We got 409000. Note also the virtual size of 1D00 bytes. Do these values look familiar? They are the read and write process parameters. (Although, Write- ProcessMemory writes 1D14, the extra 14 bytes can be ignored for our purposes).

So, to complete our studies, let's summarize. The protection scheme is:

1. encrypted code starting at address :00409000.
2. 1D00 bytes in length.
3. stored at the .etext section.
4. decrypted and written back to :00409000.
It's time to reverse our target.

IV. Cry 'Havoc' and let slip the dogs of War. -Shakespeare

As we are unable to patch encrypted code we must decrypt it and write it to a binary file. In this format it can be appended to .text, thereby, providing smooth, uninterrupted code and overwriting the encrypted .etext section. But, first we must relocate the .etext section and redirect the ReadProcessMemory function so as to continue reading encrypted code. The program checks the validity of its protection and will crash if this code is not present.

The best place to relocate .etext is at the end of the program following the .rsrc section. This section starts at RVA 00012000 and is 1EC0 bytes long. The .etext can go after 12000+1EC0=13EC0 RVA. However, this address must be  rounded up to the next multiple of the required section alignment (1000). The new RVA for .etext would, therefore, be 00014000. We will actually be copying to the disk file at the raw offset E400+2000=10400. As this offset falls on the required file alignment (200) a rounding adjustment, such as made with the RVA above, is not needed.

Open zmwiz in Hiew and press <ctrl end> to go to the bottom of the file. Note the last byte is at offset 000103FF, and virtual address (VA).00413FFF. You may toggle between offset and VA with <alt F1> (<alt G> on earlier versions of Hiew). The pattern of repetitive zeros preceding this address is used as padding to the file boundary. Padding is used between each of the other sections, as well, to allow subsequent sections to align themselves at the boundary. It would be well to remember that padding is empty space available for any short routines you might wish to write in future projects, and is accessible via the jump command.

It is good practice to always backup the target before making file changes.
Now GOTO .00409000 and select the .etext block. To do this place the cursor
at .00409000, press <shift *> , and <down arrow>. The selected bytes appear
high-lighted. Continue selecting until you reach .0040A070 (offset 9470). The rest of .etext is padding in which we have no interest. Turn off block select with the <shift *> combination. Select PutBlk <F2> and enter Path\zmwiz.exe for file name, 10400 as the offset, and table "AS IS". You will not see the appended bytes until you reload the target, as Hiew writes directly to the file and not to the image. The actual number of bytes copied is about 1070. This includes some padding which is acceptable.

We must now tell the loader where the new .etext section is located. To do
this requires a header change. In Hiew, perform an ASCII search <F7> for
".etext" (without quotes).

 00000190:  00 00 00 00-00 00 00 00-00 00 00 00-20 00 00 60              
 000001A0:  2E 65 74 65-78 74 00 00-00 1D 00 00-00 90 00 00 .etext       
 000001B0:  00 1E 00 00-00 84 00 00-00 00 00 00-00 00 00 00               
The original RVA was 9000; this value is at 1AD in low-byte, high-byte format.
 
We also need to update the virtual size, the raw data offset, and the raw data size. These values are easily identified.
 00000190:  00 00 00 00-00 00 00 00-00 00 00 00-20 00 00 60           
 000001A0:  2E 65 74 65-78 74 00 00-00 12 00 00-00 40 01 00  .etext          
 000001B0:  00 12 00 00-00 04 01 00-00 00 00 00-00 00 00 00
 
The raw data size decreases from 1E00 because we did not copy the padding,
but was raised over the 1070 actually copied to 1200 which is the size of
the session's data rounded up to the next multiple of File Alignment.

At this point, you may wish to recheck the ReadProcessMemory parameters in
SoftICE to be sure that they have been reset. The target should run to the nag screen without crashing.

We are now ready to overwrite our original .etext with decrypted code. To do this we need to run sdump95.exe and SoftICE and allow them to read the
decrypted sequence and write it to a binary file.

Set a BPX in SoftICE for WriteProcessMemory and leave SoftICE. Copy sdump95
to your target's directory and enter a dos window here. Enter the following: sdump95.exe zmwiz.bin 1D00 <enter>. Sdump95 will respond with:

Mapping address: 0x82531000
Go into SoftICE and do your thing!
Hit return when you are ready for the file to be written to disk.
Your address may differ from the example. Run the target; at the breakpoint
dump :00409000 to the data window, <F12> to zmwiz. The data window should show decrypted code. On the command line enter: M address1 L length address2, where address1=409000, length=1D00, and address2=82531000 (or your address). You may dump address2 to see if the code has been copied. Leave SoftICE, returning to the sdump95 dos window, and hit <enter>. Check the .bin file in Hiew for appropriate content and length. Considering the critical nature of this, we  cannot be too cautious.

If all seems well, while still in Hiew, select the .bin file with <shift *>, <ctrl end>, then <shift *>. Select PutBlk <F2> and enter Path\zmwiz.exe
for file name, 8400 as the offset, and table "AS IS".

We must look at how we have changed the .text section. Its location has
not changed, but its virtual and raw data sizes have increased. They can be
calculated by subtracting the VA and raw data offset for .text from the VA
and offset of the next section .rdata. (Remember, .etext now follows .rsrc).
 
We got B000-1000=A000 for the new virtual size, and A200-400=9E00 for the new raw size. Search for ".text" in Hiew and make changes as shown below.

 00000160:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
 00000170:  00 00 00 00-00 00 00 00-2E 74 65 78-74 00 00 00   .text     
 00000180:  00 A0 00 00-00 10 00 00-00 9E 00 00-00 04 00 00
The final header change will be made to the image size which is currently
014000. This must increase to 1070 bytes + alignment padding = 016000.
 00000080:  50 45 00 00-4C 01 06 00-CA C5 48 35-00 00 00 00    PE       
 00000090:  00 00 00 00-E0 00 0F 01-0B 01 05 02-00 9E 00 00    <clip>
 000000D0:  00 60 01 00-00 04 00 00-00 00 00 00-02 00 00 00
V. A correct answer is like an affectionate kiss. -Goethe

Check your work--run zmwiz. It should run to the expiration nag without
crashing. If this is the case you may continue with some optional changes that may speed the program up slightly, but are not imperative to its reversal.

Replace the call to the decryption routine:

:00403CF0 E80BFEFFFF      call 00403B00; with
:00403CF0 EB08            jmp  00403CF5

And, jump over the call to WriteProcessMemory:

:00403D21 FF15F0124100     Call dword ptr [004112F0]; with
:00403D17 EB0E             jmp 00403D27
It should be obvious why these routines are now useless to the program, as
there is no reason to decrypt or rewrite code. The program can now be patched in the usual way.

VI. It ain't over till the fat lady sings. -Cook

Return to your reversing notes from section I above. Now, use Hiew to change the conditional jump (jz) at :0040A748 to an unconditional jump (jmp). Similar reversals must be performed at the following addresses:

:0040A7B0
:0040A831
:0040A488
VII. If I have seen farther than other men, it is because I stood on the shoulders of giants. -Newton

Resources abound on the Web; for further information refer to the following
documents:

PE File Information:

http://www.microsoft.com/win32dev/base/pefile.htm
http://www.geocities.com/SiliconValley/Lakes/5008/pefile.html

Related Tutorials:

http://www.phase-one.com.au/fravia/uvessa_2.htm
http://www.phase-one.com.au/fravia/natz_mp2.htm
http://www.phase-one.com.au/fravia/stone1.htm
http://www.phase-one.com.au/fravia/timelock.htm
 


 
 
 Return