PESpin v0.3 - manually unpacking

Tuesday, July 26 2005 @ 09:14 PM CEST

Contributed by: haggar

Level : beginner

PESpin v0.3 has some nice features; good IAT redirection with some emulation API opcodes and stolen OEP are it's best weapon. It has also some antidebug tricks based on exceptions, but Olly is imune on that, it checks for SoftICE and NTICE, it has couple CRC checks, and it has password locking and time limit.

~~~~~~~~~~~~~~~~~~~~~~~
PESpin v0.3 - manually unpacking
~~~~~~~~~~~~~~~~~~~~~~~



author: haggar
OS: Windows XP
level: beginner-advanced
target and stuff: http://www.reversing.be/binaries/articles/200507262109322.rar


Greets!

This is older version of PESpin, but I have decide to start with it, tought it will be easier later to try new versions. Tutorial is for beginners who have some experience with unpacking protectors, also you must be familiar with tools (Olly, ImpREc, LordPE).

PESpin v0.3 has some nice features; good IAT redirection with some emulation API opcodes and stolen OEP are it's best weapon. It has also some antidebug tricks based on exceptions, but Olly is imune on that, it checks for SoftICE and NTICE, it has couple CRC checks, and it has password locking and time limit, but that's not problem. Also there is some small diferences between packed files compiled with different compilers (VC++,Borland C++, Delphi, VB, ASM...).




1. ANALYZING PESpin CODE

Grab Olly, exclude all exceptions in options and load target, cruehead's CRACKME3.EXE which I packed with PESpin. Scroll little down and you'll see two decrypting loops that decrypt PESpin code section:

00407127 XOR BYTE PTR DS:[ECX+EDI],BL
0040712A DEC BL
0040712C LOOPD SHORT CRACKME3.00407127 ;First loop.
0040712E PUSH 13C
00407133 POP ECX
00407134 LEA EDI,DWORD PTR SS:[EBP+4036B6]
0040713A ROR BYTE PTR DS:[ECX+EDI],2
0040713E LOOPD SHORT CRACKME3.0040713A ;Second loop.
00407140 CALL CRACKME3.00407147 ;Place bp here to pass them.

Pass those loops and trace to here:

00408138 MOV EDI,DWORD PTR SS:[ESP+20]
0040813C AND EDI,FFFF0000
00408142 CMP WORD PTR DS:[EDI],5A4D
00408147 JNZ SHORT CRACKME3.0040815A
00408149 MOVZX EDX,WORD PTR DS:[EDI+3C]
0040814D TEST DX,0F800
00408152 JNZ SHORT CRACKME3.0040815A
00408154 CMP EDI,DWORD PTR DS:[EDX+EDI+34]
00408158 JE SHORT CRACKME3.00408162
0040815A SUB EDI,10000
00408160 JMP SHORT CRACKME3.00408142
00408162 XCHG EAX,EDI
00408163 PUSH CRACKME3.00402CFC
00408168 PUSH EAX
00408169 XCHG DWORD PTR SS:[EBP+402CED],EAX
0040816F ADD DWORD PTR SS:[ESP+4],EBP
00408173 LEA EAX,DWORD PTR SS:[EBP+EB8382F8]
00408179 LEA EAX,DWORD PTR DS:[EAX+14BCAABD]
0040817F CALL EAX

Here starts procedure that will find all API's in kernel that PESpin needs for it's work. This first part seams that want be sure that it is in kernel32.dll. Then you'll enter in CALL EAX where PESpin search for API's in some weird way. For us is important which are those API's. Trace to here and you'll see that here API addresses are stored to some location:

00408307 MOV DWORD PTR DS:[EDI+1],EAX

API that PESpin want are:

LoadLibraryA
ExitProcess
GetProcAddress
VirtualProtect
CloseHandle
VirtualAlloc
VirtualFree
CreateFileA
ReadFile
VirtualQuery
GetTickCount
GetModuleHandleA
CreateThread
Sleep
GetCurrentProcessId
OpenProcess
TerminateProcess
GetFileSize
GetModuleFileNameA

