Manual Unpacking
- ASPack (ID CrackMe 5.0) -
by /Miz

 
Below is some more detailed help regarding the seemingly endless topic of packers. I chose TORN@DO's excellent ID CrackMe #5 as a target as this was one that was mentioned here today as being 'tricky'. It's basically just what I've posted here before regarding manual unpacking but with a specific target in mind,
so that you can follow it yourselves.

Hope it helps.



Introduction
This essay was prompted after requests for information on packed targets, particularly one I'll be looking

at. Whilst many people are quite content with using de-packers, to do the work for them, often it is not
possible, e. g. if a packer is fairly new and no such automatic unpacker yet exists.

People are often put off such targets, but I hope that this essay will prove that once a few 'basics' are

understood the process is relatively straight forward.

To many the structure and importance of the PE format may initally be overhelming. Stick with it, re-read
this essay a few times and try to get hold of some other tutorials / tools. I particularly recommend Stone's
excellent work.

Target: ID CrackMe #5 by TORN@DO
Goal: To have an unpacked target AND to be able to look at a complete dead-listing of the unpacked code, complete with the original import data.
Tools: SoftICE 3.x
ProcDump 1.4.0
W32DASM 8.9x



Packed Target Basics
This we can (reasonably) assume from packed targets:

1. Code to unpack the code must run before the code ;)
2. At the end of it's duties the unpack code will jump to our target
3. Code to unpack the code must be compact (otherwise it would be pointless!)
4. Code to unpack the code will be probably be written in reasonably optimized assembler, i. e. not some overbloated garbage.
5. Quite often, tough not always, packers employ a number of anti-debugging tricks. (Like little targets in their own right hehe).
6. Windows is a flakly OS.

So what do these priciples teach us?

From point 1: This means that the unpack code is (probably) called first.
From point 2: The unpack routine must know the original entry point, and at once it has done all its work, it will (in some way) jump to the original entry point.
From point 3: This is nice ;)
It means we will probably not be overwhelmed with lots of code.
From point 4: From the point of view of an 'ASM-Head' this is great, coupled with the concrise code this should make things easier. Nice clean assembly is our native tongue ;)
From point 5: Hmmm - First bad point :(
Well - not really ;)
Because the code is concise and in clean assembler it's often VERY easy to spot these tricks. Often then code that looks 'unclean' is worth a closer inspection.
From point 6: Alot of 'tricks' packers would LIKE to do are out-of-bounds due to compability iusses.



First Obstacle
Ok, we have already observed our target is packed, but as we haven't yet seen the code we probably
don't know what with (if you really want to know what with, then use a util such as GetTyp), not to
worry, doesn't matter ;)

First we'd like to take a look from the comfort of the SoftICE Loader ... oops SoftICE Loader doesn't
stop at the entrypoint we asked it to. Hmmmm ... why is that?

Well the answer lies in the PE format (told you this stuff was important)!

Let's take a look at it in ProcDump, vie the 'PE Editor Option':
First thing we spot is that the Packer Entry Point (PEP) is 0x00006046.
Let's look at the section containing the PEP: Well we can see it's the .text section and characteristics
are 0xC0000040

