mirror-ac/driver/hw.c

297 lines
10 KiB
C
Raw Normal View History

2024-02-13 19:08:38 +01:00
#include "hw.h"
#include "modules.h"
#define PCI_VENDOR_ID_OFFSET 0x00
2024-02-14 17:16:27 +01:00
#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);
2024-02-13 19:08:38 +01:00
/*
* 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_ PVOID Buffer,
_In_ UINT32 BufferLength)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
KEVENT event = {0};
IO_STATUS_BLOCK io = {0};
PIRP irp = NULL;
PIO_STACK_LOCATION io_stack_location = NULL;
if (BufferLength == 0)
return STATUS_BUFFER_TOO_SMALL;
KeInitializeEvent(&event, NotificationEvent, FALSE);
2024-02-14 17:16:27 +01:00
/*
* we dont need to free this IRP as the IO manager will free it when the request is
* completed
*/
2024-02-13 19:08:38 +01:00
irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, DeviceObject, NULL, 0, NULL, &event, &io);
if (!irp)
{
DEBUG_ERROR("IoBuildSynchronousFsdRequest failed with no status.");
return STATUS_INSUFFICIENT_RESOURCES;
}
io_stack_location = IoGetNextIrpStackLocation(irp);
io_stack_location->MinorFunction = IRP_MN_READ_CONFIG;
io_stack_location->Parameters.ReadWriteConfig.WhichSpace = PCI_WHICHSPACE_CONFIG;
io_stack_location->Parameters.ReadWriteConfig.Offset = Offset;
io_stack_location->Parameters.ReadWriteConfig.Buffer = Buffer;
io_stack_location->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;
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;
}
/*
2024-02-13 19:13:56 +01:00
* While this isnt a perfect check to determine whether a DEVICE_OBJECT is indeed a PDO or FDO, this
2024-02-13 19:08:38 +01:00
* 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)
2024-02-13 19:29:19 +01:00
* Functional Device Object (FDO)
2024-02-13 19:08:38 +01:00
*
* 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
2024-02-13 19:13:56 +01:00
*
* 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.
2024-02-13 19:08:38 +01:00
*/
NTSTATUS
2024-02-14 17:16:27 +01:00
EnumeratePciDeviceObjects(_In_ PCI_DEVICE_CALLBACK CallbackRoutine, _In_opt_ PVOID Context)
2024-02-13 19:08:38 +01:00
{
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;
USHORT vendor_id = 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))
2024-02-14 17:16:27 +01:00
{
ObDereferenceObject(current_device);
2024-02-13 19:08:38 +01:00
continue;
2024-02-14 17:16:27 +01:00
}
2024-02-13 19:08:38 +01:00
2024-02-14 17:16:27 +01:00
status = CallbackRoutine(current_device, Context);
2024-02-13 19:08:38 +01:00
if (!NT_SUCCESS(status))
2024-02-14 17:16:27 +01:00
DEBUG_ERROR(
"EnumeratePciDeviceObjects CallbackRoutine failed with status %x",
status);
2024-02-13 19:08:38 +01:00
2024-02-14 17:16:27 +01:00
ObDereferenceObject(current_device);
2024-02-13 19:08:38 +01:00
}
end:
if (pci_device_objects)
ExFreePoolWithTag(pci_device_objects, POOL_TAG_HW);
2024-02-14 17:16:27 +01:00
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
NTSTATUS
PciDeviceQueryCallback(_In_ PDEVICE_OBJECT DeviceObject, _In_opt_ PVOID 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);
}
else
{
DEBUG_VERBOSE("Device: %llx, DeviceID: %lx, VendorID: %lx",
DeviceObject,
header.DeviceID,
header.VendorID);
}
return status;
}
NTSTATUS
ValidatePciDevices()
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
status = EnumeratePciDeviceObjects(PciDeviceQueryCallback, NULL);
if (!NT_SUCCESS(status))
DEBUG_ERROR("EnumeratePciDeviceObjects failed with status %x", status);
2024-02-13 19:08:38 +01:00
return status;
}