Created
October 6, 2018 07:05
-
-
Save typcn/6f433f344a277bb885a7dfa520f45449 to your computer and use it in GitHub Desktop.
A sample code to hide process window with HyperPlatform. https://github.com/tandasat/HyperPlatform
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2015-2016, tandasat. All rights reserved. | |
// Use of this source code is governed by a MIT-style license that can be | |
// found in the LICENSE file. | |
/// @file | |
/// Implements DdiMon functions. | |
#include "ddi_mon.h" | |
#include <ntimage.h> | |
#define NTSTRSAFE_NO_CB_FUNCTIONS | |
#include <ntstrsafe.h> | |
#include "../HyperPlatform/HyperPlatform/common.h" | |
#include "../HyperPlatform/HyperPlatform/log.h" | |
#include "../HyperPlatform/HyperPlatform/util.h" | |
#include "../HyperPlatform/HyperPlatform/ept.h" | |
#undef _HAS_EXCEPTIONS | |
#define _HAS_EXCEPTIONS 0 | |
#include <array> | |
#include "shadow_hook.h" | |
#include <windef.h> | |
#include <intrin.h> | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// macro utilities | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// constants and macros | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// types | |
// | |
// A helper type for parsing a PoolTag value | |
union PoolTag { | |
ULONG value; | |
UCHAR chars[4]; | |
}; | |
// A callback type for EnumExportedSymbols() | |
using EnumExportedSymbolsCallbackType = bool (*)( | |
ULONG index, ULONG_PTR base_address, PIMAGE_EXPORT_DIRECTORY directory, | |
ULONG_PTR directory_base, ULONG_PTR directory_end, void* context); | |
// For SystemProcessInformation | |
enum SystemInformationClass { | |
kSystemProcessInformation = 5, | |
}; | |
// For NtQuerySystemInformation | |
struct SystemProcessInformation { | |
ULONG next_entry_offset; | |
ULONG number_of_threads; | |
LARGE_INTEGER working_set_private_size; | |
ULONG hard_fault_count; | |
ULONG number_of_threads_high_watermark; | |
ULONG64 cycle_time; | |
LARGE_INTEGER create_time; | |
LARGE_INTEGER user_time; | |
LARGE_INTEGER kernel_time; | |
UNICODE_STRING image_name; | |
KPRIORITY BasePriority; | |
HANDLE UniqueProcessId; | |
}; | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// prototypes | |
// | |
_IRQL_requires_max_(PASSIVE_LEVEL) EXTERN_C | |
static void DdimonpFreeAllocatedTrampolineRegions(); | |
_IRQL_requires_max_(PASSIVE_LEVEL) EXTERN_C static NTSTATUS | |
DdimonpEnumExportedSymbols(_In_ ULONG_PTR base_address, | |
_In_ EnumExportedSymbolsCallbackType callback, | |
_In_opt_ void* context); | |
_IRQL_requires_max_(PASSIVE_LEVEL) EXTERN_C | |
static bool DdimonpEnumExportedSymbolsCallback( | |
_In_ ULONG index, _In_ ULONG_PTR base_address, | |
_In_ PIMAGE_EXPORT_DIRECTORY directory, _In_ ULONG_PTR directory_base, | |
_In_ ULONG_PTR directory_end, _In_opt_ void* context); | |
static std::array<char, 5> DdimonpTagToString(_In_ ULONG tag_value); | |
template <typename T> | |
static T DdimonpFindOrignal(_In_ T handler); | |
static NTSTATUS DdimonpHandleNtQuerySystemInformation( | |
_In_ SystemInformationClass SystemInformationClass, | |
_Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, | |
_Out_opt_ PULONG ReturnLength); | |
static NTSTATUS NTAPI DdimonpHandleNtUserBuildHwndList( | |
IN HDESK hdesk, | |
IN HWND hwndNext, | |
IN ULONG fEnumChildren, | |
IN DWORD idThread, | |
IN UINT cHwndMax, | |
IN UINT WTF, | |
OUT HWND *phwndFirst, | |
OUT ULONG *pcHwndNeeded); | |
static HWND APIENTRY DdimonpHandleNtUserFindWindowEx(HWND hwndParent, HWND hwndChildAfter, PUNICODE_STRING ucClassName, PUNICODE_STRING ucWindowName, DWORD dwUnknown); | |
static ULONG64 DdimonpHandleNtUserQueryWindow(IN ULONG64 WindowHandle, IN ULONG64 TypeInformation); | |
static HWND APIENTRY DdimonpHandleNtUserGetForegroundWindow(VOID); | |
static BOOLEAN APIENTRY DdimonpHandleMmIsAddressValid(IN ULONG64 VirtualAddress); | |
#if defined(ALLOC_PRAGMA) | |
#pragma alloc_text(INIT, DdimonInitialization) | |
#pragma alloc_text(INIT, DdimonpEnumExportedSymbols) | |
#pragma alloc_text(INIT, DdimonpEnumExportedSymbolsCallback) | |
#pragma alloc_text(PAGE, DdimonTermination) | |
#pragma alloc_text(PAGE, DdimonpFreeAllocatedTrampolineRegions) | |
#endif | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// variables | |
// | |
// Defines where to install shadow hooks and their handlers | |
// | |
// Because of simplified implementation of DdiMon, DdiMon is unable to handle any | |
// of following exports properly: | |
// - already unmapped exports (eg, ones on the INIT section) because it no | |
// longer exists on memory | |
// - exported data because setting 0xcc does not make any sense in this case | |
// - functions does not comply x64 calling conventions, for example Zw* | |
// functions. Because contents of stack do not hold expected values leading | |
// handlers to failure of parameter analysis that may result in bug check. | |
// | |
// Also the following care should be taken: | |
// - Function parameters may be an user-address space pointer and not | |
// trusted. Even a kernel-address space pointer should not be trusted for | |
// production level security. Verity and capture all contents from user | |
// supplied address to VMM, then use them. | |
static ShadowHookTarget g_ddimonp_hook_targets[] = { | |
{ | |
RTL_CONSTANT_STRING(L"NTUSERQUERYWINDOW"), | |
DdimonpHandleNtUserQueryWindow, nullptr, | |
}, | |
{ | |
RTL_CONSTANT_STRING(L"NTUSERBUILDHWNDLIST"), | |
DdimonpHandleNtUserBuildHwndList, nullptr, | |
}, | |
{ | |
RTL_CONSTANT_STRING(L"NTUSERGETFOREGROUNDWINDOW"), | |
DdimonpHandleNtUserGetForegroundWindow, nullptr, | |
}, | |
{ | |
RTL_CONSTANT_STRING(L"NTUSERFINDWINDOWEX"), | |
DdimonpHandleNtUserFindWindowEx, nullptr, | |
}, | |
{ | |
RTL_CONSTANT_STRING(L"NTQUERYSYSTEMINFORMATION"), | |
DdimonpHandleNtQuerySystemInformation, nullptr, | |
}, | |
{ | |
RTL_CONSTANT_STRING(L"MMISADDRESSVALID"), | |
DdimonpHandleMmIsAddressValid, nullptr, | |
} | |
}; | |
//////////////////////////////////////////////////////////////////////////////// | |
// | |
// implementations | |
// | |
ULONG64 MiIsAddrValid; | |
extern "C" NTKERNELAPI ULONG NTAPI PsGetProcessSessionId(PEPROCESS Process); | |
// Initializes DdiMon | |
_Use_decl_annotations_ EXTERN_C NTSTATUS | |
DdimonInitialization(SharedShadowHookData* shared_sh_data) { | |
// Get a base address of ntoskrnl | |
auto nt_base = UtilPcToFileHeader(KdDebuggerEnabled); | |
if (!nt_base) { | |
return STATUS_UNSUCCESSFUL; | |
} | |
ULONG64 MmisAddrValid = (ULONG64)UtilGetSystemProcAddress(L"MmIsAddressValid"); | |
// E9 XX XX XX XX | |
INT JmpAddr = *(INT *)(MmisAddrValid+1); | |
MiIsAddrValid = MmisAddrValid + JmpAddr + 5; | |
HYPERPLATFORM_LOG_INFO("MiIsAddressValid: Base %llx, Jmp: %d , Target: %llx",MmisAddrValid,JmpAddr,MiIsAddrValid); | |
// Install hooks by enumerating exports of ntoskrnl, but not activate them yet | |
auto status = DdimonpEnumExportedSymbols(reinterpret_cast<ULONG_PTR>(nt_base), | |
DdimonpEnumExportedSymbolsCallback, | |
shared_sh_data); | |
PVOID win32kbase = UtilGetWin32KAddress(); | |
HYPERPLATFORM_LOG_INFO("Win32KBase: %llx",win32kbase); | |
const auto current_cr3 = __readcr3(); | |
HYPERPLATFORM_LOG_INFO("KCR3: %llx", current_cr3); | |
ULONG64 pid = 5; | |
PEPROCESS p; | |
while (true) | |
{ | |
NTSTATUS s = PsLookupProcessByProcessId((HANDLE)pid, &p); | |
if (s != STATUS_SUCCESS) { | |
pid++; | |
continue; | |
} | |
BOOL bProcessInSession = PsGetProcessSessionId(p); | |
if (bProcessInSession) | |
{ | |
break; | |
} | |
else { | |
ObfDereferenceObject(p); | |
pid++; | |
} | |
} | |
KeAttachProcess(p); | |
const auto current_cr23 = __readcr3(); | |
HYPERPLATFORM_LOG_INFO("UCR3: %llx", current_cr23); | |
status = DdimonpEnumExportedSymbols(reinterpret_cast<ULONG_PTR>(win32kbase), | |
DdimonpEnumExportedSymbolsCallback, | |
shared_sh_data); | |
KeDetachProcess(); | |
HYPERPLATFORM_LOG_INFO("Got All functions"); | |
if (!NT_SUCCESS(status)) { | |
return status; | |
} | |
// Activate installed hooks | |
HYPERPLATFORM_LOG_INFO("Ready to enable hooks now"); | |
status = ShEnableHooks(); | |
if (!NT_SUCCESS(status)) { | |
DdimonpFreeAllocatedTrampolineRegions(); | |
return status; | |
} | |
HYPERPLATFORM_LOG_INFO("VtHook has been initialized."); | |
return status; | |
} | |
// Terminates DdiMon | |
_Use_decl_annotations_ EXTERN_C void DdimonTermination() { | |
PAGED_CODE(); | |
ShDisableHooks(); | |
UtilSleep(1000); | |
DdimonpFreeAllocatedTrampolineRegions(); | |
HYPERPLATFORM_LOG_INFO("VtHook has been terminated."); | |
} | |
// Frees trampoline code allocated and stored in g_ddimonp_hook_targets by | |
// DdimonpEnumExportedSymbolsCallback() | |
_Use_decl_annotations_ EXTERN_C static void | |
DdimonpFreeAllocatedTrampolineRegions() { | |
PAGED_CODE(); | |
for (auto& target : g_ddimonp_hook_targets) { | |
if (target.original_call) { | |
ExFreePoolWithTag(target.original_call, kHyperPlatformCommonPoolTag); | |
target.original_call = nullptr; | |
} | |
} | |
} | |
// Enumerates all exports in a module specified by base_address. | |
_Use_decl_annotations_ EXTERN_C static NTSTATUS DdimonpEnumExportedSymbols( | |
ULONG_PTR base_address, EnumExportedSymbolsCallbackType callback, | |
void* context) { | |
PAGED_CODE(); | |
auto dos = reinterpret_cast<PIMAGE_DOS_HEADER>(base_address); | |
auto nt = reinterpret_cast<PIMAGE_NT_HEADERS>(base_address + dos->e_lfanew); | |
auto dir = reinterpret_cast<PIMAGE_DATA_DIRECTORY>( | |
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]); | |
if (!dir->Size || !dir->VirtualAddress) { | |
return STATUS_SUCCESS; | |
} | |
auto dir_base = base_address + dir->VirtualAddress; | |
auto dir_end = base_address + dir->VirtualAddress + dir->Size - 1; | |
auto exp_dir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(base_address + | |
dir->VirtualAddress); | |
for (auto i = 0ul; i < exp_dir->NumberOfNames; i++) { | |
if (!callback(i, base_address, exp_dir, dir_base, dir_end, context)) { | |
return STATUS_SUCCESS; | |
} | |
} | |
return STATUS_SUCCESS; | |
} | |
// Checks if the export is listed as a hook target, and if so install a hook. | |
_Use_decl_annotations_ EXTERN_C static bool DdimonpEnumExportedSymbolsCallback( | |
ULONG index, ULONG_PTR base_address, PIMAGE_EXPORT_DIRECTORY directory, | |
ULONG_PTR directory_base, ULONG_PTR directory_end, void* context) { | |
PAGED_CODE(); | |
if (!context) { | |
return false; | |
} | |
auto functions = | |
reinterpret_cast<ULONG*>(base_address + directory->AddressOfFunctions); | |
auto ordinals = reinterpret_cast<USHORT*>(base_address + | |
directory->AddressOfNameOrdinals); | |
auto names = | |
reinterpret_cast<ULONG*>(base_address + directory->AddressOfNames); | |
auto ord = ordinals[index]; | |
auto export_address = base_address + functions[ord]; | |
auto export_name = reinterpret_cast<const char*>(base_address + names[index]); | |
// Check if an export is forwarded one? If so, ignore it. | |
if (UtilIsInBounds(export_address, directory_base, directory_end)) { | |
return true; | |
} | |
// convert the name to UNICODE_STRING | |
wchar_t name[100]; | |
auto status = | |
RtlStringCchPrintfW(name, RTL_NUMBER_OF(name), L"%S", export_name); | |
if (!NT_SUCCESS(status)) { | |
return true; | |
} | |
UNICODE_STRING name_u = {}; | |
RtlInitUnicodeString(&name_u, name); | |
for (auto& target : g_ddimonp_hook_targets) { | |
// Is this export listed as a target | |
if (!FsRtlIsNameInExpression(&target.target_name, &name_u, TRUE, nullptr)) { | |
continue; | |
} | |
// Yes, install a hook to the export | |
if (!ShInstallHook(reinterpret_cast<SharedShadowHookData*>(context), | |
reinterpret_cast<void*>(export_address), &target)) { | |
// This is an error which should not happen | |
DdimonpFreeAllocatedTrampolineRegions(); | |
return false; | |
} | |
HYPERPLATFORM_LOG_INFO("Hook has been installed at %p %s.", export_address, | |
export_name); | |
} | |
return true; | |
} | |
// Finds a handler to call an original function | |
template <typename T> | |
static T DdimonpFindOrignal(T handler) { | |
for (const auto& target : g_ddimonp_hook_targets) { | |
if (target.handler == handler) { | |
NT_ASSERT(target.original_call); | |
return reinterpret_cast<T>(target.original_call); | |
} | |
} | |
NT_ASSERT(false); | |
return nullptr; | |
} | |
HANDLE gProtectedProcessId = 0; | |
ULONG64 DdimonpHandleNtUserQueryWindow(IN ULONG64 WindowHandle, IN ULONG64 TypeInformation) | |
{ | |
const auto original = DdimonpFindOrignal(DdimonpHandleNtUserQueryWindow); | |
if (gProtectedProcessId && (PsGetCurrentProcessId() != gProtectedProcessId)) | |
{ | |
if (original(WindowHandle, 0) == (ULONG64)gProtectedProcessId) | |
return 0; | |
} | |
return original(WindowHandle, TypeInformation); | |
} | |
HWND gLastForegroundWindow = NULL; | |
HWND APIENTRY DdimonpHandleNtUserGetForegroundWindow(VOID) | |
{ | |
const auto original = DdimonpFindOrignal(DdimonpHandleNtUserGetForegroundWindow); | |
const auto OldNtUserQueryWindow = DdimonpFindOrignal(DdimonpHandleNtUserQueryWindow); | |
HWND result = original(); | |
if ((PsGetCurrentProcessId() != gProtectedProcessId)) | |
{ | |
ULONG64 ProcessID; | |
ProcessID = OldNtUserQueryWindow((ULONG64)result, 0); | |
if (ProcessID == (ULONG64)gProtectedProcessId) | |
result = gLastForegroundWindow; | |
else | |
gLastForegroundWindow = result; | |
} | |
return result; | |
} | |
extern "C" PDRIVER_OBJECT pDrvObj; | |
typedef BOOLEAN(*tMiIsAddressValid)(PVOID); | |
PEPROCESS gProtectedPEP; | |
typedef struct _LDR_DATA_TABLE_ENTRY | |
{ | |
LIST_ENTRY InLoadOrderLinks; | |
LIST_ENTRY InMemoryOrderLinks; | |
LIST_ENTRY InInitializationOrderLinks; | |
PVOID DllBase; | |
PVOID EntryPoint; | |
ULONG SizeOfImage; | |
UNICODE_STRING FullDllName; | |
UNICODE_STRING BaseDllName; | |
ULONG Flags; | |
USHORT LoadCount; | |
USHORT TlsIndex; | |
LIST_ENTRY HashLinks; | |
PVOID SectionPointer; | |
ULONG CheckSum; | |
ULONG TimeDateStamp; | |
PVOID LoadedImports; | |
struct _ACTIVATION_CONTEXT * EntryPointActivationContext; | |
PVOID PatchInformation; | |
LIST_ENTRY ForwarderLinks; | |
LIST_ENTRY ServiceTagLinks; | |
LIST_ENTRY StaticLinks; | |
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; | |
BOOLEAN APIENTRY DdimonpHandleMmIsAddressValid(IN ULONG64 VirtualAddress) { | |
if (pDrvObj) { | |
ULONG64 StartAddress = (ULONG64)pDrvObj; | |
ULONG64 EndAddress = StartAddress + sizeof(DRIVER_OBJECT); | |
if (VirtualAddress >= StartAddress && VirtualAddress <= EndAddress) { | |
return FALSE; | |
} | |
StartAddress = (ULONG64)pDrvObj->DriverSection; | |
EndAddress = StartAddress + sizeof(LDR_DATA_TABLE_ENTRY); | |
if (VirtualAddress >= StartAddress && VirtualAddress <= EndAddress) { | |
return FALSE; | |
} | |
StartAddress = (ULONG64)pDrvObj->DriverStart; | |
EndAddress = StartAddress + pDrvObj->DriverSize; | |
if (VirtualAddress >= StartAddress && VirtualAddress <= EndAddress) { | |
return FALSE; | |
} | |
if (gProtectedPEP) { | |
StartAddress = (ULONG64)gProtectedPEP; | |
EndAddress = StartAddress + 40; | |
if (VirtualAddress >= StartAddress && VirtualAddress <= EndAddress) { | |
return FALSE; | |
} | |
} | |
} | |
tMiIsAddressValid func = (tMiIsAddressValid)MiIsAddrValid; | |
return func((PVOID)VirtualAddress); | |
} | |
HWND APIENTRY DdimonpHandleNtUserFindWindowEx(HWND hwndParent, HWND hwndChildAfter, PUNICODE_STRING ucClassName, PUNICODE_STRING ucWindowName, DWORD dwUnknown) | |
{ | |
const auto original = DdimonpFindOrignal(DdimonpHandleNtUserFindWindowEx); | |
const auto OldNtUserQueryWindow = DdimonpFindOrignal(DdimonpHandleNtUserQueryWindow); | |
HWND result; | |
result = original(hwndParent, hwndChildAfter, ucClassName, ucWindowName, dwUnknown); | |
if ((PsGetCurrentProcessId() != gProtectedProcessId)) | |
{ | |
ULONG64 ProcessID; | |
ProcessID = OldNtUserQueryWindow((ULONG64)result, 0); | |
if (ProcessID == (ULONG64)gProtectedProcessId) | |
return (HWND)0; | |
} | |
return result; | |
} | |
NTSTATUS NTAPI DdimonpHandleNtUserBuildHwndList( | |
IN HDESK hdesk, | |
IN HWND hwndNext, | |
IN ULONG fEnumChildren, | |
IN DWORD idThread, | |
IN UINT cHwndMax, | |
IN UINT WTF, | |
OUT HWND *phwndFirst, | |
OUT ULONG *pcHwndNeeded) { | |
const auto original = DdimonpFindOrignal(DdimonpHandleNtUserBuildHwndList); | |
const auto OldNtUserQueryWindow = DdimonpFindOrignal(DdimonpHandleNtUserQueryWindow); | |
ULONG64 ProcessID; | |
if (fEnumChildren == 1 && gProtectedProcessId && PsGetCurrentProcessId() != gProtectedProcessId) | |
{ | |
ProcessID = OldNtUserQueryWindow((ULONG64)hwndNext, 0); | |
if (ProcessID == (ULONG64)gProtectedProcessId) | |
return STATUS_UNSUCCESSFUL; | |
} | |
NTSTATUS result; | |
result = original(hdesk, hwndNext, fEnumChildren, idThread, cHwndMax, WTF , phwndFirst, pcHwndNeeded); | |
if (!NT_SUCCESS(result) || !gProtectedProcessId || PsGetCurrentProcessId() == gProtectedProcessId) { | |
return result; | |
} | |
ULONG64 i = 0; | |
ULONG64 j; | |
while (i<*pcHwndNeeded) | |
{ | |
ProcessID = OldNtUserQueryWindow((ULONG64)phwndFirst[i], 0); | |
if (ProcessID == (ULONG64)gProtectedProcessId) | |
{ | |
for (j = i; j<(*pcHwndNeeded) - 1; j++) | |
phwndFirst[j] = phwndFirst[j + 1]; //shift all handles after this one place | |
phwndFirst[*pcHwndNeeded - 1] = 0; //just make it empty | |
(*pcHwndNeeded)--; //return less | |
continue; //continue the loop and check the current i | |
} | |
i++; | |
} | |
return result; | |
} | |
_Use_decl_annotations_ static NTSTATUS DdimonpHandleNtQuerySystemInformation( | |
SystemInformationClass system_information_class, PVOID system_information, | |
ULONG system_information_length, PULONG return_length) { | |
const auto original = | |
DdimonpFindOrignal(DdimonpHandleNtQuerySystemInformation); | |
const auto result = original(system_information_class, system_information, | |
system_information_length, return_length); | |
if (!NT_SUCCESS(result)) { | |
return result; | |
} | |
if (system_information_class != kSystemProcessInformation) { | |
return result; | |
} | |
auto next = reinterpret_cast<SystemProcessInformation*>(system_information); | |
while (next->next_entry_offset) { | |
auto curr = next; | |
next = reinterpret_cast<SystemProcessInformation*>( | |
reinterpret_cast<UCHAR*>(curr) + curr->next_entry_offset); | |
if (_wcsnicmp(next->image_name.Buffer, L"notepad.exe", 7) == 0) { | |
if (!gProtectedProcessId) { | |
gProtectedProcessId = next->UniqueProcessId; | |
PsLookupProcessByProcessId(gProtectedProcessId, &gProtectedPEP); | |
} | |
if (next->next_entry_offset) { | |
curr->next_entry_offset += next->next_entry_offset; | |
} else { | |
curr->next_entry_offset = 0; | |
} | |
next = curr; | |
} | |
} | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment