mirror of
https://github.com/intelpt/WindowsIntelPT
synced 2026-06-08 14:50:14 +00:00
0ab296f21c
Implemented support for HyperV Root Partitions (finally)
320 lines
11 KiB
C++
320 lines
11 KiB
C++
/**********************************************************************
|
|
* Windows Intel Processor Trace (PT) Driver
|
|
* Filename: DriverEntry.cpp
|
|
* Implement Driver Entry point and startup functions
|
|
* Last revision: 01/06/2016
|
|
*
|
|
* Copyright© 2016 Andrea Allievi, Richard Johnson
|
|
* TALOS Research and Intelligence Group and Microsoft Ltd
|
|
* All right reserved
|
|
**********************************************************************/
|
|
#include "stdafx.h"
|
|
#include "DriverEntry.h"
|
|
#include "DriverIo.h"
|
|
#include "Debug.h"
|
|
#include "UndocNt.h"
|
|
#include "IntelPtXSave.h"
|
|
#include <hv.h>
|
|
|
|
const LPTSTR g_lpDevName = L"\\Device\\WindowsIntelPtDev";
|
|
const LPTSTR g_lpDosDevName = L"\\DosDevices\\WindowsIntelPtDev";
|
|
|
|
// The global driver data
|
|
DRIVER_GLOBAL_DATA * g_pDrvData = NULL;
|
|
|
|
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
|
|
{
|
|
UNREFERENCED_PARAMETER(pRegPath);
|
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|
KAFFINITY activeProcessorsMask = 0; // The active processors mask
|
|
DWORD dwNumOfProcs = 0; // Number of system processors
|
|
DWORD dwBuffSize = 0; // The global driver data size in bytes
|
|
UNICODE_STRING devNameString = { 0 }; // The I/O device name
|
|
UNICODE_STRING dosDevNameString = { 0 }; // The DOS device name (Usermode access)
|
|
PDEVICE_OBJECT pDevObj = NULL; // The device object
|
|
INTEL_PT_CAPABILITIES ptCap = { 0 }; // The Intel PT Capabilities for this processor
|
|
|
|
// Debug helper
|
|
DBG_BREAK();
|
|
EnableDebugOutput();
|
|
|
|
// Get the total number of system processors
|
|
dwNumOfProcs = KeQueryActiveProcessorCount(&activeProcessorsMask);
|
|
|
|
// Allocate memory for my own global data
|
|
dwBuffSize = sizeof(DRIVER_GLOBAL_DATA) + (dwNumOfProcs * sizeof(PER_PROCESSOR_PT_DATA));
|
|
g_pDrvData = (PDRIVER_GLOBAL_DATA)ExAllocatePoolWithTag(NonPagedPool, dwBuffSize, MEMTAG);
|
|
if (!g_pDrvData) return STATUS_INSUFFICIENT_RESOURCES;
|
|
RtlZeroMemory(g_pDrvData, dwBuffSize);
|
|
g_pDrvData->dwNumProcs = dwNumOfProcs;
|
|
|
|
// Check Microsoft HyperV presence:
|
|
if (NT_SUCCESS(DetectMicrosoftHyperV(NULL))) {
|
|
// HyperV detected, allocate and initialize all the needed data
|
|
ntStatus = InitGlobalHv();
|
|
ASSERT(NT_SUCCESS(ntStatus));
|
|
g_pDrvData->IsUnderHyperV = TRUE;
|
|
if (g_pDrvData->HyperV_Data.Info.Features.PartitionPrivilegeMask.CreatePartitions == 0)
|
|
DbgPrint("[" DRV_NAME "] Info: Intel Processor Trace driver is running into an HyperV child VM. \r\n");
|
|
}
|
|
else
|
|
g_pDrvData->IsUnderHyperV = FALSE;
|
|
|
|
// Check PT support
|
|
ntStatus = CheckIntelPtSupport(&ptCap);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
DbgPrint("[" DRV_NAME "] Intel Processor Trace is not supported on this system. Exiting...\r\n");
|
|
RevertToDefaultDbgSettings();
|
|
ExFreePool(g_pDrvData);
|
|
return ntStatus;
|
|
}
|
|
if (ptCap.numOfAddrRanges < 4) {
|
|
DbgPrint("[" DRV_NAME "] Info: The processor %i supports maximum of %i IP ranges.\r\n", KeGetCurrentProcessorNumber(), ptCap.numOfAddrRanges);
|
|
}
|
|
|
|
#if ENABLE_EXPERIMENTAL_XSAVE
|
|
ntStatus = InitializeCpusXSaveArea();
|
|
#endif
|
|
|
|
// Create a Pmi Event name and register the PMI interrupt
|
|
CreateSharedPmiEvent(INTEL_PT_PMI_EVENT_NAME);
|
|
RegisterPmiInterrupt();
|
|
// Initialize the user-mode callbacks list
|
|
InitializeListHead(&g_pDrvData->userCallbackList);
|
|
KeInitializeSpinLock(&g_pDrvData->userCallbackListLock);
|
|
|
|
// Build the controller device
|
|
RtlInitUnicodeString(&devNameString, g_lpDevName);
|
|
RtlInitUnicodeString(&dosDevNameString, g_lpDosDevName);
|
|
|
|
// XXX: require admin to prevent side channel attacks on 3rd party programs (IoCreateDeviceSecure)
|
|
ntStatus = IoCreateDevice(pDriverObject, 0, &devNameString, FILE_DEVICE_UNKNOWN,
|
|
FILE_DEVICE_SECURE_OPEN, FALSE, &pDevObj);
|
|
|
|
if (NT_SUCCESS(ntStatus)) {
|
|
ntStatus = IoCreateSymbolicLink(&dosDevNameString, &devNameString);
|
|
g_pDrvData->pMainDev = pDevObj;
|
|
}
|
|
|
|
if (!NT_SUCCESS(ntStatus)) {
|
|
if (g_pDrvData->pMainDev) IoDeleteDevice(g_pDrvData->pMainDev);
|
|
ExFreePool(g_pDrvData);
|
|
return ntStatus;
|
|
}
|
|
|
|
// Put the needed routines in the NonPaged pool
|
|
MmLockPagableCodeSection(CheckIntelPtSupport);
|
|
|
|
// Initialize Driver dispatch routine
|
|
for (DWORD i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
|
|
pDriverObject->MajorFunction[i] = DeviceUnsupported;
|
|
|
|
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceIoControl;
|
|
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DevicePassThrough;
|
|
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DevicePassThrough;
|
|
pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = DevicePassThrough;
|
|
pDriverObject->MajorFunction[IRP_MJ_READ] = DeviceUnsupported;
|
|
pDriverObject->MajorFunction[IRP_MJ_WRITE] = DeviceUnsupported;
|
|
|
|
pDriverObject->DriverUnload = DriverUnload;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Create the shared PMI event
|
|
NTSTATUS CreateSharedPmiEvent(LPTSTR lpEvtName)
|
|
{
|
|
NTSTATUS ntStatus = STATUS_SUCCESS; // Returned NT_STATUS value
|
|
DWORD dwNameLen = 0; // Size in CHARs
|
|
HANDLE hEvent = NULL; // Handle to the named event
|
|
PKEVENT pEvent = NULL; // The EVENT object body
|
|
OBJECT_ATTRIBUTES oa = { 0 }; // The EVENT object attributes
|
|
UNICODE_STRING eventNameString = { 0 }; // The EVENT name string
|
|
TCHAR newName[COUNTOF(g_pDrvData->pmiEventName)] = { 0 };
|
|
|
|
dwNameLen = (DWORD)wcslen(lpEvtName);
|
|
if (!lpEvtName || dwNameLen < 2)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
// Preliminary buffer checks
|
|
if (lpEvtName[0] != L'\\') {
|
|
// Add the trailing "\BasedNamedObject\" (18 chars)
|
|
if ((dwNameLen + 1 + 18) > COUNTOF(g_pDrvData->pmiEventName))
|
|
return STATUS_INVALID_BUFFER_SIZE;
|
|
|
|
wcscpy_s(newName, COUNTOF(newName), L"\\BaseNamedObjects\\");
|
|
wcscat_s(newName, COUNTOF(newName), lpEvtName);
|
|
|
|
} else {
|
|
if ((dwNameLen + 1) > COUNTOF(g_pDrvData->pmiEventName))
|
|
return STATUS_INVALID_BUFFER_SIZE;
|
|
|
|
wcscpy_s(newName, COUNTOF(newName), lpEvtName);
|
|
}
|
|
|
|
if (g_pDrvData->pPmiEvent) {
|
|
KeResetEvent(g_pDrvData->pPmiEvent);
|
|
// Delete the object (DO NOT use ExFreePool, the Object Manager has allocated this)
|
|
ObDereferenceObject(g_pDrvData->pPmiEvent);
|
|
if (g_pDrvData->hPmiEvent) ZwClose(g_pDrvData->hPmiEvent);
|
|
g_pDrvData->pPmiEvent = NULL;
|
|
RtlZeroMemory(g_pDrvData->pmiEventName, COUNTOF(g_pDrvData->pmiEventName));
|
|
}
|
|
|
|
RtlInitUnicodeString(&eventNameString, newName);
|
|
InitializeObjectAttributes(&oa, &eventNameString, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
|
|
// Create the named event
|
|
ntStatus = ZwCreateEvent(&hEvent, EVENT_ALL_ACCESS, &oa, SynchronizationEvent, FALSE);
|
|
|
|
if (NT_SUCCESS(ntStatus)) {
|
|
ntStatus = ObReferenceObjectByHandle(hEvent, EVENT_ALL_ACCESS, *ExEventObjectType, KernelMode, (PVOID*)&pEvent, NULL);
|
|
if (NT_SUCCESS(ntStatus)) {
|
|
RtlCopyMemory(g_pDrvData->pmiEventName, newName, COUNTOF(g_pDrvData->pmiEventName));
|
|
g_pDrvData->pPmiEvent = pEvent;
|
|
g_pDrvData->hPmiEvent = hEvent;
|
|
}
|
|
else
|
|
ZwClose(hEvent);
|
|
}
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
// Initialize each CPU XSave area (Experimental XSAVE support)
|
|
NTSTATUS InitializeCpusXSaveArea() {
|
|
NTSTATUS ntStatus = STATUS_SUCCESS; // Returned NTSTATUS value
|
|
KAFFINITY activeProcessorsMask = 0; // The active processors mask
|
|
DWORD dwNumOfProcs = 0; // Total number of processor in the system
|
|
DWORD dwAreaSize = 0; // The maximum XSAVE area size
|
|
|
|
// Get the total number of system processors
|
|
dwNumOfProcs = KeQueryActiveProcessorCount(&activeProcessorsMask);
|
|
|
|
// Check the XSAVE support for this processor (is enough)
|
|
ntStatus = CheckPtXSaveSupport(&dwAreaSize, NULL, NULL);
|
|
if (!NT_SUCCESS(ntStatus)) return ntStatus;
|
|
|
|
for (int i = 0; i < (int)dwNumOfProcs; i++) {
|
|
PER_PROCESSOR_PT_DATA & pCurCpuData = g_pDrvData->procData[i];
|
|
LPVOID lpBuff = NULL;
|
|
DWORD dwBuffSize = 0;
|
|
|
|
if (dwAreaSize > PAGE_SIZE)
|
|
dwBuffSize = dwAreaSize;
|
|
else
|
|
// I should allocate a PAGE_SIZE buffer to obey at the 64-BYTE aligment requirement
|
|
dwBuffSize = 0x1000;
|
|
|
|
lpBuff = ExAllocatePoolWithTag(NonPagedPool, dwBuffSize, MEMTAG);
|
|
if (((ULONG_PTR)lpBuff & 0x00FF) != 0) {
|
|
// I am too lazy to use the MDL (see MmAllocatePagesForMdl)
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
pCurCpuData.lpXSaveArea = lpBuff;
|
|
pCurCpuData.dwXSaveAreaSize = dwAreaSize;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID UnloadPtDpc(struct _KDPC *Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)
|
|
{
|
|
UNREFERENCED_PARAMETER(Dpc);
|
|
UNREFERENCED_PARAMETER(SystemArgument1);
|
|
UNREFERENCED_PARAMETER(SystemArgument2);
|
|
|
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|
ULONG dwCurProc = 0;
|
|
|
|
dwCurProc = KeGetCurrentProcessorNumber();
|
|
|
|
DbgPrint("[" DRV_NAME "] Stopping and unloading the Trace for CPU #%i...\r\n", dwCurProc);
|
|
ntStatus = StopAndDisablePt();
|
|
ntStatus = FreeCpuResources(dwCurProc);
|
|
|
|
if (DeferredContext)
|
|
{
|
|
// This is a pointer to the KEVENT, signal it without wait anything (It could be done at DISPATCH IRQL)
|
|
KeSetEvent((PRKEVENT)DeferredContext, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
// END
|
|
}
|
|
|
|
// XXX: This will currently bugcheck if the IOCTL is called from within the traced process
|
|
VOID DriverUnload(PDRIVER_OBJECT pDrvObj)
|
|
{
|
|
UNREFERENCED_PARAMETER(pDrvObj);
|
|
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
|
|
UNICODE_STRING dosDevNameString = { 0 };
|
|
ULONG dwCurProc = 0;
|
|
KDPC unloadDpc = { 0 };
|
|
|
|
PAGED_CODE();
|
|
|
|
dwCurProc = KeGetCurrentProcessorNumber();
|
|
for (DWORD i = 0; i < g_pDrvData->dwNumProcs; i++)
|
|
{
|
|
KEVENT kUnloadEvent = { 0 };
|
|
PER_PROCESSOR_PT_DATA * procData = &g_pDrvData->procData[i];;
|
|
KeInitializeEvent(&kUnloadEvent, NotificationEvent, FALSE);
|
|
|
|
// This will fail if called from within the traced process
|
|
ntStatus = UnmapTraceBuffToUserVa(i);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
// Memory mappings are inconsistent so we bugcheck
|
|
KeBugCheckEx(PROCESS_HAS_LOCKED_PAGES, 0x00, (ULONG_PTR)procData->lpMappedProc, procData->pPtBuffDesc->qwBuffSize / PAGE_SIZE, 0);
|
|
}
|
|
|
|
// Queue the unload DPC
|
|
KeInitializeDpc(&unloadDpc, UnloadPtDpc, (LPVOID)&kUnloadEvent);
|
|
KeSetTargetProcessorDpc(&unloadDpc, (CCHAR)i);
|
|
KeInsertQueueDpc(&unloadDpc, NULL, NULL);
|
|
|
|
KeWaitForSingleObject(&kUnloadEvent, Executive, KernelMode, FALSE, NULL);
|
|
|
|
// Free the XSAVE area (if any)
|
|
if (procData->lpXSaveArea) {
|
|
ExFreePool(procData->lpXSaveArea);
|
|
procData->dwXSaveAreaSize = 0;
|
|
procData->lpXSaveArea = NULL;
|
|
}
|
|
}
|
|
|
|
// Unload each registered User-mode PMI Callback
|
|
ClearAndFreePmiCallbackList();
|
|
|
|
// Unload the device object and the Symbolic Link
|
|
if (g_pDrvData->pMainDev)
|
|
{
|
|
// Delete the symbolic Link
|
|
RtlInitUnicodeString(&dosDevNameString, g_lpDosDevName);
|
|
IoDeleteSymbolicLink(&dosDevNameString);
|
|
IoDeleteDevice(g_pDrvData->pMainDev);
|
|
}
|
|
|
|
// Unload HyperV data
|
|
if (g_pDrvData->IsUnderHyperV)
|
|
DestroyGlobalHv();
|
|
|
|
// uninstall PMI
|
|
if (g_pDrvData->bPmiInstalled)
|
|
UnregisterPmiInterrupt();
|
|
|
|
// delete the PMI event
|
|
if (g_pDrvData->hPmiEvent)
|
|
ZwClose(g_pDrvData->hPmiEvent);
|
|
g_pDrvData->hPmiEvent = NULL;
|
|
|
|
if (g_pDrvData->pPmiEvent)
|
|
ObDereferenceObject(g_pDrvData->pPmiEvent);
|
|
g_pDrvData->pPmiEvent = NULL;
|
|
|
|
if (g_pDrvData)
|
|
ExFreePool(g_pDrvData);
|
|
|
|
DbgPrint("[" DRV_NAME "] driver successfully unloaded.");
|
|
RevertToDefaultDbgSettings();
|
|
} |