Then PESpin will make some exceptions which purpose is to detect debugger, but olly doesn't fall on this so you do this: press Shift+F9 4. times and then, place memory bp on PESpin section press Shift+F9 untill you break on mem bp. Code that we skiped doesnt hold anything important for our unpacking, just some exceptions tricks and one decryption loop. You should be here:

00407276 MOV ECX,13
0040727B CALL CRACKME3.00407280
...
...

Trace with F7 and you'll get here:

004072BF JMP DWORD PTR SS:[EBP+402D57]

There is called GetModuleFileNameA API that searchin path for our packed file, then CreateFile opens our file, GetFileSize, VirtualAlloc will reserve enough place in memory for (starting from 390000 on my computer) loading that file, ReadFile will load it to that allocated place, CloseHandle. Than you will enter to CRC calculating routine which calculates CRC of whole file:

00408864 PUSH ECX
00408865 OR DL,4
00408868 INC EDI
00408869 XOR AH,BYTE PTR DS:[EDI]
0040886B SHR EAX,3
0040886E XOR AL,BH
00408870 ADD EAX,7801A018
00408875 XOR EAX,EBX
00408877 MOV CL,BL
00408879 ROR EAX,CL
0040887B XCHG EAX,EBX
0040887C DEC EDX
0040887D JNZ SHORT CRACKME3.0040886E
0040887F POP ECX
00408880 LOOPD SHORT CRACKME3.00408864
00408882 XCHG EAX,EBX
00408883 POP EBX
00408884 POP EDX
00408885 RETN

Then that CRC is substracted from real one which is written in file and stored to it's place:

00407397 SUB DWORD PTR SS:[EBP+403827],EAX

If you open crackme in hex editor or take look a in olly dump last section, you can find where is written reall CRC. Than is caled VirtualFree. After that you will get on one exception, put mem bp on .taz section and press Shift+F9 untill you break here:

00408240 NOP
00408241 NOP
00408242 XOR EBX,EBX
00408244 POP DWORD PTR FS:[EBX]
00408247 POP EBX
00408248 SUB EBX,16
0040824E JMP SHORT CRACKME3.00408251
....
....

Remove bp and trace in to:

00408251 CMP BYTE PTR DS:[EBX],0CC
00408254 JNZ SHORT CRACKME3.00408261
00408256 AND ESP,0FFFF
0040825C CALL CRACKME3.0040827B
00408261 JMP EBX

Here packer is checking is breakpoint placed on [ebx]=4073e0 address. Pass that check and you'll get to that address, then trace and trace untill you get here:

0040745A MOVZX ECX,WORD PTR SS:[EBP+402CCF]
00407461 MOV EDX,DWORD PTR SS:[EBP+402CD5]
00407467 ADD EDX,0F8
0040746D MOV EBX,DWORD PTR SS:[EBP+403817]
00407473 XOR EAX,EAX
00407475 PUSH ECX
00407476 BT EBX,EAX
00407479 JNB SHORT CRACKME3.0040749F
0040747B PUSH EDX
0040747C MOV EDI,DWORD PTR DS:[EDX+C]
0040747F ADD EDI,DWORD PTR SS:[EBP+402CCB]
00407485 MOV ECX,DWORD PTR DS:[EDX+10]
00407488 MOV EDX,DWORD PTR SS:[EBP+403827] ;CRC value is taken.
0040748E SHR EDX,1
00407490 JB SHORT CRACKME3.00407498
00407492 XOR EDX,ED43AF32
00407498 XOR BYTE PTR DS:[EDI],DL ;Decrypting section.
0040749A INC EDI
0040749B DEC ECX
0040749C LOOPD SHORT CRACKME3.0040748E
0040749E POP EDX
0040749F INC EAX
004074A0 ADD EDX,28
004074A3 POP ECX
004074A4 LOOPD SHORT CRACKME3.00407475
004074A6 OR DWORD PTR SS:[EBP+4036B0],0
004074AD JE SHORT CRACKME3.004074BC
004074AF LEA EAX,DWORD PTR SS:[EBP+403524]
004074B5 SUB EAX,3D1
004074BA CALL EAX

