1 способ:
Здесь будет демонстрироваться 1 из путей обхода проверки CRC в VMProtect 3.X
Таргет напичкан различными анти-дебаг триками, и при срабатывании одного из них отладчик сообщает нам о каком то непонятном исключении:
Исключение происходит во время трассировки кода, это значит что оно срабатывает из другого потока. Следуя логике ставим бряк на CreateThread и выходим на код создающий исключение при срабатывании одного из анти-дебаг трика:
Автор этого байпассми сказал что в программе используется пользовательская ВМ. Что это значит? Это значит что для каждого вызова (call) есть свой хендлер, если мы протрассируем код из скрина выше то наткнёмся на тот самый хендлер:
Теперь наша задача сделать патч всего этого дела. Мешают сделать патч здесь 2 вещи:
1) Проверка CRC от VMProtect
2) Хук на NtProtectVirtualMemory для выдачи прав на запись региону памяти.
Начнём с первого пункта. Все ведь знают что винда при загрузке длл из таблицы импорта сначала ищет её в папке с exe? Будем использовать этот стандартный метод атаки (с его помощью кстати часто повышают привилегии в UNIX системе). Смотрим таблицу импорта и видим дллку которая вообще не используется в программе вместе с импортом:
Создаём проект в визуалке и добавляем эту функцию в экспорт:
#define DLL_EXPORT extern "C" __declspec( dllexport )
DLL_EXPORT BOOL WTSSendMessageW(
HANDLE hServer,
DWORD SessionId,
LPWSTR pTitle,
DWORD TitleLength,
LPWSTR pMessage,
DWORD MessageLength,
DWORD Style,
DWORD Timeout,
DWORD* pResponse,
BOOL bWait
)
{
return TRUE;
}
Компилируем длл с именем "WTSAPI32.dll" и суём её в папку с exe. Теперь винда будет грузить нашу длл вместо оригинальной и мы можем прописать в ней всё что душе угодно.
Теперь второй пункт, автор хукнул NtProtectVirtualMemory что б нам жизнь малиной не казалась. Обходим это через шеллкод, ниже код из нашей длл-заглушки:
int StartRoutine()
{
HMODULE main_module = GetModuleHandleA( 0 );
if ( main_module )
{
//
// Ждём распаковки оригинального кода программы
//
uintptr_t patch_addr = (uintptr_t)main_module + 0x7179;
while ( *(WORD*)( patch_addr ) != 0x50FF )
{
Sleep( 100 );
}
//
// Ждём установку хука
//
FARPROC nt_protect = GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "NtProtectVirtualMemory" );
while ( *(BYTE*)nt_protect == 0xE9 )
{
Sleep( 100 );
}
Sleep( 500 ); // Дополнительный слип так как триггерится CRC VMProtect'a если патчить сразу после распаковки кода
//
// Шеллкод для вызова NtProtectVirtualMemory
//
unsigned char shell_syscall[] = {
0xB8, 0x50, 0x00, 0x00, 0x00, // mov eax, number ($+0x00)
0xBA, 0x40, 0x8D, 0x70, 0x77, // mov edx, Wow64Transition ($+0x05)
0xFF, 0xD2, // call edx ($+0x0A)
0xC2, 0x14, 0x00 // ret 0x14 ($+0x0C)
};
//
// Заполняем шеллкод нужными данными
//
uintptr_t shell_mem = (uintptr_t)VirtualAlloc( 0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
memcpy( (void*)shell_mem, shell_syscall, sizeof( shell_syscall ) );
*(uint32_t*)( shell_mem + 0x1 ) = GetSystemNumber( "NtProtectVirtualMemory" );
*(uintptr_t*)( shell_mem + 0x6 ) = *(uintptr_t*)( GetProcAddress( GetModuleHandleA( "ntdll.dll" ), "Wow64Transition" ) );
t_NtProtectVirtualMemory m_NtProtectVirtualMemory = (t_NtProtectVirtualMemory)shell_mem;
//
// Выдаём региону памяти права на запись через шеллкод
//
PVOID BaseAddress = (PVOID)patch_addr;
SIZE_T RegionSize = 0x1000;
ULONG oldProtect = 0;
m_NtProtectVirtualMemory( NtCurrentProcess, &BaseAddress, &RegionSize, PAGE_EXECUTE_READWRITE, &oldProtect );
//
// Патч vm_call
//
memset( (void*)patch_addr, 0x90, 3 );
}
return 0;
}
Теперь отрубаем все плагины и запускаем софт в отладчике:
2 способ:
=========================================================
Все опыты проводились на Windows 10 x64 1909 (Сборка ОС 18363.1016) – процессор Intel
Подопытный: C++ x64 PE-файл, максимальный пресет защиты от протектора
TitanHide – не использовался
=========================================================
Давайте разбираться, что же такого завезли нам в новой версии.
Для начала подготовим ScyllaHide:
Далее, в настройках ядра самого отладчика(x64dbg) нужно установить “Тип точек останова по умолчанию” на “UD2”:
Всё дело в том, что UD2 меньше обнаруживается самим VMProtect, пользуйтесь) Наверняка, если хотите, подгрузите TitanHide, но мне не пригодился.
Дело в том, что как вы могли заметить, при отладке какого-либо приложения, защищенного новой версией протектора, вы, как в старых версиях, не получаете 3-nop-исключения. Всё это происходит из-за одного трюка вмпшки, который мы с вами сейчас будем обходить. Плагины тоже не могут это обойти, т.к. вызов происходит непосредственно через SYSCALL.
Ищем все SYSCALL’s путем поиска паттерна “0F0568”, где “0F05” – сама инструкция SYSCALL, а “68” – нужен для отсеивания лишних SYSCALL’s.
Далее, устанавливаем на все наши найденные инструкции брейкпоинты.
И нажимаем “Выполнить” до тех пор, пока в регистре RAX не будет значение 0xD.
Почему 0xD? Потому что это и есть новый трюк протектора. Дело в том, что SYSCALL 0xD, это вызов WinAPI-функции “NtSetInformationThread” с константой, которая думаю вам всё скажет своим названием - “ThreadHideFromDebugger”(или же 0x11, значение лежит в регистре RDX):
Далее меняем 0x11, в регистре RDX на любое другое, но только не 0x11, будете получать результат выполнения функции в регистре RAX с ошибкой – не обращайте внимание на нее, всё хорошо ?
УДАЛЯЕМ ВСЕ ТОЧКИ ОСТАНОВА, и только после этого нажимаем “Выполнить”
Вуаля, что мы видим? Наши заветные NOP-исключения:
Секция с кодом программы расшифрована. Далее просто ставим аппаратный бряк на ваше OEP, как его искать, думаю вы знаете) Ну или WinAPI-шные функции брякайте. GetCommandLineA - ваш друг