What does this mean? Well, let's take a look at WINNT.H to see ...


   [Excerpt from WinNT.h]
    // Section characteristics.
    #define IMAGE_SCN_TYPE_NO_PAD            0x00000008 // Reserved.
    #define IMAGE_SCN_CNT_CODE               0x00000020 // Section contains code.
    #define IMAGE_SCN_CNT_INITIALIZED_DATA   0x00000040 // Section contains initialized data.
    #define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 // Section contains uninitialized data.
    #define IMAGE_SCN_LNK_OTHER              0x00000100 // Reserved.
    #define IMAGE_SCN_LNK_INFO               0x00000200 // Section contains comments or some other type of information.
    #define IMAGE_SCN_LNK_REMOVE             0x00000800 // Section contents will not become part of image.
    #define IMAGE_SCN_LNK_COMDAT             0x00001000 // Section contents comdat.
    #define IMAGE_SCN_NO_DEFER_SPEC_EXC      0x00004000 // Reset speculative exceptions handling bits in the TLB entries for this section.
    #define IMAGE_SCN_GPREL                  0x00008000 // Section content can be accessed relative to GP
    #define IMAGE_SCN_MEM_FARDATA            0x00008000 //
    #define IMAGE_SCN_MEM_PURGEABLE          0x00020000 //
    #define IMAGE_SCN_MEM_16BIT              0x00020000 //
    #define IMAGE_SCN_MEM_LOCKED             0x00040000 //
    #define IMAGE_SCN_MEM_PRELOAD            0x00080000 //
    #define IMAGE_SCN_ALIGN_1BYTES           0x00100000 //
    #define IMAGE_SCN_ALIGN_2BYTES           0x00200000 //
    #define IMAGE_SCN_ALIGN_4BYTES           0x00300000 //
    #define IMAGE_SCN_ALIGN_8BYTES           0x00400000 //
    #define IMAGE_SCN_ALIGN_16BYTES          0x00500000 // Default alignment if no others are specified.
    #define IMAGE_SCN_ALIGN_32BYTES          0x00600000 //
    #define IMAGE_SCN_ALIGN_64BYTES          0x00700000 //
    #define IMAGE_SCN_ALIGN_128BYTES         0x00800000 //
    #define IMAGE_SCN_ALIGN_256BYTES         0x00900000 //
    #define IMAGE_SCN_ALIGN_512BYTES         0x00A00000 //
    #define IMAGE_SCN_ALIGN_1024BYTES        0x00B00000 //
    #define IMAGE_SCN_ALIGN_2048BYTES        0x00C00000 //
    #define IMAGE_SCN_ALIGN_4096BYTES        0x00D00000 //
    #define IMAGE_SCN_ALIGN_8192BYTES        0x00E00000 //
    #define IMAGE_SCN_LNK_NRELOC_OVFL        0x01000000 // Section contains extended relocations.
    #define IMAGE_SCN_MEM_DISCARDABLE        0x02000000 // Section can be discarded.
    #define IMAGE_SCN_MEM_NOT_CACHED         0x04000000 // Section is not cachable.
    #define IMAGE_SCN_MEM_NOT_PAGED          0x08000000 // Section is not pageable.
    #define IMAGE_SCN_MEM_SHARED             0x10000000 // Section is shareable.
    #define IMAGE_SCN_MEM_EXECUTE            0x20000000 // Section is executable.
    #define IMAGE_SCN_MEM_READ               0x40000000 // Section is readable.
    #define IMAGE_SCN_MEM_WRITE              0x80000000 // Section is writeable.

   

From this we can see our 0xC00000040 actually means:

0xC00000040 = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_INITIALIZED_DATA
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Read Write Initialized Data

Hmmm - but we know that SoftICE Loader won't break on entry unless we specify that this section
contains *CODE*, and we should also specify that it is executable.

So (using ProcDump) change the Section Characteristics for the .text section to 0xE0000020

0xE0000020 = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_CODE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Executable Read Write Contains Code


Does SoftICE Loader break in now? Yes it does!



Second Obstacle
Having gained entry with SoftICE Loader we should investigate the code, run through it a few times,
looking for anything out of ordinary. We should also be looking for the *Original* entry point (OEP) which
can be found when control passed to our (by now UNPACKED) target ...

Find it? Pretty simple eh?

xxxx:xxxxxxxx ...................
xxxx:xxxxxxxx ...................
xxxx:xxxxxxxx MOV [ESP + 1C], EAX <---Put OEP (in EAX) into EAX's space from a previous PUSHAD
xxxx:xxxxxxxx POPAD <---Pop all the registers back (EAX now contains OEP)
xxxx:xxxxxxxx PUSH EAX <---Push OEP onto stack xxxx:xxxxxxxx RET <---Jump To OEP

By tracing through with SIce we find that the OEP was (in my case) 0x00405500.

[Side note: I say 'in my case' because, although very unlikely, it could be different on your machine
because 0x00400000 is only the PREFERRED image base.]

So you have put a BP on the PUSH EAX above, the packer has done all it's work, and we know the
OEP, so what now? We need to get back to ProcDump to Dump the whole process so just put the
code in an infinite loop. Do :

* 'a eip'
* 'jmp eip' '
* ''
* 'g'

Goto ProcDump and right click on our target and do Dump (Full). Remember to save it as some other
name as we may need the original. Once the process is dumped we need to kill it, so right click again
on our target process and select 'Kill'.

The target is now unpacked, look at the filesizes .... however, if you try running the target now you
will find it crashes. This is because we haven't fixed the entry point.

So using ProcDump's PE Editor again, we change the entry point to 0x00005500.
(OEP minus the Image Base, ie 0x00405500 - 0x0040000).