OK, we see that it takes that CRC value and then it decrypts .code section with it. Hmm, maybe that value isn't CRC at all, but some key that purpose is to decrypt sections? And maybe is both? I think that it's some key that decrypts sectiones. Pass this check and all sections will be decrypted , but that's only one layer. Trace further and you'll get to SOFTICE check, procedure starts at 408620 and ends at 40867D. It uses CreateFileA to search for SICE and NTICE files and if finds them, program will terminate. Tracing further you'll find one more decryptor loop at 4074dc and one more at 4088B9, not much interesting for us. Let we speed up a little and place bp on VirtualAlloc in command bar, then press Shift+F9 until we break on it. Remove it and keep tracing untill you get here

004078A4 PUSHAD
004078A5 MOV ESI,DWORD PTR SS:[ESP+24]
004078A9 MOV EDI,DWORD PTR SS:[ESP+28]
004078AD CLD
004078AE MOV DL,80
004078B0 XOR EBX,EBX
004078B2 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
004078B3 MOV BL,2
004078B5 CALL CRACKME3.00407927
004078BA JNB SHORT CRACKME3.004078B2
...
...
0040792D INC ESI
0040792E ADC DL,DL
00407930 RETN
00407931 XOR ECX,ECX
00407933 INC ECX
00407934 CALL CRACKME3.00407927
00407939 ADC ECX,ECX
0040793B CALL CRACKME3.00407927
00407940 JB SHORT CRACKME3.00407934
00407942 RETN
00407943 SUB EDI,DWORD PTR SS:[ESP+28]
00407947 MOV DWORD PTR SS:[ESP+1C],EDI
0040794B POPAD
0040794C RETN ;Place bp here!!!

This is unpacking procedure which will unpack or decrypt sections. It goes like this: first was allocated enough memory, then section is unpacked there, than original section is erased and unpacked is copied from alocated memory to original section, then VirtuallFree is called and VirtualProtect. Place bp on last RETN and run 2 times untill packed section are unpacked, remove bp , put bp on VirtualFree, Shift+F9 untill you break on it, return to user code and you should be here:

00408B60 LEA ESP,DWORD PTR SS:[ESP+4]
00408B64 LEA EAX,DWORD PTR SS:[EBP+12256A5]
00408B6A SUB EAX,0E23546
00408B6F JMP EAX

Enter in that EAX and you'll get to the most interesting place:

00407620 CALL CRACKME3.00407628 ;You're here!!!!
00407625 JMP SHORT CRACKME3.0040762B
00407627 STC
00407628 JMP SHORT CRACKME3.00407625
0040762A SBB EAX,DWORD PTR DS:[EBX+C30C2404]
00407630 CMP BYTE PTR SS:[EBP+40218485],CL
00407636 ADD BYTE PTR DS:[EAX+9D8D7C00],AL
0040763C MOV FS,WORD PTR DS:[EAX+EAX*2]
0040763F ADD BYTE PTR DS:[EBX],CH
00407641 FADD DWORD PTR DS:[EAX-77]
00407644 SBB BYTE PTR SS:[EBP-5],CH
00407647 AAA
00407648 MOV AL,BYTE PTR DS:[EAX]
0040764A CALL CRACKME3.00407669
0040764F PUSHAD ;Here starts import redirection code:
....
.... ;Lot of jumps and stuff here.
....
....
00407708 POPAD ;Here ends import redirection.
00407709 RETN
0040770A ADD DWORD PTR DS:[EDI],ECX
0040770C PUSH EDI
0040770D ADD EDX,ECX
0040770F MOV EDI,ESI
00407711 MOV ESI,EAX
00407713 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>
00407715 MOV EAX,ESI
00407717 MOV ESI,EDI
00407719 POP EDI
0040771A JMP CRACKME3.00407675

We will not enter in imports redirection procedure yet. I will talk about that later so trace further with F7, you'll get to VirtualProtect again, just trace until you get to this place that looks like dead end street:

