Contribute  :  Web Resources  :  Past Polls  :  Site Statistics  :  Downloads  :  Forum  
    BiW ReversingThe challenge is yours    
 Welcome to BiW Reversing
 Wednesday, September 22 2021 @ 05:28 AM CEST

ExeCryptor 2.2.4


TutorialsLevel : intermediate

1. Quick Intro

Hi folks, this is my second tutorial about ExeCryptor , altough I don't know will I publish it before one that I wrote first (for official ExeCryptor crackme). Target for this tutorial is diProtector v1.0 (google it). I don't know is it latest since I had it on my hard drive for a while. Target is compiled in MASM and it doesn't have stolen OEP or functions, plus there are very few imports in the file. All that makes unpacking much easier. Tools that I used for this job are OllyDbg 1.10, LordPE, ImpREC, some hex editor. You will not need any plugins or patches to hide Olly. We will do all manually.

Tutorial in rtf format for download

2. Running protected file under Olly

Let's see what tricks ExeCryptor has prepared for us:

- Because TLS callbacks, our target will run before OEP is reached. Olly will be detected by other debugging tricks and protected program will exit. To avoid that , we must set Olly to break on system breakpoint. You can set that in "debugging options" , under "events" (make first pause at - system, breakpoint). Now we can load target in Olly. But here we have first EC (ExeCryptor) trap. When you loaded target in Olly, check breakpoints window and you will see one breakpoint:

Address Module Active Disassembly Comment
0045F3D0 diProtec One-shot CALL diProtec.0045F2CC

Olly places that one-shot breakpoint at the ModuleEntryPoint of file. That breakpoint will be detected by EC and target will close. Here is temp breakpoint:

0045F3D0 > E8 F7FEFFFF CALL diProtec.0045F2CC <-------- Here!
0045F3D5 05 57840000 ADD EAX,8457
0045F3DC E8 EBFEFFFF CALL diProtec.0045F2CC
0045F3E1 05 8B0A0000 ADD EAX,0A8B
0045F3E6 -FFE0 JMP EAX
0045F3E8 E8 04000000 CALL diProtec.0045F3F1
0045F3ED FFFF ??? ; Unknown command
0045F3EF FFFF ??? ; Unknown command
0045F3F1 5E POP ESI ; diProtec.0045591F
0045F3F2 C3 RETN
0045F3F3 0000 ADD BYTE PTR DS:[EAX],AL
0045F3F5 0000 ADD BYTE PTR DS:[EAX],AL
0045F3F7 0000 ADD BYTE PTR DS:[EAX],AL
0045F3F9 0000 ADD BYTE PTR DS:[EAX],AL

And this is where breakpoint is detected:


In Olly pane you can see information that on that address is E8 byte


but when we step in with trace over the opcode and byte is loaded to AL, check registers window:

EAX 000000CC <----------------------- EAX = CC = BREAKPOINT IS DETECTED !!!
ECX 00000018
EDX 5A266E6B
EBX 00000000
ESP 0012F998
EBP 0012F9AC
ESI 0045F3D1 diProtec.0045F3D1
EDI 00400000 diProtec.00400000
EIP 00455EB2 diProtec.00455EB2

This doesn't have to be breakpoint check on that opcode. It could be some checksup on code block which is same thing. It's not important. To avoid this trick just delete breakpoint in bp window and problem is solved.

- Further EC debugger tricks are described in PNLUCK's tutorial. These are:


These flags needs to be set to 0 so EC doesn't detect debugger. You can use any plugin for this, but we will see how manually this can be resolved. Open memory window , scroll down to the block "data block of main thread":

Memory map
Address Size Owner Section Contains Type Access Initial Mapped as
00400000 00001000 diProtec PE header Imag R RWE
00401000 00002000 diProtec .text Imag R RWE
00403000 00001000 diProtec c3o2hlw9 Imag R RWE
00404000 00001000 diProtec .data data Imag R RWE
00405000 00004000 diProtec WCE_ARM Imag R RWE
00409000 00002000 diProtec KG Imag R RWE
0040B000 00003000 diProtec .rsrc resources Imag R RWE
0040E000 00001000 diProtec bjmdft2e Imag R RWE
0040F000 00025000 diProtec n9cjl4.c Imag R RWE
00434000 0002C000 diProtec g0qnncsj code,imports Imag R RWE
...loaded DLLs
7F6F0000 00007000 Map R E R E
7FFB0000 00024000 Map R R
7FFDA000 00001000 Priv RW RW
7FFDF000 00001000 data block o Priv RW RW <------------ HERE!!!
7FFE0000 00001000 Priv R R

