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.
EXPERIMENT
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!
DISASSEMBLE
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.
OPERATING PRINCIPLE
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
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.
CONCLUSION
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.
Comments
Loading…