004001F8 MOV ESI,CRACKME3.004001FF
004001FD POP DWORD PTR DS:[ESI]
004001FF ADD BYTE PTR DS:[EAX],AL
00400201 ADC BYTE PTR DS:[EAX],AL
00400203 ADD BYTE PTR DS:[EAX],AL
00400205 ADC BYTE PTR DS:[EAX],AL
00400207 ADD BYTE PTR DS:[EAX],AL
00400209 ADD AL,0
0040020B ADD BYTE PTR DS:[EAX],AL
0040020D PUSH ES
0040020E ADD BYTE PTR DS:[EAX],AL
00400210 ADD BYTE PTR DS:[EAX],AL

Place mem bp on .taz section and run untill you break to:

00408553 SUB EBX,EBX
00408555 MOV ESP,DWORD PTR SS:[ESP+8]
00408559 POP DWORD PTR FS:[EBX]
0040855C POP ECX
0040855D POP EBP
0040855E MOV EDI,CRACKME3.00400100

Trace till you get to first GetTickCount API call:

0040857D CALL DWORD PTR SS:[ESP] ; kernel32.GetTickCount

Than till second (right below it):

00408591 JMP DWORD PTR SS:[ESP+EBX-4] ; kernel32.GetTickCount

Packer uses two GetTickCount calls, why I don't know but this will be usefull information for us. Press Shift+F9 once to get to INT3 exception. This is another debugger check, place mem bp on .taz section, run and you're here (right below):

00408A27 JMP SHORT CRACKME3.00408A2A

Now trace and trace alot untill you enter to VirtualAlloc, exit it, trace and trace ..... and you'll get here where packer decrypts DLL names:

004086B3 JE SHORT CRACKME3.004086BA
004086B5 NOT BYTE PTR DS:[EDI] ;This decrypts.
004086B7 INC EDI
004086B8 JMP SHORT CRACKME3.004086B0
004086BA POP EDI
004086BB RETN

Trace and you'll be thrown to LoadLibraryA. Place mem bp on .taz section and press Shift+F9 to get out from there:

77ED6FC4 >JMP BAE8CFE1
77ED6FC9 NOP
77ED6FCA NOP
77ED6FCB NOP
77ED6FCC NOP
77ED6FCD NOP
77ED6FCE NOP
77ED6FCF NOP
77ED6FD0 NOP
77ED6FD1 NOP
77ED6FD2 NOP
77ED6FD3 >JMP BAE8D155
77ED6FD8 NOP
77ED6FD9 NOP
77ED6FDA NOP
77ED6FDB NOP

You should be here:

00407A4D TEST EAX,EAX

You know what to do :), trace again and you will get to DLL name erasing:

00407A8A OR BYTE PTR DS:[EBX],0
00407A8D JE SHORT CRACKME3.00407A9C
00407A8F MOV BYTE PTR DS:[EBX],DL
00407A91 ROL EDX,4

When erasing is over trace to this interesting place

004077D4 CMP BYTE PTR SS:[EBP+4026E5],0CC
004077DB JNZ SHORT CRACKME3.004077E2
004077DD JE SHORT CRACKME3.004077E0

Here it checks is there bp placed on that [EBP+xxxxx] address which is here:

00407BA6 STC
00407BA7 JB SHORT CRACKME3.00407BAE
00407BA9 CALL CRACKME3.0040867E
00407BAE JMP SHORT CRACKME3.00407BB1

That is important place since PESpin is checking it. This place is very close to the end of protector code. That call between two jumps is optional thing; if you chose in PESpin options "Close program after: xxx minutes" that CALL will be executed and there will start thread that will close program after specified time. After this, protector will start with IAT redirection in that big procedure that I mention earlier. Then we must find stolen OEP bytes. That is not far away from here. We must find one POPAD opcode that is last PESpin code before stolen bytes and jump to real OEP. Scroll down:

