Jump to content
Tuts 4 You

VMProtect Heaven's Gate Anti-Debug Bypass to VectorHandler


Recommended Posts

kuazi GA
Posted
8 hours ago, The Binary Expert said:

您需要通过以下方式禁用 SharpOD 的选项@Xjun 使用这个插件

 

屏幕截图-146.png

 

@启动 它就像魔法一样。非常感谢您的努力。

顺便问一下,您是如何修改原始 ScyllaHide x64 插件的源代码的?

问候。

肖恩。

https://bbs.kanxue.com/thread-282244.htm

  • Thanks 1
  • Haha 1
Posted (edited)

ahhhh! he modified ntdll's export table to trick VMP like the wine_get_version function exists! Very nice trick :D

 

3.8.8 is working.

 

capture.PNG.023829a87dc9cb1546d71819213ec2e8.PNG

Edited by karan
  • Like 1
Posted
On 7/6/2024 at 12:46 AM, kuazi GA said:

This method has some shortcomings. If compiling a 32-bit ScyllaHide plugin, although it can bypass VMP's Anti-DeBug on Win7 x64 SP1, it does not work on Win10/11 x64.

  • Like 1
Sean the hard worker
Posted
36 minutes ago, karan said:

ahhhh! he modified ntdll's export table to trick VMP like the wine_get_version function exists! Very nice trick :D

 

3.8.8 is working.

 

capture.PNG.023829a87dc9cb1546d71819213ec2e8.PNG

@karan Is this vmprotect a cracked version?

Regards.

sean.

  • Like 1
Posted
10 minutes ago, The Binary Expert said:

@karan Is this vmprotect a cracked version?

Regards.

sean.

 

I bought it because I personally respect VMP developers. :D
He can take my money with him!

  • Thanks 1
Sean the hard worker
Posted
2 minutes ago, karan said:

 

I bought it because I personally respect VMP developers. :D
He can take my money with him!

@karan Okay, I got it. Thanks.

Regards..

sean.

  • Like 1
Posted
46 minutes ago, boot said:

If compiling a 32-bit ScyllaHide plugin, although it can bypass VMP's Anti-DeBug on Win7 x64 SP1, it does not work on Win10/11 x64.

Haven't tried yet but I don't see a (theoretical) reason why it should not work on Win10/11 x64. Can you explain that?

  • Like 2
Posted
44 minutes ago, kao said:

Haven't tried yet but I don't see a (theoretical) reason why it should not work on Win10/11 x64. Can you explain that?

I tested the original author's code and found that it doesn't seem to bypass the protection properly on x86 systems.

VMProtect does not appear to search through the entire Export Table to find the desired function.

So, I modified the code to overwrite the last export function of ntdll.dll with wine_get_version and then place the original function right after it. As a result, the bypass worked successfully!

 

