mirror of
https://github.com/HyperDbg/HyperDbg.git
synced 2026-07-10 01:29:59 +00:00
2690 lines
90 KiB
C
2690 lines
90 KiB
C
/**
|
|
* @file Transparency.c
|
|
* @author Sina Karvandi (sina@hyperdbg.org)
|
|
* @brief try to hide the debugger from anti-debugging and anti-hypervisor methods
|
|
* @details
|
|
* @version 0.1
|
|
* @date 2020-07-07
|
|
*
|
|
* @copyright This project is released under the GNU Public License v3.
|
|
*
|
|
*/
|
|
#include "pch.h"
|
|
|
|
/**
|
|
* @brief Hide debugger on transparent-mode (activate transparent-mode)
|
|
*
|
|
* @param HyperevadeCallbacks
|
|
* @param TransparentModeRequest
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
TransparentHideDebugger(HYPEREVADE_CALLBACKS * HyperevadeCallbacks,
|
|
DEBUGGER_HIDE_AND_TRANSPARENT_DEBUGGER_MODE * TransparentModeRequest)
|
|
{
|
|
//
|
|
// Check if any of the required callbacks are NULL
|
|
//
|
|
for (UINT32 i = 0; i < sizeof(HYPEREVADE_CALLBACKS) / sizeof(UINT64); i++)
|
|
{
|
|
if (((PVOID *)HyperevadeCallbacks)[i] == NULL)
|
|
{
|
|
//
|
|
// The callback has null entry, so we cannot proceed
|
|
//
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Save the callbacks
|
|
//
|
|
RtlCopyMemory(&g_Callbacks, HyperevadeCallbacks, sizeof(HYPEREVADE_CALLBACKS));
|
|
|
|
//
|
|
// Check whether the transparent-mode was already initialized or not
|
|
//
|
|
if (!g_TransparentMode)
|
|
{
|
|
//
|
|
// Store the system-call numbers information
|
|
//
|
|
RtlCopyBytes(&g_SystemCallNumbersInformation,
|
|
&TransparentModeRequest->SystemCallNumbersInformation,
|
|
sizeof(SYSTEM_CALL_NUMBERS_INFORMATION));
|
|
|
|
//
|
|
// Choose a random genuine vendor string to replace hypervisor vendor data
|
|
//
|
|
TRANSPARENT_GENUINE_VENDOR_STRING_INDEX = TransparentGetRand() %
|
|
(sizeof(TRANSPARENT_LEGIT_VENDOR_STRINGS_WCHAR) / sizeof(TRANSPARENT_LEGIT_VENDOR_STRINGS_WCHAR[0]));
|
|
|
|
//
|
|
// Enable the transparent mode
|
|
//
|
|
g_TransparentMode = TRUE;
|
|
TransparentModeRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
|
|
//
|
|
// Successfully enabled the transparent-mode
|
|
//
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
TransparentModeRequest->KernelStatus = DEBUGGER_ERROR_DEBUGGER_ALREADY_HIDE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Deactivate transparent-mode
|
|
* @param TransparentModeRequest
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
TransparentUnhideDebugger(DEBUGGER_HIDE_AND_TRANSPARENT_DEBUGGER_MODE * TransparentModeRequest)
|
|
{
|
|
if (g_TransparentMode)
|
|
{
|
|
//
|
|
// Disable the transparent-mode
|
|
//
|
|
g_TransparentMode = FALSE;
|
|
|
|
TransparentModeRequest->KernelStatus = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
TransparentModeRequest->KernelStatus = DEBUGGER_ERROR_DEBUGGER_ALREADY_UNHIDE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle Cpuid Vmexits when the Transparent mode is enabled
|
|
*
|
|
* @param CpuInfo The temporary logical processor registers
|
|
* @param Regs The virtual processor's state of registers
|
|
*
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentCheckAndModifyCpuid(INT32 CpuInfo[], PGUEST_REGS Regs)
|
|
{
|
|
if (Regs->rax == CPUID_PROCESSOR_AND_PROCESSOR_FEATURE_IDENTIFIERS)
|
|
{
|
|
//
|
|
// Unset the Hypervisor Present-bit in RCX, which Intel and AMD have both
|
|
// reserved for this indication
|
|
//
|
|
CpuInfo[2] &= ~HYPERV_HYPERVISOR_PRESENT_BIT;
|
|
}
|
|
else if (Regs->rax == CPUID_HV_VENDOR_AND_MAX_FUNCTIONS || Regs->rax == HYPERV_CPUID_INTERFACE)
|
|
{
|
|
//
|
|
// When transparent, all CPUID leaves in the 0x40000000+ range should contain no usable data
|
|
//
|
|
CpuInfo[0] = CpuInfo[1] = CpuInfo[2] = CpuInfo[3] = 0x40000000;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The triggered hook on KiSystemCall64 system call handler
|
|
* when the Transparency mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleSystemCallHook(GUEST_REGS * Regs)
|
|
{
|
|
PCHAR CallingProcess = g_Callbacks.CommonGetProcessNameFromProcessControlBlock(PsGetCurrentProcess());
|
|
UINT64 Context = Regs->rax;
|
|
|
|
//
|
|
// Skip the transparent mitigations of system calls when the caller process
|
|
// is a Windows process that should receive unmodified data
|
|
//
|
|
for (ULONG i = 0; i < (sizeof(TRANSPARENT_WIN_PROCESS_IGNORE) / sizeof(TRANSPARENT_WIN_PROCESS_IGNORE[0])); i++)
|
|
{
|
|
if (strstr(CallingProcess, TRANSPARENT_WIN_PROCESS_IGNORE[i]))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Context == g_SystemCallNumbersInformation.SysNtQuerySystemInformation ||
|
|
Context == g_SystemCallNumbersInformation.SysNtQuerySystemInformationEx)
|
|
{
|
|
//
|
|
// Handle the NtQuerySystemInformation System call
|
|
//
|
|
|
|
TransparentHandleNtQuerySystemInformationSyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtSystemDebugControl)
|
|
{
|
|
//
|
|
// Handle the NtSystemDebugControl System call
|
|
//
|
|
TransparentHandleNtSystemDebugControlSyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtQueryAttributesFile)
|
|
{
|
|
//
|
|
// Handle the NtQueryAttributesFile System call
|
|
//
|
|
TransparentHandleNtQueryAttributesFileSyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtOpenDirectoryObject)
|
|
{
|
|
//
|
|
// Handle the NtOpenDirectoryObject System call
|
|
//
|
|
TransparentHandleNtOpenDirectoryObjectSyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtQueryDirectoryObject)
|
|
{
|
|
//
|
|
// Handle the NtQueryDirectoryObject System call
|
|
//
|
|
// TransparentHandleNtQueryDirectoryObjectSyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtQueryInformationProcess)
|
|
{
|
|
//
|
|
// Handle the NtQueryInformationProcess System call
|
|
//
|
|
TransparentHandleNtQueryInformationProcessSyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtQueryInformationThread)
|
|
{
|
|
//
|
|
// Handle the NtQueryInformationThread System call
|
|
//
|
|
// TransparentHandleNtQueryInformationThreadSyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtOpenFile)
|
|
{
|
|
//
|
|
// Handle the NtOpenFile System call
|
|
//
|
|
TransparentHandleNtOpenFileSyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtOpenKeyEx || Context == g_SystemCallNumbersInformation.SysNtOpenKey)
|
|
{
|
|
//
|
|
// Handle the NtOpenKey System call
|
|
//
|
|
TransparentHandleNtOpenKeySyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtQueryValueKey)
|
|
{
|
|
//
|
|
// Handle the NtQueryValueKey System call
|
|
//
|
|
TransparentHandleNtQueryValueKeySyscall(Regs);
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtEnumerateKey)
|
|
{
|
|
//
|
|
// Handle the NtEnumerateKey System call
|
|
//
|
|
TransparentHandleNtEnumerateKeySyscall(Regs);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The syscall is not important to us
|
|
//
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The NtQuerySystemInformation system call
|
|
* when the Transparent mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleNtQuerySystemInformationSyscall(GUEST_REGS * Regs)
|
|
{
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
|
|
switch (Regs->r10)
|
|
{
|
|
case SystemProcessInformation:
|
|
case SystemExtendedProcessInformation:
|
|
{
|
|
ContextParams.OptionalParam1 = SystemProcessInformation;
|
|
ContextParams.OptionalParam2 = Regs->rdx;
|
|
ContextParams.OptionalParam3 = Regs->r8 - 0x400;
|
|
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
|
|
break;
|
|
}
|
|
case SystemModuleInformation:
|
|
{
|
|
ContextParams.OptionalParam1 = SystemModuleInformation;
|
|
ContextParams.OptionalParam2 = Regs->rdx;
|
|
ContextParams.OptionalParam3 = Regs->r8;
|
|
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
|
|
break;
|
|
}
|
|
case SystemKernelDebuggerInformation:
|
|
{
|
|
ContextParams.OptionalParam1 = SystemKernelDebuggerInformation;
|
|
ContextParams.OptionalParam2 = Regs->rdx;
|
|
ContextParams.OptionalParam3 = Regs->r8;
|
|
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
break;
|
|
}
|
|
case SystemCodeIntegrityInformation:
|
|
{
|
|
ContextParams.OptionalParam1 = SystemCodeIntegrityInformation;
|
|
ContextParams.OptionalParam2 = Regs->rdx;
|
|
ContextParams.OptionalParam3 = 0x8;
|
|
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Currently SystemFirmwareTableInformation transparent handler is not implemented
|
|
// As the queries produce a data buffer too large to safely copy and modify in root-mode
|
|
//
|
|
|
|
// case SystemFirmwareTableInformation:
|
|
// {
|
|
//
|
|
// ContextParams.OptionalParam1 = SystemFirmwareTableInformation;
|
|
// ContextParams.OptionalParam2 = Regs->rdx;
|
|
// ContextParams.OptionalParam3 = Regs->r8;
|
|
// ContextParams.OptionalParam4 = Regs->r9;
|
|
//
|
|
// g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
// HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
// HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
// Regs->rax,
|
|
// &ContextParams);
|
|
// break;
|
|
// }
|
|
default:
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Obtain a copy of the PWCHAR wide character string from a OBJECT_ATTRIBUTES structure at a guest virtual address
|
|
*
|
|
* @details Returns an allocated tagged memory pointer which needs to be freed with PlatformMemFreePool()
|
|
*
|
|
* @param virtPtr A pointer to a guest virutal memory address, containing a OBJECT_ATTRIBUTES structure
|
|
* @return PVOID Pointer to an allocated tagged memory pool, which needs to be freed with PlatformMemFreePool()
|
|
*/
|
|
PVOID
|
|
TransparentGetObjectNameFromAttributesVirtualPointer(UINT64 virtPtr)
|
|
{
|
|
// PVOID buf = PlatformMemAllocateZeroedNonPagedPool(sizeof(OBJECT_ATTRIBUTES));
|
|
OBJECT_ATTRIBUTES Buf = {0};
|
|
|
|
//
|
|
// Read the OBJECT_ATTRIBUTES structure from the virtual address pointer
|
|
//
|
|
if (g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(virtPtr, &Buf, sizeof(OBJECT_ATTRIBUTES)))
|
|
{
|
|
// PVOID Namebuf = PlatformMemAllocateZeroedNonPagedPool(sizeof(UNICODE_STRING));
|
|
UNICODE_STRING NameBuf = {0};
|
|
|
|
//
|
|
// Read the UNICODE_STRING structure from a virtual address pointer, pointed to by OBJECT_ATTRIBUTES.ObjectName struct entry
|
|
//
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess((UINT64)Buf.ObjectName, &NameBuf, sizeof(UNICODE_STRING)))
|
|
{
|
|
LogInfo("BadRead");
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// The OBJECT_ATTRIBUTES structure contains a PUNICODE_STRING pointer to a guest virtual address which contains this UNICODE_STRING
|
|
// This in turn will contain another pointer, this time a PWCHAR, to another virtual address, which will contain the wide char string we need
|
|
//
|
|
PVOID ObjectNameBuf = PlatformMemAllocateZeroedNonPagedPool(NameBuf.Length + sizeof(WCHAR));
|
|
|
|
if (ObjectNameBuf == NULL)
|
|
{
|
|
LogInfo("Error allocating ImageName memory buffer");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Read the PWCHAR string from a virtual address pointer, pointed to by OBJECT_ATTRIBUTES.ObjectName.Buffer struct entry
|
|
//
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess((UINT64)NameBuf.Buffer, ObjectNameBuf, NameBuf.Length + sizeof(WCHAR)))
|
|
{
|
|
LogInfo("BadRead");
|
|
PlatformMemFreePool(ObjectNameBuf);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// The caller is responsible for freeing the memory buffer, using PlatformMemFreePool()
|
|
//
|
|
return ObjectNameBuf;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The NtQueryAttributesFile system call
|
|
* when the Transparent mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleNtQueryAttributesFileSyscall(GUEST_REGS * Regs)
|
|
{
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
ContextParams.OptionalParam1 = Regs->rdx;
|
|
|
|
//
|
|
// Check if the pointer given as the 3rd argument to the system call with type POBJECT_ATTRIBUTES is valid
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Regs->r10, sizeof(OBJECT_ATTRIBUTES)))
|
|
{
|
|
//
|
|
// From the POBJECT_ATTRIBUTES structure obtain the wide character string of the requested file path
|
|
//
|
|
PVOID PathBuf = TransparentGetObjectNameFromAttributesVirtualPointer(Regs->r10);
|
|
PWCH FilePath = (PWCH)PathBuf;
|
|
|
|
//
|
|
// If the file Attributes request is for a listed file, insert the SYSCALL trap flag and continue execution
|
|
//
|
|
for (UINT16 j = 0; j < (sizeof(HV_FILES) / sizeof(HV_FILES[0])); j++)
|
|
{
|
|
if (wcsstr(FilePath, HV_FILES[j]))
|
|
{
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free the allocated copy of the directory path, obtained from TransparentGetObjectNameFromAttributesVirtualPointer()
|
|
//
|
|
PlatformMemFreePool(PathBuf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The NtOpenDirectoryObject system call
|
|
* when the Transparent mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleNtOpenDirectoryObjectSyscall(GUEST_REGS * Regs)
|
|
{
|
|
//
|
|
// Set up the context data for the callback after SYSRET
|
|
//
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
ContextParams.OptionalParam1 = Regs->r10;
|
|
|
|
//
|
|
// Check if the pointer given as the 3rd argument to the system call with type POBJECT_ATTRIBUTES is valid
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Regs->r8, sizeof(OBJECT_ATTRIBUTES)))
|
|
{
|
|
//
|
|
// From the POBJECT_ATTRIBUTES structure obtain the wide character string of the requested directory path
|
|
//
|
|
PVOID PathBuf = TransparentGetObjectNameFromAttributesVirtualPointer(Regs->r8);
|
|
PWCH DirPath = (PWCH)PathBuf;
|
|
|
|
if (DirPath == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If the directory object request is for a listed directory, insert the SYSCALL trap flag and continue execution
|
|
//
|
|
for (UINT16 j = 0; j < (sizeof(HV_DIRS) / sizeof(HV_DIRS[0])); j++)
|
|
{
|
|
if (wcsstr(DirPath, HV_DIRS[j]))
|
|
{
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free the allocated copy of the directory path, obtained from TransparentGetObjectNameFromAttributesVirtualPointer()
|
|
//
|
|
PlatformMemFreePool(PathBuf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The NtSystemDebugControl system call
|
|
* when the Transparent mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleNtSystemDebugControlSyscall(GUEST_REGS * Regs)
|
|
{
|
|
//
|
|
// Corrupt the system call arguments, to cause the kernel to return an error
|
|
//
|
|
Regs->r9 = 0x0;
|
|
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
|
|
//
|
|
// Set the trap flag to intercept the SYSRET instruction
|
|
//
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The NtQueryInformationProcess system call
|
|
* when the Transparent mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleNtQueryInformationProcessSyscall(GUEST_REGS * Regs)
|
|
{
|
|
//
|
|
// Set up the context parameters for the interception callback
|
|
//
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
ContextParams.OptionalParam1 = Regs->rdx; // ProcessInformationClass
|
|
ContextParams.OptionalParam2 = Regs->r8; // BufferPtr
|
|
ContextParams.OptionalParam3 = Regs->r9; // BufferSize
|
|
|
|
//
|
|
// Set the trap flag to intercept the SYSRET instruction
|
|
//
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The NtOpenFile system call
|
|
* when the Transparent mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleNtOpenFileSyscall(GUEST_REGS * Regs)
|
|
{
|
|
//
|
|
// Check if the user-mode pointer in R8 to a OBJECT_ATTRIBUTES struct is valid
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Regs->r8, sizeof(OBJECT_ATTRIBUTES)))
|
|
{
|
|
//
|
|
// From the OBJECT_ATTRIBUTES struct pointer extract the file path for which this syscall is called
|
|
//
|
|
PVOID NameBuf = TransparentGetObjectNameFromAttributesVirtualPointer(Regs->r8);
|
|
PWCH FileName = (PWCH)NameBuf;
|
|
if (FileName == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Check if the requested file includes any hypervisor specific strings
|
|
// This also checks parent directory names of the requested file
|
|
//
|
|
for (UINT16 j = 0; j < (sizeof(HV_FILES) / sizeof(HV_FILES[0])); j++)
|
|
{
|
|
if (wcsstr(FileName, HV_FILES[j]))
|
|
{
|
|
LogInfo("A call to NtOpenFile systemcall for a hypervisor specific file was made");
|
|
|
|
//
|
|
// If a match was found, corrupt the user-mode pointers in CPU registers, so that, when the kernel-mode execution continues, it would fail.
|
|
//
|
|
Regs->r8 = 0x0;
|
|
Regs->r10 = 0x0;
|
|
|
|
//
|
|
// Set the trap flag to intercept the SYSRET instruction
|
|
//
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
|
|
break;
|
|
}
|
|
}
|
|
//
|
|
// Clean up the allocated memory
|
|
//
|
|
PlatformMemFreePool(NameBuf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The NtOpenKey system call
|
|
* when the Transparent mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleNtOpenKeySyscall(GUEST_REGS * Regs)
|
|
{
|
|
//
|
|
// Check if the user-mode pointer in R8 to a OBJECT_ATTRIBUTES struct is valid
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Regs->r8, sizeof(OBJECT_ATTRIBUTES)))
|
|
{
|
|
//
|
|
// From the OBJECT_ATTRIBUTES struct pointer extract the registry key path for which this syscall is called
|
|
//
|
|
PVOID NameBuf = TransparentGetObjectNameFromAttributesVirtualPointer(Regs->r8);
|
|
PWCH KeyName = (PWCH)NameBuf;
|
|
|
|
if (KeyName == NULL)
|
|
{
|
|
LogInfo("BADRET");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Check if the requested registry entry path includes any hypervisor specific strings
|
|
//
|
|
for (UINT16 j = 0; j < (sizeof(HV_REGKEYS) / sizeof(HV_REGKEYS[0])); j++)
|
|
{
|
|
if (wcsstr(KeyName, HV_REGKEYS[j]) > 0)
|
|
{
|
|
//
|
|
// If a match was found, corrupt the user-mode pointer in CPU registers, so that, when the kernel-mode execution continues, it would fail.
|
|
//
|
|
Regs->r8 = 0x0;
|
|
|
|
//
|
|
// Set the trap flag to intercept the SYSRET instruction
|
|
//
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean up the allocated memory
|
|
//
|
|
PlatformMemFreePool(NameBuf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The NtQueryValueKey system call
|
|
* when the Transparent mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleNtQueryValueKeySyscall(GUEST_REGS * Regs)
|
|
{
|
|
//
|
|
// Check if the user-mode pointer in RDX to a UNICODE_STRING struct is valid
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Regs->rdx, sizeof(UNICODE_STRING)))
|
|
{
|
|
UNICODE_STRING NameUString = {0};
|
|
|
|
//
|
|
// Read the UNICODE_STRING structure from a virtual address pointer, pointed to by RDX struct entry
|
|
//
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Regs->rdx, &NameUString, sizeof(UNICODE_STRING)))
|
|
return;
|
|
|
|
//
|
|
// Read the PWCH wide char string from the address pointer in the UNICODE_STRING
|
|
//
|
|
PVOID NameBuf = PlatformMemAllocateZeroedNonPagedPool(NameUString.Length + sizeof(WCHAR));
|
|
if (NameBuf == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess((UINT64)NameUString.Buffer, NameBuf, NameUString.Length + sizeof(WCHAR)))
|
|
{
|
|
PlatformMemFreePool(NameBuf);
|
|
return;
|
|
}
|
|
|
|
PWCH KeyName = (PWCH)NameBuf;
|
|
|
|
//
|
|
// If the registry key request was for kay that could contain hypervisor specific information in its data,
|
|
// the return buffer(%R9) needs to be modified, but the buffer length is in the user mode stack
|
|
//
|
|
for (ULONG i = 0; i < (sizeof(TRANSPARENT_DETECTABLE_REGISTRY_KEYS) / sizeof(TRANSPARENT_DETECTABLE_REGISTRY_KEYS[0])); i++)
|
|
{
|
|
if (!wcscmp(KeyName, TRANSPARENT_DETECTABLE_REGISTRY_KEYS[i]))
|
|
{
|
|
//
|
|
// If a match is found, set up the context values and set the trap flag for the SYSRET callback
|
|
//
|
|
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
|
|
ContextParams.OptionalParam1 = Regs->r8;
|
|
ContextParams.OptionalParam2 = Regs->r9;
|
|
|
|
//
|
|
// Read the 5th argument of the system call from the stack at location %RSP + 0x28
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Regs->rsp + 0x28, sizeof(UINT64)))
|
|
{
|
|
g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess((UINT64)(Regs->rsp + 0x28), &ContextParams.OptionalParam3, sizeof(ULONG));
|
|
}
|
|
else
|
|
{
|
|
LogInfo("Process 0x%llx on thread %llx executed NtQueryValueKey systemcall but reading the provided arguments from %RSP failed", HANDLE_TO_UINT32(PsGetCurrentProcessId()), HANDLE_TO_UINT32(PsGetCurrentThreadId()));
|
|
|
|
PlatformMemFreePool(NameBuf);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Read the 6th argument of the system call from the stack at location %RSP + 0x30
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Regs->rsp + 0x30, sizeof(UINT64)))
|
|
{
|
|
g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess((UINT64)(Regs->rsp + 0x30), &ContextParams.OptionalParam4, sizeof(UINT64));
|
|
}
|
|
else
|
|
{
|
|
LogInfo("Process 0x%llx on thread %llx executed NtQueryValueKey systemcall but reading the provided arguments from %RSP failed", HANDLE_TO_UINT32(PsGetCurrentProcessId()), HANDLE_TO_UINT32(PsGetCurrentThreadId()));
|
|
|
|
PlatformMemFreePool(NameBuf);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Set the trap flag to intercept the SYSRET instruction
|
|
//
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
|
|
//
|
|
// Clean-up and return to guest exection
|
|
//
|
|
PlatformMemFreePool(NameBuf);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the call was for a registry key that contains a hypervisor specific string,
|
|
// The user-mode caller should just receive an error return code not a modified data buffer
|
|
//
|
|
for (UINT16 j = 1; j < (sizeof(HV_REGKEYS) / sizeof(HV_REGKEYS[0])); j++)
|
|
{
|
|
if (wcsstr(KeyName, HV_REGKEYS[j]) > 0)
|
|
{
|
|
//
|
|
// When the match is found, corrupt the buffer pointers in the registers
|
|
// and set the SYSRET callback trap flag
|
|
//
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
|
|
Regs->rdx = 0x0;
|
|
Regs->r9 = 0x0;
|
|
|
|
//
|
|
// Set the trap flag to intercept the SYSRET instruction
|
|
//
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean up the allocated memory
|
|
//
|
|
PlatformMemFreePool(NameBuf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle The NtEnumerateKey system call
|
|
* when the Transparent mode is enabled
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentHandleNtEnumerateKeySyscall(GUEST_REGS * Regs)
|
|
{
|
|
//
|
|
// Set up the context parameters for the interception callback
|
|
//
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS ContextParams = {0};
|
|
ContextParams.OptionalParam1 = Regs->r8;
|
|
ContextParams.OptionalParam2 = Regs->r9;
|
|
|
|
//
|
|
// Read the 5th argument of the system call from the stack at location %RSP + 0x28
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Regs->rsp + 0x28, sizeof(UINT64)))
|
|
{
|
|
g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess((UINT64)(Regs->rsp + 0x28), &ContextParams.OptionalParam3, sizeof(ULONG));
|
|
}
|
|
else
|
|
{
|
|
LogInfo("Process 0x%llx on thread %llx executed NtEnumerateKey systemcall but reading the provided arguments from %RSP failed", HANDLE_TO_UINT32(PsGetCurrentProcessId()), HANDLE_TO_UINT32(PsGetCurrentThreadId()));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Read the 6th argument of the system call from the stack at location %RSP + 0x30
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Regs->rsp + 0x30, sizeof(UINT64)))
|
|
{
|
|
g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess((UINT64)(Regs->rsp + 0x30), &ContextParams.OptionalParam4, sizeof(UINT64));
|
|
}
|
|
else
|
|
{
|
|
LogInfo("Process 0x%llx on thread %llx executed NtEnumerateKey systemcall but reading the provided arguments from %RSP failed", HANDLE_TO_UINT32(PsGetCurrentProcessId()), HANDLE_TO_UINT32(PsGetCurrentThreadId()));
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If the call was made without an allocated buffer (with size 0)
|
|
// we have no need to intercept it
|
|
//
|
|
if (ContextParams.OptionalParam3 == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Set the trap flag to intercept the SYSRET instruction
|
|
//
|
|
g_Callbacks.SyscallCallbackSetTrapFlagAfterSyscall(Regs,
|
|
HANDLE_TO_UINT32(PsGetCurrentProcessId()),
|
|
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
|
|
Regs->rax,
|
|
&ContextParams);
|
|
}
|
|
|
|
/**
|
|
* @brief Handle the request for SystemModuleInformation
|
|
*
|
|
* @details This function removes entries from a list of system drivers that could reveal the presence of hypervisors
|
|
* This depends on an incomplete list HV_DRIVER, of known hypervisor drivers
|
|
* The revealing list entries are removed and overwritten, but the memory buffer is not reallocated, so
|
|
* it is possible to still detect that some tampering was done from the user space
|
|
*
|
|
* @param Ptr The pointer to a valid read/writable SYSTEM_MODULE_INFORMATION memory buffer
|
|
* @param VirualAddress A pointer to a user-mode virual address
|
|
* @param BufferSize Size of the user-mode buffer
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
TransparentHandleModuleInformationQuery(PVOID Ptr, UINT64 VirtualAddress, UINT32 BufferSize)
|
|
{
|
|
PSYSTEM_MODULE_INFORMATION StructBuf = (PSYSTEM_MODULE_INFORMATION)Ptr;
|
|
PSYSTEM_MODULE_ENTRY ModuleList = StructBuf->Module;
|
|
|
|
//
|
|
// Traverse the list of system modules and remove the system drivers
|
|
// matching a known list of hypervisor drivers based on their filename
|
|
//
|
|
for (UINT16 i = 0; i < StructBuf->Count; i++)
|
|
{
|
|
PCHAR path = (PCHAR)ModuleList[i].FullPathName;
|
|
|
|
for (UINT16 j = 0; j < (sizeof(HV_DRIVER) / sizeof(HV_DRIVER[0])); j++)
|
|
{
|
|
if (strstr(path, HV_DRIVER[j]))
|
|
{
|
|
//
|
|
// If a module file name matches, remove the entry from the list by shifting it forward by one entry
|
|
//
|
|
for (UINT16 k = i; k < StructBuf->Count - 1; k++)
|
|
{
|
|
ModuleList[k] = ModuleList[k + 1];
|
|
}
|
|
|
|
//
|
|
// Decrement the list size as one entry has been removed
|
|
//
|
|
i--;
|
|
StructBuf->Count--;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(VirtualAddress, Ptr, BufferSize))
|
|
{
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Handle the request for SystemProcessInformation
|
|
*
|
|
* @details This function removes entries from a list of active system processes that could reveal the presence of hypervisors
|
|
*
|
|
* @param Params Preset transparent callback params that contain:
|
|
in OptionalParam2 a pointer to a valid read/writable memory buffer that contains a SYSTEM_PROCESS_INFORMATION structure
|
|
in OptionalParam3 max size in bytes of the allocated buffer
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
TransparentHandleProcessInformationQuery(SYSCALL_CALLBACK_CONTEXT_PARAMS * Params)
|
|
{
|
|
SYSTEM_PROCESS_INFORMATION PrevStructBuf = {0};
|
|
SYSTEM_PROCESS_INFORMATION CurStructBuf = {0};
|
|
|
|
ULONG ReadOffset, WriteOffset;
|
|
BOOLEAN MatchFound = FALSE;
|
|
ULONG PrevOffset = 0;
|
|
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Params->OptionalParam2, &PrevStructBuf, sizeof(SYSTEM_PROCESS_INFORMATION)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ReadOffset = PrevStructBuf.NextEntryOffset;
|
|
WriteOffset = 0;
|
|
|
|
//
|
|
// The first entry will always be System Idle Process, which can be skipped
|
|
//
|
|
if (PrevStructBuf.NextEntryOffset == 0 ||
|
|
!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Params->OptionalParam2 + ReadOffset, &CurStructBuf, sizeof(SYSTEM_PROCESS_INFORMATION)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Loop through all the entries and filter out the offending ones
|
|
//
|
|
do
|
|
{
|
|
MatchFound = FALSE;
|
|
|
|
if (CurStructBuf.ImageName.Length != 0)
|
|
{
|
|
//
|
|
// We need to search for the Image name of the process which requires extra allocation
|
|
//
|
|
PVOID StringBuf = PlatformMemAllocateZeroedNonPagedPool(CurStructBuf.ImageName.Length + sizeof(WCHAR));
|
|
|
|
if (StringBuf == NULL)
|
|
{
|
|
LogInfo("Error allocating ImageName memory buffer");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Read the WCHAR process image name from the user-mode pointer
|
|
//
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess((UINT64)CurStructBuf.ImageName.Buffer, StringBuf, CurStructBuf.ImageName.Length + sizeof(WCHAR)))
|
|
{
|
|
PlatformMemFreePool(StringBuf);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
PWCH ImageName = (PWCH)StringBuf;
|
|
|
|
if (ImageName == NULL)
|
|
{
|
|
PlatformMemFreePool(StringBuf);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Loop through the known list of identifiable hypervisor related processes
|
|
//
|
|
for (UINT16 i = 0; i < (sizeof(HV_Processes) / sizeof(HV_Processes[0])); i++)
|
|
{
|
|
if (!_wcsnicmp(ImageName, HV_Processes[i], (CurStructBuf.ImageName.Length) / sizeof(WCHAR)))
|
|
{
|
|
//
|
|
// If the name matches, bypass it by increasing the previous entries .nextEntryOffset value
|
|
//
|
|
|
|
//
|
|
// The offset to this matching entry need to preserved for zeroing later
|
|
//
|
|
PrevOffset = PrevStructBuf.NextEntryOffset;
|
|
|
|
PrevStructBuf.NextEntryOffset = PrevStructBuf.NextEntryOffset + CurStructBuf.NextEntryOffset;
|
|
|
|
MatchFound = TRUE;
|
|
|
|
//
|
|
// Write the modified offset back to the usermode buffer
|
|
//
|
|
if (!g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess((UINT64)(Params->OptionalParam2 + WriteOffset), &PrevStructBuf, sizeof(SYSTEM_PROCESS_INFORMATION)))
|
|
{
|
|
LogError("Failed to modify memory buffer for the SystemProcessInformation query system call");
|
|
}
|
|
|
|
//
|
|
// The entry gets bypassed, but since the Image name is a pointer in the struct, to completely clear any presence of these processes
|
|
// zero out the name buffer as well
|
|
//
|
|
memset(StringBuf, 0x0, CurStructBuf.ImageName.Length);
|
|
ULONG BufOffset = (ULONG)((PBYTE)&CurStructBuf.ImageName.Length - (PBYTE)&CurStructBuf) + sizeof(USHORT);
|
|
|
|
if (!g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess((UINT64)(Params->OptionalParam2 + WriteOffset + PrevOffset + BufOffset), StringBuf, CurStructBuf.ImageName.Length))
|
|
{
|
|
LogError("Failed to modify memory buffer for the SystemProcessInformation query system call");
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
PlatformMemFreePool(StringBuf);
|
|
}
|
|
|
|
//
|
|
// If the last entry been reached, exit
|
|
//
|
|
if (CurStructBuf.NextEntryOffset == 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// If the current entry didnt match any process names, move forward
|
|
//
|
|
if (!MatchFound)
|
|
{
|
|
WriteOffset += PrevStructBuf.NextEntryOffset;
|
|
|
|
PrevStructBuf = CurStructBuf;
|
|
}
|
|
|
|
//
|
|
// Move over to the next entry
|
|
//
|
|
ReadOffset += CurStructBuf.NextEntryOffset;
|
|
|
|
//
|
|
// Some internal Windows calls to this system call use different offsetting/entry structure layout and causes errors
|
|
//
|
|
if (!g_Callbacks.CheckAccessValidityAndSafety((UINT64)(Params->OptionalParam2 + ReadOffset), sizeof(SYSTEM_PROCESS_INFORMATION)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Zero out the matching entry, so that its data doesnt remain in memory
|
|
//
|
|
if (MatchFound)
|
|
{
|
|
CurStructBuf = (SYSTEM_PROCESS_INFORMATION) {0};
|
|
if (!g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess((UINT64)(Params->OptionalParam2 + WriteOffset + PrevOffset), &CurStructBuf, sizeof(SYSTEM_PROCESS_INFORMATION)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Read from the user buffer the next process entry
|
|
//
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Params->OptionalParam2 + ReadOffset, &CurStructBuf, sizeof(SYSTEM_PROCESS_INFORMATION)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
} while (TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Handle the request for SystemFirmwareTableInformation
|
|
*
|
|
* @param ptr The pointer to a valid read/writable SYSTEM_FIRMWARE_TABLE_INFORMATION memory buffer
|
|
* @param BufMaxSize The size of the allocated user-mode buffer
|
|
* @param BufSizePtr A pointer to a ULONG field containing the size of the written data
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
UINT64
|
|
TransparentHandleFirmwareInformationQuery(UINT64 Ptr, UINT32 BufMaxSize, UINT64 BufSizePtr)
|
|
{
|
|
ULONG BufSize = 0;
|
|
|
|
//
|
|
// Read the size of the data the kernel wrote in the buffer
|
|
//
|
|
if (!g_Callbacks.CheckAccessValidityAndSafety(BufSizePtr, sizeof(ULONG)) ||
|
|
!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(BufSizePtr, &BufSize, sizeof(ULONG)))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// If the memory buffer was too small for the kernel to write the needed information, bypass the mitigations
|
|
//
|
|
if (BufSize > BufMaxSize)
|
|
{
|
|
//
|
|
// NOTE: might need zeroing the memory if the kernel did infact write to the pointer
|
|
//
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// If the buffer size is too big, we cant do any mitigations with the current implementation an it exceeds
|
|
// the size of an EPT page, risking curruption when allocating the copy buffer space in root-mode.
|
|
//
|
|
if (BufMaxSize > PAGE_SIZE / 2)
|
|
{
|
|
LogInfo("The intercepted data buffer was too large for modification, in total 0x%x bytes", BufMaxSize);
|
|
LogInfo("The system call return value was set to STATUS_INVALID_INFO_CLASS, but this could be detected as hypervisor intervention");
|
|
|
|
return (UINT64)(UINT32)STATUS_INVALID_INFO_CLASS;
|
|
}
|
|
|
|
if (BufSize == 0)
|
|
{
|
|
BufSize = BufMaxSize;
|
|
}
|
|
//
|
|
// From the user-mode pointer, read the SYSTEM_FIRMWARE_TABLE_INFORMATION struct
|
|
//
|
|
PVOID Buf = PlatformMemAllocateZeroedNonPagedPool(BufMaxSize + 1);
|
|
if (Buf == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Ptr, Buf, BufSize))
|
|
{
|
|
PlatformMemFreePool(Buf);
|
|
return 0;
|
|
}
|
|
|
|
PSYSTEM_FIRMWARE_TABLE_INFORMATION StructBuf = (PSYSTEM_FIRMWARE_TABLE_INFORMATION)Buf;
|
|
|
|
//
|
|
// The request needs to be a "get" request for an existing table
|
|
// with 'RSMB', 'ACPI' or 'FIRM' table providers
|
|
//
|
|
if (StructBuf->Action == SystemFirmwareTable_Get &&
|
|
StructBuf->TableID != 0 &&
|
|
(StructBuf->ProviderSignature == 0x52534D42 ||
|
|
StructBuf->ProviderSignature == 0x41435049 ||
|
|
StructBuf->ProviderSignature == 0x4649524D))
|
|
{
|
|
PCHAR StringBuf = (PCHAR)StructBuf->TableBuffer;
|
|
|
|
for (ULONG i = 0; i < (sizeof(HV_FIRM_NAMES) / sizeof(HV_FIRM_NAMES[0])); i++)
|
|
{
|
|
for (ULONG j = 0; j < StructBuf->TableBufferLength; j++)
|
|
{
|
|
WORD Count = 0;
|
|
PCHAR MatchStart = strstr(StringBuf + j, HV_FIRM_NAMES[i]);
|
|
if (MatchStart != 0)
|
|
{
|
|
LogInfo("Found Match for %s", HV_FIRM_NAMES[i]);
|
|
|
|
PCHAR NewVendorString = NULL;
|
|
ULONG NewSubstringSize = 0;
|
|
|
|
//
|
|
// Replace the first occurace of the vendor string with AMERICAN MEGATRENDS INC.
|
|
// The rest with To Be Filled By O.E.M.
|
|
//
|
|
if (Count == 0)
|
|
{
|
|
NewVendorString = "AMERICAN MEGATRENDS INC.";
|
|
NewSubstringSize = 24 * sizeof(CHAR);
|
|
}
|
|
else
|
|
{
|
|
NewVendorString = "To Be Filled By O.E.M.";
|
|
NewSubstringSize = 22 * sizeof(CHAR);
|
|
}
|
|
|
|
//
|
|
// Obtain the lengths of all the strings and substring
|
|
//
|
|
|
|
ULONG MatchedStringLen = (ULONG)strlen(HV_FIRM_NAMES[i]);
|
|
ULONG oldLength = StructBuf->TableBufferLength;
|
|
|
|
ULONG NewStringSize = oldLength - MatchedStringLen + NewSubstringSize;
|
|
|
|
//
|
|
// Check if the buffer size allows the modification, in case of expansion
|
|
//
|
|
if (BufSize - MatchedStringLen + NewSubstringSize > BufMaxSize)
|
|
{
|
|
//
|
|
// If adding the new string exceeds the user allocated size,
|
|
// zero out the buffer
|
|
//
|
|
memset(Buf, 0x0, BufMaxSize);
|
|
g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Ptr, Buf, BufMaxSize);
|
|
|
|
//
|
|
// Update the required buffer size for the next call
|
|
//
|
|
BufSize = (BufSize - oldLength) + NewStringSize;
|
|
g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(BufSizePtr, &BufSize, sizeof(ULONG));
|
|
|
|
//
|
|
// And return STATUS_BUFFER_TOO_SMALL error
|
|
//
|
|
|
|
PlatformMemFreePool(Buf);
|
|
return (UINT64)(UINT32)STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
//
|
|
// Calculate the positions of the replacement
|
|
//
|
|
ULONG MatchOffset = (ULONG)((MatchStart - StringBuf));
|
|
PCHAR MatchEnd = StringBuf + MatchOffset + MatchedStringLen;
|
|
|
|
//
|
|
// Move the data after the matched string forward
|
|
// and replace the identified hypervisor string with the genuine one
|
|
//
|
|
memmove((PVOID)(StringBuf + MatchOffset + NewSubstringSize), (PVOID)MatchEnd, oldLength - MatchedStringLen - MatchOffset);
|
|
memcpy((PVOID)MatchStart, (PVOID)NewVendorString, NewSubstringSize);
|
|
|
|
StructBuf->TableBufferLength = NewStringSize;
|
|
BufSize = BufSize - MatchedStringLen + NewSubstringSize;
|
|
|
|
//
|
|
// Write the changes back to the user buffers
|
|
//
|
|
if (!g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Ptr, Buf, BufSize))
|
|
{
|
|
LogInfo("Error writing to user-mode buffer: %llx", Ptr);
|
|
}
|
|
|
|
if (!g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(BufSizePtr, &BufSize, sizeof(ULONG)))
|
|
{
|
|
LogInfo("Error writing to user-mode buffer: %llx", BufSizePtr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PlatformMemFreePool(Buf);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* @brief Replace occurances of a hypervisor specific strings with legitimate vendor strings in a provided buffer
|
|
*
|
|
* @param Params Set transparent callback params that contain:
|
|
in OptionalParam2 a pointer to a valid read/writable memory buffer that contains both, a WCHAR string and its length in bytes
|
|
in OptionalParam3 max size in bytes of the allocatec buffer
|
|
in OptionalParam4 a pointer to a ULONG containing current size of the buffer
|
|
*
|
|
* @param DataOffset Offset in bytes from OptionalParam2 to the start of the WCHAR data string
|
|
*
|
|
* @param DataLenOffset Offset in bytes from OptionalParam2 to a ULONG containing the string length(in bytes)
|
|
*
|
|
* @return UINT64
|
|
*/
|
|
UINT64
|
|
TransparentReplaceVendorStringFromBufferWChar(SYSCALL_CALLBACK_CONTEXT_PARAMS * Params, ULONG DataOffset, ULONG DataLenOffset)
|
|
{
|
|
PVOID Buf = NULL;
|
|
BOOL PoolAlloc = FALSE;
|
|
|
|
//
|
|
// Check that the user provided pointers are safe to read from
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam4, sizeof(ULONG)))
|
|
{
|
|
//
|
|
// Read the size of the data that the kernel wrote to the buffer
|
|
//
|
|
ULONG BufSize = 0;
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Params->OptionalParam4, &BufSize, sizeof(ULONG)))
|
|
{
|
|
goto ReturnWithError;
|
|
}
|
|
|
|
//
|
|
// If the data the kernel wanted to write is bigger than what was allocated by the user
|
|
//
|
|
if (BufSize > Params->OptionalParam3 || BufSize == 0)
|
|
{
|
|
//
|
|
// NOTE: might need zeroing the memory if the kernel did infact write to the pointer
|
|
//
|
|
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Check that the user provided pointers are safe to read from and the buffer is not too large
|
|
//
|
|
if (Params->OptionalParam3 >= 0xC00 || !g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam2, (UINT32)Params->OptionalParam3))
|
|
{
|
|
goto ReturnWithError;
|
|
}
|
|
|
|
//
|
|
// If the buffer is small, e.g. for just a single word, store it on the stack
|
|
// else, allocate a nonpaged memory buffer
|
|
//
|
|
CHAR StackBuf[MAX_PATH] = {0};
|
|
|
|
if (Params->OptionalParam3 + sizeof(WCHAR) > MAX_PATH)
|
|
{
|
|
Buf = PlatformMemAllocateZeroedNonPagedPool(Params->OptionalParam3 + sizeof(WCHAR));
|
|
PoolAlloc = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Buf = &StackBuf;
|
|
}
|
|
|
|
if (!Buf || !g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Params->OptionalParam2, Buf, Params->OptionalParam3))
|
|
{
|
|
goto ReturnWithError;
|
|
}
|
|
|
|
//
|
|
// Get the actual data we are trying to modify(in wide char form)
|
|
//
|
|
PWCH StringBuf = (PWCH)((PBYTE)Buf + DataOffset);
|
|
|
|
//
|
|
// Traverse the list of registry key names and vendor strings that are specific to common hypervisors
|
|
// if a match is found perform the modification
|
|
//
|
|
for (UINT16 i = 0; i < (sizeof(HV_REGKEYS) / sizeof(HV_REGKEYS[0])); i++)
|
|
{
|
|
PWCH MatchStart = wcsstr(StringBuf, HV_REGKEYS[i]);
|
|
|
|
while (MatchStart != 0)
|
|
{
|
|
PWCH NewVendorString = NULL;
|
|
|
|
//
|
|
// If the match was for a device id, the replacement should be with a different ID string not vendor name
|
|
//
|
|
if (i < 3)
|
|
{
|
|
//
|
|
// SPOOFS PCI device ID's(in the registry), This might be implemented in other ways that are not part of this implementation
|
|
//
|
|
WORD Idx = TRANSPARENT_GENUINE_VENDOR_STRING_INDEX % (sizeof(TRANSPARENT_LEGIT_DEVICE_ID_VENDOR_STRINGS_WCHAR) / sizeof(TRANSPARENT_LEGIT_DEVICE_ID_VENDOR_STRINGS_WCHAR[0]));
|
|
NewVendorString = TRANSPARENT_LEGIT_DEVICE_ID_VENDOR_STRINGS_WCHAR[Idx];
|
|
}
|
|
|
|
//
|
|
// Remove common VM strings from the data
|
|
//
|
|
else if (i < 9)
|
|
{
|
|
NewVendorString = L" ";
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Obtain the replacement vendor name string, randomized when the transparency mode was enabled
|
|
//
|
|
NewVendorString = TRANSPARENT_LEGIT_VENDOR_STRINGS_WCHAR[TRANSPARENT_GENUINE_VENDOR_STRING_INDEX];
|
|
}
|
|
|
|
//
|
|
// Obtain the lengths of all the strings and substring
|
|
//
|
|
ULONG tempSize = (ULONG)wcslen(NewVendorString) * sizeof(WCHAR);
|
|
|
|
ULONG MatchedStringLen = (ULONG)wcslen(HV_REGKEYS[i]) * sizeof(WCHAR);
|
|
ULONG oldLength = *((PBYTE)Buf + DataLenOffset);
|
|
|
|
ULONG NewStringSize = oldLength - MatchedStringLen + tempSize;
|
|
|
|
//
|
|
// Check if the buffer size allows the modification, in case of expansion
|
|
//
|
|
if (BufSize - MatchedStringLen + tempSize > Params->OptionalParam3)
|
|
{
|
|
//
|
|
// If adding the new string exceeds the user allocated size,
|
|
// zero out the buffer
|
|
//
|
|
memset(Buf, 0x0, Params->OptionalParam3);
|
|
g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Params->OptionalParam2, Buf, Params->OptionalParam3);
|
|
|
|
//
|
|
// Update the required buffer size for the next call
|
|
//
|
|
BufSize = (tempSize - MatchedStringLen) + oldLength;
|
|
g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Params->OptionalParam4, &BufSize, sizeof(ULONG));
|
|
|
|
//
|
|
// And return STATUS_BUFFER_OVERFLOW error
|
|
//
|
|
|
|
if (PoolAlloc)
|
|
PlatformMemFreePool(Buf);
|
|
return (UINT64)(UINT32)STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
//
|
|
// Calculate the positions of the replacement
|
|
//
|
|
ULONG MatchOffset = (ULONG)((MatchStart - StringBuf));
|
|
PWCH MatchEnd = StringBuf + MatchOffset + (MatchedStringLen / sizeof(WCHAR));
|
|
|
|
//
|
|
// Move the data after the matched string forward
|
|
//
|
|
memmove((PVOID)(StringBuf + MatchOffset + (tempSize / sizeof(WCHAR))), (PVOID)MatchEnd, oldLength - MatchedStringLen - (MatchOffset * sizeof(WCHAR)));
|
|
|
|
//
|
|
// Replace the identified hypervisor string with the genuine one, if needed
|
|
//
|
|
memcpy((PVOID)MatchStart, (PVOID)NewVendorString, tempSize);
|
|
|
|
*(PULONG)((PBYTE)Buf + DataLenOffset) = NewStringSize;
|
|
BufSize = BufSize - MatchedStringLen + tempSize;
|
|
|
|
//
|
|
// Write the changes back to the user buffers
|
|
//
|
|
if (!g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Params->OptionalParam2, Buf, BufSize))
|
|
{
|
|
goto ReturnWithError;
|
|
}
|
|
|
|
if (!g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Params->OptionalParam4, &BufSize, sizeof(ULONG)))
|
|
{
|
|
goto ReturnWithError;
|
|
}
|
|
|
|
//
|
|
// Cleanup
|
|
//
|
|
|
|
MatchStart = wcsstr(StringBuf, HV_REGKEYS[i]);
|
|
|
|
if (!MatchStart)
|
|
i = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The data buffer contained no detectable strings
|
|
//
|
|
if (PoolAlloc)
|
|
PlatformMemFreePool(Buf);
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// An error occured while performing the mitigations, the user buffer might be left unmodified
|
|
//
|
|
ReturnWithError:
|
|
LogInfo("A call for to read a registry entry, which could contain hypervisor specific data, was intercepted but the mitigations failed");
|
|
LogInfo("The caller process recieved the results in this virtual address: %llx", Params->OptionalParam2);
|
|
|
|
if (Buf != NULL)
|
|
{
|
|
if (PoolAlloc)
|
|
PlatformMemFreePool(Buf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Callback function to handle the returns from the NtQueryValueKey syscall
|
|
*
|
|
* @param Params The set transparent callback params that contain:
|
|
in OptionalParam1 the KEY_VALUE_INFORMATION_CLASS enum value
|
|
in OptionalParam2 a pointer to a valid read/writable memory buffer that contains both, a WCHAR string and its length in bytes
|
|
in OptionalParam3 max size in bytes of the allocatec buffer
|
|
in OptionalParam4 a pointer to a ULONG containing current size of the buffer
|
|
*
|
|
* @return UINT64
|
|
*/
|
|
UINT64
|
|
TransparentCallbackHandleAfterNtQueryValueKeySyscall(SYSCALL_CALLBACK_CONTEXT_PARAMS * Params)
|
|
{
|
|
ULONG LenOffset = 0;
|
|
ULONG BufOffset = 0;
|
|
|
|
//
|
|
// Based on the KEY_VALUE_INFORMATION_CLASS given, set the struct offsets for the data buffer and its length fields
|
|
//
|
|
switch (Params->OptionalParam1)
|
|
{
|
|
//
|
|
// KeyValueBasicInformation and queries for a key that has a hypervisor specific name
|
|
//
|
|
case 0x0:
|
|
{
|
|
if (Params->OptionalParam2 != 0)
|
|
return 0;
|
|
|
|
//
|
|
// If the key query was for a registry key that has a hypervisor specific name, return an error code
|
|
//
|
|
return (UINT64)(UINT32)STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// KeyValuePartialInformation
|
|
//
|
|
case 0x2:
|
|
{
|
|
LenOffset = sizeof(ULONG) * 2;
|
|
BufOffset = sizeof(ULONG) * 3;
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// KeyValueFullInformation
|
|
//
|
|
case 0x3:
|
|
case 0x1:
|
|
{
|
|
LenOffset = sizeof(ULONG) * 3;
|
|
BufOffset = sizeof(ULONG) * 4; // Name offset, Data is after it
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// KeyValuePartialInformationAlign64
|
|
//
|
|
case 0x4:
|
|
{
|
|
LenOffset = sizeof(ULONG) * 1;
|
|
BufOffset = sizeof(ULONG) * 2;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
LogInfo("NtQueryValueKey was called with KeyValueInformationClass 0x%x, a handler for which has not been implemented", Params->OptionalParam1);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Given the user buffer, read and exchange any Hypervisor vendor strings in the registry key data
|
|
//
|
|
return TransparentReplaceVendorStringFromBufferWChar(Params, BufOffset, LenOffset);
|
|
}
|
|
|
|
/**
|
|
* @brief Callback function to handle the returns from the NtQueryValueKey syscall
|
|
*
|
|
* @param Params The set transparent callback params that contain:
|
|
in OptionalParam1 the KEY_VALUE_INFORMATION_CLASS enum value
|
|
in OptionalParam2 a pointer to a valid read/writable memory buffer that contains both, a WCHAR string and its length in bytes
|
|
in OptionalParam3 max size in bytes of the allocatec buffer
|
|
in OptionalParam4 a pointer to a ULONG containing current size of the buffer
|
|
*
|
|
* @return UINT64
|
|
*/
|
|
UINT64
|
|
TransparentCallbackHandleAfterNtEnumerateKeySyscall(SYSCALL_CALLBACK_CONTEXT_PARAMS * Params)
|
|
{
|
|
ULONG LenOffset = 0;
|
|
ULONG BufOffset = 0;
|
|
|
|
//
|
|
// Based on the KEY_INFORMATION_CLASS given, set the struct offsets for the data buffer and its length fields
|
|
//
|
|
switch (Params->OptionalParam1)
|
|
{
|
|
//
|
|
// KeyBasicInformation
|
|
//
|
|
case 0x0:
|
|
{
|
|
LenOffset = sizeof(LARGE_INTEGER) + sizeof(ULONG);
|
|
BufOffset = sizeof(LARGE_INTEGER) + (sizeof(ULONG) * 2);
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// KeyNodeInformation
|
|
//
|
|
case 0x1:
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// KeyNameInformation
|
|
//
|
|
case 0x3:
|
|
{
|
|
LenOffset = 0;
|
|
BufOffset = sizeof(ULONG);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
LogInfo("NtEnumerateKey was called with KeyInformationClass 0x%x, a handler for which has not been implemented", Params->OptionalParam1);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Given the user buffer, read and exchange any Hypervisor vendor strings in the registry key names
|
|
//
|
|
return TransparentReplaceVendorStringFromBufferWChar(Params, BufOffset, LenOffset);
|
|
}
|
|
|
|
/**
|
|
* @brief Callback function to handle the returns from the NtQuerySystemInformation syscall
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @param Params The (optional) parameters of the caller
|
|
*
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentCallbackHandleAfterNtQuerySystemInformationSyscall(GUEST_REGS * Regs, SYSCALL_CALLBACK_CONTEXT_PARAMS * Params)
|
|
{
|
|
//
|
|
// Handle each defined SYSTEM_INFORMATION_CLASS
|
|
//
|
|
switch (Params->OptionalParam1)
|
|
{
|
|
case SystemCodeIntegrityInformation:
|
|
{
|
|
//
|
|
// Check if the obtained buffer pointer is valid
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam2, (UINT32)Params->OptionalParam3))
|
|
{
|
|
SYSTEM_CODEINTEGRITY_INFORMATION Temp = {0};
|
|
|
|
//
|
|
// Read data from the saved pointer of the now filled information buffer
|
|
//
|
|
g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Params->OptionalParam2, &Temp, Params->OptionalParam3);
|
|
|
|
//
|
|
// Modify the data and write it back to the information buffer to be passed to user mode
|
|
//
|
|
Temp.CodeIntegrityOptions = 0x01;
|
|
g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Params->OptionalParam2, &Temp, Params->OptionalParam3);
|
|
}
|
|
else
|
|
{
|
|
LogInfo("A call for the NtQuerySystemInformation system call requesting SystemCodeIntegrityInformation structure was made, but the usermode buffer was not captured");
|
|
}
|
|
|
|
break;
|
|
}
|
|
case SystemProcessInformation:
|
|
case SystemExtendedProcessInformation:
|
|
{
|
|
//
|
|
// Check if the obtained buffer pointer is valid
|
|
//
|
|
if (Params->OptionalParam2 != 0x0 &&
|
|
Params->OptionalParam3 != 0x0 &&
|
|
g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam2, (UINT32)Params->OptionalParam3))
|
|
{
|
|
if (!TransparentHandleProcessInformationQuery(Params))
|
|
{
|
|
//
|
|
// Some internal Windows calls to these system calls use different offsetting/entry structure layout and causes errors
|
|
//
|
|
|
|
// LogInfo("Error while modifying the buffer for data query 0x02x", Params->OptionalParam1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SystemModuleInformation:
|
|
{
|
|
//
|
|
// Check if the obtained buffer pointer is valid
|
|
//
|
|
if (Params->OptionalParam2 != 0x0 &&
|
|
Params->OptionalParam3 != 0x0 &&
|
|
g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam2, (UINT32)Params->OptionalParam3))
|
|
{
|
|
//
|
|
// Allocate a buffer to copy user buffer data to for modification
|
|
//
|
|
PVOID Buf = PlatformMemAllocateZeroedNonPagedPool(Params->OptionalParam3);
|
|
if (Buf == NULL)
|
|
{
|
|
LogError("Err, insufficient memory");
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Copy over the data and perform the modifications
|
|
//
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Params->OptionalParam2, Buf, Params->OptionalParam3))
|
|
{
|
|
LogInfo("Error reading memory buffer given by the usermode call");
|
|
}
|
|
else
|
|
{
|
|
if (!TransparentHandleModuleInformationQuery(Buf, Params->OptionalParam2, (UINT32)Params->OptionalParam3))
|
|
{
|
|
//
|
|
// Some internal Windows calls to these system calls use different offsetting/entry structure layout and causes errors
|
|
//
|
|
|
|
// LogInfo("Error while modifying the buffer for data query 0x02x", Params->OptionalParam1);
|
|
}
|
|
}
|
|
|
|
PlatformMemFreePool(Buf);
|
|
}
|
|
break;
|
|
}
|
|
case SystemKernelDebuggerInformation:
|
|
{
|
|
//
|
|
// Check if the obtained buffer pointer is valid
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam2, (UINT32)Params->OptionalParam3))
|
|
{
|
|
//
|
|
// Write to the output buffer 0x0001 for "Debugger not present"
|
|
//
|
|
WORD Temp = 0x0100;
|
|
g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Params->OptionalParam2, &Temp, 2);
|
|
}
|
|
}
|
|
case SystemFirmwareTableInformation:
|
|
{
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam2, (UINT32)Params->OptionalParam3))
|
|
{
|
|
UINT64 RetVal = TransparentHandleFirmwareInformationQuery(Params->OptionalParam2, (UINT32)Params->OptionalParam3, Params->OptionalParam4);
|
|
if (RetVal == 0x0)
|
|
{
|
|
LogInfo("A query for SystemFirmwareTableInformation was made, but the transparent mitigation failed");
|
|
}
|
|
else if (RetVal != 0x1)
|
|
{
|
|
LogInfo("Changing to %llx", RetVal);
|
|
Regs->rax = RetVal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogInfo("A query for SystemFirmwareTableInformation was made, but the user-mode buffer was not captured");
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Callback function to handle returns from the syscall
|
|
*
|
|
* @param Regs The virtual processor's state of registers
|
|
* @param ProcessId The process id of the thread
|
|
* @param ThreadId The thread id of the thread
|
|
* @param Context The context of the caller
|
|
* @param Params The (optional) parameters of the caller
|
|
*
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
TransparentCallbackHandleAfterSyscall(GUEST_REGS * Regs,
|
|
UINT32 ProcessId,
|
|
UINT32 ThreadId,
|
|
UINT64 Context,
|
|
SYSCALL_CALLBACK_CONTEXT_PARAMS * Params)
|
|
{
|
|
//
|
|
// Handle each defined system call separately, after the kernel execution has finished(at the SYSRET instruction)
|
|
//
|
|
|
|
//
|
|
// Handle the memory buffer and return code modification after NtQuerySystemInformation system call
|
|
//
|
|
if (Context == g_SystemCallNumbersInformation.SysNtQuerySystemInformation)
|
|
{
|
|
TransparentCallbackHandleAfterNtQuerySystemInformationSyscall(Regs, Params);
|
|
}
|
|
//
|
|
// Handle the memory buffer and return code modification after NtQueryAttributesFile system call
|
|
//
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtQueryAttributesFile)
|
|
{
|
|
//
|
|
// Check if the obtained buffer pointer is valid
|
|
//
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam1, sizeof(FILE_BASIC_INFORMATION)))
|
|
{
|
|
FILE_BASIC_INFORMATION Buf = {0};
|
|
//
|
|
// Copy over the data from the output buffer pointer
|
|
//
|
|
if (!g_Callbacks.MemoryMapperReadMemorySafeOnTargetProcess(Params->OptionalParam1, &Buf, sizeof(FILE_BASIC_INFORMATION)))
|
|
{
|
|
LogError("Err, Virtual memory read failed");
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Modify the file attribute to INVALID_FILE_ATTRIBUTES and write it back to the pointer
|
|
//
|
|
Buf.FileAttributes = ((DWORD)-1);
|
|
|
|
if (!g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Params->OptionalParam1, &Buf, sizeof(FILE_BASIC_INFORMATION)))
|
|
{
|
|
LogError("Err, Virtual memory write failed");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogInfo("A call for the NtQueryAttributeFile system call for a marked file was made, but the output buffer was not captured");
|
|
}
|
|
}
|
|
|
|
//
|
|
// Handle the memory buffer and return code modification after NtOpenDirectoryObject system call.
|
|
//
|
|
// NOTE: No transparent mitigations of this call have been implemented
|
|
//
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtOpenDirectoryObject)
|
|
{
|
|
LogInfo("A NtOpenDirectoryObject system call was made for a known directory that reveals hypervisor presence. process: %x, thread: %x\n",
|
|
ProcessId,
|
|
ThreadId);
|
|
LogInfo("No action to mitigate this was made as a handler for NtOpenDirectoryObject has not been implemented");
|
|
}
|
|
//
|
|
// Handle the memory buffer modification after NtQueryInformationProcess system call
|
|
//
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtQueryInformationProcess)
|
|
{
|
|
switch (Params->OptionalParam1)
|
|
{
|
|
case 0x07:
|
|
{
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam2, sizeof(DWORD_PTR)))
|
|
{
|
|
//
|
|
// Zero out the return buffer to user-mode
|
|
//
|
|
DWORD_PTR NoDebugPort = 0x0;
|
|
|
|
g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Params->OptionalParam2, &NoDebugPort, sizeof(DWORD_PTR));
|
|
}
|
|
break;
|
|
}
|
|
case 0x1f:
|
|
{
|
|
//
|
|
// Zero out the return buffer to user-mode
|
|
//
|
|
ULONG notDebugged = 0x0;
|
|
g_Callbacks.MemoryMapperWriteMemorySafeOnTargetProcess(Params->OptionalParam2, ¬Debugged, sizeof(ULONG));
|
|
break;
|
|
}
|
|
case 0x1e:
|
|
{
|
|
if (g_Callbacks.CheckAccessValidityAndSafety(Params->OptionalParam2, (UINT32)Params->OptionalParam3))
|
|
{
|
|
LogInfo("Process %llx called the NtQueryInformationProcess system call with the ProcessDebugObject class, no transparent mitigations were performed", ProcessId);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Handle the return code modification after NtSystemDebugControl system call
|
|
//
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtSystemDebugControl)
|
|
{
|
|
//
|
|
// In the entry handler, the Syscall number was changed to corrupt this call, after the SYSRET, change the return code to STATUS_DEBUGGER_INACTIVE
|
|
//
|
|
Regs->rax = (UINT64)(UINT32)STATUS_DEBUGGER_INACTIVE;
|
|
}
|
|
|
|
//
|
|
// Handle the return code modification after SysNtOpenFile system call
|
|
//
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtOpenFile)
|
|
{
|
|
//
|
|
// In the entry handler, the Syscall number was changed to corrupt this call if the request was for a known hypervisor file
|
|
// after the SYSRET, change the return code to STATUS_OBJECT_NAME_NOT_FOUND
|
|
//
|
|
Regs->rax = (UINT64)(UINT32)STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// Handle the return code modification after NtNtQueryValueKey system call
|
|
//
|
|
// NOTE: The transparent mitigation will replace all occurances of a hypervisor vendor string in the registry
|
|
// key data to a randomized real hardware vendor string, no matter the meaning of the key,
|
|
// This can cause some keys to illogical data, for example,
|
|
// a disk drive ID having a vendor string of ASUS even though(as far as I know) ASUS doesnt produce storage devices.
|
|
//
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtQueryValueKey)
|
|
{
|
|
UINT64 RetVal;
|
|
|
|
//
|
|
// Call the handler of NtQueryValueKey syscall callback
|
|
//
|
|
RetVal = TransparentCallbackHandleAfterNtQueryValueKeySyscall(Params);
|
|
|
|
//
|
|
// If a custom(Specific to transparency) error code should be returned,
|
|
// set it to %RAX
|
|
// Else leave it to what the kernel already set it to
|
|
//
|
|
if (RetVal != 0)
|
|
{
|
|
Regs->rax = RetVal;
|
|
}
|
|
}
|
|
//
|
|
// Handle the memory buffer modification after NtOpenKey system call and its derivatives
|
|
//
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtOpenKey || Context == g_SystemCallNumbersInformation.SysNtOpenKeyEx)
|
|
{
|
|
//
|
|
// In the entry handler, the Syscall number was changed to corrupt this call if the request was for a known hypervisor registry key
|
|
// after the SYSRET, change the return code to STATUS_OBJECT_NAME_NOT_FOUND
|
|
//
|
|
Regs->rax = (UINT64)(UINT32)STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
else if (Context == g_SystemCallNumbersInformation.SysNtEnumerateKey)
|
|
{
|
|
UINT64 RetVal;
|
|
|
|
//
|
|
// Call the handler of NtEnumerateKey syscall callback
|
|
//
|
|
RetVal = TransparentCallbackHandleAfterNtEnumerateKeySyscall(Params);
|
|
|
|
//
|
|
// If a custom(Specific to transparency) error code should be returned,
|
|
// set it to %RAX
|
|
// Else leave it to what the kernel already set it to
|
|
//
|
|
if (RetVal != 0)
|
|
{
|
|
Regs->rax = RetVal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// A SYSRET trap flag was inserted for a System call that does not have a transparency handler implemented
|
|
//
|
|
LogInfo("Transparent callback for an unimplemented system call handle with the trap flag for process: %x, thread: %x, context: %llx RAX: %llx (p1: %llx, p2: %llx, p3: %llx, p4: %llx) \n",
|
|
ProcessId,
|
|
ThreadId,
|
|
Context,
|
|
Regs->rax,
|
|
Params->OptionalParam1,
|
|
Params->OptionalParam2,
|
|
Params->OptionalParam3,
|
|
Params->OptionalParam4);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Generate a random number by utilizing RDTSC instruction.
|
|
*
|
|
* Masking 16 LSB of the measured clock time.
|
|
* @return UINT32
|
|
*/
|
|
UINT32
|
|
TransparentGetRand()
|
|
{
|
|
UINT64 Tsc;
|
|
UINT32 Rand;
|
|
|
|
Tsc = __rdtsc();
|
|
Rand = Tsc & 0xffff;
|
|
|
|
return Rand;
|
|
}
|
|
//
|
|
// /**
|
|
// * @brief maximum random value
|
|
// */
|
|
// #define MY_RAND_MAX 32768
|
|
//
|
|
// /**
|
|
// * @brief pre-defined log result
|
|
// * @details we used this because we want to avoid using floating-points in
|
|
// * kernel
|
|
// */
|
|
// int TransparentTableLog[] =
|
|
// {
|
|
// 0,
|
|
// 69,
|
|
// 110,
|
|
// 139,
|
|
// 161,
|
|
// 179,
|
|
// 195,
|
|
// 208,
|
|
// 220,
|
|
// 230,
|
|
// 240,
|
|
// 248,
|
|
// 256,
|
|
// 264,
|
|
// 271,
|
|
// 277,
|
|
// 283,
|
|
// 289,
|
|
// 294,
|
|
// 300,
|
|
// 304,
|
|
// 309,
|
|
// 314,
|
|
// 318,
|
|
// 322,
|
|
// 326,
|
|
// 330,
|
|
// 333,
|
|
// 337,
|
|
// 340,
|
|
// 343,
|
|
// 347,
|
|
// 350,
|
|
// 353,
|
|
// 356,
|
|
// 358,
|
|
// 361,
|
|
// 364,
|
|
// 366,
|
|
// 369,
|
|
// 371,
|
|
// 374,
|
|
// 376,
|
|
// 378,
|
|
// 381,
|
|
// 383,
|
|
// 385,
|
|
// 387,
|
|
// 389,
|
|
// 391,
|
|
// 393,
|
|
// 395,
|
|
// 397,
|
|
// 399,
|
|
// 401,
|
|
// 403,
|
|
// 404,
|
|
// 406,
|
|
// 408,
|
|
// 409,
|
|
// 411,
|
|
// 413,
|
|
// 414,
|
|
// 416,
|
|
// 417,
|
|
// 419,
|
|
// 420,
|
|
// 422,
|
|
// 423,
|
|
// 425,
|
|
// 426,
|
|
// 428,
|
|
// 429,
|
|
// 430,
|
|
// 432,
|
|
// 433,
|
|
// 434,
|
|
// 436,
|
|
// 437,
|
|
// 438,
|
|
// 439,
|
|
// 441,
|
|
// 442,
|
|
// 443,
|
|
// 444,
|
|
// 445,
|
|
// 447,
|
|
// 448,
|
|
// 449,
|
|
// 450,
|
|
// 451,
|
|
// 452,
|
|
// 453,
|
|
// 454,
|
|
// 455,
|
|
// 456,
|
|
// 457,
|
|
// 458,
|
|
// 460,
|
|
// 461};
|
|
//
|
|
//
|
|
// /**
|
|
// * @brief Integer power function definition.
|
|
// *
|
|
// * @params x Base Value
|
|
// * @params p Power Value
|
|
// * @return int
|
|
// */
|
|
// int
|
|
// TransparentPow(int x, int p)
|
|
// {
|
|
// int Res = 1;
|
|
// for (int i = 0; i < p; i++)
|
|
// {
|
|
// Res = Res * x;
|
|
// }
|
|
// return Res;
|
|
// }
|
|
//
|
|
// /**
|
|
// * @brief Integer Natural Logarithm function estimation.
|
|
// *
|
|
// * @params x input value
|
|
// * @return int
|
|
// */
|
|
// int
|
|
// TransparentLog(int x)
|
|
// {
|
|
// int n = x;
|
|
// int Digit = 0;
|
|
//
|
|
// while (n >= 100)
|
|
// {
|
|
// n = n / 10;
|
|
// Digit++;
|
|
// }
|
|
//
|
|
// //
|
|
// // Use pre-defined values of logarithms and estimate the total value
|
|
// //
|
|
// return TransparentTableLog[n] / 100 + (Digit * 23) / 10;
|
|
// }
|
|
// /**
|
|
// * @brief Integer root function estimation.
|
|
// *
|
|
// * @params x input value
|
|
// * @return int
|
|
// */
|
|
// int
|
|
// TransparentSqrt(int x)
|
|
// {
|
|
// int Res = 0;
|
|
// int Bit;
|
|
//
|
|
// //
|
|
// // The second-to-top bit is set.
|
|
// //
|
|
// Bit = 1 << 30;
|
|
//
|
|
// //
|
|
// // "Bit" starts at the highest power of four <= the argument.
|
|
// //
|
|
// while (Bit > x)
|
|
// Bit >>= 2;
|
|
//
|
|
// while (Bit != 0)
|
|
// {
|
|
// if (x >= Res + Bit)
|
|
// {
|
|
// x -= Res + Bit;
|
|
// Res = (Res >> 1) + Bit;
|
|
// }
|
|
// else
|
|
// Res >>= 1;
|
|
// Bit >>= 2;
|
|
// }
|
|
// return Res;
|
|
// }
|
|
//
|
|
// /**
|
|
// * @brief Integer Gaussian Random Number Generator(GRNG) based on Box-Muller method. A Float to Integer
|
|
// * mapping is used in the function.
|
|
// *
|
|
// * @params Average Mean
|
|
// * @parans Sigma Standard Deviation of the targeted Gaussian Distribution
|
|
// * @return int
|
|
// */
|
|
// int
|
|
// TransparentRandn(int Average, int Sigma)
|
|
// {
|
|
// int U1, r1, U2, r2, W, Mult;
|
|
// int X1, X2 = 0, XS1;
|
|
// int LogTemp = 0;
|
|
//
|
|
// do
|
|
// {
|
|
// r1 = TransparentGetRand();
|
|
// r2 = TransparentGetRand();
|
|
//
|
|
// U1 = (r1 % MY_RAND_MAX) - (MY_RAND_MAX / 2);
|
|
//
|
|
// U2 = (r2 % MY_RAND_MAX) - (MY_RAND_MAX / 2);
|
|
//
|
|
// W = U1 * U1 + U2 * U2;
|
|
// } while (W >= MY_RAND_MAX * MY_RAND_MAX / 2 || W == 0);
|
|
//
|
|
// LogTemp = (TransparentLog(W) - TransparentLog(MY_RAND_MAX * MY_RAND_MAX));
|
|
//
|
|
// Mult = TransparentSqrt((-2 * LogTemp) * (MY_RAND_MAX * MY_RAND_MAX / W));
|
|
//
|
|
// X1 = U1 * Mult / MY_RAND_MAX;
|
|
// XS1 = U1 * Mult;
|
|
//
|
|
// X2 = U2 * Mult / MY_RAND_MAX;
|
|
//
|
|
// return (Average + (Sigma * XS1) / MY_RAND_MAX);
|
|
// }
|
|
//
|
|
// /**
|
|
// * @brief Add name or process id of the target process to the list
|
|
// * of processes that HyperDbg should apply transparent-mode on them
|
|
// *
|
|
// * @param Measurements
|
|
// * @return BOOLEAN
|
|
// */
|
|
// BOOLEAN
|
|
// TransparentAddNameOrProcessIdToTheList(PDEBUGGER_HIDE_AND_TRANSPARENT_DEBUGGER_MODE Measurements)
|
|
// {
|
|
// SIZE_T SizeOfBuffer;
|
|
// PTRANSPARENCY_PROCESS PidAndNameBuffer;
|
|
//
|
|
// //
|
|
// // Check whether it's a process id or it's a process name
|
|
// //
|
|
// if (Measurements->TrueIfProcessIdAndFalseIfProcessName)
|
|
// {
|
|
// //
|
|
// // It's a process Id
|
|
// //
|
|
// SizeOfBuffer = sizeof(TRANSPARENCY_PROCESS);
|
|
// }
|
|
// else
|
|
// {
|
|
// //
|
|
// // It's a process name
|
|
// //
|
|
// SizeOfBuffer = sizeof(TRANSPARENCY_PROCESS) + Measurements->LengthOfProcessName;
|
|
// }
|
|
//
|
|
// //
|
|
// // Allocate the Buffer
|
|
// //
|
|
// PidAndNameBuffer = PlatformMemAllocateZeroedNonPagedPool(SizeOfBuffer);
|
|
//
|
|
// if (PidAndNameBuffer == NULL)
|
|
// {
|
|
// return FALSE;
|
|
// }
|
|
//
|
|
// //
|
|
// // Save the address of the buffer for future de-allocation
|
|
// //
|
|
// PidAndNameBuffer->BufferAddress = PidAndNameBuffer;
|
|
//
|
|
// //
|
|
// // Check again whether it's a process id or it's a process name
|
|
// // then fill the structure
|
|
// //
|
|
// if (Measurements->TrueIfProcessIdAndFalseIfProcessName)
|
|
// {
|
|
// //
|
|
// // It's a process Id
|
|
// //
|
|
// PidAndNameBuffer->ProcessId = Measurements->ProcId;
|
|
// PidAndNameBuffer->TrueIfProcessIdAndFalseIfProcessName = TRUE;
|
|
// }
|
|
// else
|
|
// {
|
|
// //
|
|
// // It's a process name
|
|
// //
|
|
// PidAndNameBuffer->TrueIfProcessIdAndFalseIfProcessName = FALSE;
|
|
//
|
|
// //
|
|
// // Move the process name string to the end of the buffer
|
|
// //
|
|
// RtlCopyBytes((void *)((UINT64)PidAndNameBuffer + sizeof(TRANSPARENCY_PROCESS)),
|
|
// (const void *)((UINT64)Measurements + sizeof(DEBUGGER_HIDE_AND_TRANSPARENT_DEBUGGER_MODE)),
|
|
// Measurements->LengthOfProcessName);
|
|
//
|
|
// //
|
|
// // Set the process name location
|
|
// //
|
|
// PidAndNameBuffer->ProcessName = (PVOID)((UINT64)PidAndNameBuffer + sizeof(TRANSPARENCY_PROCESS));
|
|
// }
|
|
//
|
|
// //
|
|
// // Link it to the list of process that we need to transparent
|
|
// // vm-exits for them
|
|
// //
|
|
// InsertHeadList(&g_TransparentModeMeasurements->ProcessList, &(PidAndNameBuffer->OtherProcesses));
|
|
//
|
|
// return TRUE;
|
|
// }
|
|
//
|
|
// /**
|
|
// * @brief Hide debugger on transparent-mode (activate transparent-mode)
|
|
// *
|
|
// * @param Measurements
|
|
// * @return NTSTATUS
|
|
// */
|
|
// NTSTATUS
|
|
// TransparentHideDebugger(PDEBUGGER_HIDE_AND_TRANSPARENT_DEBUGGER_MODE Measurements)
|
|
// {
|
|
// //
|
|
// // Check whether the transparent-mode was already initialized or not
|
|
// //
|
|
// if (!g_TransparentMode)
|
|
// {
|
|
// //
|
|
// // Allocate the measurements buffer
|
|
// //
|
|
// g_TransparentModeMeasurements = (PTRANSPARENCY_MEASUREMENTS)PlatformMemAllocateZeroedNonPagedPool(sizeof(TRANSPARENCY_MEASUREMENTS));
|
|
//
|
|
// if (!g_TransparentModeMeasurements)
|
|
// {
|
|
// return STATUS_INSUFFICIENT_RESOURCES;
|
|
// }
|
|
//
|
|
// //
|
|
// // Initialize the lists
|
|
// //
|
|
// InitializeListHead(&g_TransparentModeMeasurements->ProcessList);
|
|
//
|
|
// //
|
|
// // Fill the transparency details CPUID
|
|
// //
|
|
// g_TransparentModeMeasurements->CpuidAverage = Measurements->CpuidAverage;
|
|
// g_TransparentModeMeasurements->CpuidMedian = Measurements->CpuidMedian;
|
|
// g_TransparentModeMeasurements->CpuidStandardDeviation = Measurements->CpuidStandardDeviation;
|
|
//
|
|
// //
|
|
// // Fill the transparency details RDTSC
|
|
// //
|
|
// g_TransparentModeMeasurements->RdtscAverage = Measurements->RdtscAverage;
|
|
// g_TransparentModeMeasurements->RdtscMedian = Measurements->RdtscMedian;
|
|
// g_TransparentModeMeasurements->RdtscStandardDeviation = Measurements->RdtscStandardDeviation;
|
|
//
|
|
// //
|
|
// // add the new process name or Id to the list
|
|
// //
|
|
// TransparentAddNameOrProcessIdToTheList(Measurements);
|
|
//
|
|
// //
|
|
// // Enable RDTSC and RDTSCP exiting on all cores
|
|
// //
|
|
// BroadcastEnableRdtscExitingAllCores();
|
|
//
|
|
// //
|
|
// // Finally, enable the transparent-mode
|
|
// //
|
|
// g_TransparentMode = TRUE;
|
|
// }
|
|
// else
|
|
// {
|
|
// //
|
|
// // It's already initialized, we just need to
|
|
// // add the new process name or Id to the list
|
|
// //
|
|
// TransparentAddNameOrProcessIdToTheList(Measurements);
|
|
// }
|
|
//
|
|
// return STATUS_SUCCESS;
|
|
// }
|
|
//
|
|
// /**
|
|
// * @brief Deactivate transparent-mode
|
|
// *
|
|
// * @return NTSTATUS
|
|
// */
|
|
// NTSTATUS
|
|
// TransparentUnhideDebugger()
|
|
// {
|
|
// PLIST_ENTRY TempList = 0;
|
|
// PVOID BufferToDeAllocate = 0;
|
|
//
|
|
// if (g_TransparentMode)
|
|
// {
|
|
// //
|
|
// // Disable the transparent-mode
|
|
// //
|
|
// g_TransparentMode = FALSE;
|
|
//
|
|
// //
|
|
// // Disable RDTSC and RDTSCP emulation
|
|
// //
|
|
// BroadcastDisableRdtscExitingAllCores();
|
|
//
|
|
// //
|
|
// // Free list of allocated buffers
|
|
// //
|
|
// // Check for process id and process name, if not match then we don't emulate it
|
|
// //
|
|
// TempList = &g_TransparentModeMeasurements->ProcessList;
|
|
// while (&g_TransparentModeMeasurements->ProcessList != TempList->Flink)
|
|
// {
|
|
// TempList = TempList->Flink;
|
|
// PTRANSPARENCY_PROCESS ProcessDetails = (PTRANSPARENCY_PROCESS)CONTAINING_RECORD(TempList, TRANSPARENCY_PROCESS, OtherProcesses);
|
|
//
|
|
// //
|
|
// // Save the buffer so we can de-allocate it
|
|
// //
|
|
// BufferToDeAllocate = ProcessDetails->BufferAddress;
|
|
//
|
|
// //
|
|
// // We have to remove the event from the list
|
|
// //
|
|
// RemoveEntryList(&ProcessDetails->OtherProcesses);
|
|
//
|
|
// //
|
|
// // Free the buffer
|
|
// //
|
|
// PlatformMemFreePool(BufferToDeAllocate);
|
|
// }
|
|
//
|
|
// //
|
|
// // Deallocate the measurements buffer
|
|
// //
|
|
// PlatformMemFreePool(g_TransparentModeMeasurements);
|
|
// g_TransparentModeMeasurements = NULL;
|
|
//
|
|
// return STATUS_SUCCESS;
|
|
// }
|
|
// else
|
|
// {
|
|
// return STATUS_UNSUCCESSFUL;
|
|
// }
|
|
// }
|
|
//
|
|
// /**
|
|
// * @brief VM-Exit handler for different exit reasons
|
|
// * @details Should be called from vmx-root
|
|
// *
|
|
// * @param Regs The virtual processor's state of registers
|
|
// * @param ExitReason Exit Reason
|
|
// * @return BOOLEAN Return True we should emulate RDTSCP
|
|
// * or return false if we should not emulate RDTSCP
|
|
// */
|
|
// BOOLEAN
|
|
// TransparentModeStart(GUEST_REGS * Regs, UINT32 ExitReason)
|
|
// {
|
|
// UINT32 Aux = 0;
|
|
// PLIST_ENTRY TempList = 0;
|
|
// PCHAR CurrentProcessName = 0;
|
|
// UINT32 CurrentProcessId;
|
|
// UINT64 CurrrentTime;
|
|
// HANDLE CurrentThreadId;
|
|
// BOOLEAN Result = TRUE;
|
|
// BOOLEAN IsProcessOnTransparencyList = FALSE;
|
|
//
|
|
// //
|
|
// // Save the current time
|
|
// //
|
|
// CurrrentTime = __rdtscp(&Aux);
|
|
//
|
|
// //
|
|
// // Save time of vm-exit on each logical processor separately
|
|
// //
|
|
// VCpu->TransparencyState.PreviousTimeStampCounter = CurrrentTime;
|
|
//
|
|
// //
|
|
// // Find the current process id and name
|
|
// //
|
|
// CurrentProcessId = HANDLE_TO_UINT32(PsGetCurrentProcessId());
|
|
// CurrentProcessName = CommonGetProcessNameFromProcessControlBlock(PsGetCurrentProcess());
|
|
//
|
|
// //
|
|
// // Check for process id and process name, if not match then we don't emulate it
|
|
// //
|
|
// TempList = &g_TransparentModeMeasurements->ProcessList;
|
|
// while (&g_TransparentModeMeasurements->ProcessList != TempList->Flink)
|
|
// {
|
|
// TempList = TempList->Flink;
|
|
// PTRANSPARENCY_PROCESS ProcessDetails = (PTRANSPARENCY_PROCESS)CONTAINING_RECORD(TempList, TRANSPARENCY_PROCESS, OtherProcesses);
|
|
// if (ProcessDetails->TrueIfProcessIdAndFalseIfProcessName)
|
|
// {
|
|
// //
|
|
// // This entry is process id
|
|
// //
|
|
// if (ProcessDetails->ProcessId == CurrentProcessId)
|
|
// {
|
|
// //
|
|
// // Let the transparency handler to handle it
|
|
// //
|
|
// IsProcessOnTransparencyList = TRUE;
|
|
// break;
|
|
// }
|
|
// }
|
|
// else
|
|
// {
|
|
// //
|
|
// // This entry is a process name
|
|
// //
|
|
// if (CurrentProcessName != NULL && CommonIsStringStartsWith(CurrentProcessName, ProcessDetails->ProcessName))
|
|
// {
|
|
// //
|
|
// // Let the transparency handler to handle it
|
|
// //
|
|
// IsProcessOnTransparencyList = TRUE;
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// //
|
|
// // Check whether we find this process on transparency list or not
|
|
// //
|
|
// if (!IsProcessOnTransparencyList)
|
|
// {
|
|
// //
|
|
// // No, we didn't let's do the normal tasks
|
|
// //
|
|
// return TRUE;
|
|
// }
|
|
//
|
|
// //
|
|
// // Get current thread Id
|
|
// //
|
|
// CurrentThreadId = PsGetCurrentThreadId();
|
|
//
|
|
// //
|
|
// // Check whether we are in new thread or in previous thread
|
|
// //
|
|
// if (VCpu->TransparencyState.ThreadId != CurrentThreadId)
|
|
// {
|
|
// //
|
|
// // It's a new thread Id reset everything
|
|
// //
|
|
// VCpu->TransparencyState.ThreadId = CurrentThreadId;
|
|
// VCpu->TransparencyState.RevealedTimeStampCounterByRdtsc = NULL64_ZERO;
|
|
// VCpu->TransparencyState.CpuidAfterRdtscDetected = FALSE;
|
|
// }
|
|
//
|
|
// //
|
|
// // Now, it's time to check and play with RDTSC/P and CPUID
|
|
// //
|
|
//
|
|
// if (ExitReason == VMX_EXIT_REASON_EXECUTE_RDTSC || ExitReason == VMX_EXIT_REASON_EXECUTE_RDTSCP)
|
|
// {
|
|
// if (VCpu->TransparencyState.RevealedTimeStampCounterByRdtsc == NULL64_ZERO)
|
|
// {
|
|
// //
|
|
// // It's a timing and the previous time for the thread is null
|
|
// // so we need to save the time (maybe) for future use
|
|
// //
|
|
// VCpu->TransparencyState.RevealedTimeStampCounterByRdtsc = CurrrentTime;
|
|
// }
|
|
// else if (VCpu->TransparencyState.CpuidAfterRdtscDetected == TRUE)
|
|
// {
|
|
// //
|
|
// // Someone tries to know about the hypervisor
|
|
// // let's play with them
|
|
// //
|
|
//
|
|
// // LogInfo("Possible RDTSC+CPUID+RDTSC");
|
|
// }
|
|
// else if (VCpu->TransparencyState.RevealedTimeStampCounterByRdtsc != NULL64_ZERO &&
|
|
// VCpu->TransparencyState.CpuidAfterRdtscDetected == FALSE)
|
|
// {
|
|
// //
|
|
// // It's a new rdtscp, let's save the new value
|
|
// //
|
|
// VCpu->TransparencyState.RevealedTimeStampCounterByRdtsc +=
|
|
// TransparentRandn((UINT32)g_TransparentModeMeasurements->CpuidAverage,
|
|
// (UINT32)g_TransparentModeMeasurements->CpuidStandardDeviation);
|
|
// }
|
|
//
|
|
// //
|
|
// // Adjust the rdtsc based on RevealedTimeStampCounterByRdtsc
|
|
// //
|
|
// Regs->rax = 0x00000000ffffffff &
|
|
// VCpu->TransparencyState.RevealedTimeStampCounterByRdtsc;
|
|
//
|
|
// Regs->rdx = 0x00000000ffffffff &
|
|
// (VCpu->TransparencyState.RevealedTimeStampCounterByRdtsc >> 32);
|
|
//
|
|
// //
|
|
// // Check if we need to adjust rcx as a result of rdtscp
|
|
// //
|
|
// if (ExitReason == VMX_EXIT_REASON_EXECUTE_RDTSCP)
|
|
// {
|
|
// Regs->rcx = 0x00000000ffffffff & Aux;
|
|
// }
|
|
// //
|
|
// // Shows that vm-exit handler should not emulate the RDTSC/P
|
|
// //
|
|
// Result = FALSE;
|
|
// }
|
|
// else if (ExitReason == VMX_EXIT_REASON_EXECUTE_CPUID &&
|
|
// VCpu->TransparencyState.RevealedTimeStampCounterByRdtsc != NULL64_ZERO)
|
|
// {
|
|
// //
|
|
// // The guy executed one or more CPUIDs after an rdtscp so we
|
|
// // need to add new cpuid value to previous timer and also
|
|
// // we need to store it somewhere to remember this behavior
|
|
// //
|
|
// VCpu->TransparencyState.RevealedTimeStampCounterByRdtsc +=
|
|
// TransparentRandn((UINT32)g_TransparentModeMeasurements->CpuidAverage,
|
|
// (UINT32)g_TransparentModeMeasurements->CpuidStandardDeviation);
|
|
//
|
|
// VCpu->TransparencyState.CpuidAfterRdtscDetected = TRUE;
|
|
// }
|
|
//
|
|
// return Result;
|
|
// }
|
|
//
|