00407BFA 68 E8030000 PUSH 3E8
00407BFF 00EB ADD BL,CH
00407C01 04 1F ADD AL,1F
00407C03 ^EB FB JMP SHORT CRACKME3.00407C00
00407C05 ^7C 83 JL SHORT CRACKME3.00407B8A
00407C07 04 24 ADD AL,24
00407C09 0C C3 OR AL,0C3
00407C0B E8 616A00EB CALL EB40E671 ;Here it is POPAD opcode!!!!!!!
00407C10 01E4 ADD ESP,ESP
00407C12 E8 6D98FFFF CALL CRACKME3.00401484
00407C17 -E9 EB93FFFF JMP CRACKME3.00401007
00407C1C AE SCAS BYTE PTR ES:[EDI]

Since this place has obfuscated code, Olly doesn't disply it right, but you will see one 61 byte at 407C0B. 61hex=POPAD. Binary edit (NOP one byte) that line of code to make it clear:

00407C07 04 24 ADD AL,24
00407C09 0C C3 OR AL,0C3
00407C0B 90 NOP
00407C0C 61 POPAD
00407C0D 6A 00 PUSH 0
00407C0F EB 01 JMP SHORT CRACKME3.00407C12

Now place bp on POPAD and run crackme. Trace and you'll enter in code section at some 401xxx address, but you will probably be confused by the look of code. Than you will enter to kernel32 and then return to packer code. That is becuse PESpin stole couple first instructions and first instructions of this crackme are some API calls so here is PESpin still interfear with crackme. If you would like, you can trace further and enter to IAT relocations. There you can see how PESpin has mangled imports.

We now know what PESpin is doing and now we gonna unpack our target in a fast way, fix IAT and find stolen OEP.




2. UNPACKING, FIXING IAT AND STOLEN OEP

PESpin , when packing our file, has already changed IAT and OEP of our packed program. But IAT relocation will be done on the fly, in the memory and we can prevent that to happened. GetTickCount is one of last API that are called by protector code and it's very useful since it throw us at most interesting parts, so place bp on it in command bar and run crackme twice (ignore all exceptions in olly before, we don't need them anymore). When you break second time in kernel, remove bp and return to:

0040797F E8 03000000 CALL CRACKME3.00407987
00407984 EB 04 JMP SHORT CRACKME3.0040798A
00407986 DFEB FUCOMIP ST,ST(3)
00407988 FB STI
00407989 DE83 04240CC3 FIADD WORD PTR DS:[EBX+C30C2404]
0040798F D88D 8533734B FMUL DWORD PTR SS:[EBP+4B733385]



Scroll down to our hot spot:

00407BA6 F9 STC ;This opcode is checked for bp.
00407BA7 72 05 JB SHORT CRACKME3.00407BAE
00407BA9 E8 D00A0000 CALL CRACKME3.0040867E ;Timer call, not used here.
00407BAE EB 01 JMP SHORT CRACKME3.00407BB1

If program is using timer feature, it will have this shape since it will enter in timer CALL:

00406BA6 F8 CLC
00406BA7 72 05 JB SHORT abexcrac.00406BAE
00406BA9 E8 D00A0000 CALL abexcrac.0040767E ;Patch this CALL, to kill timer.
00406BAE EB 01 JMP SHORT abexcrac.00406BB1

If you cannot find that opcodes sometimes, think that code is inproperly shown in Olly and search bytes for bytes.



Then scroll down and find POPAD opcode or 61 byte:

00407C0B E8 616A00EB CALL EB40E671 ; It's obfuscated so you will not see it at first.

Patch first byte E8 to 90:

00407C0B 90 NOP
00407C0C 61 POPAD ; Place bp here!!!!!!!!!!!!!
00407C0D 6A 00 PUSH 0

We will return to this place later. We have to fix IAT now.




Scroll waaaaaay up to here:

0040764F 60 PUSHAD
00407650 8DBD D92C4000 LEA EDI,DWORD PTR SS:[EBP+402CD9]
....
....
00407701 C1CA 08 ROR EDX,8
00407704 66:8956 01 MOV WORD PTR DS:[ESI+1],DX
00407708 61 POPAD
00407709 C3 RETN

