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] call .00007FF7`BA4E49A0 mov r8,0 mov rdx,1 mov rcx,[rsp] 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 pushfq 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 7FF8DBD7CBE0 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] nop 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] ; 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] pop rbp retn
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.