Thursday, August 11 2005 @ 05:32 PM CEST Contributed by: haggar Views: 28676
Level : intermediate
PESpin v1.0 , 1.1 & 1.3 - manually unpacking
This is a detailed tutorial about manually unpacking a couple of PESpin versions.(discussing crypted parts, IAT redirection, stolen bytes, ...). Inside archive you'll find tutorial, targets, scripts and some more stuff.
This tutorial is about manually unpacking PESpin 1.x versions, to be more
precisely about 1.0, 1.1 and 1.3 versions. However, this tutorial has
- PESpin v0.7 will not be described because there is no need for that.
If you know how to unpack version 1.3, then you know how to unpack
all previous versions.
- Version 1.2 is private version not available to public so I was not
able to examne this one.
- PESpin v1.3 have option for advanced code redirection which is not
available in public version so this tutorial will not cover that kind
of protection (if you find target packed with that option, send me a
PM on the forum).
- All unpacked targets in this tutorial runs only on machine on which
they where unpacked. More about that read in the first tutorial.
is for advanced beginners. You must have basic knowledge about PE
file structure and how to use usuall unapcaking tools.
Get the attachment with all examples, scripts, ...here
Tools and useful stuff
Tutorial 1 - PESpin v1.0
Tutorial 2 - PESpin v1.1
Tutorial 3 - PESpin v1.3
Final words and thanks
1. Tools and useful stuff
- OllyDbg 1.10
- Hex Editor
In this archive you will find two folders: "PESpin scripts"
and "PESpin signatures". First folder holds some useful
scripts for unpacking PESpin. Those scripts are far away from
perfect, but they could hep you a bit. More about them later, or just
open them in notepad and read info. In the second folder you will
find signatures for PEiD. If you scan with PEiD some file that is
packed with some PEspin 1.x version, it will not recognize it
properly. Just copy-paste those signatures to userdb.txt that is in
PEiD folder, use external scan on packed file and PEiD will recognize
real PESpin version. If you notice some bugs, mail or PM me.
There is some good papers that you should read if you want understand this
- Descryption of PE format by Michael J.O'Leary ; I think that this can
be found on old BIW site.
- PORTABLE EXECUTABLE FILE FORMAT by Goppit ; find it here
http://cracking.accessroot.com/ , it's 6 MB big file which is worth
- Read my previous tutorial about unpacking PESpin 0.3 because new
versions are improved previous.
2. Tutorial 1 - PESpin v1.0
In this version, PESpin has two new tricks; API bp check and code
redirection. Grab first target packed1.0.exe and open it in Olly (set
ignore ALL exceptions). Finding OEP and blocking imports redirection
is similar as we done it in 0.3 version.
Protector has a check on API breakpoints so open "Executable Modules"
window, select kernel32.dll and click "view names". Find
GetTickCount and double click on it. You should be in kernel32 now:
00409109 F9 STC ;Can be CLC (E8hex).
0040910A 72 0D JB SHORT packed1_.00409119 ;Place bp here!!!
0040910C 8D85 0660271E LEA EAX,DWORD PTR SS:[EBP+1E276006]
00409112 2D 8417E71D SUB EAX,1DE71784
00409117 FFD0 CALL EAX ;Call to timer function.
00409119 EB 01 JMP SHORT packed1_.0040911C
This is the last place before jumping to OEP, just like in 0.3 version
except here is little different. If target is packed with timer
option, instead of first STC opcode there will be CLC=E8hex. In that
case JB will not be executed and program flow will continue to CAL
EAX where timer thread is created. This place is same for all next
PESpin versions. You just place bp on JB opcode (don't run target).
Scroll up again and take a look at this part of code:
0040905F JMP DWORD PTR SS:[ESP-4] ;Important jump, place bp here.
00409063 CALL FAR EB00:000003E8
0040909C CALL packed1_.004089F2
004090A1 CALL packed1_.004090A9
Place bp at that jump and run target. When you break on it, you'll see that
jump leads to 409022 address. That jump is resposible for IAT
redirecting. If there are no IAT redirecting, that jump will jump to
40909C address to that CALL and skip all redirection procedure. So to
avoid IAT redirecting, you just patch all those bytes between jump (you
can patch it too) and CALL at 40909C:
0040910A JB SHORT packed1_.00409119 ;You're here!!!
0040910C LEA EAX,DWORD PTR SS:[EBP+1E276006]
00409112 SUB EAX,1DE71784
00409117 CALL EAX
00409119 JMP SHORT packed1_.0040911C
Now, if first opcode instead STC is CLC, patch all instructions at
0040910C,00409112 and 00409117 to prevent calling timer fuction.
Acctualy, you can always patch that instructiones, it will change
nothing in that case. Then scroll down and search for POPAD opcode.
You won't see it so search for 61hex byte in some instruction. I
should be here:
You're now at the last part of the protectors code. Here you can find the stolen OEP
bytes mixed with junk code. I didn't chose to remove OEP when I
packed file so here is only junk to confuse you. No matter if you
have stolen OEP or not, you can always dump file when you reach first
opcode after POPAD one and dumped file will normally work. It doesn't matter
that OEP is in the protectors code and that target
execution starts from here. But if you want you can always find
stolen bytes, restore it in original place and dump from there.
just trace untill you get to some jump that leads to the code section:
They lead to the address below 401000, that means to the PE header. That is not
normal. I will explain this briefly:
PESpin will load all sections in memory, decrypt and do all that it needs to
be done for unpacking. After that PEheader isn't anymore important
for anything so it can be modyfied as you like - it won't affect the
program in memory. PESpin will take adwantage of that fact and it
will fill one part of PEheader with FFFFF... (and btw destroy part of the
header which holds sections info). Then it will search in code
section for PUSH xxxxxxxx or CALL xxxxxxxx (maybe JMP too)
instructions because they are 5 bytes long. Then it will substitute
that opcode with some JMP or CALL to PEheader where it will place
original instruction and return to code section (if needed). The
reason why instructions must be 5 bytes long is because they are easy
to substitute with JMP opcode (it's 5 bytes long). Let's see two
a) CALL example:
00401002 . E8 F1F1FFFF CALL packed1_.004001F8
This CALL points to PEheader. Right click on it and folow it. There you
will find original "stolen code":
004001F8 -E9 09130000 JMP packed1_.00401506
So you need just to replace CALL 4001F8 with JMP 401506. As you see it's
b) JMP example:
0040100E .-E9 EBF1FFFF JMP packed1_.004001FE
Again, follow this jump to PEheader and you'll see stolen original code:
004001FE 68 F4204000 PUSH packed1_.004020F4
You gonna fix this as we fixed it in first example, instead of jump you
place PUSH 4020F4 instruction.
Ok, you sow it and it's not hard. First tought would be "Why
restoring that code, why just not leaving it there and just dump
it?". Reason is that PEheader is destroyed (sections info is
erased) and we doesn't have valid PE file anymore. If we paste
original header from disk, then program will jump to it and it will
not find redirected code, so result is crushing. Second tought is
"But there is lot of redirected code, how to fix them all?
Manually? I'm not crazy to waste whole night in front of computer!".
Acctualy, there can be limited number of redirected code because size
of PEheader, but that number is pretty big too. So to fix lot of that
code we will just use/write Olly Script for automating job. That is
not hard at all. It should be like this:
- First we need to find such JMP/CALL opcodes. Our srcipt must search
for JMP/CALL which ends on F because it means that it jumps backword.
So search for instructions with E9???????F or E8???????F bytes.
-Then we must check does it jumps to PEheader. Take our a) example.
Take address of CALL, 401002 and take CALL value (four bytes after E8
in reversed order) FFFFF1F1. Now add those two values
401002+FFFFF1F1=4001F3, and if result is between
400000<=RESULT<401000 than you know that you are at right
- Than, using basic math you must restore original opcode. It can't be
copy-pasted, it must be calculated. Basic hex math is needed here,
little calculation and some time. After that you will have script
that works perfect. If you don't (or don't know how to) do it your
self, you can use mine "PESpin - Code Fixer.txt" (included
Now, if you have done all just like I wrote and then use script to fix
redirected code, dump file. If you run it, you'll probably get error
message that tels you how this app is not valid Win32 app. Don't
worry. Open LordPE, go to options and under rebuilder check only
"Validate PE" option. Close options, open rebuilder and
open our dumped file in it. After validating of dump, run it and it
will work just perfect ;) Good job! You have just unpacked PEspin
This aproach will work for ASM and BC++ programs , but not for Delphi and
MSVC++. Reason is different IAT redirection which OllyDump plugin
isn't able to handle. That can be fixed by building new import
section. That we will see in next tutorial.
But there is one reason why dumped file will probably work only on
machine on which file was unpacked. If you take look at import jumps,
you'll see that most of those jumps are absolute jumps that jump to
specified loacation in memory. Because of that, dumped file will
probably crush on another computer that has different versions of
dll's. This can be fixed , but for bigger file it's impossible to do
it manually. This is problem with all PESpin versions.
3. Tutorial 2 - PESpin v1.1
this version everything is same as in 1.0 except this one brings new
option - CRYPT and CLEAR MARKERS. What is that? Simple, read PESpin's
between encryption markers CRYPT_START and CRYPT_END is encrypted
during process of file protection and when protected file is
executed, code is being decrypted closely before it is used and is
again encrypted right after use (runtime protection against dumping).
and CLEAR_END markers offers ability to remove blocks of code after
macros should only be placed in regions of code that are executed
once in your application.Any
attempt to re-execute a block of code inside those macros will cause
an exception in the protected application.
This is not a big problem for us. For our second target we will chose
keygen.exe which is packed with all CLEAR and CRYPT options (thanks
to detten ;). Procedure of finding OEP is exactly the same as in 1.0
version so there is no point to write it all again. In a short:
exclude all exceptions, place bp to the end of GetTickCount API and
run target, remove bp and return to target code. But... We will meet
one problem here that we'll had in all PESpin 1.x versions with
Delphi and MSVC++ targets. Problem is OllyDump cannot rebuild IAT. We
will had to do it all manually. Because of that problem we'll had to
know which one dll's our packed target is using. Lets go!
First we will find what dll's keygen.exe needs. Place bp on LoadLibraryA in
command line and run target. You will break in weird place (maybe
values are different at you):
What are we searching here? If you remember from last chapter, PESpin
decrypts DLL name, load it in memory and then delete it's name. We
are now at place where protector loads dll and we will find place
where he erases DLL name. We will patch that erasing procedure so
later we can see what DLL's are used. Trace with F7 untill you get
here (that's lot of tracing):
0040D4F0 800B 00 OR BYTE PTR DS:[EBX],0
0040D4F3 74 0D JE SHORT keygen.0040D502
0040D4F5 8813 MOV BYTE PTR DS:[EBX],DL
0040D4F7 C1C2 04 ROL EDX,4
0040D4FA 75 01 JNZ SHORT keygen.0040D4FD
0040D4FC E8 43FF6424 CALL 24A5D444
0040D501 FC CLD
0040D502 93 XCHG EAX,EBX
0040D503 8B56 10 MOV EDX,DWORD PTR DS:[ESI+10]
is place where DLL name is being erased. Get to this line
8813 MOV BYTE PTR DS:[EBX],DL
follow it in dump window. There you will see KERNEL32.DLL name. Patch
this opcode MOV BYTE PTR DS:[EBX],DL and then just run target. Our
target will start, but that's what we want. Go in dump and there you
will find all dll's that target uses:
Our target is small and it uses only kernel32.dll, user32.dll and
gdi32.dll. Good! Now we gonna restart target in Olly and find OEP.
Place bp on last RETN in GetTickCount API, run target, remove bp and
returne to code:
This is that place where timer is maybe called. Our target doesn't have
that option enabled and we don't need to patch opcodes from 40D6DB
Ok, scroll down and find the POPUP opcode. You will find it a little
E8 610FC90F CALL 1009E860
so patch first byte to get clear picture
0040D8FA 90 NOP
0040D8FB 61 POPAD
After that POPAD starts stolen OEP code mixed with junk code. It seams that
there is no stolen OEP bytes because jump that leads to code section
jumps to 401000 at beginning of section:
First we gonna check if there is stolen/redirected code to PE header
section. Just run "PESpin - Code Fixer.txt" script. Check
log window and you'll see that script has fixed 4A opcodes:
[Simple Code Fixing - number of stolen opcodes ] stolen = 0000004A
Ok,that's all we know from previous version of PESpin, but now we gonna
met new trick - code decrypting and erasing. This part of code at
very beginning is start of first decrypt procedure:
Trace with F8 to line that I showed abowe and you'll see that it changed
00401022 FF10 CALL DWORD PTR DS:[EAX]
That call will lead you to one procedure that will decrypt some code in
our target and erase something. You can go ther by F7 if you would
like to see what is happening there. It will just decrypt couple
opcodes below POPFD opcode and erase all above. Instead F7 press F8
and you'll see changes:
You see that these two calls leads to protector section. Place bp on
first call and run target. You will get dialog box which asks for
name. Enter some name and press generate button. You will break on
first bp. Remove bp and enter in call with F7, then press Ctrl+F9 to
get to RETN opcode. Execute it and return to target code, press
Ctrl+A and scroll up few lines:
is decrypted and now wee need to patch deryption calls. Patch first
call wich decrypts (abowe picture). Now enter in second call at
and trace to this line
003C004E AA STOS BYTE PTR ES:[EDI]
That line again encrypts decrypted code. So patch it to prevent
encrypting. Then return to target code. You will notice that you are
not imidiatley after encryptor call, but couple bytes further. That
mean some bytes after call are junk, so patch encryptor call and all
useless bytes. Now that decrypted and patched code should look like
Now open OllyDump plugin, set OEP = 1000 and dump our unpacked target. If
you try to run it, it will just crush. Reason is that OllyDump has
failed to rebuild IAT (btw, if you get "Not valid PE file
message" run LordPE's rebuilder to validate file). Scroll down
00401442 .-E9 1C84A777 JMP kernel32.ExitProcess
00401447 FF DB FF
00401448 $-E9 8C98A777 JMP kernel32.GetModuleHandleA
0040144D FF DB FF
0040144E $-E9 8B929477 JMP 77D4A6DE
00401453 FF DB FF
00401454 $-E9 87429677 JMP 77D656E0
00401459 FF DB FF
0040145A $-E9 85C59477 JMP 77D4D9E4
0040145F FF DB FF
00401460 $-E9 D3539677 JMP 77D66838
00401465 FF DB FF
00401466 $-E9 B4829477 JMP 77D4971F
0040146B FF DB FF
0040146C $-E9 72699477 JMP 77D47DE3
00401471 FF DB FF
00401472 $-E9 DB509677 JMP 77D66552
00401477 FF DB FF
00401478 $-E9 A04A9477 JMP 77D45F1D
0040147D FF DB FF
0040147E $-E9 636A9477 JMP 77D47EE6
00401483 FF DB FF
00401484 $-E9 5B0CC07E JMP 7F0020E4
00401489 FF DB FF
0040148A $-E9 F308C07E JMP 7F001D82
0040148F FF DB FF
00401490 $-E9 BE09C07E JMP 7F001E53
00401495 FF DB FF
00401496 $-E9 3C08C07E JMP 7F001CD7
What happened? You see that required dll's like USER32.DLL and GDI32.DLl
are not loaded in memory. As you can see, imports jumps jump at
properly place where API's should be, but because dll are not there,
those jumps leads to nowhere. OllyDump plugin has added one new
section .newIID which should hold IAT but if you take look at the
dump of that section you'll see that it is totally empty.
How we gonna fix this? There is way. Since all import jumps point to
correct API's, we need just to load required dll's and that we can do
by manually making basic smallest IAT. That IAT will hold every dll
with just one API per each DLL and that API even doesn't matter. It's
important only that windows loader loads required dll's.
First you must know how basic import section must look. Import section must
have following parts:
Thunks - these are groups of 4 bytes. That 4 bytes are
relative address (from image base) that point to hints. There is one
thunk per each dll. After PE file is loaded in memory, thunks are
filled with API addresses. Every thunk must end with 4 zero bytes.
IMAGE_IMPORT_DESCRIPTOR - this is group of 5x4 bytes. There is
one descriptor per each dll. Every 4 bytes of descriptor holds
First 4 bytes represent RVA to the hint array.
Second 4 bytes represent TimeDateStamp for dll. This is not important
and it can be filled with zeros.
Third 4 bytes represent some ForwarderChain which is not important
and it can be filled with zeros too. This ForwarderChain is something
Forth 4 bytes are address that point to RVA of dll ASCII name in
The last 4 bytes points to RVA of thunks.
Then we have one more IMAGE_IMPORT_DESCRIPTOR filled with zeros (null
Hint arrays - these arrays holds RVA's of hints. This is
acctually same thing as thunks, but they will not be filled with
import addresses like thunks will.
Hint - it is a two byte value that is hint for windows loader
where it should look first for API. If hint doesn't work (because
newer/different version of dll) loader will perform binary search for
ASCII API name. Hint is not neccessery and we can just fill it with
zeros, just like some compilers do.
API name - it is ASCII name of API function. It's purpose is
that windows loader can perform binary search for API. It's length
isn't determined, but last byte must be 0 (null terminator).
DLL name - same thing as API name but for DLL and it must end
with two zero bytes.
That is basic import section. We will make our import section in .newIID
section which is added by OllyDump. We have three DLL's which need to
be loaded, so we'll have three thunks, three decriptors + one zero
filled, three hint arrays, and three hint-api-dll names. Open our
dumped file with LordPE's PE editor and click on sections. You'll see
that RawOffset of .newIID section is 10000. Close LordPE and open
dumped file in hex editor. We need to organise place for IAT. Next
picture shows how did I reserve place and for what. Note that all
values and chars are just symbolic to give you (and me) better view
how section should look:
are thunks, then with numbers 1,2,3,0 I've fill 1. 2. 3. and null one
Image_Import_Descriptors. Hint arrays are that arr1... Hint, API and
DLL place cannot be reserved because I don't know length of API and
DLL names before I search them. This is first area that I will fill
with data. We said that we need kernel32.dll, user32.dll and
gdi32.dll, and one API per each dll. I will chose first API that I
know name. After filling that area, I have this picture:
Now we need to fill all other data; thunks, descryptors and arrays. All
that data needs to be filled with RVA's so open this file in Olly and
see dump of .newIID section. From that you can easy read all RVA's or
even make changes in Olly. Next picture shows how arrays must be
filled for first dll , KERNEL32.DLL:
After you fill other arrays, you should have this picture:
That's it, import section is done but you have save these changes to our
file if you have done all this in Olly. But here is no option for
save to file, so we ganna do that different. Go in CPU window to
all "opcodes" (these are not opcodes, but our import
section) from 410000 to 4100CF, right click, copy to executable ->
selection, then in new window select save file. Save it as our
Now we need only to tell windows where is our IAT and how big it is. What
is IAT? IAT isn't whole imports section, but only
IMAGE_IMPORT_DESCRIPTORS. First descriptor starts at RVA
410018==>RawA 10018. It's size is three descryptors + last one
zero filled = 5*4*4 = 50h. Open dump.exe in LeordPE's PE editor,
click on directories and there change ImportTable RVA and Size to
10018 and 50. Save changes and run dump.exe. Is it working? Mine
works fine and yours should too, unless you didn't made some mistake.
Thats all for this version of PESpin. It's hard to fix IAT manually because
if you have lot of dll's than you have big job to do and you can make
mistake on any step. There are some tools that claim that they can
build import section, but non of them didn't work for me.
4. Tutorial 3 - PESpin v1.3
Unpacking PESpin v1.3 is same as unpacking 1.1 version except 1.3 version has
option for advanced code redirection. Unfortunatley for us that
option isn't available in the public version so I couldn't use it
when packing our target. Target file is Crackme05.exe, which is a
Delphi 3 app. Delphi and VC++ programs have different import
redirection which make it more difficult to repair. Grab our target
and load it in Olly. Find place where stolen OEP starts , but also
fix import redirection on your way. Let me help you a bit; import
jump is at 4137D9, timer check is at 4138D1 and stolen OEP you'll
But notice how the imports look. Usually import is one absolute jump that
jumps to API, but in this case PESpin didn't change JMP
DWORD[xxxxxxxx] to JMP xxxxxxx like he did with ASM and BC++
programs. Instead he redirected DWORD[xxxxxxxx] which was in .idata
import section to it's own section. OllyDump cannot rebuild this kind
of redirection, but there is way around it.
As I said, find where the stolen OEP is , fix that redirection jump before and
use script for stolen code. Now just dump it with OllyDump plugin. My
dumped file has lost icon and it crashes when I run it. Thats good
;)!? Open it in another Olly and check some imports (don't close
first Olly). You will be at stolen OEP beginning so trace with F7
until you enter to crackme code and to the place where first import
As you can see, our import is bad just like most of the restof them
because they all point to 00000000 address. There are just couple
good ones in whole dump. But there is one good thing, all required
dll's are loaded in memory:
What's good there if we don't have imports? It's good because we can change
all import jumps to absolute jumps that jump directly to API. We will
done that in our first/original crackme and just copy-paste all code
section and dumped file will work. But why didn't I do that the
first time? Because OllyDump isn't able to find DLL's then. To change
jumps you can use my script which I made for that "PESpin v1.x -
Delphi & VC++.txt". You can do this manually, but there is
lot of jumps so this script will do the job for you. But
maybe/probably it will not change all jumps and it may *censored* some of
them. For example: if you want that this script finds all import
jumps, you'll need to select all code from 401000 to 40104D and NOP
it (because it some code can confuse Olly and script). Then run
script and it will change all jumps. Note that you will have to run
script several times for bigger files (~bigger than 500kb). Then
after script has fixed all jumps
select all code from 401054 to 407D10 and binary copy it, paste it to our
dumped file. Save changes and run dump, it works! If your dump is not
working, then open it in Olly and try to find what did went wrong. As
usuall, dumped file will only work on machine with same version of
operating system because absolute imports. There it is, that was
unpacking of PESpin 1.3. We didn't bothered with stolen OEP bytes
because dump will work fine in this way too.
5. Final words and thanks
PESpin is solid protector which cannot be unpacked by some totally noob.
Also some programing knowledge is required if you want to make dump
that will work on any OS because way how PESpin handles imports. For
second example I was searching for tools that can add imports to some
section, but I just couldn't find nothing that will done that.
With this tutorial we sow how all PESpin versions can be unpacked, except
we didn't fully explain 1.3 version because public version doesn't
have Advanced Code Redirection option enabled. If you find some
target - PM me.
the game is not over - new PESpin is on it's way. Check version 1.3
which is packed with 1.3beta2 (as cyberbob sad on one forum) and
you'll notice big differences in behaviour of packer.
Sorry for bugs, errors and gramatical mistakes.
Big greets goes to whole BIW crew and all BIW users. Special thanks to
detten who made unpackme for 1.1 version. Of course, thanks to you
reader and I hope that you will learn something from this text. I
learned a lot while writting it.