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