Double click on that block to open new dump window:

7FFDF000 0012FD0C (Pointer to SEH chain)
7FFDF004 00130000 (Top of thread's stack)
7FFDF008 0012E000 (Bottom of thread's stack)
7FFDF00C 00000000
7FFDF010 00001E00
7FFDF014 00000000
7FFDF018 7FFDF000
7FFDF01C 00000000
7FFDF020 00000284
7FFDF024 000002F8 (Thread ID)
7FFDF028 00000000
7FFDF02C 00142A18 (Pointer to Thread Local Storage)
7FFDF030 7FFDA000 <----------------- FOLLOW IN DUMP!!!
7FFDF034 00000000 (Last error = ERROR_SUCCESS)
7FFDF038 00000000
7FFDF03C 00000000
7FFDF040 00000000

Follow that address in dump (this block and that address will be different for you, but block is always labeled by olly as "data block of main thread" and addres that we want inside is data_block_base+30). In dump:

7FFDA000 00 00 01 00 FF FF FF FF 00 00 40 00 A0 1E 24 00 ..........@...$.
7FFDA010 00 00 02 00 00 00 00 00 00 00 14 00 C0 E4 97 7C ...............|
7FFDA020 05 10 90 7C ED 10 90 7C 01 00 00 00 00 00 00 00 ...|...|........
7FFDA030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
7FFDA040 80 E4 97 7C 01 00 00 00 00 00 00 00 00 00 6F 7F ...|..........o.
7FFDA050 00 00 6F 7F 88 06 6F 7F 00 00 FB 7F 00 10 FC 7F ..o...o.........
7FFDA060 00 20 FD 7F 01 00 00 00 70 00 00 00 00 00 00 00 . ......p.......

01 byte, which is placed at block_base+2 is BeingDebug byte that is also used by IsDebuggerPresent API. If program is debugged, that byte is 1, if not then 0. We must set it to 0.

At block_base+68 we have NtGlobalFlag byte. It is set to 70 if program is debugged and if not, then 0. Again , set it to 0.

At block_base+18 we have address of heap block 00001400=00140000. It will differ on your machine. Follow that address in dump:

00140000 C8 00 00 00 31 01 00 00 FF EE FF EE 62 00 00 50 ....1.......b..P
00140010 60 00 00 40 00 FE 00 00 00 00 10 00 00 20 00 00 `..@......... ..
00140020 00 02 00 00 00 20 00 00 B6 00 00 00 FF EF FD 7F ..... ..........

DWORD at block_base+10 must be 0 if program is not debugged. In this case it is 60000040=40000060. So set it to 0.

And these three checks are now disabled. This is also the same thing what hide plugins for Olly do.

- Almost every import that EC uses is checked for breakpoints, but also it is checked for hooks and redirections. So if some plugin redirects some API to it's own code (like some hide plugins do for ZwQueryInformationProcess) it will be detected.

- IsDebuggerPresent API is used , but we already disabled that check.

- CheckRemoteDebuggerPresent API is used to detect debugger. It is big API and we can place bp at the and to break there. EC didn't check whole API for bp. Just set return value in EAX to 0.

3. Finding OEP

When you have disabled all debugger checks, just place memory breakpoint on access on first section and run. First time we will break in the unpacking procedure:

00454D56 ^EB E9 JMP SHORT diProtec.00454D41
00454D58 E8 FC000000 CALL diProtec.00454E59
00454D5D 0F82 97000000 JB diProtec.00454DFA
00454E85 11C9 ADC ECX,ECX
00454E87 E8 CDFFFFFF CALL diProtec.00454E59
00454E8C ^72 F2 JB SHORT diProtec.00454E80
00454E8E C3 RETN
00454E91 5D POP EBP
00454E92 C3 RETN <------------ Place bp and run.

This procedure unpacks code to 401000 section. Remove mem bp, place bp at RETN (one belov MOV ESP,EBP and POP EBP) and run. Remove that bp, code section is unpacked now. Olace memory bp again and run. You will stop here:

00434143 D0E8 SHR AL,1
00434145 80F8 74 CMP AL,74
00434148 75 0E JNZ SHORT diProtec.00434158
0043414C 0FC8 BSWAP EAX ; diProtec.00401000
0043414E 01C8 ADD EAX,ECX
00434150 8906 MOV DWORD PTR DS:[ESI],EAX ; diProtec.00401000
00434152 83C6 04 ADD ESI,4
00434155 83E9 04 SUB ECX,4
00434158 49 DEC ECX
00434159 ^7F E7 JG SHORT diProtec.00434142
0043415B 59 POP ECX
0043415C 5E POP ESI ; diProtec.00401000
0043415D C3 RETN <------------- Place bp here !

Again , remove mem bp, place bp at RETN and run, remove that bp and place again mem bp on code. You will break on OEP:

00401000 6A 00 PUSH 0
00401002 68 1C104000 PUSH diProtec.0040101C
00401007 6A 00 PUSH 0
00401009 68 D0304000 PUSH diProtec.004030D0 ; ASCII "diProtector"
0040100E 6A 00 PUSH 0
00401010 E8 5F1A0000 CALL diProtec.00402A74
00401015 6A 00 PUSH 0
00401017 E8 B21A0000 CALL diProtec.00402ACE
0040101C 55 PUSH EBP
0040101F 817D 0C 10010000 CMP DWORD PTR SS:[EBP+C],110
00401026 75 1A JNZ SHORT diProtec.00401042
00401028 FF75 08 PUSH DWORD PTR SS:[EBP+8] ; diProtec.<ModuleEntryPoint>
0040102B 8F05 54414000 POP DWORD PTR DS:[404154] ; kernel32.7C816D4F
00401031 E8 B8050000 CALL diProtec.004015EE
00401036 68 DC304000 PUSH diProtec.004030DC ; ASCII "diProtector v1.0 started..."
0040103B E8 AB080000 CALL diProtec.004018EB
00401040 EB 67 JMP SHORT diProtec.004010A9

EC has some more checks but they are performed with threads and they aren't running yet. Why? I don't know really. But if you run app it will close Olly. We will talk about this more later.

4. Imports

There are just few imports in this target and they can be fixed manually , but it is possible to write script that will fix them. Program is MASM app and it has small numbers of imports:

00402A68 -FF25 BC304000 JMP DWORD PTR DS:[4030BC] ; diProtec.00410531
00402A6E -FF25 B8304000 JMP DWORD PTR DS:[4030B8] ; diProtec.00413D26
00402A74 -FF25 B4304000 JMP DWORD PTR DS:[4030B4] ; diProtec.00415AB7
00402A7A -FF25 B0304000 JMP DWORD PTR DS:[4030B0] ; diProtec.0041AEF6
00402A80 -FF25 AC304000 JMP DWORD PTR DS:[4030AC] ; diProtec.00420D0B
00402A86 -FF25 A8304000 JMP DWORD PTR DS:[4030A8] ; diProtec.0041F637
00402A8C -FF25 A4304000 JMP DWORD PTR DS:[4030A4] ; diProtec.00431355
00402A92 -FF25 A0304000 JMP DWORD PTR DS:[4030A0] ; diProtec.00421441
00402A98 -FF25 9C304000 JMP DWORD PTR DS:[40309C] ; diProtec.0042F9A8
00402A9E -FF25 94304000 JMP DWORD PTR DS:[403094] ; diProtec.0042E1A0
00402AA4 -FF25 98304000 JMP DWORD PTR DS:[403098] ; diProtec.00413226
00402AAA -FF25 8C304000 JMP DWORD PTR DS:[40308C] ; diProtec.00417665
00402AB0 -FF25 90304000 JMP DWORD PTR DS:[403090] ; diProtec.00421BC4
00402AB6 -FF25 58304000 JMP DWORD PTR DS:[403058] ; diProtec.0041D62D
00402ABC -FF25 44304000 JMP DWORD PTR DS:[403044] ; diProtec.00421F2F
00402AC2 -FF25 40304000 JMP DWORD PTR DS:[403040] ; diProtec.00428B7D
00402AC8 -FF25 70304000 JMP DWORD PTR DS:[403070] ; diProtec.004127A6
00402ACE -FF25 48304000 JMP DWORD PTR DS:[403048] ; diProtec.00418D4A
00402AD4 -FF25 4C304000 JMP DWORD PTR DS:[40304C] ; diProtec.00428FD5
00402ADA -FF25 50304000 JMP DWORD PTR DS:[403050] ; diProtec.00427DE5
00402AE0 -FF25 54304000 JMP DWORD PTR DS:[403054] ; diProtec.00412D68
00402AE6 -FF25 6C304000 JMP DWORD PTR DS:[40306C] ; diProtec.0041D5CD
00402AEC -FF25 5C304000 JMP DWORD PTR DS:[40305C] ; diProtec.00419CEF
00402AF2 -FF25 60304000 JMP DWORD PTR DS:[403060] ; diProtec.00416D00
00402AF8 -FF25 64304000 JMP DWORD PTR DS:[403064] ; diProtec.0040FEF3
00402AFE -FF25 68304000 JMP DWORD PTR DS:[403068] ; diProtec.0041A1AE
00402B04 -FF25 24304000 JMP DWORD PTR DS:[403024] ; comdlg32.GetOpenFileNameA
00402B0A -FF25 34304000 JMP DWORD PTR DS:[403034] ; GDI32.BitBlt
00402B10 -FF25 30304000 JMP DWORD PTR DS:[403030] ; GDI32.CreateCompatibleDC
00402B16 -FF25 2C304000 JMP DWORD PTR DS:[40302C] ; GDI32.DeleteDC
00402B1C -FF25 38304000 JMP DWORD PTR DS:[403038] ; GDI32.SelectObject
00402B22 -FF25 80304000 JMP DWORD PTR DS:[403080] ; ntdll.memcpy
00402B28 -FF25 7C304000 JMP DWORD PTR DS:[40307C] ; ntdll.memset
00402B2E -FF25 78304000 JMP DWORD PTR DS:[403078] ; ntdll.strcat
00402B34 -FF25 84304000 JMP DWORD PTR DS:[403084] ; ntdll.strtol
00402B3A -FF25 1C304000 JMP DWORD PTR DS:[40301C] ; diProtec.004207F7
00402B40 -FF25 18304000 JMP DWORD PTR DS:[403018] ; diProtec.0042DC7C
00402B46 -FF25 14304000 JMP DWORD PTR DS:[403014] ; diProtec.00420F76
00402B4C -FF25 10304000 JMP DWORD PTR DS:[403010] ; diProtec.00411957
00402B52 -FF25 0C304000 JMP DWORD PTR DS:[40300C] ; diProtec.0041A2D3
00402B58 -FF25 08304000 JMP DWORD PTR DS:[403008] ; diProtec.0043229B
00402B5E -FF25 04304000 JMP DWORD PTR DS:[403004] ; diProtec.0041C5F3
00402B64 -FF25 00304000 JMP DWORD PTR DS:[403000] ; diProtec.0041946F

As we can see, some imports are OK, but most of them are redirected. Imports are hashed, EC decrypts them, then it jumps to them with JMP or RETN. If JMP is used, then original import is decrypted and placed in IAT. In case of RETN, import is never writen in IAT and decrypting will always be performed. Note that these types can have sub-types and these are just examples. Also these subtypes are not important, we can write script that will fix all imports. I will descrybe how did I managed to fix imports.

First type:

00402A68 -FF25 BC304000 JMP DWORD PTR DS:[4030BC] ; diProtec.00410531

If we enter into jump and trace, we will see some random opcodes:

00410531 53 PUSH EBX
00410532 68 DC55D60F PUSH 0FD655DC
00410537 5B POP EBX ; kernel32.7C816D4F
00410538 81F3 DEF8E860 XOR EBX,60E8F8DE
0041053E E9 DE2F0000 JMP diProtec.00413521
00413521 81EB AFB951E4 SUB EBX,E451B9AF
00413527 81F3 D6E9A330 XOR EBX,30A3E9D6
0041352D 81C3 DEE4F145 ADD EBX,45F1E4DE
00413533 871C24 XCHG DWORD PTR SS:[ESP],EBX
00413536 ^E9 2ECAFFFF JMP diProtec.0040FF69
0040FF69 0F88 AB7D0000 JS diProtec.00417D1A
0040FF6F 5A POP EDX ; diProtec.0040FF63
0040FF70 8B0424 MOV EAX,DWORD PTR SS:[ESP] ; diProtec.0040FF63
0040FF73 52 PUSH EDX
0040FF74 52 PUSH EDX
0040FF75 68 E91D47E8 PUSH E8471DE9
0040FF7A 5A POP EDX ; diProtec.0040FF63
0040FF7B E9 EF150000 JMP diProtec.0041156F

But check stack now:

$-8 > 0040FF63 diProtec.0040FF63
$-4 > 0040FF63 diProtec.0040FF63
$ ==> > 7C816D4F RETURN to kernel32.7C816D4F <--- This is start esp value.
$+4 > 7C910738 ntdll.7C910738
$+C > 7FFD7000

There was some changes in stack. All above opcodes will push to stack some value and change it. That value (at $-4 from staring esp value) is return address. Place bp on that address and run:

0040FF63 -FF25 BC304000 JMP DWORD PTR DS:[4030BC] ; user32.wsprintfA

This is first type of import. Import is decrypted from and here it jumps to it. Also that import is written in IAT section and this decryption is performed only once. Check stack and you will see that it is maintained , ei. ESP again points to starting value.

Second type:

00402A74 -FF25 B4304000 JMP DWORD PTR DS:[4030B4] ; diProtec.00415AB7

Traced opcodes:

00415AB7 ^0F84 E0D3FFFF JE diProtec.00412E9D
00415ABD 8B05 24014100 MOV EAX,DWORD PTR DS:[410124]
00415AC3 E9 12080100 JMP diProtec.004262DA
004262DA 09C0 OR EAX,EAX
004262DC ^0F85 F0F4FFFF JNZ diProtec.004257D2
004262E2 ^E9 8937FFFF JMP diProtec.00419A70
00419A70 E9 5FEB0000 JMP diProtec.004285D4
004285D4 ^0F84 67F9FEFF JE diProtec.00417F41
00417F41 68 28C8DC1D PUSH 1DDCC828
00417F46 58 POP EAX ; kernel32.7C816D4F
00417F47 81F0 28008F23 XOR EAX,238F0028
00417F4D 81F8 8ABFB3C1 CMP EAX,C1B3BF8A
00417F53 E9 063B0000 JMP diProtec.0041BA5E
0041BA5E E9 495F0100 JMP diProtec.004319AC
004319AC ^0F8D 6D41FFFF JGE diProtec.00425B1F
00425B1F 81C8 EFE9A6F2 OR EAX,F2A6E9EF
00425B25 81F0 D6C68BEA XOR EAX,EA8BC6D6
00425B2B 81E8 052CBAC5 SUB EAX,C5BA2C05
00425B31 ^E9 3B99FEFF JMP diProtec.0040F471
0040F471 E9 D08A0100 JMP diProtec.00427F46
00427F46 81C0 94005B72 ADD EAX,725B0094
00427F4C E8 FA120000 CALL diProtec.0042924B
0042924B E8 438F0000 CALL diProtec.00432193
00432193 873C24 XCHG DWORD PTR SS:[ESP],EDI ; ntdll.7C910738
00432196 5F POP EDI ; diProtec.00429250
00432197 ^0F84 FE22FFFF JE diProtec.0042449B

I check stack:

$-4 > 00427F51 RETURN to diProtec.00427F51 from diProtec.0042924B
$ ==> > 7C816D4F RETURN to kernel32.7C816D4F

I place bp on that addres and run:

00427F51 E8 4E91FFFF CALL diProtec.004210A4

But check value in EAX register:

EAX 77D588E1 user32.DialogBoxParamA

And that is good import. Plus, stack is again maintained. Note this! If at that addres EAX doesn't hold IMPORT, it means that it will just after that trace couple opcodes to the final JMP_IMPORT. So it can be a sub-type of first type. Always check EAX at that point.

So we see, that address in stack is return address where we will see EAX=IMPORT or JMP_IMPORT. Problem is that we need to trace some code untill stack value is formed. But that is not problem for a script, only that it will take some time to trace (my script traces 30 opcodes , then finds return address in stack, places bp there, checks type and fixes import if needed).

5. Dumping

We can dump image from memory with LordPE. But notice that ImageSize is 00001000. In LordPE, right click on target process and select "Correct ImageSize". New image size will be set to 00060000. Now dump file.

Funny part is, that if we leave EC sections in dump and we doesn't change TLS data, then we use ImpREC to rebuild IAT, unpacked target will detect debugger! It is because EC layer is present and activated with TLS callbacks. If we delete EC sections but leave TLS info, target will fail to work. So open dump with LordPE and set TLS table address and size to 0. Then fix imports with ImpREC and unpacking is finished :)

To make perfect dump, you first dump it, then erase last three sections (EC sections) and then fix imports. Such dump is around 60Kb while with EC code it is 300kb big.

6. Last words

Btw, about threads. They check for Olly, regmon, filemon, and maybe some other tools with FindWindowA (for olly) and EnumWindows/GetClassNameA (for rest). Some "EBX+" string exist in Olly which is also used for detection. Also, they use ReadProcessMemory for checking all processes. I didn't examne that, just throw quick look. You can disable threads by just patching CreateThread API. Oh, yeah, OutputDebugStringA is also used to crush Olly.

I will not include my dump for obvious reasons, neither ImpREC tree, but you can check how my IAT script works. I wrote it just for this tutorial, but I unpacked ExeCryptor 2.2.4 itself using this script.

In this tutorial we didn't see case when OEP is stolen. That part is indentical as in my first tutorial.

Greets .........

haggar 2006

//----------- ExeCryptor 2.2.4 IAT (ASM,Delphi,BorlandC++) - by haggar -----------
var addr
var oep
var pointer
var counter
var esp_ref
var temp

mov addr,401000
mov oep,eip

find addr,#ff25????4E00#
cmp $RESULT,0
je END_01
mov addr,$RESULT
add addr,2
mov pointer,addr
mov pointer,[pointer]
mov pointer,[pointer]
cmp pointer,10000000 //Check is import placed in thunk, or redirection.
ja LABEL_01
cmp pointer,0 //For delphi.
je LABEL_01

sub addr,2
mov eip,addr
add addr,2

mov esp_ref,esp //Stack reference.
mov counter,0
LABEL_02: //Trace some code without purpose.
add counter,1
cmp counter,30
jne LABEL_02

mov temp,esp
LABEL_03: //Find referenced stack value.
add temp,4
cmp temp,esp_ref
jne LABEL_03
sub temp,4

mov temp,[temp] //Go to "Magic address".
bp temp
bc eip

mov temp,[eip]
and temp,0ffff
cmp temp,025ff //SelfWriting import type? No need to fix it then.
je LABEL_01

cmp eax,10000000 //If EAX=!IMPORT, then it is a first type.
jb LABEL_01

mov temp,addr //In this case EAX=IMPORT.
mov temp,[temp]
mov [temp],eax

jmp LABEL_01

mov eip,oep
//---------------------- end of script -------------------------------

What's Related

Story Options

ExeCryptor 2.2.4 | 1 comments | Create New Account
The following comments are owned by whomever posted them. This site is not responsible for what they say.
ExeCryptor 2.2.4
Authored by: haggar on Thursday, November 30 2006 @ 08:07 PM CET
Hi, just some quick tips:

- Script for IAT needs changes in order to work on other files. You need to change byte paterns in

find addr,#FF25????4000#

so it can match to your target.

- This method should work for all ExeCryptor versions up to (at least I think so) but there is some additional checks that will be described in next tut (that should have been first one) which also includes stolen code problem.

That's all.
 Copyright © 2021 BiW Reversing
 All trademarks and copyrights on this page are owned by their respective owners.
Powered By Geeklog 
Created this page in 0.80 seconds