Run the target again, and as if by magic, it runs! Cool.....or is it? We load up WDAsm to look at our
nice unpacked code...... D'oh!


Third Obstacle
Our unpacked code is there, everything's fine, we can goto the OEP and follow the code, but
something's missing ... We have no import data.

Basically the packer has done Windoze's job, i.e. created the import addresses.

Is this bad? YES! Apart from making our dead-listing abit cryptic (All the API calls are
referenced by 'call [xxxxxxxx]' lines), it WILL NOT WORK 100% of the time, because API
addresses are not static (DLL's!) and we can not assume that they will be.

What do I mean by all this? Well, in 'simple' terms when an executable is loaded by windoze
it create a list of addresses for all external (eg dll) calls. The executable then calls the
relevant address held within this 'table' when it needs to call external functions. The packer
must have packed the original imports and then, when called, must have built the table up itself
using GetProcAddress (KERNEL).

Hmmm ... So how do we fix this?

Well, how about letting the unpacker unpack the original import names, NOT letting it create
the addresses, instead we'll skip that part and let windows do it itself by pointing the import
address at out new table. Sounds difficult, but bear with me, it's quite easy.


Starting Over
Start again with the original exe. Follow the instructions in 'First Obstacle' again. Now..... Using
SoftICE Loader, trace to about 10 lines before the jump to the OEP. You should see 4 calls next
to each other. Trace to the last of the 4 calls but DO NOT RUN THAT CALL. Instead do

'r eip eip+5'

This will goto the next line. What have we done? I suggest you look at it more closely yourself, but
we've taken a huge short cut and let the unpacker unpack out import table, but not let it create the
addresses. Continue and do a full dump as described in 'Second Obstacle'.

Now load up ProcDump again. If you look at the section's you'll see a section called '.idata'. This is the
standard name for the import data section. Note down it's Virtual Offset and Virtual Size. Now goto
'Directory' within ProcDump. You will see a box marked 'Import Table' - Notice it is pointing to the
import's of the packer NOT our stuff - but we'll soon change that! Change the addresses and size
to those you noted down. Leave ProcDump and load our target into WDAsm - hehe - lovely unpacked
executable complete with the original import information. Job well done.



Afterword
Whilst all this may seem like hard work, it really is quite simple. The whole process takes just a minute
or so. Once you become familiar with more and more packed targets and hopefully more familiar with
the PE format you'll see how easy it all is.

Things skipped in this short essay:
* I could have gone into alot more detail about the packers GetProcAddress loop, and even the whole
unpacking routines, but thought it would have clouded the real issue.

Once you are familiar with packers you'll spot these almost immediately and switch onto 'autopilot' -
(which can be dangerous sometimes!). So following and understanding the packers manipulation of the
import table is left as an exercise for the reader (or maybe another essay ;)).

It would be nice to remove all traces of the offending packer. Quite often this is a simple case of
removing the section containing the packer, as they're often just 'bolted on' to the original executable.
Look closely at the section names, particularly the last few, sometimes it's VERY obvious ;) Alot of
people still like to COMPLETELY rebuild and exe, using SoftICE and a dumper
(such as SoftDump/IceDump etc). Commendable, but ALOT of extra effort for not much gain, if any.

ProcDump is a superb weapon, particularly good at 'subduing' packed files. A pretty good knowledge
of the PE format makes things much easier, and I suggest that you search for docs on the topic -
Stone has some excellent info and MattP's book Windows Secrets contains a wealth of information,
not just related to this topic, and (of course) a visit to +Fravia's is always rewarding ;)

Cheers!
/Miz


Questions and Answers

Question: After I placed a BP on PUSH EAX, why do I put the whole thing in a loop and where do I place the loop PUSH EAX?
Answer: You put the code in a loop so that you can return to Windows and dump the process. Putting the loop just before (or over) the jump is obviously best.
You're basically saying to SoftICE "Don't let this EXE continue to run past this address, keep it in suspended animation while I dump it to disk. Sleep my pretty one ..."

Remember, you don't care that your overwriting this code because this code belongs to the unpacker and will not be called once dumped.
Question: You said after putting it in a loop, go back to ProcDump and right-click on the file and select dump (full). I stared ProcDump again and what do I have to do now? Select unpack or what??
Answer: If you look in the list box on the left you should see the target listed (if not, then right-click in that box and do a 'refresh list').

Once you see your target listed, right-click over it and do a dump (full).

Remember, you are not using any of the unpack features of ProcDump, just using it to erm, Dump-the-Proc ...