源网址:http://www.reversing.be/article.php?story=20050811173217736
PESpin v1.0 , 1.1 & 1.3 - manually unpacking
Thursday, August 11 2005 @ 05:32 PM CEST
Contributed by: haggar
Views: 988
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.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PESpin v1.x -manually unpacking
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Hi and welcome to my new unpacking tutorial.
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 some limits:
- 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.
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
Content:
1. Tools and useful stuff
2. Tutorial 1 - PESpin v1.0
3. Tutorial 2 - PESpin v1.1
4. Tutorial 3 - PESpin v1.3
5. Final words and thanks
1. Tools and useful stuff
- OllyDbg 1.10
- LordPE
- 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 tutorial better:
- 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 to download.
- 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:
77E7A1EE> BA 0000FE7F MOV EDX,7FFE0000
77E7A1F3 8B02 MOV EAX,DWORD PTR DS:[EDX]
77E7A1F5 F762 04 MUL DWORD PTR DS:[EDX+4]
77E7A1F8 0FACD0 18 SHRD EAX,EDX,18
77E7A1FC C3 RETN
Place bp on RETN opcode and run targed. You will break on bp in kernel.
Remove bp and return to target code:
00409D17 8BD8 MOV EBX,EAX
00409D19 F7D3 NOT EBX
00409D1B 33D8 XOR EBX,EAX
00409D1D 43 INC EBX
...
...
Now scroll waaaaaay up and find this place:
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
...
...
...
...
0040909B ???
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:
0040905F NOP
00409060 NOP
...
...
...
0040909A NOP
0040909B NOP
0040909C CALL packed1_.004089F2
004090A1 CALL packed1_.004090A9
004090A6 JMP SHORT packed1_.004090AC
Now prss F9 again to land on previous bp:
00409109 STC
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:
00409278 E8 616A00EB CALL EB40FCDE
Patch first byte, place bp on POPAD and run:
00409278 90 NOP ;Patch this byte.
00409279 61 POPAD ;Place bp here.
0040927A C1E2 F3 SHL EDX,0F3
0040927D 23D0 AND EDX,EAX
0040927F 0FC1D1 XADD ECX,EDX
00409282 BA 69533D94 MOV EDX,943D5369
00409287 69C8 262C4AF2 IMUL ECX,EAX,F24A2C26
0040928D 11C2 ADC EDX,EAX
0040928F 0FBDD0 BSR EDX,EAX
00409292 C7C2 BDCC6DB9 MOV EDX,B96DCCBD
00409298 42 INC EDX
00409299 FFC2 INC EDX
0040929B C1D2 83 RCL EDX,83
0040929E 0FBCC8 BSF ECX,EAX
004092A1 23D0 AND EDX,EAX
004092A3 39C2 CMP EDX,EAX
004092A5 81E9 B576A23F SUB ECX,3FA276B5
004092AB EB 01 JMP SHORT packed1_.004092AE
004092AD E7 0F OUT 0F,EAX
004092AF AF SCAS DWORD PTR ES:[EDI]
004092B0 C8 0FB7D0 ENTER 0B70F,0D0
004092B4 C7C1 0EE985D6 MOV ECX,D685E90E
004092BA F3: PREFIX REP:
004092BB F7C1 1E8ABDAB TEST ECX,ABBD8A1E
004092C1 -E9 3A7DFFFF JMP packed1_.00401000
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.
Now, just trace untill you get to some jump that leads to the code section:
004092C1 -E9 3A7DFFFF JMP packed1_.00401000
In this case jump leads straight to OEP:
00401000 . 6A 00 PUSH 0
00401002 . E8 F1F1FFFF CALL packed1_.004001F8
00401007 . A3 CA204000 MOV DWORD PTR DS:[4020CA],EAX
0040100C . 6A 00 PUSH 0
0040100E .-E9 EBF1FFFF JMP packed1_.004001FE
00401013 . E8 F1F1FFFF CALL packed1_.00400209
00401018 . 0BC0 OR EAX,EAX
0040101A . 74 01 JE SHORT packed1_.0040101D
0040101C . C3 RETN
0040101D > C705 64204000 >MOV DWORD PTR DS:[402064],4003
00401027 . C705 68204000 >MOV DWORD PTR DS:[402068],packed1_.00401>
If you dump file, it will not work. Reason is redirected code. Note where some CALLs and JMPs leads:
00401002 CALL packed1_.004001F8
...
0040100E JMP packed1_.004001FE
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 examples:
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 very simple.
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 place.
- 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 in archive).
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 v1.0.
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
In 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 help file:
Crypt markers: Code 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).
Clear markers: CLEAR_START and CLEAR_END markers offers ability to remove blocks of code after being executed.These 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):
77ED6FC4>-E9 1860FB42 JMP BAE8CFE1
77ED6FC9 90 NOP
77ED6FCA 90 NOP
77ED6FCB 90 NOP
77ED6FCC 90 NOP
77ED6FCD 90 NOP
77ED6FCE 90 NOP
77ED6FCF 90 NOP
77ED6FD0 90 NOP
77ED6FD1 90 NOP
77ED6FD2 90 NOP
77ED6FD3>-E9 7D61FB42 JMP BAE8D155
77ED6FD8 90 NOP
77ED6FD9 90 NOP
77ED6FDA 90 NOP
77ED6FDB 90 NOP
77ED6FDC 90 NOP
77ED6FDD 90 NOP
77ED6FDE 90 NOP
77ED6FDF 90 NOP
Remove bp, open memory window and place "memory bp on access" on last section. That is one SFX. Now press Shift+F9 and you'll return to protector code (then remove mem bp):
0040D491 85C0 TEST EAX,EAX
0040D493 0F84 3F090000 JE keygen.0040DDD8
0040D499 E8 01000000 CALL keygen.0040D49F
0040D49E FF59 50 CALL FAR FWORD PTR DS:[ECX+50]
0040D4A1 51 PUSH ECX
0040D4A2 55 PUSH EBP
0040D4A3 810424 12374000 ADD DWORD PTR SS:[ESP],keygen.00403712
0040D4AA 814424 04 220000>ADD DWORD PTR SS:[ESP+4],22
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]
That is place where DLL name is being erased. Get to this line
0040D4F5 8813 MOV BYTE PTR DS:[EBX],DL
and 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:
00402102 4B 45 52 4E 45 4C 33 32 2E 44 4C 4C 00 00 F7 00 KERNEL32.DLL..÷.
00402112 00 00 00 00 00 00 00 00 00 00 C4 00 00 00 00 00 ..........Ä.....
00402122 00 00 00 00 00 00 00 00 00 00 00 00 C5 00 00 00 ............Ĺ...
00402132 00 00 00 00 00 00 00 00 CC 00 00 00 00 00 00 00 ........Ě.......
00402142 00 00 00 00 00 00 CC 00 00 00 00 00 00 00 00 00 ......Ě.........
00402152 00 00 D2 00 00 00 00 00 00 00 00 00 00 00 00 00 ..Ň.............
00402162 00 00 00 00 D3 00 00 00 00 00 00 00 00 00 00 00 ....Ó...........
00402172 00 00 00 00 00 00 00 00 00 00 D3 00 00 00 00 00 ..........Ó.....
00402182 00 00 00 00 00 00 00 00 00 00 D3 00 00 00 00 00 ..........Ó.....
00402192 00 00 00 00 00 00 00 00 00 00 00 00 55 53 45 52 ............USER
004021A2 33 32 2E 44 4C 4C 00 00 C3 00 00 00 00 00 00 00 32.DLL..Ă.......
004021B2 00 00 00 00 00 00 00 00 00 00 00 00 D3 00 00 00 ............Ó...
004021C2 00 00 00 00 00 00 00 00 00 00 D3 00 00 00 00 00 ..........Ó.....
004021D2 00 00 00 00 00 00 D3 00 00 00 00 00 00 00 00 00 ......Ó.........
004021E2 00 00 00 00 00 00 47 44 49 33 32 2E 44 4C 4C 00 ......GDI32.DLL.
004021F2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
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:
0040E4D8 8BD8 MOV EBX,EAX
0040E4DA F7D3 NOT EBX
0040E4DC 33D8 XOR EBX,EAX
0040E4DE 43 INC EBX
0040E4DF 68 87000000 PUSH 87
0040E4E4 59 POP ECX
0040E4E5 66:35 4C50 XOR AX,504C
0040E4E9 66:05 8911 ADD AX,1189
0040E4ED AA STOS BYTE PTR ES:[EDI]
0040E4EE EB 01 JMP SHORT keygen.0040E4F1
0040E4F0 68 499CC12C PUSH 2CC19C49
0040E4F5 24 06 AND AL,6
Find the jump that leads to redirection procedure and patch all bytes between it and good call:
0040D5DF FF6424 FC JMP DWORD PTR SS:[ESP-4] <---Jump!!!
...
... <--- Patch all these opcodes!!!
...
0040D643 E8 C4F6FFFF CALL keygen.0040CD0C <---Good call!!!
0040D648 E8 03000000 CALL keygen.0040D650
Then scroll little down to our well known place:
BR>
0040D6D7 B0 F9 MOV AL,0F9
0040D6D9 72 3F JB SHORT keygen.0040D71A
0040D6DB 8D85 F469271E LEA EAX,DWORD PTR SS:[EBP+1E2769F4]
0040D6E1 2D 8417E71D SUB EAX,1DE71784
0040D6E6 FFD0 CALL EAX
0040D6E8 EB 02 JMP SHORT keygen.0040D6EC
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 to 40D6E6.
Ok, scroll down and find the POPUP opcode. You will find it a little obfuscated
0040D8FA
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:
0040D8FC 0FC9 BSWAP ECX
0040D8FE 0FACC2 13 SHRD EDX,EAX,13
...
...
...
0040D947 -E9 B436FFFF JMP keygen.00401000 <---Jump toOEP!!!
Execute that jump and you'll land on OEP. Code there has crappy look even if you press Ctrl+A, so right click on it, select analysis and remove analysis from module.
Then you'll have better picture:
00401000 9C PUSHFD
00401001 60 PUSHAD
00401002 B9 B5381023 MOV ECX,231038B5
00401007 BF 7896AA00 MOV EDI,0AA9678
0040100C 81E9 91381023 SUB ECX,23103891
00401012 B8 5F1360C2 MOV EAX,C260135F
...
...
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:
00401000 9C PUSHFD
00401001 60 PUSHAD
00401002 B9 B5381023 MOV ECX,231038B5
00401007 BF 7896AA00 MOV EDI,0AA9678
0040100C 81E9 91381023 SUB ECX,23103891
00401012 B8 5F1360C2 MOV EAX,C260135F
00401017 05 DCE1E03D ADD EAX,3DE0E1DC
0040101C FF0D 22104000 DEC DWORD PTR DS:[401022]
00401022 0011 ADD BYTE PTR DS:[ECX],DL <---Trace to this line!!!
00401024 61 POPAD
00401025 9D POPFD
Trace with F8 to line that I showed abowe and you'll see that it changed to:
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:
00401000 0000 ADD BYTE PTR DS:[EAX],AL
00401002 0000 ADD BYTE PTR DS:[EAX],AL
00401004 0000 ADD BYTE PTR DS:[EAX],AL
00401006 0000 ADD BYTE PTR DS:[EAX],AL
00401008 0000 ADD BYTE PTR DS:[EAX],AL
0040100A 0000 ADD BYTE PTR DS:[EAX],AL
0040100C 0000 ADD BYTE PTR DS:[EAX],AL
0040100E 0000 ADD BYTE PTR DS:[EAX],AL
00401010 0000 ADD BYTE PTR DS:[EAX],AL
00401012 0000 ADD BYTE PTR DS:[EAX],AL
00401014 0000 ADD BYTE PTR DS:[EAX],AL
00401016 0000 ADD BYTE PTR DS:[EAX],AL
00401018 0000 ADD BYTE PTR DS:[EAX],AL
0040101A 0000 ADD BYTE PTR DS:[EAX],AL
0040101C 0000 ADD BYTE PTR DS:[EAX],AL
0040101E 0000 ADD BYTE PTR DS:[EAX],AL
00401020 0000 ADD BYTE PTR DS:[EAX],AL
00401022 0000 ADD BYTE PTR DS:[EAX],AL
00401024 61 POPAD
00401025 9D POPFD
00401026 6A 00 PUSH 0
00401028 E8 83F1FFFF CALL keygen.004001B0
0040102D A3 E4304000 MOV DWORD PTR DS:[4030E4],EAX
00401032 6A 00 PUSH 0
00401034 -E9 7DF1FFFF JMP keygen.004001B6 <--- to PE header!
00401039 6A 00 PUSH 0
0040103B 6A 01 PUSH 1
0040103D 50 PUSH EAX
0040103E E8 7EF1FFFF CALL keygen.004001C1 <--- to PE header!
00401043
6A 00 PUSH 0
00401045
E8 7DF1FFFF CALL keygen.004001C7 <--- to PE header!
Notice that some of new decrypted jumps and calls lead to PE header, so we must run script for fixing redirections again and then fill all code from 401000 to 401025 with NOP's:
00401000 . 90 NOP
...
...
...
00401024 . 90 NOP
00401025 . 90 NOP
00401026 . 6A 00 PUSH 0
00401028 . E8 1B040000 CALL keygen.00401448
0040102D . A3 E4304000 MOV DWORD PTR DS:[4030E4],EAX
00401032 . 6A 00 PUSH 0
00401034 . 68 7F104000 PUSH keygen.0040107F
00401039 . 6A 00 PUSH 0
0040103B . 6A 01 PUSH 1
0040103D . 50 PUSH EAX
0040103E . E8 11040000 CALL keygen.00401454
00401043 . 6A 00 PUSH 0
00401045 . E8 F8030000 CALL keygen.00401442
0040104A . EB 0B JMP SHORT keygen.00401057
0040104C EB DB EB
We have fixed the first kind of decryption, CLEAR type. Now we gonna find and fix CRYPT one. Since target is small, you can scroll down and find two interesting calls easy:
004013D1 $ FF15 5FF54000 CALL DWORD PTR DS:[40F55F]
004013D7 . 71 27 JNO SHORT keygen.00401400
004013D9 . 0B3E OR EDI,DWORD PTR DS:[ESI]
...
...
...
0040141A . FF15 83F54000 CALL DWORD PTR DS:[40F583]
00401420 . 27 DAA
00401421 . DCE5 FSUBR ST(5),ST
00401423 . 3AFF CMP BH,BH
00401425 .^75 F8 JNZ SHORT keygen.0040141F
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:
004013D1 $ FF15 5FF54000 CALL DWORD PTR DS:[40F55F] <--NOP this!!!
004013D7 . 71 27 JNO SHORT keygen.00401400 <--NOP this!!!
004013D9 . 0B3E OR EDI,DWORD PTR DS:[ESI] <--NOP this!!!
004013DB . 55 PUSH EBP
004013DC . 8BEC MOV EBP,ESP
004013DE . 83C4 F4 ADD ESP,-0C
...
...
...
00401415 . D9E1 FABS
00401417 . DD5D F4 FSTP QWORD PTR SS:[EBP-C]
0040141A . FF15 83F54000 CALL DWORD PTR DS:[40F583]
00401420 . 27 DAA
00401421 . DCE5 FSUBR ST(5),ST
00401423 . 3AFF CMP BH,BH
00401425 .^75 F8 JNZ SHORT keygen.0040141F
00401427 . FF75 F4 PUSH DWORD PTR SS:[EBP-C]
0040142A . 68 BD304000 PUSH keygen.004030BD
Code is decrypted and now wee need to patch deryption calls. Patch first call wich decrypts (abowe picture). Now enter in second call at 40141A 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 this:
004013D1 /$ 90 NOP
004013D2 |. 90 NOP
004013D3 |. 90 NOP
004013D4 |. 90 NOP
004013D5 |. 90 NOP
004013D6 |. 90 NOP
004013D7 |. 90 NOP
004013D8 |. 90 NOP
004013D9 |. 90 NOP
004013DA |. 90 NOP
004013DB |. 55 PUSH EBP
004013DC |. 8BEC MOV EBP,ESP
004013DE |. 83C4 F4 ADD ESP,-0C
004013E1 |. 52 PUSH EDX ;
keygen.004030F0
004013E2 |. 51 PUSH ECX
004013E3 |. 33C9 XOR ECX,ECX
004013E5 |. 8B55 08 MOV EDX,DWORD PTR SS:[EBP+8]
004013E8 |. DD05 DB304000 FLD QWORD PTR DS:[4030DB]
004013EE |> 33C0 XOR EAX,EAX
004013F0 |. 8A0411 MOV AL,BYTE PTR DS:[ECX+EDX]
004013F3 |. 24 7F AND AL,7F
004013F5 |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
004013F8 |. DB45 FC FILD DWORD PTR SS:[EBP-4]
004013FB |. DEC1 FADDP ST(1),ST
004013FD |. DC0D C3304000 FMUL QWORD PTR DS:[4030C3]
00401403 |. 41 INC ECX
00401404 |. 3B4D 10 CMP ECX,DWORD PTR SS:[EBP+10]
00401407 |.^72 E5 JB SHORT keygen.004013EE
00401409 |. DC0D CB304000 FMUL QWORD PTR DS:[4030CB]
0040140F |. DC0D D3304000 FMUL QWORD PTR DS:[4030D3]
00401415 |. D9E1 FABS
00401417 |. DD5D F4 FSTP QWORD PTR SS:[EBP-C]
0040141A |. 90 NOP
0040141B |. 90 NOP
0040141C |. 90 NOP
0040141D |. 90 NOP
0040141E |. 90 NOP
0040141F |. 90 NOP
00401420 |. 90 NOP
00401421 |. 90 NOP
00401422 |. 90 NOP
00401423 |. 90 NOP
00401424 |. FF75 F8 PUSH DWORD PTR SS:[EBP-8]
00401427 |. FF75 F4 PUSH DWORD PTR SS:[EBP-C]
0040142A |. 68 BD304000 PUSH keygen.004030BD
0040142F |. 68 54314000 PUSH keygen.00403154
00401434 |. E8 15000000 CALL keygen.0040144E
00401439 |. 83C4 10 ADD ESP,10
0040143C |. 59 POP ECX
0040143D |. 5A POP EDX
0040143E |. C9 LEAVE
0040143F . C2 0C00 RETN 0C
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 to imports:
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:
1) 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.
2) IMAGE_IMPORT_DESCRIPTOR - this is group of 5x4 bytes. There is one descriptor per each dll. Every 4 bytes of descriptor holds specific information:
1. First 4 bytes represent RVA to the hint array.
2. Second 4 bytes represent TimeDateStamp for dll. This is not important and it can be filled with zeros.
3. Third 4 bytes represent some ForwarderChain which is not important and it can be filled with zeros too. This ForwarderChain is something obsolete.
4. Forth 4 bytes are address that point to RVA of dll ASCII name in import section.
5. The last 4 bytes points to RVA of thunks.
3) Then we have one more IMAGE_IMPORT_DESCRIPTOR filled with zeros (null terminating one).
4) 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.
5) 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.
6) 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).
7) 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:
thnk1... 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 410000 address:
00410000 8000 01 ADD BYTE PTR DS:[EAX],1
00410003 0000 ADD BYTE PTR DS:[EAX],AL
00410005 0000 ADD BYTE PTR DS:[EAX],AL
...
...
...
004100CF 0000 ADD BYTE PTR DS:[EAX],AL
004100D1 0000 ADD BYTE PTR DS:[EAX],AL
004100D3 0000 ADD BYTE PTR DS:[EAX],AL
Select 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 dump.exe
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 find at:
00413AF6 F7D2 NOT EDX <--------------- Start of stolen OEP!
00413AF8 39C2 CMP EDX,EAX
00413AFA F7C0 74E7F921 TEST EAX,21F9E774
00413B00 0FACC2 48 SHRD EDX,EAX,48
...
...
00413B5C 3C 68 CMP AL,68
00413B5E 67:3B41 00 CMP EAX,DWORD PTR DS:[BX+DI]
00413B62 -E9 4D0AFFFF JMP Crackme0.004045B4 <------- Jump to code section!
Trace to this last jump that leads to code section and enter in it:
004045B4 . 50 PUSH EAX
004045B5 . 6A 00 PUSH 0
004045B7 . E8 F8FEFFFF CALL Crackme0.004044B4 <------- Enter here!
004045BC . BA 98804000 MOV EDX,Crackme0.00408098
004045C1 . 52 PUSH EDX
004045C2 . 8905 B4944000 MOV DWORD PTR DS:[4094B4],EAX
Now trace a little more and enter to first call. You are at the place where one import is:
004044B4 $-FF25 D4564100 JMP DWORD PTR DS:[4156D4] ;kernel32.GetModuleHandleA
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 should be:
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.
Also, 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.
[haggar A.D 11.82005 ]
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法