Professional Documents
Culture Documents
ExeCryptor 2.1.17 Official Crackme Unpacking
ExeCryptor 2.1.17 Official Crackme Unpacking
ExeCryptor 2.1.17 Official Crackme Unpacking
1. Intruduction
ExeCryptor official crackme landed on crackmes.de somewhere around 2004. At that time ExeCryptor
looked like really hard bone and I saved crackme on my hard drive with hope that one day I will have skills
to defeat it.
The 2006 year closley comes to end and I have finally managed to break this crackme. Even more,
crackme doesn't look too hard now. Main strength of ExeCryptor is in feature called "Code Morphing".
Code morphing is another name for obfuscation, junk, garbage, transformation of original opcodes to huge
and unreadable equivalent code. Code that is doing same thing as original code, but it is almost impossibe
to trace trough it. ExeCryptor layer is also obfuscated with tons of such junk which jumps all around. But
even in such environment, it is possible to find weak spots and break protection.
Kao already solved this crackme, but in this my solution (that comes late, I know) you will see what anti-
debug tricks ExeCryptor uses and how to make good dump. Also, Kao's dump isn't fixed to the end - on
my Windows XP Pro SP2 I get errors. Reason why is that happened is also explained in this tutorial.
Tools that I used are OllyDbg 1.10 with some script plugin, LordPE and ImpREC.
I think that this version of ExeCryptor is 2.1.17. Altough this version is obsolete, remember that you always
can learn something from it :).
2. Debugging tricks
- ExeCryptor uses TLS callbacks to execute code before OEP so protected executable will run under Olly
without stoping on OEP. Side effect is that other anti debug tricks will be executed , Olly will be detected
and it will ExeCryptor will terminate it. To avoid this auto-running, we can set Olly to break on system
breakpoint instead on executable OEP (debugging options -> events -> and set "Make first pause at" -
System breakpoint).
- Software breakpoints are detected. ExeCryptor examnes whole APIs. This is example on
IsDebuggerPresent API:
That is just example for first byte, further it performs similar checks for the rest of bytes. Since software
breakpoints are detected, hardware are corupted, we must use memory breakpoints.
- Second check is FindWindowA , where ExeProtector looks for window with "OLLYDBG" class.
- Third trick is more interesting. ExeCryptor will enumerate windows to retrieve PID numbers with
GetWindowThreadProcessId. Then it examnes PE header of each process with OpenProcess and
ReadProcessMemory. Example, it reads 40h bytes from 400000:
If data in buffer is OK, it will read PE offset information and from there it wil locate export table offset. In
Olly 1.10 export section is at 50F000. ExeCryptor will examne all export names in order to find two
possible OllyDbg exports. Again it uses ReadProcessMemory to read every 40h bytes of table:
0012F738 5F 41 64 64 73 6F 72 74 65 64 64 61 74 61 00 5F _Addsorteddata._
0012F748 41 64 64 74 6F 6C 69 73 74 00 5F 41 6E 61 6C 79 Addtolist._Analy
0012F758 73 65 63 6F 64 65 00 5F 41 6E 69 6D 61 74 65 00 secode._Animate.
0012F768 5F 41 73 73 65 6D 62 6C 65 00 5F 41 74 74 61 63 _Assemble._Attac
To get rid of this protection, we can change exports (some plugins will not work then), or patch checks.
You can run crackme under Olly then.
3. Finding OEP
Protected file is Delphi program which make things easier. Delphi OEP is always at the end of code
section. We can run app within Olly (or attach with Olly) and scroll to the end of section:
...
00442FD0 8C264400 DD EXECrypt.0044268C
00442FD4 84264400 DD EXECrypt.00442684
00442FD8 54264400 DD EXECrypt.00442654
00442FDC 4C264400 DD EXECrypt.0044264C
00442FE0 1C264400 DD EXECrypt.0044261C
00442FE4 2C274400 DD EXECrypt.0044272C
00442FE8 FC264400 DD EXECrypt.004426FC
00442FEC 64274400 DD EXECrypt.00442764
00442FF0 34274400 DD EXECrypt.00442734
00442FF4 D4274400 DD EXECrypt.004427D4
00442FF8 A4274400 DD EXECrypt.004427A4
00442FFC 9C274400 DD EXECrypt.0044279C
00443000 6C274400 DD EXECrypt.0044276C
00443004 302A4400 DD EXECrypt.00442A30
00443008 D4294400 DD EXECrypt.004429D4
0044300C AC2E4400 DD EXECrypt.00442EAC
00443010 642E4400 DD EXECrypt.00442E64
00443014 00 DB 00
00443015 00 DB 00
00443016 00 DB 00
00443017 00 DB 00
00443018 B42E4400 DD EXECrypt.00442EB4
0044301C .-E9 71940300 JMP EXECrypt.0047C492 <----------------- Here it
should be OEP!!!
00443021 9F DB 9F
00443022 49 DB 49 ; CHAR
'I'
00443023 66 DB 66 ; CHAR
'f'
00443024 B1 DB B1
00443025 CB DB CB
00443026 D2 DB D2
00443027 . AB STOS DWORD PTR ES:[EDI]
00443028 . F62E IMUL BYTE PTR DS:[ESI]
0044302A . 58 POP EAX ;
user32.77D493F5
0044302B . 5A POP EDX ;
user32.77D493F5
0044302C .-E9 8BBA0000 JMP EXECrypt.0044EABC
00443031 >-E9 E5C60200 JMP EXECrypt.0046F71B
00443036 0F DB 0F
00443037 8C DB 8C
00443038 90 NOP
00443039 . C603 00 MOV BYTE PTR DS:[EBX],0
0044303C . 59 POP ECX ;
user32.77D493F5
0044303D . C1C5 0B ROL EBP,0B
00443040 . C1C1 15 ROL ECX,15
00443043 . 81F1 C5E80D07 XOR ECX,70DE8C5
00443049 . 81C1 0B20AF36 ADD ECX,36AF200B
0044304F .-E9 76BC0200 JMP EXECrypt.0046ECCA
00443054 . 81DF A44999AA SBB EDI,AA9949A4
0044305A . 8731 XCHG DWORD PTR DS:[ECX],ESI
0044305C . 8D45 C7 LEA EAX,DWORD PTR SS:[EBP-39]
0044305F . 50 PUSH EAX
00443060 .-E9 981E0400 JMP EXECrypt.00484EFD
00443065 . 81C1 E798052F ADD ECX,2F0598E7
0044306B .^E9 498FFCFF JMP EXECrypt.0040BFB9
00443070 85 DB 85
00443071 F5 DB F5
00443072 45 DB 45 ; CHAR
'E'
00443073 CB DB CB
00443074 2E DB 2E ; CHAR
'.'
00443075 BA DB BA
00443076 E8 DB E8
00443077 15 DB 15
00443078 07 DB 07
00443079 FC DB FC
0044307A FF DB FF
0044307B 90 NOP
0044307C 00 DB 00
0044307D 00 DB 00
0044307E 00 DB 00
0044307F 00 DB 00
00443080 00 DB 00
00443081 00 DB 00
00443082 00 DB 00
Line 0044301C is where OEP should be but instead of usuall PUSH EBP opcode , we can see some jump
to ExeCryptor's code. Protector has replaced OEP code with it's own obfuscation and "reall OEP" is at the
location of that jump:
To break at that OEP, we can restart target, place memory breakpoint on access on that line, and run
(Shift+F9) untill we stop there.
4. Imports
I don't know what to think about import protection. It is not hard , but it is not too easy either. Most imports
are redirected to ExeCryptor code where we have tons of garbage code. There imports are stored as
some hashes. ExeCryptor then examnes all ordinals in specified DLL to find one with correct hash. When
import is found, it jumps on it with RETN or JMP. JMP type import is writen in IAT when import with correct
hash is found so this decryption is performed only once. RETN type is never writen, instead import always
needs to be decrypted. I didn't intend to fix imports manually so I needed somehow to automate job.
Because that, I divided import protection to 3 types.
Import jump goes into ExeCryptor code where we can find random junky code:
Every import of this type has different junk so it looked like impossible for automating process of finding
imports. But it is not possible that every new jump has decryption routine just for it. That would result with
huge protector code. So it must be that only first couple opcodes are random to prevent tracing and auto
tools, but soon or later all must end in one main routine. And that was the case. I checked references to
the last call destination and I found numerous references:
It is same for all imports of this type. "Magic address" for this type is 482C58 but we have one more at
4584D3.
- Second type:
0046A5B8 60 PUSHAD
0046A5B9 B8 9EDBAE8D MOV EAX,8DAEDB9E
0046A5BE E8 909B0100 CALL EXECrypt.00484153
0046A5C3 51 PUSH ECX ;
kernel32.7C809BC7
It starts with PUSHAD opcode so we can use hardware preakpoint (execute PUSHAD, place hw bp at
ESP value in dump and run) that will bring us to:
- Third type:
Third type is similar to second one but it is not convinient for script. Import starts with some random junk,
but eventually it has PUSHAD opcode and from there it is same thing.
I wrote script that fixed type 1 and 2, but there was some problems. Target would ventually crush and I
had to restart and copy-paste partialy fixed IAT. But it was pretty fast and soon I recovered whole IAT. I
dumped target and used ImpREC to rebuild new IAT. At this point target was unpacked. Dump was
running fine altough there was some interaction between ExeCryptor code and target code. I mean at
OEP code, plus some inside functions. But dump would crush on others machines.
5. Dump problems
First problem that acctualy is not problem at all, is TLS information. I left TLS info like in original file and on
every startup EC code triggered with TLS callback would start. That is not problem but it's little annoyance
if we try to debug dump. To fix that you can open any Delphi executable on your machine and see how
TLS looks on that file. Set similar value in dump and problem is solved.
The reall problem lies in fact that dump still uses EC layer which cannot be removed. Why? Because this
is the EC main strength - to mutate code so we cannot recower it. But that is not exact problem. Problem
is, that EC used some imports in the process of unpacking (and it will use them later). Those imports are
not part of IAT and they are not placed in IAT section. This is how that part of EC code works:
Step1: EC uppon startup loads some DLLs (kernel32.dll, ntdll.dll, rpcrt4.dll, etc...). After loading, EC
places image base of DLL to apropriate variable. Variable is NULL DWORD (empty 4 bytes) that is placed
inside EC code (as part of code).
Step2: EC uses some custom procedure to find APIs in that DLL. It takes that DLL base, then examnes
PE header searching for export table etc. When ordinal is found, EC places API address into new variable
(NULL DWORD for APIs). But before that EC checked is variable=NULL. If yes, it means that it needs to
find API. If not, it thinks that API is already loaded so it will just go to that address. And here is problem.
When we dump file, in our dump those variables will not be equal to NULL. They will hold API address.
But that address is static and on another machine dump will fail to work. Check my first dump:
DS:[004781BC]=7C80994E (kernel32.GetCurrentProcessId)
You see? EC checks is varable empty. But there is address of GetCurrentId API that was dumped to my
dump. EC will see that variable is not NULL and it will go straight to that address. But if I set variable to
NULL, EC will find that API again. And that is solution of that problem. How to find and fix all such imports?
There are only few of them. Use Olly great feature "Right click on CPU -> Search for -> All commands" ,
enter "CMP DWORD[CONST],0" and you will find all variables. All that holds some import needs to be
filled with 0.
Problem two comes from a step one. Dump file holds those DLL bases that will be incorrect on some other
machines. Check this example:
You need to set those variables to NULL too. But problem still remains because those variables are never
filled again after OEP is reached! So we need to place correct DLL bases at those into those variables. I
solved that by injecting some code and changing OEP that will fill those variables:
Now variables will hold correct bases and dump will work. But one more thing about that. One variable
holds image base of NTDLL.DLL that is used only on NT systems (I have unpacked file on Window XP).
On Win 9x systems this DLL doesn't exist. There is variable for this DLL, but it doesn't need to be filled
with image base. Instead it just needs to be set to NULL. Then dump will work on all machines :)
Btw, these are problems that I found in kao's dump. Kao didn't noticed it, but after I installed SP2 to my
Win XP, his dump didn't worked (exception at startup, no window tile, no serial check). And I fixed all of
these problems in his dump too, to see will it work.
I spent some thime tracing trught EC code, but that was suicide. Code is just garbaged to much. First I
couldn't find how serial is taken from dialog. Then I found that it uses GetKeboardState. Then I traced
trugh code and noticed how EC checks serial chars, comparing them with numbers (31,32,33,...) and
checking range if they are letters. Then code is totaly impossible to understand. Probably there are some
places that could be patched in order to accept any serial, but it is too hard to find it.
Since stupid patch is valid as solution, I will do the same job as Kao did in his first solution (in his second,
he managed to get new valid serials).
7. The End
Target "Official EXECryptor crackme" can be found http://crackmes.de/. Since my dump is quite big
(~650kb ziped), I will not include it with this tutorial. Instead, I give my IAT section at 446000
(IAT_section_at_0446000.dmp file) and ImpREC tree. Load crackme into Olly (all tricks are explained) and
break on OEP, paste IAT section, dump, use ImpREC tree to fix dump. It should work.
This ExeCryptor version looked like 2.1.17 to me, but I checked evaluation version 2.1.17 and it has
couple more tricks. After IsDebuggerPresent, it calls CheckRemoteDebuggerPresent and then it goes to
FindWindowA.
haggar , 2006