// Checks if the thread is executing in a context of PatchGuard. Returns an // address to overwrite a guest IP if the is the case. Otherwise, returns 0. _Use_decl_annotations_ ULONG_PTR GMonCheckExecutionContext(const GpRegisters *registers, ULONG_PTR guest_ip) { const auto pg_context = reinterpret_cast<PgContext *>(GMonIsPgExcutionContext(registers)); if (!pg_context) { return 0; } HYPERPLATFORM_LOG_INFO_SAFE("PatchGuard Context = %p", pg_context); // An epilogue of Pg_xSelfValidation(). Now the thread can be executing a DPC // function Pg_xSelfValidation(). In that case, we need to return from it // safely. // // 48 8B C3 mov rax, rbx ; Windows 7 // 48 8B C7 mov rax, rdi ; Windows 8.1 and 10 // 4C 8D 9C 24 C0 02 00 00 lea r11, [rsp+2C0h] // 49 8B 5B 30 mov rbx, [r11+30h] // 49 8B 73 38 mov rsi, [r11+38h] // 49 8B 7B 40 mov rdi, [r11+40h] // 49 8B E3 mov rsp, r11 // 41 5F pop r15 // 41 5E pop r14 // 41 5D pop r13 // 41 5C pop r12 // 5D pop rbp // C3 retn static const UCHAR kDpcEpiloguePattern8_10[] = { 0x48, 0x8B, 0xC7, 0x4C, 0x8D, 0x9C, 0x24, 0xC0, 0x02, 0x00, 0x00, 0x49, 0x8B, 0x5B, 0x30, 0x49, 0x8B, 0x73, 0x38, 0x49, 0x8B, 0x7B, 0x40, 0x49, 0x8B, 0xE3, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x5D, 0xC3, }; static const UCHAR kDpcEpiloguePattern7[] = { 0x48, 0x8B, 0xC3, 0x4C, 0x8D, 0x9C, 0x24, 0xC0, 0x02, 0x00, 0x00, 0x49, 0x8B, 0x5B, 0x30, 0x49, 0x8B, 0x73, 0x38, 0x49, 0x8B, 0x7B, 0x40, 0x49, 0x8B, 0xE3, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x5D, 0xC3, }; // Try Win8 and 10 pattern auto epilogue_address = UtilMemMem(reinterpret_cast<void *>(guest_ip), 0x400, kDpcEpiloguePattern8_10, sizeof(kDpcEpiloguePattern8_10)); if (!epilogue_address) { // Try Win7 pattern if failed epilogue_address = UtilMemMem(reinterpret_cast<void *>(guest_ip), 0x400, kDpcEpiloguePattern7, sizeof(kDpcEpiloguePattern7)); } HYPERPLATFORM_LOG_INFO_SAFE("DPC Epilogue Address = %p", epilogue_address); if (epilogue_address) { // Executing Pg_xSelfValidation(). Set a return address to its epilogue and // neutralize the context. GMonpNeutralizePgContextForDpc(pg_context); return reinterpret_cast<ULONG_PTR>(epilogue_address); } else { // Not. Likely to be in a main validation routine. Let the thread return to // AsmWaitForever(). return reinterpret_cast<ULONG_PTR>(AsmWaitForever); } }
// Locate MmPfnDatabase _Use_decl_annotations_ static NTSTATUS MmonpInitializeMmPfnDatabase() { PAGED_CODE(); RTL_OSVERSIONINFOW os_version = {}; auto status = RtlGetVersion(&os_version); if (!NT_SUCCESS(status)) { return status; } // Set appropriate patterns and based on an OS version struct MmPfnDatabaseSearchPattern { const UCHAR *bytes; SIZE_T bytes_size; bool hard_coded; }; MmPfnDatabaseSearchPattern patterns[2] = {}; if (IsX64()) { // Win 10 build 14316 is the first version implements randomized page tables if (os_version.dwMajorVersion < 10 || os_version.dwBuildNumber < 14316) { // PFN database is at the constant location on older x64 Windows g_mmonp_MmPfnDatabase = reinterpret_cast<void *>(0xfffffa8000000000); return STATUS_SUCCESS; } // Windows 10 x64 Build 14332+ static const UCHAR kPatternWin10x64[] = { 0x48, 0x8B, 0xC1, // mov rax, rcx 0x48, 0xC1, 0xE8, 0x0C, // shr rax, 0Ch 0x48, 0x8D, 0x14, 0x40, // lea rdx, [rax + rax * 2] 0x48, 0x03, 0xD2, // add rdx, rdx 0x48, 0xB8, // mov rax, 0FFFFFA8000000008h }; patterns[0].bytes = kPatternWin10x64; patterns[0].bytes_size = sizeof(kPatternWin10x64); patterns[0].hard_coded = true; } else { // x86 if (os_version.dwMajorVersion == 6 && os_version.dwMinorVersion == 1) { // Windows 7 (No PAE) static const UCHAR kPatternWin7[] = { 0x6B, 0xC0, 0x18, // imul eax, 18h 0x8B, 0x0D, // mov ecx, ds:_MmPfnDatabase }; // Windows 7 (PAE) static const UCHAR kPatternWin7Pae[] = { 0x6B, 0xC0, 0x1C, // imul eax, 1Ch 0x8B, 0x0D, // mov ecx, ds:_MmPfnDatabase }; if (UtilIsX86Pae()) { patterns[0].bytes = kPatternWin7Pae; patterns[0].bytes_size = sizeof(kPatternWin7Pae); patterns[0].hard_coded = false; } else { patterns[0].bytes = kPatternWin7; patterns[0].bytes_size = sizeof(kPatternWin7); patterns[0].hard_coded = false; } } else if ((os_version.dwMajorVersion == 6 && os_version.dwMinorVersion == 3) || (os_version.dwMajorVersion == 10 && os_version.dwMinorVersion == 0)) { // Windows 8.1 and 10 static const UCHAR kPatternWin81And10_0[] = { 0xC1, 0xF8, 0x0C, // sar eax, 0Ch 0xA1, // mov eax, ds:_MmPfnDatabase }; static const UCHAR kPatternWin81And10_1[] = { 0xC1, 0xE8, 0x0C, // shr eax, 0Ch 0xA1, // mov eax, ds:_MmPfnDatabase }; patterns[0].bytes = kPatternWin81And10_0; patterns[0].bytes_size = sizeof(kPatternWin81And10_0); patterns[0].hard_coded = false; patterns[1].bytes = kPatternWin81And10_1; patterns[1].bytes_size = sizeof(kPatternWin81And10_1); patterns[1].hard_coded = false; } else { // Unknown x86 OS version return STATUS_UNSUCCESSFUL; } } // Search the patterns from MmGetVirtualForPhysical const auto p_MmGetVirtualForPhysical = reinterpret_cast<UCHAR *>( UtilGetSystemProcAddress(L"MmGetVirtualForPhysical")); if (!p_MmGetVirtualForPhysical) { return STATUS_PROCEDURE_NOT_FOUND; } for (const auto &pattern : patterns) { if (!pattern.bytes) { break; // no more patterns } auto found = reinterpret_cast<UCHAR *>(UtilMemMem( p_MmGetVirtualForPhysical, 0x20, pattern.bytes, pattern.bytes_size)); if (!found) { continue; } // Get an address of PFN database found += pattern.bytes_size; if (pattern.hard_coded) { HYPERPLATFORM_LOG_DEBUG("Found a hard coded PFN database address at %p", found); g_mmonp_MmPfnDatabase = *reinterpret_cast<void **>(found); } else { HYPERPLATFORM_LOG_DEBUG("Found a reference to MmPfnDatabase at %p", found); const auto mmpfn_address = *reinterpret_cast<ULONG_PTR *>(found); g_mmonp_MmPfnDatabase = *reinterpret_cast<void **>(mmpfn_address); } // On Windows 10 RS, a value has 0x8. Delete it. g_mmonp_MmPfnDatabase = PAGE_ALIGN(g_mmonp_MmPfnDatabase); break; } HYPERPLATFORM_LOG_DEBUG("MmPfnDatabase = %p", g_mmonp_MmPfnDatabase); if (!g_mmonp_MmPfnDatabase) { return STATUS_UNSUCCESSFUL; } return STATUS_SUCCESS; }
// Locate MmPfnDatabase _Use_decl_annotations_ static NTSTATUS MmonpInitializeMmPfnDatabase() { PAGED_CODE(); if (IsX64()) { g_mmonp_MmPfnDatabase = reinterpret_cast<void *>(0xfffffa8000000000); } else { const auto p_MmGetVirtualForPhysical = reinterpret_cast<UCHAR *>( UtilGetSystemProcAddress(L"MmGetVirtualForPhysical")); if (!p_MmGetVirtualForPhysical) { return STATUS_PROCEDURE_NOT_FOUND; } RTL_OSVERSIONINFOW os_version = {}; auto status = RtlGetVersion(&os_version); if (!NT_SUCCESS(status)) { return status; } if (os_version.dwMajorVersion == 6 && os_version.dwMinorVersion == 1) { // Windows 7 (No PAE) // 6B C0 18 imul eax, 18h // 8B 0D 14 28 56 00 mov ecx, ds:_MmPfnDatabase static const UCHAR kPatternWin7[] = { 0x6B, 0xC0, 0x18, 0x8B, 0x0D, }; // Windows 7 (PAE) // 6B C0 1C imul eax, 1Ch // 8B 0D 14 28 56 00 mov ecx, ds:_MmPfnDatabase static const UCHAR kPatternWin7Pae[] = { 0x6B, 0xC0, 0x1C, 0x8B, 0x0D, }; const auto is_pae_enabled = Cr4{__readcr4()}.fields.pae; const auto pattern = (is_pae_enabled) ? kPatternWin7Pae : kPatternWin7; const auto size = (is_pae_enabled) ? sizeof(kPatternWin7Pae) : sizeof(kPatternWin7); auto found = reinterpret_cast<UCHAR *>( UtilMemMem(p_MmGetVirtualForPhysical, 0x20, pattern, size)); if (found) { found += size; const auto address = *reinterpret_cast<ULONG_PTR *>(found); g_mmonp_MmPfnDatabase = *reinterpret_cast<void **>(address); } } else if ((os_version.dwMajorVersion == 6 && os_version.dwMinorVersion == 3) || (os_version.dwMajorVersion == 10 && os_version.dwMinorVersion == 0)) { // Windows 8.1 and 10 // C1 F8 0C sar eax, 0Ch // A1 08 B7 62 00 mov eax, ds:_MmPfnDatabase static const UCHAR kPatternWin81And10[] = { 0xC1, 0xF8, 0x0C, 0xA1, }; auto found = reinterpret_cast<UCHAR *>( UtilMemMem(p_MmGetVirtualForPhysical, 0x20, kPatternWin81And10, sizeof(kPatternWin81And10))); if (found) { found += sizeof(kPatternWin81And10); const auto address = *reinterpret_cast<ULONG_PTR *>(found); g_mmonp_MmPfnDatabase = *reinterpret_cast<void **>(address); } } } return (g_mmonp_MmPfnDatabase) ? STATUS_SUCCESS : STATUS_PROCEDURE_NOT_FOUND; }
_Use_decl_annotations_ EXTERN_C EpilogueInfo FnparseGetEpilogueInfo(UCHAR *FunctionAddress) { PAGED_CODE(); // This function presumes byte codes of the function's epilogue from unwind // information, then searches it in the range of the function in order to // find epilogue code. Apart from that, it calculates unwind stack size from // unwind information too. UCHAR *base = nullptr; auto entry = FnparseLookupFunctionEntry( UtilDataToFp(reinterpret_cast<UCHAR *>(FunctionAddress)), reinterpret_cast<void **>(&base)); if (!entry) { return {}; } // Do not suppose anything has value in Flags for the sake of ease of // implementation and testing. auto unwind = reinterpret_cast<UNWIND_INFO *>(entry->UnwindInfoAddress + base); if (unwind->Flags != UNW_FLAG_NHANDLER) { return {}; } // Allocates a big enough memory to save epilogue byte code. auto epilogueBytesAllocationSize = unwind->SizeOfProlog + 1; // +1 for ret. auto epilogueBytesNaked = reinterpret_cast<BYTE *>(ExAllocatePoolWithTag( PagedPool, epilogueBytesAllocationSize, MEOW_POOL_TAG_NAME)); if (!epilogueBytesNaked) { return {}; } auto epilogueBytesCleaner = std::experimental::make_scope_exit([epilogueBytesNaked]() { ExFreePoolWithTag(epilogueBytesNaked, MEOW_POOL_TAG_NAME); }); memset(epilogueBytesNaked, 0, epilogueBytesAllocationSize); // Enumerates all unwind operation codes. auto current = epilogueBytesNaked; // A current position in epilogueBytes. auto stackSize = 0ul; auto isExpectingAllocSmall = true; for (auto i = 0ul; i < unwind->CountOfCodes; ++i) { const auto &code = unwind->UnwindCode[i]; if (isExpectingAllocSmall) { // Ignore until UWOP_ALLOC_SMALL shows up first. if (code.UnwindOp == UWOP_ALLOC_SMALL) { current = FnparsepInterpretAllocSmallOp(code.OpInfo, current); stackSize += (code.OpInfo * 8) + 8; isExpectingAllocSmall = false; } } else { // Once UWOP_ALLOC_SMALL was processed, it interprets necessary operation // codes. switch (code.UnwindOp) { case UWOP_PUSH_NONVOL: current = FnparsepInterpretPushNonVolOp(code.OpInfo, current); stackSize += 8; break; default: return {}; // Error. Unexpected operation code. } } // Error. Opcode interpretation failed. if (!current) { return {}; } } // Copy a ret instruction at the end. memcpy(current, &EpilogueOpcode::Retn, sizeof(EpilogueOpcode::Retn)); current++; // Get an actual epilogue size. const auto epilogueSize = current - epilogueBytesNaked; // Get a function length. const auto length = FnparseGetFunctionLength(FunctionAddress); if (!length) { return {}; } // Find an address of epilogue. auto epilogueAddress = reinterpret_cast<UCHAR *>( UtilMemMem(FunctionAddress, length, epilogueBytesNaked, epilogueSize)); if (!epilogueAddress) { return {}; } return { epilogueSize, stackSize, { epilogueAddress, }, }; }