July 21

VMProtect

Introduction

The main problem of traversing tread functions is manual traversal, i.e. traversal without automation. It slows down the analysis and you have to repeat many points. Im decided to write a small article because of this to remind you that some points can be easily bypassed + im amused that to bypass anti-debug for Ring-3 they use Ring-0, i.e. they try to run over a cockroach with a tank for some reason. Today we will look under the hood of VMP and write a bypass for SDK functions and VMP loader function(anti-debug,anti-vm, list import,spoof syscall_id and e.t.c).

The analysis will be more about x64 code(more 3.7.x-3.8.x, but most of the points are similar for earlier versions(for example, SDK CRC bypass)) because similar actions will have to be done for x32 + I don't see the point of making bloated code. Only the logic will be shown.

Important point! This is more of a PoC than a ready to insert to bypass in my opinion, but I will not argue with the reader.

Chapter 1 Breaking the SDK or Ice to meet you

1.1 Anti-debug or a return to the Off Balance technique

What is annoying about VMP?

First of all, the author is often tricky and uses WinApi/CRT shells, but we'll talk about that in chapter 2 and because of that a minimum of NtApi/WinApi is used, and trying to build anti-debug on manual syscall is annoying but sometimes inspiring + using BugCheck functions to detect hooks from anti-anti-debug tool can make you waste your time.

Secondly, by its virtualization.

Not by word but by deed we will prove the effectiveness of not needing to analyze this nonsense. It will force you to get to the bottom of everything where virtualization is missing + show you different ways to solve the problem.

VMP starting ~ from version 3.5.1 starts using single step to detect HWBP of the current thread:

The main problem in decoding ret_address because the address to the program code, after the execution of the SDK function:

Because of this, it is possible to write a simple hook:

1. Install Veh hook
2. Checking ExceptionCode == STATUS_SINGLE_STEP
3. Check that before nop instruction cpuid,rdtsc(actually it is necessary to check the address from which section is called, but this is at the discretion of the reader).
4. Scan the stack, on ret_address to the user code
5. Set the bp(0xCC) hook (copy the instruction) or you can replace it with your own hook (it is recommended to raise an exception, i.e. copy and then rewrite ret_address to non-valid => handle hook to destroy pointers[1] => don't trigger CRC
6. Handler for exception(I have:ExceptionCode == STATUS_BREAKPOINT)
7. Return to al = 0, jump to the next code and win?(surprise-surprise, but it will bypass anti-vm still because anti-debug uses only rdtsc, but it will be clarified later).

There is an error here because I don't want to put Unicore Engine because the Dll is very bloated, but I recommend to put it in and write a check on ret_address because in x32 due to the peculiarity of VMP excepthion exception handling the offset(ret_address) will be at > 0x4xx, i.e. the wrong address will be found and emulation solves the problem, but in point 2.2 another way to bypass this miracle is shown.
P.S Veh hook is used as an example, but it is possible to use earlier and less noticeable interception in exception handling (for example, Wow64PrepareForException).

1.2 CRC SDK

Similar situation with SDK anti-debug bypass, because the author uses NtQueryVirtualMemory in earlier versions of VirtualQuery.
in earlier versions of VirtualQuery (add a hook if needed).
There is an unpleasant moment because if the function will be simply called from virtualized code and it will not be from SDK VMP call, we will return a slightly different status, although for NtApi it will not affect, but we will write a check for practice.

1. Check where it was called from (from our module, but it is better to write for VMP section).
2. ProcessHandle == NtCurrentProcess && MemoryInformationClass == MemoryBasicInformation
3. Here is the main point since we do not have direct access to rsp, but we want to scan the line NtQueryVirtualMemory.
4. Take &MemoryInformationLength and subtract offset 0x30 since the 5th argument in x64 is rsp + 0x28 and the offset of the current pointer to rsp.
5. If the string exists, scan the stack and crawl ret_address and return al = 1.

Fun fact: you can change this term to another NtApi and VMP will call the other NtApi.


Line Change:

Calling NtApi, which VMP does not use:

And the cherry on the cake!

The protector checks code integrity before the SDK does, even if integrity checking is not enabled in the locker, i.e. an unpleasant gift.
But there is a pleasant moment because this value has 2 values only, i.e. hash_detect_patch and hash_no_detect_patch, i.e. you can make a cheeky hook, if necessary.
Quite a strange solution from the author, but let it be so. In fact, these values are in the same anti-debug, but I have big doubts that you will be able to trigger them, if you do not create a complete nonsense.
The only option that I can think of is to manually find the RW memory (usually > PAGE_SIZE * 4) because of the PG hook, and this is exactly hash, which will be different. In short, it is better to patch after the CRC calculation in the loader, if you are too lazy to do unnecessary and useless actions (or just use HWBP).

1.3 AntiVM SDK

In fact, anti-debug bypasses the anti-vm SDK as well, but there are a couple of problems that the im will tell you, but will not solve since:

• The problem requires scanning vm_cpuid and hooking them, but in the same VMWare the problem is solved by config customization, as well as VirtualBox (we are talking about calling cupid with eax = 1 and checking 31 bits in ecx).

• The problem requires a fake fix for TF exception_info->ExceptionRecord->ExceptionAddress to an address with opcode nop for the same VirtualBox, but I don't use it + this is a hypervisor bug and it's quite strange that it's not fixed.

Not to be boring, the i will mention scanning vm lines that are called by NtQuerySystemInformation with SystemFirmwareTableInformation (RSMB). The plan is as reliable as a Swiss watch, i.e. you can make it return that it simply does not exist:

1. Instruction scan mov rbx,qword ptr ss:[rsp+8](48 8B 5C 24)
2. Disassemble and check that this is our function
3. Find mov al,1 and rewrite it to mov al,0.
4. Win?

P.S Buffer access can be detected by buffer detection when calling manual syscall NtQuerySystemInformation and setting HWBP

Chapter 2 Breaking Loader

This is where most everyone starts to have problems because it is quite unpleasant to pick this creation.
Let's get to know what's what!

2.1 Import

Recently (a long time ago) I updated and released a toolkit for fixing emulation-based imports. However, there is an unpleasant problem that the author pointed out.
The import will be decrypted only in the .text section, which means that the virtualization will rotate this toolkit 360 degrees if the obfuscated call is under it. We're going to go down a slightly different path, but first the words.
Here I will touch on developer's malpractice because let's study VMP's import obfuscation:

3.0.9:

00 | 00007FF63F25109E | E8 DD5A1900              | call bin_64.0.9.vmp.7FF63F3E6B80
01 | 00007FF63F3E6B80 | 90                       | nop                         
02 | 00007FF63F3E6B81 | 41:0FBFCF                | movsx ecx,r15w             
03 | 00007FF63F3E6B85 | 41:8AC8                  | mov cl,r8b                 
04 | 00007FF63F3E6B88 | 41:0F47C8                | cmova ecx,r8d               
05 | 00007FF63F3E6B8C | 59                       | pop rcx                     
06 | 00007FF63F3E6B8D | E9 CF82F3FF              | jmp bin_64.0.9.vmp.7FF63F31EE61
07 | 00007FF63F31EE61 | 48:870C24                | xchg qword ptr ss:[rsp],rcx 
08 | 00007FF63F31EE65 | E9 D99D0D00              | jmp bin_64.0.9.vmp.7FF63F3F8C43
09 | 00007FF63F3F8C43 | 51                       | push rcx                   
0A | 00007FF63F3F8C44 | 0F9FC1                   | setg cl                     
0B | 00007FF63F3F8C47 | 48:8D0D 7D8FE5FF         | lea rcx,qword ptr ds:[7FF63F251BCB]
0C | 00007FF63F3F8C4E | E9 3DC8EDFF              | jmp bin_64.0.9.vmp.7FF63F2D5490
0D | 00007FF63F2D5490 | 48:8B89 96FA1B00         | mov rcx,qword ptr ds:[rcx+1BFA96]
0E | 00007FF63F2D5497 | E9 B06D0300              | jmp bin_64.0.9.vmp.7FF63F30C24C
0F | 00007FF63F30C24C | 48:8D89 D821AE71         | lea rcx,qword ptr ds:[rcx+71AE21D8]
10 | 00007FF63F30C253 | 48:870C24                | xchg qword ptr ss:[rsp],rcx 
11 | 00007FF63F30C257 | E9 24D7FFFF              | jmp bin_64.0.9.vmp.7FF63F309980
12 | 00007FF63F309980 | C3                       | ret

3.5
01 | 00007FF7A57710AC | E8 5FFFFFFF              | call bin_64_3.5.0.vmp.7FF7A5771010
02 | 00007FF7A5771010 | 48:894C24 08             | mov qword ptr ss:[rsp+8],rcx
03 | 00007FF7A5771015 | 48:895424 10             | mov qword ptr ss:[rsp+10],rdx
04 | 00007FF7A577101A | 4C:894424 18             | mov qword ptr ss:[rsp+18],r8
05 | 00007FF7A577101F | 4C:894C24 20             | mov qword ptr ss:[rsp+20],r9
06 | 00007FF7A5771024 | 53                       | push rbx                 
07 | 00007FF7A5771025 | 57                       | push rdi                 
08 | 00007FF7A5771026 | 48:83EC 38               | sub rsp,38               
09 | 00007FF7A577102A | B9 01000000              | mov ecx,1                 
0A | 00007FF7A577102F | 48:8D7C24 58             | lea rdi,qword ptr ss:[rsp+58]
0B | 00007FF7A5771034 | E8 72350500              | call bin_64_3.5.0.vmp.7FF7A57C45AB
0C | 00007FF7A57C45AB | 90                       | nop                       
0D | 00007FF7A57C45AC | 50                       | push rax                 
0E | 00007FF7A57C45AD | 41:0FB7C0                | movzx eax,r8w             
0F | 00007FF7A57C45B1 | 40:8AC6                  | mov al,sil               
10 | 00007FF7A57C45B4 | 98                       | cwde                     
11 | 00007FF7A57C45B5 | 48:8B4424 08             | mov rax,qword ptr ss:[rsp+8]
12 | 00007FF7A57C45BA | 48:8D40 01               | lea rax,qword ptr ds:[rax+1]
13 | 00007FF7A57C45BE | E9 C57C0500              | jmp bin_64_3.5.0.vmp.7FF7A581C288
14 | 00007FF7A581C288 | 48:894424 08             | mov qword ptr ss:[rsp+8],rax
15 | 00007FF7A581C28D | 48:8D05 EB4FF5FF         | lea rax,qword ptr ds:[7FF7A577127F
16 | 00007FF7A581C294 | E9 200BFEFF              | jmp bin_64_3.5.0.vmp.7FF7A57FCDB9
17 | 00007FF7A57FCDB9 | 48:8B80 AB060400         | mov rax,qword ptr ds:[rax+406AB]
18 | 00007FF7A57FCDC0 | E9 C2CA0300              | jmp bin_64_3.5.0.vmp.7FF7A5839887
19 | 00007FF7A5839887 | 48:8D80 690A0074         | lea rax,qword ptr ds:[rax+74000A69
1A | 00007FF7A583988E | E9 04B10600              | jmp bin_64_3.5.0.vmp.7FF7A58A4997
1B | 00007FF7A58A4997 | 48:870424                | xchg qword ptr ss:[rsp],rax
1C | 00007FF7A58A499B | E9 00E6FAFF              | jmp bin_64_3.5.0.vmp.7FF7A5852FA0
1D | 00007FF7A5852FA0 | C3                       | ret

3.8.1:

00 | 00007FF7B7811079 | E8 20470000              | call bin_64_3_8_1.vmp.7FF7B781579E
01 | 00007FF7B781579E | 50                       | push rax                       
02 | 00007FF7B781579F | 41:56                    | push r14                       
03 | 00007FF7B78157A1 | E8 72C10000              | call bin_64_3_8_1.vmp.7FF7B7821918
04 | 00007FF7B7821918 | 9C                       | pushfq                         
05 | 00007FF7B7821919 | 49:BE B28D379E99702E23   | mov r14,232E70999E378DB2       
06 | 00007FF7B7821923 | 57                       | push rdi                       
07 | 00007FF7B7821924 | 4D:8DB426 A3E322EA       | lea r14,qword ptr ds:[r14-15DD1C5D]
08 | 00007FF7B782192C | 48:BF 27442ED038039DBA   | mov rdi,BA9D0338D02E4427       
09 | 00007FF7B7821936 | 48:81FF 9B278BFA         | cmp rdi,FFFFFFFFFA8B279B       
0A | 00007FF7B782193D | 48:8B4424 28             | mov rax,qword ptr ss:[rsp+28] 
0B | 00007FF7B7821942 | E8 E9F51A00              | call bin_64_3_8_1.vmp.7FF7B79D0F30
0C | 00007FF7B79D0F30 | 41:50                    | push r8                       
0D | 00007FF7B79D0F32 | 4D:8BC6                  | mov r8,r14                     
0E | 00007FF7B79D0F35 | E8 E37E0000              | call bin_64_3_8_1.vmp.7FF7B79D8E1D
0F | 00007FF7B79D8E1D | 55                       | push rbp                       
10 | 00007FF7B79D8E1E | 49:8BEE                  | mov rbp,r14                   
11 | 00007FF7B79D8E21 | 48:8D40 01               | lea rax,qword ptr ds:[rax+1]   
12 | 00007FF7B79D8E25 | 41:0F9EC0                | setle r8b                     
13 | 00007FF7B79D8E29 | 6645:2BC6                | sub r8w,r14w                   
14 | 00007FF7B79D8E2D | 41:50                    | push r8                       
15 | 00007FF7B79D8E2F | E8 3174E4FF              | call bin_64_3_8_1.vmp.7FF7B7820265
16 | 00007FF7B7820265 | 66:C1E5 B8               | shl bp,B8                     
17 | 00007FF7B7820269 | E8 DD541A00              | call bin_64_3_8_1.vmp.7FF7B79C574B
18 | 00007FF7B79C574B | 4C:23C7                  | and r8,rdi                     
19 | 00007FF7B79C574E | 48:894424 60             | mov qword ptr ss:[rsp+60],rax 
1A | 00007FF7B79C5753 | 40:F6D5                  | not bpl                       
1B | 00007FF7B79C5756 | C1ED FC                  | shr ebp,FC                     
1C | 00007FF7B79C5759 | 66:C1E7 A1               | shl di,A1                     
1D | 00007FF7B79C575D | 48:81A4EC D0FFFFFF 3957B | and qword ptr ss:[rsp+rbp*8-30],79B7573
1E | 00007FF7B79C5769 | 41:55                    | push r13                       
1F | 00007FF7B79C576B | 41:50                    | push r8                       
20 | 00007FF7B79C576D | 48:814424 10 F4DFFFFF    | add qword ptr ss:[rsp+10],FFFFFFFFFFFFD
21 | 00007FF7B79C5776 | 56                       | push rsi                       
22 | 00007FF7B79C5777 | E8 08D00200              | call bin_64_3_8_1.vmp.7FF7B79F2784
23 | 00007FF7B79F2784 | 44:0B4424 11             | or r8d,dword ptr ss:[rsp+11]   
24 | 00007FF7B79F2789 | 0F8D 1F56E2FF            | jge bin_64_3_8_1.vmp.7FF7B7817DAE
25 | 00007FF7B79F278F | 48:BE 0351299631AC12E9   | mov rsi,E912AC3196295103       
26 | 00007FF7B79F2799 | 48:8B84AC 00000000       | mov rax,qword ptr ss:[rsp+rbp*4]
27 | 00007FF7B79F27A1 | 48:8B8468 F0FFFFFF       | mov rax,qword ptr ds:[rax+rbp*2-10]
28 | 00007FF7B79F27A9 | 0F82 9C100600            | jb bin_64_3_8_1.vmp.7FF7B7A5384B
29 | 00007FF7B79F27AF | 49:BD 9B961DD20F50BBD6   | mov r13,D6BB500FD21D969B       
2A | 00007FF7B79F27B9 | 6644:214424 10           | and word ptr ss:[rsp+10],r8w   
2B | 00007FF7B79F27BF | 856C24 30                | test dword ptr ss:[rsp+30],ebp 
2C | 00007FF7B79F27C3 | 4A:8D8400 D6E8229E       | lea rax,qword ptr ds:[rax+r8-61DD172A]
2D | 00007FF7B79F27CB | 48:0FCF                  | bswap rdi                       
2E | 00007FF7B79F27CE | 6644:8BC5                | mov r8w,bp                     
2F | 00007FF7B79F27D2 | E8 D999E3FF              | call bin_64_3_8_1.vmp.7FF7B782C1B0
30 | 00007FF7B782C1B0 | 0F86 757D1900            | jbe bin_64_3_8_1.vmp.7FF7B79C3F2B
31 | 00007FF7B782C1B6 | F65424 38                | not byte ptr ss:[rsp+38]       
32 | 00007FF7B782C1BA | 48:878424 80000000       | xchg qword ptr ss:[rsp+80],rax 
33 | 00007FF7B782C1C2 | 48:8B7C24 60             | mov rdi,qword ptr ss:[rsp+60] 
34 | 00007FF7B782C1C7 | 49:81C6 85A8A925         | add r14,25A9A885               
35 | 00007FF7B782C1CE | F7D6                     | not esi                       
36 | 00007FF7B782C1D0 | 66:F7DE                  | neg si                         
37 | 00007FF7B782C1D3 | 6641:C1EE 42             | shr r14w,42                   
38 | 00007FF7B782C1D8 | 4C:8BACAC 00000000       | mov r13,qword ptr ss:[rsp+rbp*4]
39 | 00007FF7B782C1E0 | 4C:037424 38             | add r14,qword ptr ss:[rsp+38] 
3A | 00007FF7B782C1E5 | C74424 38 A2A6BBED       | mov dword ptr ss:[rsp+38],EDBBA6A2
3B | 00007FF7B782C1ED | 41:C0E6 C7               | shl r14b,C7                   
3C | 00007FF7B782C1F1 | 814C24 38 B9A2BB03       | or dword ptr ss:[rsp+38],3BBA2B9
3D | 00007FF7B782C1F9 | 66:33F6                  | xor si,si                     
3E | 00007FF7B782C1FC | C1AC34 38002A96 E3       | shr dword ptr ss:[rsp+rsi-69D5FFC8],E3
3F | 00007FF7B782C204 | 4B:8DACF6 0AF4036F       | lea rbp,qword ptr ds:[r14+r14*8+6F03F40
40 | 00007FF7B782C20C | 0F87 08731A00            | ja bin_64_3_8_1.vmp.7FF7B79D351A
41 | 00007FF7B79D351A | 48:8B7424 10             | mov rsi,qword ptr ss:[rsp+10] 
42 | 00007FF7B79D351F | 49:F7D8                  | neg r8                         
43 | 00007FF7B79D3522 | 66:0FBE6C24 1D           | movsx bp,byte ptr ss:[rsp+1D] 
44 | 00007FF7B79D3528 | 45:84C6                  | test r14b,r8b                 
45 | 00007FF7B79D352B | 0FCD                     | bswap ebp                     
46 | 00007FF7B79D352D | 44:0F497424 3B           | cmovns r14d,dword ptr ss:[rsp+3B]
47 | 00007FF7B79D3533 | 0F9E846C 6E64FEFF        | setle byte ptr ss:[rsp+rbp*2-19B92]
48 | 00007FF7B79D353B | 49:85EE                  | test r14,rbp                   
49 | 00007FF7B79D353E | E8 28A20500              | call bin_64_3_8_1.vmp.7FF7B7A2D76B
4A | 00007FF7B7A2D76B | 48:8B6C24 48             | mov rbp,qword ptr ss:[rsp+48] 
4B | 00007FF7B7A2D770 | 0F85 A901F7FF            | jne bin_64_3_8_1.vmp.7FF7B799D91F
4C | 00007FF7B799D91F | 0F86 83E90000            | jbe bin_64_3_8_1.vmp.7FF7B79AC2A8
4D | 00007FF7B799D925 | 6644:854424 43           | test word ptr ss:[rsp+43],r8w 
4E | 00007FF7B799D92B | E8 FD000000              | call bin_64_3_8_1.vmp.7FF7B799DA2D
4F | 00007FF7B799DA2D | 41:0F91C0                | setno r8b                     
50 | 00007FF7B799DA31 | 4C:8BB424 88000000       | mov r14,qword ptr ss:[rsp+88] 
51 | 00007FF7B799DA39 | 814C24 28 84C59D60       | or dword ptr ss:[rsp+28],609DC584
52 | 00007FF7B799DA41 | 0F934424 28              | setae byte ptr ss:[rsp+28]     
53 | 00007FF7B799DA46 | 44:334424 4A             | xor r8d,dword ptr ss:[rsp+4A]   
56 | 00007FF7B799DA63 | 4E:8B8404 6A1D8A98       | mov r8,qword ptr ss:[rsp+r8-6775E296]
57 | 00007FF7B799DA6B | F75424 28                | not dword ptr ss:[rsp+28]     
58 | 00007FF7B799DA6F | E8 21570000              | call bin_64_3_8_1.vmp.7FF7B79A3195
59 | 00007FF7B79A3195 | 48:8D6424 48             | lea rsp,qword ptr ss:[rsp+48] 
5A | 00007FF7B79A319A | C2 1800                  | ret 18                         
5B | 00007FF7B79914B4 | FF7424 18                | push qword ptr ss:[rsp+18]     
5C | 00007FF7B79914B8 | 9D                       | popfq                         
5D | 00007FF7B79914B9 | 48:8D6424 30             | lea rsp,qword ptr ss:[rsp+30] 
5E | 00007FF7B79914BE | C3                       | ret

I don't see the point in showing you any further as we can see two things:

1. The call is always via call
2. Always xchg is present

It's funny that the author is just trying to break tools, not to think about the tracking problem:
How do they find it if I've thrown obfuscation on a bit and it's efficiency tends to NULL?
The question is, of course, rhetorical.

So, what to do? The author goes the old fashioned way, i.e. write a hook to get the import list + a couple of ideas for a hook even for the current version.

Since this is a CRC trigger from the loader, just rewrite to HWBP hook, so you don't have to worry, but less word and more deeds!

~Including before version 3.8.1 you could generally get high since you could intercept the import address and return the address on your function => $free$ hooke the import due to a simple mutation of the address getting shell(I mentioned it in 1 chat).
It can be easily found if you put on PE ntdll PG address hook:

However, since version 3.8.2, the author decided to put this function under virtualization because of the leak, which made the task more complicated, but it does not interfere anyway.

If you analyze it, VMP_STR_CMP is used, i.e. 2 strings are compared and the comparison status is returned. It is only under mutation, so it's free.

Action Plan:

1. Search pattern mov qword ptr ss:[rsp+8],rbx(48 89 5C 24 08)
2. Disassemble and check that this is the required function
3. Install the hook

It's not a perfect solution, but we have a list of imports.

If we talk about the correct solution and emulation, then:
Search lea reg64,qword ptr ds:[imm](48 8D) for versions below ~3.6 and call rip+offset(E8)

Collect this list in the .vmp0 section and you can ignore the .text section since the author keeps getting rip via call in the obfuscated call (not when going from .text to vmp0, but during the output of the obfuscated miracle), so he makes it easy.

Emulate and check that pointer points to import(can be emulated before xchg instruction) and check on ret that we are on import. But I'm too lazy to do + I'm already dragging 3 libs without it, so it's all at the reader's discretion, although I've said the logic.

You can also add export to your library with the same name or hook strcmp, hook GetModuleHandleA and return your library, if the import we are interested in is there. This is just for fun...

2.2 Fight to destroy syscall_id

VMProtect really didn't want reversers to use TitanHide and plugins like ScyllaHide to bypass anti-debug,
but discovering that VMP was having a ridiculous fight over manual syscall would have made for an absolutely hilarious introduction.

Stop. This is where we need to stop. As most readers know, VMP started using ~ since version 3.1.x manual syscall. This can be followed amusingly in the ScyllaHide updates.
As an example, here is an add-on code SharpOD, which patches ProductVersion && FileVersion and takes fake versions of ProductVersion && FileVersion when loading ntdll(orig_NtMapViewOfSection hook).
In simple words, everyone uses fake OSBuildNumber in PEB, fake ProductVersion, FileVersion in ntdll resources. And to make life not seem like a raspberry, VMP started to get NtBuildNumber from PKUSER_SHARED_DATA and surprised all the girlfriends:

It can be circumvented and will be shown here, but with a crutch, since the author is lazy. And why should I be honest about the workaround when everyone is cheating?

First of all, you need to spoof the above (OSBuildNumber in PEB and ProductVersion/FileVersion), but you also need to spoof OSMajorVersion and for good luck OSMinorVersion to bypass NtBuildNumber.

Now map ntdll.dll with NtOpenSecthion and NtMapViewOfSection, and if it fails, the old set of NtOpenFile, NtCreateSection, NtMapViewOfSection. If this fails too, the author throws MessageBox through NtRaiseException and it's awesome, because it shouldn't be done that way, but the developer knows better.

This is the point where the author got stuck because he tried to destroy what just happened to be there and this is the result, which can be done when you are in a bad mood, being drunk because the address was only to the PE mapped ntdll and the .rdata section.
So let's go to plan B, although this is the original way of bypassing anti-debug in the loader, but here it is guaranteed that syscall_id will be destroyed. What's the joke? You can just go through the stack and destroy the syscall_id, after unmap ntdll, and you can't do that. Everything worked, although this is a barbaric method (but who will judge for such a method of fair play?).

Just repeat in point 1.2, but now just destroy syscall_id, but the problem is that in x64 they are at the top of the stack because you need to go up and down the stack for complete happiness, no matter how strange it may seem.
Now you can drink mead and smoke bamboo!

2.3 AntiDebug Loader or Ivan…. You are drunk

What helps reversers who have already analyzed VMP to bypass anti-debug and what does VMP do first?

Right! VMP always checks NtCurrentPeb()->BeingDebugged and here we just set the HWBP hook on the call.

Going back to last point(2.2), this method was originally used to destroy the syscall_id of NtQueryInformationProcess and NtSetInformationThread.

The plan of action is similarly the same, but write 1 hook to save HWBP and 3 hooks (can also be for NtQuerySystemInformation, but not required).

1. NtContinue, if called from LdrInitializeThunk, because if the DLL is loaded before RIP = EP, this function will be called => Dr registers will be NULL.
2. NtQueryInformationProcess for ProcessDebugPort, ProcessDebugObjectHandle
3. NtSetInformationThread for ThreadHideFromDebugger
4. NtClose for proper processing of STATUS_INVALID_HANDLE


Relatively easy and the article is coming to an end.
There should be a CRC calc bypass from the loader.
There are 2 types of CRC calc: critical and normal, i.e.
1 variant - we make a non-valid pointer and the program causes an error.
2 variant - we throw MessageBox if crc is bad.
I wrote their search and fake on read copy, but there are problems:

1. Sometimes there is a read of calculator bytes not from CRC calc(vm-read_addr) => we can't just set bp and give allocated memory.
2. Because of point 1 we can only manually add them to bypass + part of CRC calc is not used

Chapter 3 A gift from Chinese friends

On 04.01.2024, a special patch for VMProtect v3.8.1 debugging research was posted on 1 forum.
I asked the developer of the anti-anti-debug plugin Loli to send it, so it comes as a bonus.
It is covered by VMP and I advise to just use it under VM, but it works and the author has tested it.

P.S thanks to him for this.

Chapter 4 Conclusion

"Those who are defeated today will be victors tomorrow, for defeat is a science to them." Karl Liebknecht.
The problem with publishing any article on treads is gradual fixes from developers (sooner or later, but you'll come to that conclusion), so many are reluctant to share information or pass it around amongst themselves/in a certain group.

The simplest example of fixes is catching up with build_windows destruction in ScyllaHide.
I hope this article and PoC will help some people and we are waiting for VMP 4.0 from Ivan!

Special patch for VMP

VirusTotal

I liked it...
P.S if I made a mistake in my analysis, please correct me and thank you for reading the article!