#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(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(jump_dest) - reinterpret_cast(address) - 5; if (offset < INT_MIN || offset > INT_MAX) return false; uint8_t buff[5]; buff[0] = 0xE9; *(reinterpret_cast(buff + 1)) = static_cast(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(memory) - reinterpret_cast(address) - 6; if (offset < INT_MIN || offset > INT_MAX) return false; #else // offset is absolute uint32_t offset = reinterpret_cast(memory); #endif uint8_t buff[6]; buff[0] = 0xFF; buff[1] = 0x25; *(reinterpret_cast(buff + 2)) = static_cast(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(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(pDst + ii.dwRelOffset) = static_cast(pSrc + *reinterpret_cast(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(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(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(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(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(api_); if (*p == 0xE9 && *reinterpret_cast(p + 1) == crc_) { // put original bytes back WriteProcessMemory(GetCurrentProcess(), api_, reinterpret_cast(old_handler_) + size_ + 5, size_, NULL); VirtualFree(old_handler_, 0, MEM_RELEASE); } else { // API hooked by another code WriteProcessMemory(GetCurrentProcess(), static_cast(old_handler_) + size_ * 2 + 5 + 6, &old_handler_, sizeof(old_handler_), NULL); } // cleanup size_ = 0; api_ = old_handler_ = NULL; } #endif