void AddWineFunctionName(HANDLE hProcess)
{
    BYTE* remote_ntdll = (BYTE*)GetModuleBaseRemote(hProcess, L"ntdll.dll");
    if (!remote_ntdll)
        return;

    SIZE_T readed = 0;
    IMAGE_DOS_HEADER dos_header;
    ReadProcessMemory(hProcess, remote_ntdll, &dos_header, sizeof(IMAGE_DOS_HEADER), &readed);
    if (dos_header.e_magic != IMAGE_DOS_SIGNATURE)
        return;

    IMAGE_NT_HEADERS pe_header;
    ReadProcessMemory(hProcess, (BYTE*)remote_ntdll + dos_header.e_lfanew, &pe_header, sizeof(IMAGE_NT_HEADERS), &readed);
    if (pe_header.Signature != IMAGE_NT_SIGNATURE)
        return;

    DWORD export_adress = pe_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    if (!export_adress)
        return;

    DWORD export_size = pe_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;

    BYTE* new_export_table = (BYTE*)VirtualAllocEx(hProcess, remote_ntdll + 0x1000000, export_size + 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    IMAGE_EXPORT_DIRECTORY export_directory;
    ReadProcessMemory(hProcess, remote_ntdll + export_adress, &export_directory, sizeof(IMAGE_EXPORT_DIRECTORY), &readed);

    BYTE* tmp_table = (BYTE*)malloc(export_size + 0x1000);
    if (tmp_table == nullptr) return;

    // Copy functions table
    BYTE* new_functions_table = new_export_table;
    ReadProcessMemory(hProcess, remote_ntdll + export_directory.AddressOfFunctions, tmp_table, export_directory.NumberOfFunctions * sizeof(DWORD), &readed);
    WriteProcessMemory(hProcess, new_functions_table, tmp_table, export_directory.NumberOfFunctions * sizeof(DWORD), &readed);
    g_log.LogInfo(L"[VMPBypass] new_functions_table: %p", new_functions_table);

    // Copy ordinal table
    BYTE* new_ordinal_table = new_functions_table + export_directory.NumberOfFunctions * sizeof(DWORD) + 0x100;
    ReadProcessMemory(hProcess, remote_ntdll + export_directory.AddressOfNameOrdinals, tmp_table, export_directory.NumberOfNames * sizeof(WORD), &readed);
    WriteProcessMemory(hProcess, new_ordinal_table, tmp_table, export_directory.NumberOfNames * sizeof(WORD), &readed);
    g_log.LogInfo(L"[VMPBypass] new_ordinal_table: %p", new_ordinal_table);

    // Copy name table
    BYTE* new_name_table = new_ordinal_table + export_directory.NumberOfNames * sizeof(WORD) + 0x100;
    ReadProcessMemory(hProcess, remote_ntdll + export_directory.AddressOfNames, tmp_table, export_directory.NumberOfNames * sizeof(DWORD), &readed);
    WriteProcessMemory(hProcess, new_name_table, tmp_table, export_directory.NumberOfNames * sizeof(DWORD), &readed);
    g_log.LogInfo(L"[VMPBypass] new_name_table: %p", new_name_table);

    free(tmp_table);
    tmp_table = nullptr;

    // Setup new name & name offset
    BYTE* wine_func_addr = new_name_table + export_directory.NumberOfNames * sizeof(DWORD) + 0x100;
    WriteProcessMemory(hProcess, wine_func_addr, "wine_get_version\x00", 17, &readed);
    DWORD wine_func_offset = (DWORD)(wine_func_addr - remote_ntdll);
    WriteProcessMemory(hProcess, new_name_table + export_directory.NumberOfNames * sizeof(DWORD), &wine_func_offset, 4, &readed);

    // Set fake ordinal
    WORD last_ordinal = export_directory.NumberOfNames;
    WriteProcessMemory(hProcess, new_ordinal_table + export_directory.NumberOfNames * sizeof(WORD), &last_ordinal, 2, &readed);

    // Get address of GetCurrentTeb function to be placed after the new function
    BYTE* get_current_teb = reinterpret_cast<BYTE*>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCurrentTeb"));
    DWORD get_current_teb_offset = (DWORD)(get_current_teb - remote_ntdll);

    // Set new function address (wine_get_version) and GetCurrentTeb function address
    DWORD new_function_offset = get_current_teb_offset;
    WriteProcessMemory(hProcess, new_functions_table + export_directory.NumberOfFunctions * sizeof(DWORD), &new_function_offset, 4, &readed);

    // Setup new directory
    export_directory.NumberOfNames++;
    export_directory.NumberOfFunctions++;

    DWORD name_table_offset = (DWORD)(new_name_table - remote_ntdll);
    export_directory.AddressOfNames = name_table_offset;

    DWORD function_table_offset = (DWORD)(new_functions_table - remote_ntdll);
    export_directory.AddressOfFunctions = function_table_offset;

    DWORD ordinal_table_offset = (DWORD)(new_ordinal_table - remote_ntdll);
    export_directory.AddressOfNameOrdinals = ordinal_table_offset;

    // Change the offset of header data
    DWORD old_prot;
    VirtualProtectEx(hProcess, remote_ntdll + export_adress, sizeof(IMAGE_EXPORT_DIRECTORY), PAGE_EXECUTE_READWRITE, &old_prot);
    WriteProcessMemory(hProcess, remote_ntdll + export_adress, &export_directory, sizeof(IMAGE_EXPORT_DIRECTORY), &readed);
    VirtualProtectEx(hProcess, remote_ntdll + export_adress, sizeof(IMAGE_EXPORT_DIRECTORY), old_prot, &old_prot);
}

I confirmed that my Windows 10 version works fine.

cheers!

ScyllaHide_x86.zip

  • Like 3
  • Thanks 3
Posted
55 minutes ago, karan said:

VMProtect does not appear to search through the entire Export Table to find the desired function.

That's correct. It does binary search (see the implementation here), as the names in the export table are supposed to be ordered lexically. The code in https://bbs.kanxue.com/thread-282244.htm doesn't take that into account.
However, this hasn't changed between Win7/Win10/Win11 - if it worked on one system, it should have worked on all of them.

 

 

  • Like 1
Progman
Posted

I think the issue has to do with ntdll differences between Win7 vs Win10/11.  Keep in mind the WinSDK is free to change and there are many times where they don't maintain backwards compatibility on internal APIs, unlike the normal WinAPI

  • Like 1
Posted
10 hours ago, kao said:

Win7/Win10/Win11 - if it worked on one system, it should have worked on all of them.

I have already conducted testing before, and if you compile the 32-bit plugin according to the original source code provided here (https://bbs.kanxue.com/thread-282244.htm). 

Original 32-bit (Imperfect Version).zip
This plugin is effective on Win7 x64 SP1; But it fails in Win10/11 x64. 
e.g.

VMP_3.8.7_x86_32-bit.vmp.exe

  • Win7 x64 SP1 √
  • Win10 x64 ×
  • Win11 x64 ×

By recompiling the 32-bit plugin according to the modified code provided by karan, the above issue has been resolved.

The revised and recompiled complete version is now uploaded as follows, and has been tested to be effective in Win7/10/11 x64.

ScyllaHide_2024_x86_x64_v0.002.zip

  • Like 3
  • Thanks 7
fReestYler
Posted (edited)

@boot Could you help me compile the InjectorCLIx64.exe ? Just want to try it with ollydbg 64 !

Thanks

Edited by fReestYler
  • Like 3
jackyjask
Posted

@boot  do you have IDA8.3  plugin in your collection?

  • Like 1
Posted
6 hours ago, jackyjask said:

@boot  do you have IDA8.3  plugin in your collection?

I think are available in the forum a nice guy shared already the most useful plugins and also he included them in his repostery in GitHub.

  • Like 1
Posted (edited)
11 hours ago, jackyjask said:

IDA8.3  plugin

IIRC, you can use the generic injector with IDA. Check the docs and let us know, if you test it..

Edited by kao
  • Like 1
jackyjask
Posted
14 hours ago, RADIOX said:

he included them in his repostery in GitHub.

yes, exactly, good pointer, found and used

 

  • Like 1
TRISTAN Pro
Posted (edited)
On 7/7/2024 at 6:58 PM, karan said:

I tested the original author's code and found that it doesn't seem to bypass the protection properly on x86 systems.

VMProtect does not appear to search through the entire Export Table to find the desired function.

So, I modified the code to overwrite the last export function of ntdll.dll with wine_get_version and then place the original function right after it. As a result, the bypass worked successfully!

 

void AddWineFunctionName(HANDLE hProcess)
{
    BYTE* remote_ntdll = (BYTE*)GetModuleBaseRemote(hProcess, L"ntdll.dll");
    if (!remote_ntdll)
        return;

    SIZE_T readed = 0;
    IMAGE_DOS_HEADER dos_header;
    ReadProcessMemory(hProcess, remote_ntdll, &dos_header, sizeof(IMAGE_DOS_HEADER), &readed);
    if (dos_header.e_magic != IMAGE_DOS_SIGNATURE)
        return;

    IMAGE_NT_HEADERS pe_header;
    ReadProcessMemory(hProcess, (BYTE*)remote_ntdll + dos_header.e_lfanew, &pe_header, sizeof(IMAGE_NT_HEADERS), &readed);
    if (pe_header.Signature != IMAGE_NT_SIGNATURE)
        return;

    DWORD export_adress = pe_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    if (!export_adress)
        return;

    DWORD export_size = pe_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;

    BYTE* new_export_table = (BYTE*)VirtualAllocEx(hProcess, remote_ntdll + 0x1000000, export_size + 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    IMAGE_EXPORT_DIRECTORY export_directory;
    ReadProcessMemory(hProcess, remote_ntdll + export_adress, &export_directory, sizeof(IMAGE_EXPORT_DIRECTORY), &readed);

    BYTE* tmp_table = (BYTE*)malloc(export_size + 0x1000);
    if (tmp_table == nullptr) return;

    // Copy functions table
    BYTE* new_functions_table = new_export_table;
    ReadProcessMemory(hProcess, remote_ntdll + export_directory.AddressOfFunctions, tmp_table, export_directory.NumberOfFunctions * sizeof(DWORD), &readed);
    WriteProcessMemory(hProcess, new_functions_table, tmp_table, export_directory.NumberOfFunctions * sizeof(DWORD), &readed);
    g_log.LogInfo(L"[VMPBypass] new_functions_table: %p", new_functions_table);

    // Copy ordinal table
    BYTE* new_ordinal_table = new_functions_table + export_directory.NumberOfFunctions * sizeof(DWORD) + 0x100;
    ReadProcessMemory(hProcess, remote_ntdll + export_directory.AddressOfNameOrdinals, tmp_table, export_directory.NumberOfNames * sizeof(WORD), &readed);
    WriteProcessMemory(hProcess, new_ordinal_table, tmp_table, export_directory.NumberOfNames * sizeof(WORD), &readed);
    g_log.LogInfo(L"[VMPBypass] new_ordinal_table: %p", new_ordinal_table);

    // Copy name table
    BYTE* new_name_table = new_ordinal_table + export_directory.NumberOfNames * sizeof(WORD) + 0x100;
    ReadProcessMemory(hProcess, remote_ntdll + export_directory.AddressOfNames, tmp_table, export_directory.NumberOfNames * sizeof(DWORD), &readed);
    WriteProcessMemory(hProcess, new_name_table, tmp_table, export_directory.NumberOfNames * sizeof(DWORD), &readed);
    g_log.LogInfo(L"[VMPBypass] new_name_table: %p", new_name_table);

    free(tmp_table);
    tmp_table = nullptr;

    // Setup new name & name offset
    BYTE* wine_func_addr = new_name_table + export_directory.NumberOfNames * sizeof(DWORD) + 0x100;
    WriteProcessMemory(hProcess, wine_func_addr, "wine_get_version\x00", 17, &readed);
    DWORD wine_func_offset = (DWORD)(wine_func_addr - remote_ntdll);
    WriteProcessMemory(hProcess, new_name_table + export_directory.NumberOfNames * sizeof(DWORD), &wine_func_offset, 4, &readed);

    // Set fake ordinal
    WORD last_ordinal = export_directory.NumberOfNames;
    WriteProcessMemory(hProcess, new_ordinal_table + export_directory.NumberOfNames * sizeof(WORD), &last_ordinal, 2, &readed);

    // Get address of GetCurrentTeb function to be placed after the new function
    BYTE* get_current_teb = reinterpret_cast<BYTE*>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCurrentTeb"));
    DWORD get_current_teb_offset = (DWORD)(get_current_teb - remote_ntdll);

    // Set new function address (wine_get_version) and GetCurrentTeb function address
    DWORD new_function_offset = get_current_teb_offset;
    WriteProcessMemory(hProcess, new_functions_table + export_directory.NumberOfFunctions * sizeof(DWORD), &new_function_offset, 4, &readed);

    // Setup new directory
    export_directory.NumberOfNames++;
    export_directory.NumberOfFunctions++;

    DWORD name_table_offset = (DWORD)(new_name_table - remote_ntdll);
    export_directory.AddressOfNames = name_table_offset;

    DWORD function_table_offset = (DWORD)(new_functions_table - remote_ntdll);
    export_directory.AddressOfFunctions = function_table_offset;

    DWORD ordinal_table_offset = (DWORD)(new_ordinal_table - remote_ntdll);
    export_directory.AddressOfNameOrdinals = ordinal_table_offset;

    // Change the offset of header data
    DWORD old_prot;
    VirtualProtectEx(hProcess, remote_ntdll + export_adress, sizeof(IMAGE_EXPORT_DIRECTORY), PAGE_EXECUTE_READWRITE, &old_prot);
    WriteProcessMemory(hProcess, remote_ntdll + export_adress, &export_directory, sizeof(IMAGE_EXPORT_DIRECTORY), &readed);
    VirtualProtectEx(hProcess, remote_ntdll + export_adress, sizeof(IMAGE_EXPORT_DIRECTORY), old_prot, &old_prot);
}

I confirmed that my Windows 10 version works fine.

cheers!

ScyllaHide_x86.zip 307.15 kB · 16 downloads

I don't know coding and understand this code but I see they import some api and try to protect one function which I was protected in my old loader at that times because they have src code of code and can devirtualized with it.

I mean after virtualized some function they still virtualized the part of code.

@boot would yuo like send one example of vmp latest  version with api redirection for the vb please ? 

Mazotoa daholy😁 TP-Mada.

 

Edited by TRISTAN Pro
Edit some word
  • Like 2
Posted
On 7/8/2024 at 12:08 PM, boot said:

The revised and recompiled complete version is now uploaded as follows, and has been tested to be effective in Win7/10/11 x64.

I tried to rebuild ScyllaHide using some old versions of project code to make it compatible with Windows XP 32-bit/x86. I think XP is a classic OS...

  • Like 1
Keichi Sakamoto
Posted

Hey, I'm new to this. I'm testing this with vmprotected game, it does not show debugger present error but it shows `File corrupted! This program has been manipulated and maybe it's infected by a Virus or cracked. This file won't work anymore` anyone have similar issue?

  • Like 1
Posted (edited)

O~~K..

VMProtect has been updated now!

The latest version changed to find the "wine_get_host_version" function instead of "wine_get_version".

:D

Edited by karan
  • Like 3
  • 3 weeks later...
Sean the hard worker
Posted
On 7/8/2024 at 1:08 PM, boot said:

I have already conducted testing before, and if you compile the 32-bit plugin according to the original source code provided here (https://bbs.kanxue.com/thread-282244.htm). 

Original 32-bit (Imperfect Version).zip
This plugin is effective on Win7 x64 SP1; But it fails in Win10/11 x64. 
e.g.

VMP_3.8.7_x86_32-bit.vmp.exe 856 kB · 12 downloads

  • Win7 x64 SP1 √
  • Win10 x64 ×
  • Win11 x64 ×

By recompiling the 32-bit plugin according to the modified code provided by karan, the above issue has been resolved.

The revised and recompiled complete version is now uploaded as follows, and has been tested to be effective in Win7/10/11 x64.

ScyllaHide_2024_x86_x64_v0.002.zip 1.77 MB · 52 downloads

@boot View this video.

Can I debug the vmprotected .net executable with your one?

Regards.

sean.

  • Like 1
  • 2 months later...
fReestYler
Posted (edited)

@karan Could you help me compile a 32 bit unpackme with kernel + user mode ?

(for the latest version (3.9.1) if it is possible !)

Just want to try it with SharpOD v0.6e !

Thanks

Edited by fReestYler
  • Like 1
Posted (edited)
On 10/23/2024 at 11:54 PM, fReestYler said:

@karan Could you help me compile a 32 bit unpackme with kernel + user mode ?

(for the latest version (3.9.1) if it is possible !)

Just want to try it with SharpOD v0.6e !

Thanks

Sharp OD is no longer bypassed, I think that have to use TitanHide now.

and, Bypassing Anti-Debug using wine-related functions also no longer works.

 

Edited by karan
  • Like 1
jackyjask
Posted

you could build up ScyllaHide plugin with addons mentioned in this topic

it does the job

 

  • Like 1

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...