Jump to content
Tuts 4 You

VMProtect Heaven's Gate Anti-Debug Bypass to VectorHandler


karan

Recommended Posts

kuazi GA
8 hours ago, The Binary Expert said:

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

 

屏幕截图-146.png

 

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

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

问候。

肖恩。

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

  • Thanks 1
  • Haha 1
Link to comment
Share on other sites

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
Link to comment
Share on other sites

The Binary Expert
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.

Link to comment
Share on other sites

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
Link to comment
Share on other sites

The Binary Expert
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.

Link to comment
Share on other sites

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
Link to comment
Share on other sites

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
Link to comment
Share on other sites

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
Link to comment
Share on other sites

Progman

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
Link to comment
Share on other sites

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
Link to comment
Share on other sites

fReestYler

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

Thanks

Edited by fReestYler
  • Like 3
Link to comment
Share on other sites

jackyjask

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

  • Like 1
Link to comment
Share on other sites

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
Link to comment
Share on other sites

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
Link to comment
Share on other sites

jackyjask
14 hours ago, RADIOX said:

he included them in his repostery in GitHub.

yes, exactly, good pointer, found and used

 

  • Like 1
Link to comment
Share on other sites

TRISTAN Pro
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
Link to comment
Share on other sites

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
Link to comment
Share on other sites

Keichi Sakamoto

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
Link to comment
Share on other sites

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
Link to comment
Share on other sites

  • 3 weeks later...
The Binary Expert
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.

Link to comment
Share on other sites

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...