Manual Unpacking - Tutorial by r!sc

"Taken from r!sc's very good webpage at http://csir.cjb.net."

Target : aspack.exe, 231,424 bytes, v2.001.
Packed with : ASProtect (section added named .aspr).
URL : All files for this tutorial here (429k).

A PE-file compressor or encryptor has to append its compressed / crypted exe with unpacking / decrypting code . If it crypts / packs the import table, it also has to load the import table itself (attack point). Also, as the exe loads, it has to have enough memory allocated to uncompress / decrypt the original code/data (+ a bit of an overhead for its own code (and maybe more
overhead for the compressed data)). We dont know the size of the original program, but checking out the PE-header, we can get the process size, which will be ample for the process to load / unpack into and run.

For me, when unpacking a file, I try to locate the import table before its initialised, dump that, then trace to the original entry point, and dump the rest of the process. Paste these together, fix the PE-header, job done. Some packers leave all the original sections in the PE-header, making it quite easy to find the VA for the import table, others 'merge sections' to create one empty section for unpacking the original code/data and one section containing the compressed data, and the last section containing its own import table / icon / unpacking code. Its still an easy job, but you need a different approach.

Fire up ProcDump's PE-editor, borrow some information from our target file. We want the process size, and if possible, the VA (virtual address) and the size of the import table. Luckily this file has every section left intact :-

Size of image : 00079000 ; how much memory to allocate for this PE-file.
Image Base : 00400000

.idata
Virtual Size : 00002000 ; size of idata in memory.
Virtual Offset : 00046000 ; VA of idata (+imagebase == 00446000).

.rdata
Virtual Size : 00001000
Virtual Offset : 00049000

Well, this may be a bad time to tell you but the import table may be in the idata section, or the rdata section. Looking at their
sizes, I'd put my money on .idata. Fire up FrogsICE, as this program has some anti-SoftICE code. We set a nice bpx on loadlibrarya, and run aspack.exe. When SoftICE breaks, check the memory at 446000 and 449000, to see if the import's have unpacked.

:bpx loadlibrarya
Break due to BPX KERNEL32!LoadLibraryA
Break due to G
:dd 446000 l 40

0030:00446000 00000000  00000000  00000000  0004669C      .............f..
0030:00446010 0004612C  00000000  00000000  00000000      ,a..............
0030:00446020 000468B6  000461AC  00000000  00000000      .h...a..........
0030:00446030 00000000  000468D0  000461B4  00000000      .....h...a......

Great!, 446000 isnt all ?? ?? ?? ?? which means it unpacked! and the better news is that this looks like exactly what we need. The image_import_descriptors, a structure of 5 dwords containing pointers to the first_thunk, library_name & original_first_thunk (not in that order). What is the format of image_import_descriptor's?, well they contain five dwords.

dd offset original_first_thunk
dd timedatestamp
dd forwardchain
dd offset library name
dd offset first_thunk

timedatestamp and forwardchain are usually set to 00000000, original first thunk isn't needed, its just an exact copy of the first thunk. libraryname is a pointer to the libraryname and the first thunk is the array of pointers to the asciiz's. Its the first thunk that gets overwritten with the api's address's. The image_import_descriptors are terminated with five NULL dwords. A good hint for locating these is searching for the beginning of the first thunk (will contain either pointers to the asciiz's or the actual API address (xx xx F7 BF or for kernel API's (on 9x)). Or search for a libraryname, then using the RVA of that, you can locate
the image_import_descriptors. Just a small reversing session of our image_import_descriptors.

:dd 446000 l 40
0030:00446000 00000000  00000000  00000000  0004669C      .............f..
0030:00446010 0004612C  00000000  00000000  00000000      ,a..............

4669C is the pointer to the LibraryName (RVA, you need to add the imagebase).

:db 44669c l 10
0030:0044669C 4B 45 52 4E 45 4C 33 32-2E 44 4C 4C 00 00 00 00  KERNEL32.DLL....

and 4612c is the pointer to the firstthunk for that library.

:dd 44612c l 10
0030:0044612C 000466AA  000466C2  000466DA  000466F2      .f...f...f...f..

These are pointers to the asciiz's, the actual API names, but they point to two bytes before the asciiz, 466AA is the first API to load, 466C2 is the second.

