Files

395 lines
12 KiB
C

#include <Ntifs.h>
#include <Ntddk.h>
//#include <aux_klib.h>
#include "driver.h"
#include "public.h"
_Use_decl_annotations_ DRIVER_INITIALIZE DriverEntry;
_Use_decl_annotations_ DRIVER_UNLOAD DriverUnload;
_Dispatch_type_(IRP_MJ_CREATE) DRIVER_DISPATCH irp_mj_create;
_Dispatch_type_(IRP_MJ_CLOSE) DRIVER_DISPATCH irp_mj_close;
_Dispatch_type_(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH irp_mj_device_control;
NTSTATUS queryVA(getPageInfoRequest* params, getPageInfoResponse* response);
NTSTATUS queryVAFromIRP(PIRP Irp);
NTSTATUS setPFNDatabase(setPFNDatabaseRequest* req);
NTSTATUS setPFNDatabaseFromIRP(PIRP Irp);
int isTableEntryValid(unsigned long long entry);
unsigned long long getChildTableFromTableEntry(unsigned long long entry);
__drv_requiresIRQL(APC_LEVEL) NTSTATUS readMemoryFromPhysical(unsigned long long address, char* errMsg, void* tableOut);
privateInfo prv;
// TODO: Get PFN structure info via PDBs instead of hardcoding it here.
struct PFN
{
// 0x00
unsigned long long padding1;
// 0x08
unsigned long long PTEAddress;
// 0x10
unsigned long long OriginalPte;
// 0x18
unsigned long long u2;
// 0x20 - u3
unsigned short referenceCount;
unsigned char e1;
unsigned char e3;
unsigned long e4; // or e2
// 0x28
unsigned long long u4;
};
_Use_decl_annotations_ NTSTATUS DriverEntry(_In_ struct _DRIVER_OBJECT *DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
NTSTATUS s;
UNICODE_STRING deviceName;
UNICODE_STRING DOSDeviceName;
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->DriverUnload = DriverUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = irp_mj_create;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = irp_mj_close;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = irp_mj_device_control;
// Initialise our 'private' data, shared throughout the driver
memset(&prv, 0, sizeof(privateInfo));
// Create our device and the DOS symlink to it, as usual
RtlInitUnicodeString(&deviceName, DEVICE_NAME);
s = IoCreateDevice(DriverObject, 0, &deviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &prv.deviceObject);
if (!NT_SUCCESS(s))
{
DbgPrint("Failed IoCreateDevice: 0x%08lx\n", s);
return s;
}
RtlInitUnicodeString(&DOSDeviceName, DOS_DEVICE_NAME);
s = IoCreateSymbolicLink(&DOSDeviceName, &deviceName);
if (!NT_SUCCESS(s))
{
IoDeleteDevice(prv.deviceObject);
DbgPrint("Failed IoCreateSymbolicLink: 0x%08lx\n", s);
return s;
}
return STATUS_SUCCESS;
}
VOID DriverUnload(_In_ struct _DRIVER_OBJECT *DriverObject)
{
UNICODE_STRING DOSDeviceName;
UNREFERENCED_PARAMETER(DriverObject);
RtlInitUnicodeString(&DOSDeviceName, DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&DOSDeviceName);
IoDeleteDevice(prv.deviceObject);
}
_Use_decl_annotations_ NTSTATUS irp_mj_create(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
_Use_decl_annotations_ NTSTATUS irp_mj_close(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
// FIXME: Make sure all pending requests on this handle are complete
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS setPFNDatabaseFromIRP(PIRP Irp)
{
PIO_STACK_LOCATION irpStack;
setPFNDatabaseRequest inputBuffer;
int bytesReturned;
NTSTATUS s;
bytesReturned = 0;
irpStack = IoGetCurrentIrpStackLocation(Irp);
if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(setPFNDatabaseRequest))
{
s = STATUS_BUFFER_TOO_SMALL;
goto out;
}
memcpy(&inputBuffer, Irp->AssociatedIrp.SystemBuffer, sizeof(setPFNDatabaseRequest));
s = setPFNDatabase(&inputBuffer);
out:
Irp->IoStatus.Status = s;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return s;
}
_Use_decl_annotations_ NTSTATUS irp_mj_device_control(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION irpStack;
unsigned long functionCode;
UNREFERENCED_PARAMETER(DeviceObject);
irpStack = IoGetCurrentIrpStackLocation(Irp);
functionCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch (functionCode)
{
case IOCTL_DRIVER_QUERY_VA:
return queryVAFromIRP(Irp);
case IOCTL_DRIVER_SET_PFN_DATABASE:
return setPFNDatabaseFromIRP(Irp);
default:
DbgPrint("IRP_MJ_DEVICE_CONTROL: Unrecognised function code 0x%08lx\n", functionCode);
}
Irp->IoStatus.Status = STATUS_ILLEGAL_FUNCTION;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS setPFNDatabase(setPFNDatabaseRequest* req)
{
PVOID MmPfnDatabaseUnsafe;
unsigned long long MmPfnDatabase;
MM_COPY_ADDRESS src;
SIZE_T bytesRead;
NTSTATUS s;
int didExcept;
unsigned long numberOfPFNs = 0x2000; // FIXME
// Since we don't know the base address of ntdll (and don't want to call any undocumented stuff to get it), we accept an offset to MmPfnDatabase
// from an exported entry (ExAllocatePoolWithTag). Since this comes from userspace, we still need to santise it as best we can. We can't make it
// foolproof but we can do some basic checks. Since we only ever read the PFN database via MmCopyMemory, it should be safe for userspace to give
// us a bad address, anyway.
MmPfnDatabaseUnsafe = (PVOID)( ((unsigned long long)ExAllocatePoolWithTag) - req->offsetToMmPfnDatabaseInNtDllFromExAllocatePoolWithTag );
// We now have a pointer to MmPfnDatabase, which is itself a pointer to the first PFN. We should try to read it, and find the PFN DB base.
src.VirtualAddress = MmPfnDatabaseUnsafe;
#pragma warning( push )
#pragma warning( disable : 6001 ) // VS things 'MmPfnDatabase' can be uninitialized in this call. It cannot.
s = MmCopyMemory(&MmPfnDatabase, src, sizeof(PVOID), MM_COPY_MEMORY_VIRTUAL, &bytesRead);
#pragma warning( pop )
if (!NT_SUCCESS(s))
{
DbgPrint("Cannot read MmPfnDatabase pointer %p as provided by userspace\n", MmPfnDatabaseUnsafe);
return s;
}
if (bytesRead != sizeof(PVOID))
{
DbgPrint("Short read of read MmPfnDatabase pointer %p as provided by userspace (read %llu of %llu bytes)\n", MmPfnDatabaseUnsafe, bytesRead, sizeof(PVOID));
return STATUS_ACCESS_VIOLATION;
}
// Now we have the PFN database pointer, and we can do some basic checks on it.
// It should be aligned on a 4K boundary (I think?). This is totally from observation
// and may be incorrect.
if ((MmPfnDatabase & 0x0000000000000fff) != 0)
{
DbgPrint("Dereferenced MmPfnDatabase pointer is not correctly aligned?\n");
return STATUS_BAD_DATA;
}
// This should not be in a user-space buffer
__try
{
ProbeForRead((PVOID)MmPfnDatabase, sizeof(struct PFN) * numberOfPFNs, 1);
didExcept = FALSE;
}
#pragma warning( push )
#pragma warning( disable : 6320 ) // "warning C6320: Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER. This might mask exceptions that were not intended to be handled."
__except (EXCEPTION_EXECUTE_HANDLER)
#pragma warning( pop )
{
didExcept = TRUE;
}
if (!didExcept)
{
DbgPrint("Dereferenced MmPfnDatabase pointer is in userspace\n");
return STATUS_BAD_DATA;
}
// TODO: more checks. We're giving userspace the ability to give kernel space a pointer here
// so we should be as careful as we possibly can be.
// OK, all our checks passed!
prv.PFNDatabase = MmPfnDatabase;
DbgPrint("MmPfnDatabase is 0x%016llx\n", prv.PFNDatabase);
return STATUS_SUCCESS;
}
NTSTATUS queryVAFromIRP(PIRP Irp)
{
PIO_STACK_LOCATION irpStack;
getPageInfoRequest inputBuffer;
getPageInfoResponse* outputBuffer;
int bytesReturned;
NTSTATUS s;
bytesReturned = 0;
irpStack = IoGetCurrentIrpStackLocation(Irp);
if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(getPageInfoRequest))
{
s = STATUS_BUFFER_TOO_SMALL;
goto out;
}
memcpy(&inputBuffer, Irp->AssociatedIrp.SystemBuffer, sizeof(getPageInfoRequest));
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(getPageInfoResponse) * inputBuffer.numberOfPagesToCheck)
{
s = STATUS_BUFFER_TOO_SMALL;
goto out;
}
outputBuffer = (getPageInfoResponse*)Irp->AssociatedIrp.SystemBuffer;
memset(outputBuffer, 0, sizeof(getPageInfoResponse) * inputBuffer.numberOfPagesToCheck);
s = queryVA(&inputBuffer, outputBuffer);
if (NT_SUCCESS(s))
bytesReturned = sizeof(getPageInfoResponse) * inputBuffer.numberOfPagesToCheck;
out:
Irp->IoStatus.Status = s;
Irp->IoStatus.Information = bytesReturned;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return s;
}
NTSTATUS queryVA(getPageInfoRequest* params, getPageInfoResponse* response)
{
PMDL mdl;
PPFN_NUMBER pfnArray;
unsigned int pfnArrayCount;
unsigned int pfnIdx;
struct PFN* MmPfnDatabase = (struct PFN*)prv.PFNDatabase;
struct PFN ourPFN;
unsigned long long pte;
KAPC_STATE state;
PEPROCESS eprocess;
NTSTATUS s;
MM_COPY_ADDRESS srcAddress;
PHYSICAL_ADDRESS phys;
SIZE_T numRead;
UNREFERENCED_PARAMETER(response);
if (params->numberOfPagesToCheck == 0)
{
DbgPrint("Asked to scan 0 pages\n");
return STATUS_INVALID_PARAMETER;
}
mdl = IoAllocateMdl((PVOID)params->pageToCheck, 0x1000 * params->numberOfPagesToCheck, FALSE, FALSE, NULL);
if (!mdl)
{
return STATUS_NO_MEMORY;
}
s = PsLookupProcessByProcessId((HANDLE)params->targetPID, &eprocess);
if (!NT_SUCCESS(s))
{
IoFreeMdl(mdl);
DbgPrint("PsLookupProcesByProcessId failed for PID 0x%04lx: 0x%08lx\n", params->targetPID, s);
return s;
}
KeStackAttachProcess(eprocess, &state);
__try
{
MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
}
#pragma warning( push )
#pragma warning( disable : 6320 ) // "warning C6320: Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER. This might mask exceptions that were not intended to be handled."
__except (EXCEPTION_EXECUTE_HANDLER)
#pragma warning( pop )
{
s = STATUS_BAD_DATA;
goto out;
}
pfnArray = MmGetMdlPfnArray(mdl);
pfnArrayCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(MmGetMdlVirtualAddress(mdl), MmGetMdlByteCount(mdl));
// Read the PTE from the PFN database using MmCopyMemory, in case we have the pfn database base address wrong.
// MmCopyMemory won't let me read us the memory by VA - not 100% sure why but I suspect because it is checked
// against the PFN table and no mapping is found (?) - so we just translate to physical address and read that
// instead.
for (pfnIdx = 0; pfnIdx < pfnArrayCount; pfnIdx++)
{
phys = MmGetPhysicalAddress(&MmPfnDatabase[pfnArray[pfnIdx]]);
srcAddress.PhysicalAddress.QuadPart = phys.QuadPart;
#pragma warning( push )
#pragma warning( disable : 6001 ) // VS things 'ourPFN' can be uninitialized in this call. It cannot.
s = MmCopyMemory(&ourPFN, srcAddress, sizeof(struct PFN), MM_COPY_MEMORY_PHYSICAL, &numRead);
#pragma warning( pop )
if (!NT_SUCCESS(s) || numRead != sizeof(struct PFN))
{
DbgPrint("Failed to read PFN from PFN database at %p (%p[0x%16llx]): NTSTATUS 0x%08lx, transferred %llu of %llu bytes\n", srcAddress.VirtualAddress, MmPfnDatabase, pfnArray[pfnIdx], s, numRead, sizeof(struct PFN));
if (NT_SUCCESS(s))
s = STATUS_PARTIAL_COPY;
goto out;
}
pte = ourPFN.PTEAddress;
// DbgPrint("VA 0x%016llx PFN %p\n", params->pageToCheck, &MmPfnDatabase[pfn[0]]);
response[pfnIdx].isValid = TRUE; // TODO
response[pfnIdx].isDirty = (ourPFN.e1 >> 4) & 0x01;
}
s = STATUS_SUCCESS;
out:
MmUnlockPages(mdl);
IoFreeMdl(mdl);
KeUnstackDetachProcess(&state);
return s;
}
__drv_requiresIRQL(APC_LEVEL)
NTSTATUS readMemoryFromPhysical(unsigned long long address, char* errMsg, void* tableOut)
{
NTSTATUS s;
MM_COPY_ADDRESS srcAddress;
SIZE_T numRead;
SIZE_T bytesToRead = 0x200 * sizeof(unsigned long long);
srcAddress.PhysicalAddress.QuadPart = address;
s = MmCopyMemory(tableOut, srcAddress, bytesToRead, MM_COPY_MEMORY_PHYSICAL, &numRead);
if (!NT_SUCCESS(s) || numRead != bytesToRead)
{
DbgPrint("Failed to MmCopyMemory table '%s' from physical location 0x%08llx: 0x%08lx (read 0x%08llx of 0x%08llx bytes)\n", errMsg, srcAddress.PhysicalAddress.QuadPart, s, numRead, bytesToRead);
return STATUS_UNSUCCESSFUL;
}
return STATUS_SUCCESS;
}
unsigned long long getChildTableFromTableEntry(unsigned long long entry)
{
// TODO/FIXME: We should honour the size of the child table pointer here, which is set as
// M-12 (M being set in the sillicon I think). Bit 63 is XD, and 62-52 is ignored, but 51
// through M is reserved by the sillicon so we should ignore it..
return ((unsigned long long)((entry & ~(0xfff0'0000'0000'0FFF)) ));
}
int isTableEntryValid(unsigned long long entry)
{
return (entry & 0x01) != 0;
}