We sad that is redirection procedure. To prevent IAT redirection, NOP all bytes between PUSHAD and POPAD:

0040764F 60 PUSHAD
00407650 90 NOP
00407651 90 NOP
00407652 90 NOP
....
....
00407705 90 NOP
00407706 90 NOP
00407707 90 NOP
00407708 61 POPAD
00407709 C3 RETN

So this procedure will not do anything now ;).

Maybe this is not the best and the most clever solution, but I couldn't find place to patch or change only few instructions. There is one jump which can be redirected, but I had some errors while testing bigger files (Mozilla ~7 MB,~2000 Imports).




IAT redirection is fixed and now we can go to OEP problem. Run crackme and you will break on our bp:

00407C0C 61 POPAD
00407C0D 6A 00 PUSH 0
00407C0F EB 01 JMP SHORT CRACKME3.00407C12
...
...

Trace to PUSH 0. Below POPAD is first stolen opcode of our OEP. If on that place is some jump to code section, that means that there are no stolen bytes. Our example has them. As I sad, that is the place where stolen OEP is and we can find our bytes now, but guess what? We don't need to do that! If you like, you can dump file now and fix IAT with ImpREC and file is properly unpacked and it is not protected anymore. Fact that it starts from protectors section doesn't change anything. But if you would like to be Mr. Perfect, we will find stolen bytes and restore them. It's easy!

Obfuscated code looks like this in Olly:

00407C0C 61 POPAD
00407C0D 6A 00 PUSH 0 ;First stolen opcode.
00407C0F EB 01 JMP SHORT CRACKME3.00407C12
00407C11 E4 E8 IN AL,0E8 ;Patch first byte.
00407C13 6D INS DWORD PTR ES:[EDI],DX
00407C14 98 CWDE
00407C15 FFFF ???
00407C17 -E9 EB93FFFF JMP CRACKME3.00401007


Patch false bytes to get correct picture (only one byte in this example):

00407C0C 61 POPAD
00407C0D 6A 00 PUSH 0 ;Stolen code.
00407C0F EB 01 JMP SHORT CRACKME3.00407C12 ;Junk jump.
00407C11 90 NOP ;Patched byte, junk too.
00407C12 E8 6D98FFFF CALL CRACKME3.00401484 ;Stolen code.
00407C17 -E9 EB93FFFF JMP CRACKME3.00401007 ;Jump to false OEP, go to it.

Write those stolen bytes and jump to false OEP, scroll up and restore bytes instead zeroes, dump file with ollydump, OEP is 401000=1000. Run file and it works fine! Yeah ;)





3. ISSUES
-Delphi programs dump with OllyDump plugin (do not check repair IAT option) and repair IAT with ImpREC. If ImpREC cannot find imports at real OEP, set OEP to 1000 and then try. When it finds imports, restore real OEP and then fix dump. This common issue with ImpREC and Delphi packed programs with lot of packers.
-ASM and Borland C++ programs can be dumped and fixed with OllyDump plugin. You can use ImpREc if you like or have problems with plugin in-built fixer.
-MSVC++ , dump with OllyDump fix IAT with it or with ImpREC.
-MSVB, use what you want.
LordPE cannot dump files. Why, I don't know.
I have included OllyScript for PESpin 0.3 that fix IAT and finds stolen bytes. Works pretty well ,although some Delphi file can crush after it ;)
Note: If you have some target that PEiD sad it's PESpin 0.3 protected, and you cannot unpack it with this method, that's because that file is packed with different version of PESpin no mater what PEiD sad.




4. FINAL WORDS

Huh, this tutorial was exhausting to write. Sorry for errors, mistakes and bad grammar. Included abexcrackme2.exe is for your homework, it's packed with timer (it will autoclose in 2 minutes) and password. Password is 123456. Unpack it!
Good by, and good night. zZZzzzzZZZZzzZZZZzzZZZzzZZzzZZzzzzzzzz.......




0 comments



http://www.reversing.be/article.php?story=20050726211417143