mirror-ac/driver/hw.c
2024-08-04 16:30:31 +10:00

356 lines
No EOL
10 KiB
C

#include "hw.h"
#include "crypt.h"
#include "imports.h"
#include "lib/stdlib.h"
#include "modules.h"
#define PCI_VENDOR_ID_OFFSET 0x00
#define PCI_DEVICE_ID_OFFSET 0x02
#define FLAGGED_DEVICE_ID_COUNT 2
USHORT FLAGGED_DEVICE_IDS[FLAGGED_DEVICE_ID_COUNT] = {
0x0666, // default PCIe Squirrel DeviceID (used by PCI Leech)
0xffff};
typedef NTSTATUS (*PCI_DEVICE_CALLBACK)(
_In_ PDEVICE_OBJECT DeviceObject, _In_opt_ PVOID Context);
/*
* Every PCI device has a set of registers commonly referred to as the PCI
* configuration space. In modern PCI-e devices an extended configuration space
* was implemented. These configuration spaces are mapped into main memory and
* this allows us to read/write to the registers.
*
* The configuration space consists of a standard header, containing information
* such as the DeviceID, VendorID, Status and so on. Below is the header schema
* including offsets.
*
* | Offset 0x00: Header Type
* | Offset 0x01: Multi-Function Device Indicator
* | Offset 0x02: Device ID (Low Byte)
* | Offset 0x03: Device ID (High Byte)
* | Offset 0x04: Status Register (16 bits)
* | Offset 0x06: Command Register (16 bits)
* | Offset 0x08: Class Code
* | Offset 0x09: Subclass Code
* | Offset 0x0A: Prog IF (Programming Interface)
* | Offset 0x0B: Revision ID
* | Offset 0x0C: BIST (Built-in Self-Test)
* | Offset 0x0D: Header Type (Secondary)
* | Offset 0x0E: Latency Timer
* | Offset 0x0F: Cache Line Size
* | Offset 0x10: Base Address Register 0 (BAR0) - 32 bits
* | Offset 0x14: Base Address Register 1 (BAR1) - 32 bits
* | Offset 0x18: Base Address Register 2 (BAR2) - 32 bits
* | Offset 0x1C: Base Address Register 3 (BAR3) - 32 bits
* | Offset 0x20: Base Address Register 4 (BAR4) - 32 bits
* | Offset 0x24: Base Address Register 5 (BAR5) - 32 bits
* | Offset 0x28: Cardbus CIS Pointer (for Cardbus bridges)
* | Offset 0x2C: Subsystem Vendor ID
* | Offset 0x2E: Subsystem ID
* | Offset 0x30: Expansion ROM Base Address
* | Offset 0x34: Reserved
* | Offset 0x38: Reserved
* | Offset 0x3C: Max_Lat (Maximum Latency)
* | Offset 0x3D: Min_Gnt (Minimum Grant)
* | Offset 0x3E: Interrupt Pin
* | Offset 0x3F: Interrupt Line
*
* We can use this to then query important information from PCI devices within
* the device tree. To keep up with modern windows kernel programming, we can
* make use of the IRP_MN_READ_CONFIG code, which as the name suggests, reads
* from a PCI devices configuration space.
*/
STATIC
NTSTATUS
QueryPciDeviceConfigurationSpace(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ UINT32 Offset,
_Out_opt_ PVOID Buffer,
_In_ UINT32 BufferLength)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
KEVENT event = {0};
IO_STATUS_BLOCK io = {0};
PIRP irp = NULL;
PIO_STACK_LOCATION packet = NULL;
if (BufferLength == 0)
return STATUS_BUFFER_TOO_SMALL;
KeInitializeEvent(&event, NotificationEvent, FALSE);
/*
* we dont need to free this IRP as the IO manager will free it when the
* request is completed
*/
irp = IoBuildSynchronousFsdRequest(
IRP_MJ_PNP,
DeviceObject,
NULL,
0,
NULL,
&event,
&io);
if (!irp) {
DEBUG_ERROR("IoBuildSynchronousFsdRequest failed with no status.");
return STATUS_INSUFFICIENT_RESOURCES;
}
packet = IoGetNextIrpStackLocation(irp);
packet->MinorFunction = IRP_MN_READ_CONFIG;
packet->Parameters.ReadWriteConfig.WhichSpace = PCI_WHICHSPACE_CONFIG;
packet->Parameters.ReadWriteConfig.Offset = Offset;
packet->Parameters.ReadWriteConfig.Buffer = Buffer;
packet->Parameters.ReadWriteConfig.Length = BufferLength;
status = IoCallDriver(DeviceObject, irp);
if (status == STATUS_PENDING) {
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
status = io.Status;
}
if (!NT_SUCCESS(status))
DEBUG_ERROR(
"Failed to read configuration space with status %x",
status);
return status;
}
/*
* NOTE: Caller is responsible for freeing the array.
*/
STATIC
NTSTATUS
EnumerateDriverObjectDeviceObjects(
_In_ PDRIVER_OBJECT DriverObject,
_Out_ PDEVICE_OBJECT** DeviceObjectArray,
_Out_ PUINT32 ArrayEntries)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
UINT32 object_count = 0;
PDEVICE_OBJECT* buffer = NULL;
UINT32 buffer_size = 0;
*DeviceObjectArray = NULL;
*ArrayEntries = 0;
status = IoEnumerateDeviceObjectList(DriverObject, NULL, 0, &object_count);
if (status != STATUS_BUFFER_TOO_SMALL) {
DEBUG_ERROR(
"IoEnumerateDeviceObjectList failed with status %x",
status);
return status;
}
buffer_size = object_count * sizeof(UINT64);
buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, buffer_size, POOL_TAG_HW);
if (!buffer)
return STATUS_INSUFFICIENT_RESOURCES;
status = IoEnumerateDeviceObjectList(
DriverObject,
buffer,
buffer_size,
&object_count);
if (!NT_SUCCESS(status)) {
DEBUG_ERROR(
"IoEnumerateDeviceObjectList failed with status %x",
status);
ExFreePoolWithTag(buffer, POOL_TAG_HW);
return status;
}
DEBUG_VERBOSE(
"EnumerateDriverObjectDeviceObjects: Object Count: %lx",
object_count);
*DeviceObjectArray = buffer;
*ArrayEntries = object_count;
return status;
}
/*
* While this isnt a perfect check to determine whether a DEVICE_OBJECT is
* indeed a PDO or FDO, this is Peters preferred method... hence it is now my
* preferred method... :smiling_imp:
*/
STATIC
BOOLEAN
IsDeviceObjectValidPdo(_In_ PDEVICE_OBJECT DeviceObject)
{
return DeviceObject->Flags & DO_BUS_ENUMERATED_DEVICE ? TRUE : FALSE;
}
/*
* Windows splits DEVICE_OBJECTS up into 2 categories:
*
* Physical Device Object (PDO)
* Functional Device Object (FDO)
*
* A PDO represents each device that is connected to a physical bus. Each PDO
* has an associated DEVICE_NODE. An FDO represents the functionality of the
* device. Its how the system interacts with the device objects.
*
* More information can be found here:
* https://learn.microsoft.com/en-gb/windows-hardware/drivers/gettingstarted/device-nodes-and-device-stacks
*
* A device stack can have multiple PDO's, but can only have one FDO. This means
* to access each PCI device on the system, we can enumerate all device objects
* given the PCI FDO which is called pci.sys.
*/
NTSTATUS
EnumeratePciDeviceObjects(
_In_ PCI_DEVICE_CALLBACK CallbackRoutine, _In_opt_ PVOID Context)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
UNICODE_STRING pci = RTL_CONSTANT_STRING(L"\\Driver\\pci");
PDRIVER_OBJECT pci_driver_object = NULL;
PDEVICE_OBJECT* pci_device_objects = NULL;
PDEVICE_OBJECT current_device = NULL;
UINT32 pci_device_objects_count = 0;
status = GetDriverObjectByDriverName(&pci, &pci_driver_object);
if (!NT_SUCCESS(status)) {
DEBUG_ERROR(
"GetDriverObjectByDriverName failed with status %x",
status);
return status;
}
status = EnumerateDriverObjectDeviceObjects(
pci_driver_object,
&pci_device_objects,
&pci_device_objects_count);
if (!NT_SUCCESS(status)) {
DEBUG_ERROR(
"EnumerateDriverObjectDeviceObjects failed with status %x",
status);
return status;
}
for (UINT32 index = 0; index < pci_device_objects_count; index++) {
current_device = pci_device_objects[index];
/* make sure we have a valid PDO */
if (!IsDeviceObjectValidPdo(current_device)) {
ObDereferenceObject(current_device);
continue;
}
status = CallbackRoutine(current_device, Context);
if (!NT_SUCCESS(status))
DEBUG_ERROR(
"EnumeratePciDeviceObjects CallbackRoutine failed with status %x",
status);
ObDereferenceObject(current_device);
}
if (pci_device_objects)
ExFreePoolWithTag(pci_device_objects, POOL_TAG_HW);
return status;
}
BOOLEAN
IsPciConfigurationSpaceFlagged(_In_ PPCI_COMMON_HEADER Configuration)
{
for (UINT32 index = 0; index < FLAGGED_DEVICE_ID_COUNT; index++) {
if (Configuration->DeviceID == FLAGGED_DEVICE_IDS[index])
return TRUE;
}
return FALSE;
}
STATIC
VOID
ReportBlacklistedPcieDevice(
_In_ PDEVICE_OBJECT DeviceObject, _In_ PPCI_COMMON_HEADER Header)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
UINT32 len = 0;
PBLACKLISTED_PCIE_DEVICE_REPORT report = NULL;
len = CryptRequestRequiredBufferLength(
sizeof(BLACKLISTED_PCIE_DEVICE_REPORT));
report = ImpExAllocatePool2(POOL_FLAG_NON_PAGED, len, REPORT_POOL_TAG);
if (!report)
return;
INIT_REPORT_PACKET(report, REPORT_BLACKLISTED_PCIE_DEVICE, 0);
report->device_object = (UINT64)DeviceObject;
report->device_id = Header->DeviceID;
report->vendor_id = Header->VendorID;
status = CryptEncryptBuffer(report, len);
if (!NT_SUCCESS(status)) {
DEBUG_ERROR("CryptEncryptBuffer: %lx", status);
ImpExFreePoolWithTag(report, len);
return;
}
IrpQueueSchedulePacket(report, len);
}
STATIC
NTSTATUS
PciDeviceQueryCallback(_In_ PDEVICE_OBJECT DeviceObject, _In_opt_ PVOID Context)
{
UNREFERENCED_PARAMETER(Context);
NTSTATUS status = STATUS_UNSUCCESSFUL;
PCI_COMMON_HEADER header = {0};
status = QueryPciDeviceConfigurationSpace(
DeviceObject,
PCI_VENDOR_ID_OFFSET,
&header,
sizeof(PCI_COMMON_HEADER));
if (!NT_SUCCESS(status)) {
DEBUG_ERROR(
"QueryPciDeviceConfigurationSpace failed with status %x",
status);
return status;
}
if (IsPciConfigurationSpaceFlagged(&header)) {
DEBUG_VERBOSE(
"Flagged DeviceID found. Device: %llx, DeviceId: %lx",
(UINT64)DeviceObject,
header.DeviceID);
ReportBlacklistedPcieDevice(DeviceObject, &header);
}
return status;
}
NTSTATUS
ValidatePciDevices()
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
status = EnumeratePciDeviceObjects(PciDeviceQueryCallback, NULL);
if (!NT_SUCCESS(status))
DEBUG_ERROR("EnumeratePciDeviceObjects failed with status %x", status);
return status;
}