:db 0004466aa l 20
0030:004466AA 00 00 44 65 6C 65 74 65-43 72 69 74 69 63 61 6C  ..DeleteCritical
0030:004466BA 53 65 63 74 69 6F 6E 00-00 00 4C 65 61 76 65 43  Section...LeaveC

Well, thats definitely our virgin import table, dump it! and remember the address of the image_import_descriptors.

:pagein d 446000 2000 c:\aspack.idata.bin

Well I hope you know how to trace?. Trace from the break on loadlibrarya, F8 or F10, setting hardware break points before you trace over a call helps, as these breakpoints don't patch the memory with a '0xCC' thus, don't get overwritten when code/data is unpacked over them. 'bpm <address> x'. All the unpacker code appears to be around VA 00C1xxxx. Thus, if we find execution jumps to a lower address than this, we could be pretty sure thats the original code.

:bpm c10da8 x
Break due to BPMB #016F:00C10DA8 X DR3

0167:00C10E98 MOV ESP,EAX
0167:00C10E9A MOV EAX,EDX
0167:00C10E9C MOV EBX,[00C1666C]
0167:00C10EA2 MOV [EBX+ESP],EAX
0167:00C10EA5 POPAD
0167:00C10EA6 PUSH EAX ; push 442B98.
0167:00C10EA7 RET ; ret to that address.

0167:00442B98 PUSH EBP
0167:00442B99 MOV EBP,ESP
0167:00442B9B ADD ESP,-0C

How nice this is. Write that down, the original entry point, dump the whole process now, best to bc* beforehand.

:pagein d 400000 79000 c:\aspack.dumped.exe

Now we have to copy & paste the virgin import table into this dump, fix the PE-header, then its all over!. Since we dumped from 400000, the file-offset's will be equal to the RVA's. Our import table was at RVA 46000, so it will be at file offset 46000. Use Hex Workshop or whatever you have to copy the 2000 bytes of 'aspack.idata.bin' over the 2000 bytes of data in 'aspack.dumped.exe', from file offset 46000 to 48000.

psize==vsize, offset==rva

Every RAW offset will now be equal to the VA's as well.

CODE RVA : 00001000 .... CODE new file offset : 00001000

Every Virtual Size will be equal to the RAW size, unless (they are normally aligned on a 1000h byte boundary). Virtual size could be 45001, but when windows loads it, it would allocate 46000 bytes).

CODE virtual size : 00042000 .... CODE new RAW size : 00042000

OK, go fix the sections using ProcDump. If this is done properly, when you save changes and refresh explorer the icon should come back. Two more things to fix before it runs, the programs entry point, and the location of the import table. New entry point is 00042B98 (original entry point - imagebase) click on the 'directory' button to edit the address for the import table. New import table RVA is 46000 (rva of the image_import_descriptors) size of the import table? hopefully bigger than 0.

Save changes, exit ProcDump, cross your fingers and run aspack.dumped.exe. Well, it runs sweet, its not fully restored, you still have the extra unpacking code, and the resource section is a bit mangled, with some resources moved into the .aspr section. You can't WD32asm the file!, edit the section characteristics for the first section, change them from C0000060 (data, writable) to 60000040 (code, executable) or E0000060.

Petite v2.2

Target : petgui.exe, 55,558 bytes, http://www.un4seen.com/petite

Fire up ProcDump's PE-editor and attempt to borrow some information from our target file. We want the process size, and if possible, the VA and the size of the import table. Unluckily this file has no section names left intact and it appears to have merged the sections.

Size of image : 000E1000
Image Base : 00400000

bpx loadlibrarya / getmodulehandlea, run petgui.exe.

On your break take a look at the code ,check out the registers, look for interesting stuff. I found this :-

esi==414924 : 4C400100 88410100 1C400100 00420100 F4410100 74410100 00400100
                ----     ----     ----
           dd 0001404c 00014188 0001401c etc      etc

Add the imagebase to these, and we end up with pointers to the first thunk, part of our import table. Looks like Ian Luck has been doing evil things with this PE-file, merged all the sections, killed the image_import_descriptors, and if you look a bit closer, you will see no library names in the remainder of our import table. Anyway, on the break for loadlibrarya, dump the whole process.

pagein d 400000 E1000 c:\petgui.bin

Then trace to the exit point, so we know the original entry point.

