Hacking Enigma x64 applications

We have repeatedly published detailed articles on Apps disassembling. However, many programs use different anti-debugging mechanisms, which make them difficult to access. Today we will talk about how to break the Enigma version 5 and higher protection, popular among developers, using advanced anti-analysis and hacking tools.

There is a widespread belief that VMProtect and Themida belong to the category of cool, good suitable protections. They supposedly contain encryption, virtual machines, and anti-debugging. Everything else – so, for suckers and younger students. For example, for some “Enigma” on the network you can find a bunch of tutorials and vidos with instructions on how to hack, using which, any noob can feel like a cool hacker.

The statement is partly true: there are really a lot of tutors, including videos, on the Internet, and if you wish, you can find one-click tools for hacking applications. Some of them even work. With a small caveat – this applies to old versions of protection, which was, to put it mildly, not very good. In the latest versions of Enigma, the creators tried to make life as difficult for hackers as possible.

They borrowed most of the solutions from “adult” defenses (below the reader will be able to verify this). However, as you know, there is nothing new in this world, and the use of well-known solutions does not make it easier and more enjoyable to crack protection. Quite the opposite. Let’s see this by feeling Enigma with our own hands.


For the sake of experiment, download some program (for simplicity – dotnet) and set on it, for example, Exeinfo. Suppose the analyzer recognized the packer as Enigma Protector x64 [v.5.0 – 7.0]. That’s great, we mentally exhale: not VMProtect and not Themida, but by Enigma, something can be found on YouTube. We climb into Google – indeed, there are a lot of vidos, but the maximum breaking version is 4, and for x64 there is nothing at all.

Well, we think, it is not the gods who burn the pots: couldn’t they have come up with something fundamentally new? Let’s try to act in the image and likeness, maybe it will work out. And here an unpleasant surprise awaits us.

Almost all “cool hacker” manuals begin with the same unchanged phrase: “load the program into the debugger” or “create an application dump.” It is at this step that the first insidious bummer lies in wait for the researcher. The program is not loaded into the beloved x64dbg at all, the dumpers of the running software also do not want to dump, and if they succeed, then the output is a completely inoperative (and not nearly dotnet) piece of memory. Even in a running application .NET is not recognized from the word “at all”, dnSpy does not recognize its native and does not want to attach. It is possible to attach the program only in x64dbg, but at the slightest attempt to move from the breakpoint, the process is instantly closed. In general, there are all the signs of adult protection – code encryption, protection from dump and debugger.

We’ll have to get down to business thoroughly. First, we will install the ScyllaHide and Scylla plugins so that debugging somehow works. Thanks to the first tool, the program finally allows itself to be loaded into the debugger. However, the joy turns out to be premature: after interruption in the process, no ScyllaHide settings allow us to go further, the program shuts down with enviable persistence. Scylla will come to the rescue a little later, for now let’s try to take full advantage of the small victory we just won.

So, the program is loaded into the debugger and is traced step by step. We move slowly, bypassing several self-decrypting sections of the code, and we find that they are just a strapping for a huge packed and encrypted section, the starting point of which looks like this:

push rcx 
push rdx 
push r8 
push r9 
mov r8,000B5ED8E 
call .00007FF7`BA4D9610 
mov r8,0 
mov rdx,1 
mov rcx,[rsp][000000018] 
call .00007FF7`BA4E49A0 
mov r8,0 
mov rdx,1 
mov rcx,[rsp][000000018] 
call .00007FF7`BA13E400 
pop r9 
pop r8 
pop rdx 
pop rcx 
call .00007FF7`BA12A210

This is where the previously installed Scylla comes in handy – this section can be dumped into an EXE file. There is little sense, however, from this: the module is inoperative. In addition, this is clearly not our desired protected module, it still doesn’t smell there, but it does smell – surprise! – Delphi. It turns out Enigma is written in Delphi! But this does not make life easier for us.

Going deeper into the last call one step by step , we quickly get bogged down in a jumble of crazy code. It seems that we have not made much progress in our research: you will be tortured to move step by step to the nearest meaningful place, and the antidebugger still does not allow you to painlessly interrupt during the execution of the code.

Let’s try to go from the other side. Taking a closer look at the dumped module, we find that it is not so useless. It suddenly has exported symbols:

0 .00007FF7`BABBD06E EP_RegHardwareID 
1 .00007FF7`BABBD073 EP_RegHardwareIDA 
2 .00007FF7`BABBD078 EP_RegHardwareIDW 
3 .00007FF7`BABBD07D EP_RegCheckKey 
4 .00007FF7`BABBD082 EP_RegCheckKeyA 
5 .00007FF7`BABBD087 EP_RegCheckKeyW 
6 .00007FF7`BABBD08C EP_RegSaveKey 
7 .00007FF7`BABBD091 EP_RegSaveKeyA 
8 .00007FF7`BABBD096 EP_RegSaveKeyW 
9 .00007FF7`BABBD09B EP_RegLoadKey 
10 .00007FF7`BABBD0A0 EP_RegLoadKeyA 
11 .00007FF7`BABBD0A5 EP_RegLoadKeyW 
12 .00007FF7`BABBD0AA EP_RegLoadAndCheckKey 
13 .00007FF7`BABBD0AF EP_RegCheckAndSaveKey 
14 .00007FF7`BABBD0B4 EP_RegCheckAndSaveKeyA 
15 .00007FF7`BABBD0B9 EP_RegCheckAndSaveKeyW 
16 .00007FF7`BABBD0BE EP_RegDeleteKey ...

As you might guess, EP this is Enigma Protection, and the entry points point to some very useful stuff contained inside.

Having poked into some of them in a row, we will find that after a couple of long jmp they all run into the following construction:

push 005C7AC29 
jmp .00007FF7`BA4F3F50

We have already seen something similar in VMProtect. Of course: we did go to a virtual machine!


We are looking for the beginning of the interpreter in the unpacked-decrypted code:

push rsp 
push rax 
push rcx 
push rdx 
push rbx 
push rbp 
push rsi 
push rdi 
push r8 
push r9 
push r10 
push r11 
push r12 
push r13 
push r14 
push r15 
mov dl,1 
mov rsi,0 
lea rdi,[00007FF7`BA5032F0] 
add rdi,rsi 
lea rdi,[rdi] 
mov rcx,1 

Now we will set the breakpoint at the beginning of the interpreter and once again make sure – yes, it is, this fragment is periodically called among the vast expanses of insane code.

Let’s try to make the most of our discovery. We write logging to the freshly baked breakpoint – for a start, RAX and the parameter on the stack , after which we launch the program. It, of course, instantly slams, because the anti-debugger is not asleep, but at parting it will give us a detailed log of calls to the virtual machine:

610 5C7C535 
143336BE190 5C7CACA 
143336BE1D0 5C7CACE 
14335219760 5C7CAC5 
630 5C7C754 
7FF8DBCE0000 5C7C76D 
143350D0000 5C7C778 
7FF8DB160000 5C7C77F 
7FF8DB17A360 5C7C770 
615FF24EC83C3 5C7C777 
7FF8DBD7C8C0 5C7C73F 
7FF8D69911B0 5C7C73F 
7FF8DBD7CD40 5C7C73F 
7FF8DBD7D020 5C7C73F 
7FF8DBD7CC20 5C7C73F 
7FF8DBD7FF90 5C7C73F 
143352119F0 5C7C549 
143336BE928 5C7C544 
F12A436D40745745 5C7C55E 
15 5C7C56D 
63BFFFD10 5C7C5AE 
0 5C7C5BB 
12 5C7C5BC 
143352118F0 5C7C549 
14335211910 5C7C549 
14335211930 5C7C549 
14335211950 5C7C549 
14335211970 5C7C549 
14335211990 5C7C549 
143352119B0 5C7C549 
143352119D0 5C7C549 
143336BE968 5C7C544 
704562405E78BE75 5C7C55E 
8 5C7C56D

The insane code is slowly beginning to gain meaning: in particular, we have found a specific place after which the anti-debugger is triggered in the program. We set a condition for a breakpoint and observe the behavior of the program immediately after this call to the virtual machine. It turns out that the call is part of the following construction:

mov rcx,[00007FF7`BA49D9F0] 
mov r8d,7 
call .00007FF7`BA17C750 // VM RECALL 
mov rcx,[rbp][-018] 
test rcx,rcx jnz .00007FF7`BA167AA2 
lea rcx,[00007FF7`BA49B470] 
call .00007FF7`BA1270C0 // GetModuleHandleA (ntdll.dll) 
mov rcx,rax 
mov rdx,rbx 
call .00007FF7`BA127160 // GetProcAddress (NtSetInformationThread) 
mov [rbp][-8],rax cmp q,[rbp][-8],0 jz .00007FF7`BA167ADD 
call .00007FF7`BA1278A0 
mov rcx,rax 
mov r9d,0 
mov r8,0 
mov edx,000000011 
call q,[rbp][-8] 
mov rcx,rbp 
call .00007FF7`BA1679D0 

Accordingly, by short-circuiting the transition jz .00007FF7`BA167ADD, you can safely move on (in the sense, until the next such trap!). In order not to take too much of the reader’s time, let us touch on the principle of operation of the virtual machine, the entry point into the interpreter of which we have just found.


To begin with, I note that this machine is mutating, that is, the code is generated randomly, all addresses and links on two different protected modules will be different. That is, catching similar calls by offset or mask is rather unpleasant, although some blocks of code (for example, the above ones) are saved and can be found by mask.

Recalling VMProtect, suppose that the magic number on the stack when entering the interpreter is somehow related to the address of the beginning of the block of threaded code executed by the interpreter when called. Thoughtfully tracing the interpreter, we come across the following code:

mov          [rbp][-8],ecx            ; Magic number to enter steck
mov          [rbp][-010],rdx
mov          edx,[00007FF7`BA5032B0]  ; Magic code transition number 
mov          eax,[rbp][-8]
xor          eax,edx                  ; it happen here
mov          [rbp][-020],eax
mov          rax,[00007FF7`BA5032C0]  ; 
mov          edx,[rbp][-020]
mov          edx,[rax][rdx]*4
mov          rax,[00007FF7`BA5032B8]  ; Absolute database — module bigining
lea          rax,[rdx][rax]
 ; result, first command code of VM entering interpreter = [00007FF7`BA5032B8]+ [00007FF7`BA5032C0]+ (ecx xor [00007FF7`BA5032B0])*4 
mov          [rbp][-000000088],rax
mov          rax,[rbp][-000000088]
cmp          d,[rax],000000231        ; 
jnz         .00007FF7`BA4ECB49

I leave the reader to deal with the command system of the virtual machine for himself, because it is confusing, meaningless and merciless. At the same time, its complete preparation is not necessary – as the above example with the anti-debugger showed, in places it is enough to know the calls of individual blocks of code, which, by the way, are indicated by the ears sticking out of the exported functions. For those who are especially curious, I note that the command with the opcode that got to the end of the above-described fragment 0x231 is an indirect call to the processor (not virtual) code at a given relative offset.

Let us dwell in more detail on how the specified offset is formed – this will help us understand the principle of interaction of individual blocks of the sewn code with each other. Consider the sequence for exiting the interpreter:

mov          rax,[rbp][-000000088]    ; VM current pointer
mov          rdx,[rax][020]           ; relative address of machine code
mov          rax,[00007FF7`BA5032D0]  ; Start module address
lea          rax,[rdx][rax]           ; Machine code address = [00007FF7`BA5032D0] + relative address inside VM
mov          [rbp][-018],rax
jmps        .00007FF7`BA4F3E04
jmp         .00007FF7`BA4ECAB8
mov          rax,[rbp][-018]          ;  RAX=address of next command for RET
mov          rbx,[rbp][-0000000B8]
lea          rsp,[rbp][0]
pop          rbp

We see that relative calls directly to machine code (command 0x631) can be made from virtual code . In addition, upon returning from a block of virtual code (well, for example, a command with an opcode 0x60), there is a return to the specified block of machine code that performs specific actions. Thus, fragments of machine and virtual code are executed in a certain branching and nested sequence, available for investigation by an inquiring mind with sufficient free time and motivation.


The conclusion suggests itself: the updated and improved version of Enigma Protector x64 is capable of delivering a hefty headache to the researcher, however, for any clever bolt, there is always a nut with a left-hand thread. Borrowing anti-debugging methods from well-known and popular protectors allows finding hidden loopholes in protection, acting by analogy.

As a result, we can safely say: there are no universal protections. 

What do you think?

18 Points
Upvote Downvote
Red Hat Professional

Written by Admin

NewbieAvatar uploadFirst contentFirst commentPublishing content 3 times


Leave a Reply



When metadata is your enemy

Hacking the Big Railway Company