VMProtect/runtime/hook_manager.cc

738 lines
18 KiB
C++

#ifdef WIN_DRIVER
#else
#include "common.h"
#include "objects.h"
#include "utils.h"
#include "hook_manager.h"
/**
* HookManager
*/
HookManager::HookManager()
: update_count_(0)
{
}
void *HookManager::HookAPI(HMODULE dll, const char *api_name, void *handler, bool show_error, void **result)
{
void *res = NULL;
void *api_address = InternalGetProcAddress(dll, api_name);
if (api_address) {
Begin();
HookedAPI *hooked_api = new HookedAPI();
if (hooked_api->Hook(api_address, handler, result)) {
api_list_.push_back(hooked_api);
res = hooked_api->old_handler();
} else {
delete hooked_api;
}
End();
}
if (!res && show_error) {
wchar_t error[512] = {};
{
// string "Error at hooking API \"%S\"\n"
wchar_t str[] = { L'E', L'r', L'r', L'o', L'r', L' ', L'a', L't', L' ', L'h', L'o', L'o', L'k', L'i', L'n', L'g', L' ', L'A', L'P', L'I', L' ', L'\"', L'%', L'S', L'\"', L'\n', 0 };
swprintf_s(error, str, api_name);
}
if (api_address){
int n = 32;
wchar_t line[100] = {};
{
// string "Dumping first %d bytes:\n"
wchar_t str[] = { L'D', L'u', L'm', L'p', L'i', L'n', L'g', L' ', L'f', L'i', L'r', L's', L't', L' ', L'%', L'd', L' ', L'b', L'y', L't', L'e', L's', L':', L'\n', 0 };
swprintf_s(line, str, n);
}
wcscat_s(error, line);
for (int i = 0; i < n; i++) {
wchar_t str[] = { L'%', L'0', L'2', L'X', (i % 16 == 15) ? L'\n' : L' ', 0};
swprintf_s(line, str, reinterpret_cast<uint8_t *>(api_address)[i]);
wcscat_s(error, line);
}
}
ShowMessage(error);
::TerminateProcess(::GetCurrentProcess(), 0xDEADC0DE);
}
return res;
}
bool HookManager::UnhookAPI(void *handler)
{
if (!handler)
return false;
bool res = false;
Begin();
for (size_t i = 0; i < api_list_.size(); i++) {
HookedAPI *hooked_api = api_list_[i];
if (hooked_api->old_handler() == handler) {
api_list_.erase(i);
hooked_api->Unhook();
delete hooked_api;
res = true;
break;
}
}
End();
return res;
}
void HookManager::GetThreads()
{
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (h == INVALID_HANDLE_VALUE)
return;
THREADENTRY32 thread_entry;
thread_entry.dwSize = sizeof(thread_entry);
if (Thread32First(h, &thread_entry)) {
DWORD process_id = GetCurrentProcessId();
DWORD thread_id = GetCurrentThreadId();
do {
if (thread_entry.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(thread_entry.th32OwnerProcessID))
{
if (thread_entry.th32OwnerProcessID == process_id && thread_entry.th32ThreadID != thread_id) {
HANDLE thread = ::OpenThread(THREAD_SUSPEND_RESUME, FALSE, thread_entry.th32ThreadID);
if (thread)
thread_list_.push_back(thread);
}
}
thread_entry.dwSize = sizeof(thread_entry);
} while (Thread32Next(h, &thread_entry));
}
::CloseHandle(h);
}
void HookManager::SuspendThreads()
{
for (size_t i = 0; i < thread_list_.size(); i++) {
::SuspendThread(thread_list_[i]);
}
}
void HookManager::ResumeThreads()
{
for (size_t i = 0; i < thread_list_.size(); i++) {
::ResumeThread(thread_list_[i]);
}
}
void HookManager::FreeThreads()
{
for (size_t i = 0; i < thread_list_.size(); i++) {
::CloseHandle(thread_list_[i]);
}
thread_list_.clear();
}
void HookManager::Begin()
{
if (!update_count_) {
GetThreads();
SuspendThreads();
}
update_count_++;
}
void HookManager::End()
{
update_count_--;
if (!update_count_) {
ResumeThreads();
FreeThreads();
}
}
/**
* HookedAPI
*/
static bool PutJump(uint8_t *address, const uint8_t *jump_dest)
{
INT64 offset = reinterpret_cast<uint64_t>(jump_dest) - reinterpret_cast<uint64_t>(address) - 5;
if (offset < INT_MIN || offset > INT_MAX)
return false;
uint8_t buff[5];
buff[0] = 0xE9;
*(reinterpret_cast<uint32_t *>(buff + 1)) = static_cast<uint32_t>(offset);
return FALSE != WriteProcessMemory(GetCurrentProcess(), address, buff, sizeof(buff), NULL);
}
static bool PutJumpMem(uint8_t *address, const uint8_t *memory)
{
#ifdef _WIN64
// offset is relative
INT64 offset = reinterpret_cast<uint64_t>(memory) - reinterpret_cast<uint64_t>(address) - 6;
if (offset < INT_MIN || offset > INT_MAX)
return false;
#else
// offset is absolute
uint32_t offset = reinterpret_cast<uint32_t>(memory);
#endif
uint8_t buff[6];
buff[0] = 0xFF;
buff[1] = 0x25;
*(reinterpret_cast<uint32_t *>(buff + 2)) = static_cast<uint32_t>(offset);
return FALSE != WriteProcessMemory(GetCurrentProcess(), address, buff, sizeof(buff), NULL);
}
enum eCommandType
{
CT_UNKNOWN = 0,
CT_JMP,
CT_RET
};
struct sCommandInfo
{
DWORD dwSize;
DWORD dwRelOffset;
eCommandType cmdType;
};
#define C_66 0x00000001 // 66-prefix
#define C_67 0x00000002 // 67-prefix
#define C_LOCK 0x00000004 // lock
#define C_REP 0x00000008 // repz/repnz
#define C_SEG 0x00000010 // seg-prefix
#define C_OPCODE2 0x00000020 // 2nd opcode present (1st == 0F)
#define C_MODRM 0x00000040 // modrm present
#define C_SIB 0x00000080 // sib present
bool GetInstructionSize(const byte *pOpCode, sCommandInfo &info)
{
const byte *opcode = pOpCode;
// BYTE disasm_seg = 0; // CS DS ES SS FS GS
// BYTE disasm_rep = 0; // REPZ/REPNZ
BYTE disasm_opcode = 0; // opcode
BYTE disasm_opcode2 = 0; // used when opcode == 0F
BYTE disasm_modrm = 0; // modxxxrm
BYTE disasm_sib = 0; // scale-index-base
#ifdef _WIN64
BYTE disasm_preffix = {0};
#endif
// DWORD disasm_len = 0; // 0 if error
DWORD disasm_flag = 0; // C_xxx
DWORD disasm_memsize = 0; // value = disasm_mem
DWORD disasm_datasize = 0; // value = disasm_data
DWORD disasm_defdata = 4; // == C_66 ? 2 : 4
DWORD disasm_defmem = 4; // == C_67 ? 2 : 4
DWORD disasm_offset = 0;
BYTE mod = 0;
BYTE rm = 0;
if (! pOpCode) return false;
info.dwSize = 0;
info.dwRelOffset = 0;
info.cmdType = CT_UNKNOWN;
RETRY:
disasm_opcode = *opcode ++;
switch (disasm_opcode)
{
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x08: case 0x09: case 0x0A: case 0x0B:
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x18: case 0x19: case 0x1A: case 0x1B:
case 0x20: case 0x21: case 0x22: case 0x23:
case 0x28: case 0x29: case 0x2A: case 0x2B:
case 0x30: case 0x31: case 0x32: case 0x33:
case 0x38: case 0x39: case 0x3A: case 0x3B:
case 0x84: case 0x85: case 0x86: case 0x87:
case 0x88: case 0x89: case 0x8A: case 0x8B:
case 0x8C: case 0x8D: case 0x8E:
case 0xD0: case 0xD1: case 0xD2: case 0xD3:
case 0xD8: case 0xD9: case 0xDA: case 0xDB:
case 0xDC: case 0xDD: case 0xDE: case 0xDF:
disasm_flag |= C_MODRM;
break;
case 0x04: case 0x05: case 0x0C: case 0x0D:
case 0x14: case 0x15: case 0x1C: case 0x1D:
case 0x24: case 0x25: case 0x2C: case 0x2D:
case 0x34: case 0x35: case 0x3C: case 0x3D:
case 0xA8: case 0xA9:
if (disasm_opcode & 1)
{
disasm_datasize += disasm_defdata;
}
else
{
disasm_datasize ++;
}
break;
case 0xA0: case 0xA1: case 0xA2: case 0xA3:
if (disasm_opcode & 1)
{
#ifdef _WIN64
if (!(disasm_flag & C_66) && (disasm_preffix & 8))
{
disasm_datasize += 8;
}
else
#endif
{
disasm_datasize += disasm_defdata;
}
}
else
{
disasm_datasize ++;
}
break;
case 0x06: case 0x07: case 0x0E:
case 0x16: case 0x17: case 0x1E: case 0x1F:
case 0x27: case 0x2F:
case 0x37: case 0x3F:
case 0x60: case 0x61:
case 0xCE:
#ifdef _WIN64
return false;
#else
break;
#endif
case 0x26: case 0x2E:
case 0x36: case 0x3E:
case 0x64: case 0x65:
disasm_flag |= C_SEG;
goto RETRY;
case 0x40: case 0x41: case 0x42: case 0x43:
case 0x44: case 0x45: case 0x46: case 0x47:
case 0x48: case 0x49: case 0x4A: case 0x4B:
case 0x4C: case 0x4D: case 0x4E: case 0x4F:
#ifdef _WIN64
disasm_preffix = disasm_opcode;
goto RETRY;
#else
break;
#endif
case 0x50: case 0x51: case 0x52: case 0x53:
case 0x54: case 0x55: case 0x56: case 0x57:
case 0x58: case 0x59: case 0x5A: case 0x5B:
case 0x5C: case 0x5D: case 0x5E: case 0x5F:
case 0x6C: case 0x6D: case 0x6E: case 0x6F:
case 0x90: case 0x91: case 0x92: case 0x93:
case 0x94: case 0x95: case 0x96: case 0x97:
case 0x98: case 0x99: case 0x9B: case 0x9C:
case 0x9D: case 0x9E: case 0x9F:
case 0xA4: case 0xA5: case 0xA6: case 0xA7:
case 0xAA: case 0xAB: case 0xAC: case 0xAD:
case 0xAE: case 0xAF:
case 0xC3: case 0xC9: case 0xCB: case 0xCC:
case 0xCF:
case 0xD7:
case 0xEC: case 0xED: case 0xEE: case 0xEF:
case 0xF4: case 0xF5: case 0xF8: case 0xF9:
case 0xFA: case 0xFB: case 0xFC: case 0xFD:
break;
case 0x62:
case 0xC4: case 0xC5:
#ifdef _WIN64
return false;
#else
disasm_flag |= C_MODRM;
break;
#endif
case 0x66:
disasm_flag |= C_66;
disasm_defdata = 2;
goto RETRY;
case 0x67:
disasm_flag |= C_67;
disasm_defmem = 2;
goto RETRY;
case 0x69:
case 0x81:
case 0xC1:
disasm_flag |= C_MODRM;
disasm_datasize += disasm_defdata;
break;
case 0x6A:
case 0x70: case 0x71: case 0x72: case 0x73:
case 0x74: case 0x75: case 0x76: case 0x77:
case 0x78: case 0x79: case 0x7A: case 0x7B:
case 0x7C: case 0x7D: case 0x7E: case 0x7F:
case 0xB0: case 0xB1: case 0xB2: case 0xB3:
case 0xB4: case 0xB5: case 0xB6: case 0xB7:
case 0xCD:
case 0xE0: case 0xE1: case 0xE2: case 0xE3:
case 0xE4: case 0xE5: case 0xE6: case 0xE7:
disasm_datasize ++;
break;
case 0xEB:
info.cmdType = CT_JMP;
disasm_offset = (DWORD) (opcode - pOpCode);
disasm_datasize ++;
break;
case 0x8F:
if ((*opcode >> 3) & 7)
return false;
disasm_flag |= C_MODRM;
break;
case 0x9A: case 0xEA:
#ifdef _WIN64
return false;
#else
disasm_datasize += (disasm_defdata + 2);
break;
#endif
case 0x68:
disasm_datasize += disasm_defdata;
break;
case 0xB8: case 0xB9: case 0xBA: case 0xBB:
case 0xBC: case 0xBD: case 0xBE: case 0xBF:
#ifdef _WIN64
if (!(disasm_flag & C_66) && (disasm_preffix & 8))
disasm_datasize += 8;
else
#endif
disasm_datasize += disasm_defdata;
break;
//case 0x68:
//case 0xB8: case 0xB9: case 0xBA: case 0xBB:
//case 0xBC: case 0xBD: case 0xBE: case 0xBF:
// disasm_datasize += disasm_defdata;
// break;
case 0xE8: case 0xE9:
if (disasm_defdata == 2)
return false;
disasm_offset = (DWORD) (opcode - pOpCode);
disasm_datasize += disasm_defdata;
break;
case 0x6B:
case 0x80: case 0x82: case 0x83:
case 0xC0:
disasm_flag |= C_MODRM;
disasm_datasize ++;
break;
case 0xC2:
disasm_datasize += 2;
break;
case 0xC6: case 0xC7:
if ((*opcode >> 3) & 7)
return false;
disasm_flag |= C_MODRM;
if (disasm_opcode & 1)
{
disasm_datasize += disasm_defdata;
}
else
{
disasm_datasize ++;
}
break;
case 0xC8:
disasm_datasize += 3;
break;
case 0xCA:
disasm_datasize += 2;
break;
case 0xD4: case 0xD5:
#ifdef _WIN64
return false;
#else
disasm_datasize ++;
break;
#endif
case 0xF0:
disasm_flag |= C_LOCK;
goto RETRY;
case 0xF2: case 0xF3:
disasm_flag |= C_REP;
goto RETRY;
case 0xF6: case 0xF7:
disasm_flag |= C_MODRM;
if (((*opcode >> 3) & 7) < 2)
{
if (disasm_opcode & 1)
{
disasm_datasize += disasm_defdata;
}
else
{
disasm_datasize ++;
}
}
break;
case 0xFE:
if (((*opcode >> 3) & 7) > 1)
return false;
disasm_flag |= C_MODRM;
break;
case 0xFF:
if (((*opcode >> 3) & 7) == 7)
return false;
disasm_flag |= C_MODRM;
break;
case 0x0F:
disasm_flag |= C_OPCODE2;
disasm_opcode2 = *opcode ++;
switch (disasm_opcode2)
{
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x90: case 0x91: case 0x92: case 0x93:
case 0x94: case 0x95: case 0x96: case 0x97:
case 0x98: case 0x99: case 0x9A: case 0x9B:
case 0x9C: case 0x9D: case 0x9E: case 0x9F:
case 0xA3: case 0xA5: case 0xAB: case 0xAD:
case 0xAF:
case 0xB0: case 0xB1: case 0xB2: case 0xB3:
case 0xB4: case 0xB5: case 0xB6: case 0xB7:
case 0xBB:
case 0xBC: case 0xBD: case 0xBE: case 0xBF:
case 0xC0: case 0xC1:
disasm_flag |= C_MODRM;
break;
case 0x06:
case 0x08: case 0x09: case 0x0A: case 0x0B:
case 0xA0: case 0xA1: case 0xA2: case 0xA8:
case 0xA9: case 0xAA:
case 0xC8: case 0xC9: case 0xCA: case 0xCB:
case 0xCC: case 0xCD: case 0xCE: case 0xCF:
break;
case 0x80: case 0x81: case 0x82: case 0x83:
case 0x84: case 0x85: case 0x86: case 0x87:
case 0x88: case 0x89: case 0x8A: case 0x8B:
case 0x8C: case 0x8D: case 0x8E: case 0x8F:
if (disasm_defdata == 2)
return false;
disasm_offset = (DWORD) (opcode - pOpCode);
disasm_datasize += disasm_defdata;
break;
case 0xA4: case 0xAC:
case 0xBA:
disasm_datasize ++;
disasm_flag |= C_MODRM;
break;
default:
return false;
}
default:
return false;
}
if (disasm_flag & C_MODRM)
{
disasm_modrm = *opcode ++;
mod = disasm_modrm & 0xC0;
rm = disasm_modrm & 0x07;
if (mod != 0xC0)
{
if (mod == 0x40) disasm_memsize ++;
if (mod == 0x80) disasm_memsize += disasm_defmem;
if (disasm_defmem == 2) // modrm16
{
if ((mod == 0x00) && (rm == 0x06))
{
disasm_memsize += 2;
}
}
else // modrm32
{
if (rm == 0x04)
{
disasm_flag |= C_SIB;
disasm_sib = *opcode ++;
rm = disasm_sib & 0x07;
}
if ((mod == 0x00) && (rm == 0x05))
{
#ifdef _WIN64
if (!(disasm_flag & C_SIB))
disasm_offset = (DWORD) (opcode - pOpCode);
#endif
disasm_memsize += 4;
}
}
}
}
info.dwSize =(DWORD) (opcode - pOpCode) + disasm_memsize + disasm_datasize;
info.dwRelOffset = disasm_offset;
return true;
}
size_t MoveCode(byte *&pSrcOriginal, byte *pDst, size_t nNeedBytes)
{
size_t nBytes = 0;
byte *pSrc = pSrcOriginal;
sCommandInfo ii;
while (nBytes < nNeedBytes)
{
if (!GetInstructionSize(pSrc, ii))
return 0; // error
if (nBytes == 0 && ii.cmdType == CT_JMP && ii.dwSize - ii.dwRelOffset == 1)
{ // this is a short jump and we need to follow it
ptrdiff_t offset = static_cast<char>(pSrc[ii.dwSize - 1]);
pSrcOriginal += offset + ii.dwSize; // skip JMP XX
pSrc = pSrcOriginal;
continue;
}
memcpy(pDst, pSrc, ii.dwSize); // copy original bytes
if (ii.dwRelOffset)
*reinterpret_cast<int32_t*>(pDst + ii.dwRelOffset) = static_cast<int32_t>(pSrc + *reinterpret_cast<int32_t*>(pSrc + ii.dwRelOffset) - pDst);
pSrc += ii.dwSize;
pDst += ii.dwSize;
nBytes += ii.dwSize;
}
return nBytes; // return number of bytes copied
}
size_t HookedAPI::page_size_ = 0;
HookedAPI::HookedAPI()
: size_(0), api_(NULL), old_handler_(NULL), crc_(0)
{
}
bool HookedAPI::Hook(void *api, void *handler, void **result)
{
if (!page_size_) {
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
page_size_ = (system_info.dwPageSize > 2048) ? system_info.dwPageSize : 2048;
}
uint8_t *p = static_cast<uint8_t *>(api);
// alloc virtual memory
#ifdef _WIN64
for (size_t i = 0; i < 0x70000000; i += page_size_) {
old_handler_ = ::VirtualAlloc(p + i, page_size_, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (old_handler_)
break;
}
#else
old_handler_ = ::VirtualAlloc(NULL, page_size_, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
#endif
if (!old_handler_)
return false;
uint8_t *memory = static_cast<uint8_t *>(old_handler_);
if (result)
*result = memory;
// structure of the block:
// some api commands
// jmp api + 5
// original commands
// jmp [handler]
// handler
// copy api code to the new place
size_ = MoveCode(p, memory, 5);
if (!size_)
return false;
// add JMP command
if (!PutJump(memory + size_, p + size_))
return false;
// copy original commands
memcpy(memory + size_ + 5, p, size_);
// jmp [handler]
uint8_t *jump = memory + size_ * 2 + 5;
if (!PutJumpMem(jump, jump + 6))
return false;
// put handler to [handler]
*(reinterpret_cast<void **>(jump + 6)) = handler;
// put JMP at the beginning of the API
if (!PutJump(p, jump))
return false;
api_ = p; // this line should be below MoveCode, because pAPI can be corrected by it
crc_ = *reinterpret_cast<uint32_t *>(p + 1);
DWORD old_protect;
VirtualProtect(old_handler_, page_size_, PAGE_EXECUTE_READ, &old_protect);
return true;
}
void HookedAPI::Unhook()
{
if (!api_)
return;
// check CRC
uint8_t *p = static_cast<uint8_t *>(api_);
if (*p == 0xE9 && *reinterpret_cast<uint32_t *>(p + 1) == crc_) {
// put original bytes back
WriteProcessMemory(GetCurrentProcess(), api_, reinterpret_cast<uint8_t *>(old_handler_) + size_ + 5, size_, NULL);
VirtualFree(old_handler_, 0, MEM_RELEASE);
} else {
// API hooked by another code
WriteProcessMemory(GetCurrentProcess(), static_cast<uint8_t *>(old_handler_) + size_ * 2 + 5 + 6, &old_handler_, sizeof(old_handler_), NULL);
}
// cleanup
size_ = 0;
api_ = old_handler_ = NULL;
}
#endif