0167:004E003C POPAD
0167:004E003D POPF
0167:004E003F ADD ESP,08
0167:004E0042 JMP 0040D578 <-- original entry point.
0167:004E0047 JMP KERNEL32!_llseek

Import mangling at its prime, the import table isn't here, but it is, you see?.

OK, load the dump into your favourite Hex editor and examine it carefully.

code section appears to be from 1000 - 14000 ... size 13000
rdata section appears to be from 14000 - 16000 ... size 2000
data section 16000 - 1a000 (but continues with NULL's for quite a lot of bytes)
rsrc from df000 - e1000 ...

Right, we can either leave the sections merged, and fix the PE-header, or leave the sections merged, kill all those excess NULL's between data and rsrc and fix the PE-header, or we can create a new PE-file with four sections and fix it all up. I'm going for leaving it exactly as it is, but adding an extra section into the header, fix rdata section, and cross my fingers before I execute the file. 'if it works' after this, we can kill those extra NULL's between the data and rsrc section and hopefully have a decent sized fully working file afterwards.

Copy and paste the rdata section into a new file from 14000h to 16000h in our dump, offset 924h in the new file is where the 'first thunk' pointers begin. We have a lot of NULL bytes here, somewhere we could paste the library names?, search the dump for the library names, and copy/paste them into offset A00h in our new rdata section (see 'rdata.bin').

We have to re-create our image_import_descriptors now. We have the first thunk pointers, we have the VA's of the librarys used, so we just have to use our heads and our Hex editor to make some new image_import_descriptors.

dd 0,0,0,VA lib1,first_thunk_pointer1
dd 0,0,0,VA lib2,first_thunk_pointer2
...
dd 0,0,0,VA lib7,first_thunk_pointer7
dd 0,0,0,0,0

Our rdata section VA is 14000. We already have the first thunk pointers, our first image_import_descriptor would look like this :-

00000000 00000000 00000000 004a0100 4C400100

The best way to rebuild these is to copy the first thunk pointers into a new file, then edit this file with 'insert' turned on. Insert 3 NULL dwords in front of each pointer, and then the VA of each library name in front of each pointer. VA of each library name being its file offset + 14000h.

00014a00, 00014a0d, 00014a18, 00014a22, 00014a2f, 00014a39, 00014a45 ..

See iid1.bin / iid2.bin / iid3.bin for hands on information. Lets just follow the first first thunk pointer, then follow one of those
pointers and see the asciiz. Well, blow me, our first pointer, 0001404c points to 0000004c in our rdata section, at 0000004c is 0C4C0100 reversed is 00014c0c, points to c0c in our rdata section, at c0c is .. '_llseek',0,0,0.. This is wrong. These pointers SHOULD point 2 bytes in front of the asciiz, to the HINT.. since they point directly to the asciiz, this makes it easier to load the imports yourself, but screws windows up when it tries to load the imports. We have two choices.

#1. subtract 2 from each pointer in the first thunk's (a lot of work).
#2. insert 2 bytes infront of the asciiz's hoping it aligns them all.

OK, I go for #2. The first asciiz I have is 'ExitProcess', starts at 0BCEh, if I insert two bytes here, it pushes 'ExitProcess' so it begins at 0BD0h, good, then delete two bytes after the asciiz's, while were at it, paste our new image_import_descriptors back into our rdata section. 8ch bytes from offset 924h in rdata.bin, see rdata.fixed.bin if your confused. Copy fixed rdata.bin back into petgui.bin, offset 86h in petgui.bin is the number of sections. Change this from 03 to 04, rename to *.exe, ProcDump comes back into play now.

new entry point : 0000d578
import table    : 00014924

section v-size    v-offset  raw-size  raw-offset
code    00013000  00001000  00013000  00001000
rdata   00002000  00014000  00002000  00014000
data    000c9000  00016000  000c9000  00016000
rsrc    00002000  000df000  00002000  000df000

Save changes, run the exe, it works, pity its 900kb now though. Lets kill the un-needed NULL's from 1a000 - df000 and re-fix the PE-header (see pet.exe). Fix all sections RAW Sizes and RAW Offsets. I had problems with ProcDump, but
everything turned out OK, don't forget to fix the entry point and the directory entry for the import table.

-r!sc.


Packers Return to Main Entrance


© 1998, 1999, 2000 r!sc. 19th July 2000.