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 ... |
|