void dump_interesting_fields(u8 bus, u8 slot, u8 func) { u16 vendor_id = read_pci_config_16(bus, slot, func, PCI_VENDOR_ID); u16 device_id = read_pci_config_16(bus, slot, func, PCI_DEVICE_ID); WinDbgPrint("pci 0000:%02x:%02x.%d ", bus, slot, func); WinDbgPrint("Id %04x:%04x config space:\n", vendor_id, device_id); };
void DumpConfigSpace(u8 bus, u8 slot, u8 func) { u8 i, j; for(i=0; i<0x80; i+=0x10) { WinDbgPrint("0x%02X ", i); for(j=i; j<i+0x10; j++) { u8 b = read_pci_config_byte(bus, slot, func, j); WinDbgPrint("%02X ", b); }; WinDbgPrint("\n"); } };
// Parse a 64 bit page table entry and print it. static void print_pte_contents(PTE_MMAP_OBJ *self, PTE_LOGLEVEL loglevel, PTE *pte) { WinDbgPrint("Virtual Address:%#016llx\n" "\tpresent: %lld\n" "\trw: %lld\n" "\tuser: %lld\n" "\twrite_through:%lld\n" "\tcache_disable:%lld\n" "\taccessed: %lld\n" "\tdirty: %lld\n" "\tpat: %lld\n" "\tglobal: %lld\n" "\txd: %lld\n" "\tpfn: %010llx", (pte_uint64)pte, (pte_uint64)pte->present, (pte_uint64)pte->rw, (pte_uint64)pte->user, (pte_uint64)pte->write_through, (pte_uint64)pte->cache_disable, (pte_uint64)pte->accessed, (pte_uint64)pte->dirty, (pte_uint64)pte->pat, (pte_uint64)pte->global, (pte_uint64)pte->xd, (pte_uint64)pte->page_frame); }
void DumpRuns(struct PmemMemoryInfo *info) { int i; for(i=0; i<info->NumberOfRuns.QuadPart; i++) { WinDbgPrint("0x%llx 0x%llx 0x%llx\n", info->Run[i].BaseAddress.QuadPart, info->Run[i].BaseAddress.QuadPart + info->Run[i].NumberOfBytes.QuadPart, info->Run[i].NumberOfBytes.QuadPart); }; };
void dump_bar(u8 bus, u8 slot, u8 func) { u16 vendor_id = read_pci_config_16(bus, slot, func, PCI_VENDOR_ID); u16 device_id = read_pci_config_16(bus, slot, func, PCI_DEVICE_ID); u32 base0 = read_pci_config(bus, slot, func, PCI_BASE_ADDRESS_0); u32 base1 = read_pci_config(bus, slot, func, PCI_BASE_ADDRESS_1); u32 base2 = read_pci_config(bus, slot, func, PCI_BASE_ADDRESS_2); u32 base3 = read_pci_config(bus, slot, func, PCI_BASE_ADDRESS_3); u32 base4 = read_pci_config(bus, slot, func, PCI_BASE_ADDRESS_4); u32 base5 = read_pci_config(bus, slot, func, PCI_BASE_ADDRESS_5); WinDbgPrint("Base Addresses: %08X %08X %08X %08X %08X %08X\n", base0, base1, base2, base3, base4, base5); WinDbgPrint("Masks: %08X %08X %08X %08X %08X %08X\n", get_base_register_size(bus, slot, func, PCI_BASE_ADDRESS_0), get_base_register_size(bus, slot, func, PCI_BASE_ADDRESS_1), get_base_register_size(bus, slot, func, PCI_BASE_ADDRESS_2), get_base_register_size(bus, slot, func, PCI_BASE_ADDRESS_3), get_base_register_size(bus, slot, func, PCI_BASE_ADDRESS_4), get_base_register_size(bus, slot, func, PCI_BASE_ADDRESS_5)); }
static NTSTATUS InsertMemoryHole(struct PmemMemoryInfo *info, int len, u64 start, u64 end) { int i; #if WINPMEM_PCI_DEBUG WinDbgPrint("Inserting memory hole at Start %llX end %llx\n", start, end); #endif // Round start and end to page boundaries. start = start & 0xFFFFFFFFFFFFF000; end = end | 0xFFF; if (start == end){ goto exit; }; for (i=0; i<info->NumberOfRuns.QuadPart; i++) { PHYSICAL_MEMORY_RANGE Run = info->Run[i]; u64 run_start = Run.BaseAddress.QuadPart; u64 run_end = Run.BaseAddress.QuadPart + Run.NumberOfBytes.QuadPart; if (run_start < start && start < run_end) { // Make some room for a new entry. RtlMoveMemory(&info->Run[i+1], &info->Run[i], sizeof(Run) * (info->NumberOfRuns.LowPart - i)); info->NumberOfRuns.QuadPart++; info->Run[i].NumberOfBytes.QuadPart = start - run_start - 1; info->Run[i+1].BaseAddress.QuadPart = start; info->Run[i+1].NumberOfBytes.QuadPart = run_end - start - 1; continue; }; if (run_start < end && end < run_end) { info->Run[i].BaseAddress.QuadPart = end + 1; info->Run[i].NumberOfBytes.QuadPart = run_end - end - 1; goto exit; }; }; exit: #if WINPMEM_PCI_DEBUG DumpRuns(info); #endif return STATUS_SUCCESS; };
// Edit the page tables to point a virtual address to a specific physical page. // // Args: // self: The this pointer to the object using this function. // target: The physical address to map to. // // Returns: // PTE_SUCCESS or PTE_ERROR // static PTE_STATUS pte_remap_rogue_page(PTE_MMAP_OBJ *self, PHYS_ADDR target) { // Can only remap pages, addresses must be page aligned. if (((!target) & PAGE_MASK) || self->rogue_page.offset) { WinDbgPrint("Failed to map %#016llx, " "only page aligned remapping is supported!", target); return PTE_ERROR; } WinDbgPrintDebug("Remapping pte at %p to %#016llx", self->rogue_pte, target); // Change the pte to point to the new offset. self->rogue_pte->page_frame = PAGE_TO_PFN(target); // Flush the old pte from the tlbs in the system. self->flush_tlbs_page_(self->rogue_page.pointer); return PTE_SUCCESS; }
// Get a free, non-paged page of memory. On windows we can not // allocate from pool because pool storage is controlled by large page // PTEs. So we just use a static page in the driver executable. static void *pte_get_rogue_page(void) { if (page_aligned_space == NULL) { rogue_mdl = IoAllocateMdl(&rogue_page, sizeof(rogue_page), FALSE, FALSE, NULL); if (!rogue_mdl) { return NULL; }; try { MmProbeAndLockPages(rogue_mdl, KernelMode, IoWriteAccess); } except(EXCEPTION_EXECUTE_HANDLER) { NTSTATUS ntStatus = GetExceptionCode(); WinDbgPrint("Exception while locking inBuf 0X%08X in METHOD_NEITHER\n", ntStatus); IoFreeMdl(rogue_mdl); rogue_mdl = NULL; return NULL; } page_aligned_space = rogue_page; page_aligned_space += PAGE_SIZE - ((__int64)rogue_page) % PAGE_SIZE; };
/* Uses direct PCI probing to add accessible physical memory ranges. */ NTSTATUS PCI_AddMemoryRanges(struct PmemMemoryInfo *info, int len) { int required_length = (sizeof(struct PmemMemoryInfo) + sizeof(PHYSICAL_MEMORY_RANGE)); unsigned int bus, slot, func; if (len < required_length) { return STATUS_INFO_LENGTH_MISMATCH; }; // Initialize the physical memory range. info->NumberOfRuns.QuadPart = 1; info->Run[0].BaseAddress.QuadPart = 0; info->Run[0].NumberOfBytes.QuadPart = -1; for (bus = 0; bus < 256; bus++) { for (slot = 0; slot < 32; slot++) { for (func = 0; func < 8; func++) { u8 type; u16 vendor_id = read_pci_config_16((u8)bus, (u8)slot, (u8)func, PCI_VENDOR_ID); // Device not present. if (vendor_id == 0xffff) continue; type = read_pci_config_byte((u8)bus, (u8)slot, (u8)func, PCI_HEADER_TYPE); // Standard header. if ((type & 0x1f) == 0) { #if WINPMEM_PCI_DEBUG WinDbgPrint("PCI Type %X\n", type); dump_interesting_fields((u8)bus, (u8)slot, (u8)func); dump_bar((u8)bus, (u8)slot, (u8)func); DumpConfigSpace((u8)bus, (u8)slot, (u8)func); #endif DumpStandardHeader((u8)bus, (u8)slot, (u8)func, info, len); // PCI-PCI bridge. } else if ((type & 0x1f) == 1) { #if WINPMEM_PCI_DEBUG WinDbgPrint("PCI Type %X\n", type); dump_interesting_fields((u8)bus, (u8)slot, (u8)func); DumpConfigSpace((u8)bus, (u8)slot, (u8)func); #endif DumpPCIBridge((u8)bus, (u8)slot, (u8)func, info, len); } else { WinDbgPrint("Unknown header PCI at 0000:%02x:%02x.%d type %d\n", bus, slot, func, type); }; // This is not a multi function device. if (func == 0 && (type & 0x80) == 0) { break; }; } } } return STATUS_SUCCESS; };
NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { UNICODE_STRING DeviceName, DeviceLink; NTSTATUS NtStatus; PDEVICE_OBJECT DeviceObject = NULL; PDEVICE_EXTENSION extension; WinDbgPrint("WinPMEM - " PMEM_VERSION " - Physical memory acquisition\n"); #if PMEM_WRITE_ENABLED == 1 WinDbgPrint("WinPMEM write support available!"); #endif WinDbgPrint("Copyright (c) 2017, Michael Cohen <*****@*****.**>\n"); // Initialize import tables: if(PmemGetProcAddresses() != STATUS_SUCCESS) { WinDbgPrint("Failed to initialize import table. Aborting.\n"); goto error; }; RtlInitUnicodeString (&DeviceName, L"\\Device\\" PMEM_DEVICE_NAME); // We create our secure device. // http://msdn.microsoft.com/en-us/library/aa490540.aspx NtStatus = IoCreateDeviceSecure(DriverObject, sizeof(DEVICE_EXTENSION), &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &SDDL_DEVOBJ_SYS_ALL_ADM_ALL, &GUID_DEVCLASS_PMEM_DUMPER, &DeviceObject); if (!NT_SUCCESS(NtStatus)) { WinDbgPrint ("IoCreateDevice failed. => %08X\n", NtStatus); return NtStatus; } DriverObject->MajorFunction[IRP_MJ_CREATE] = wddCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = wddClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = wddDispatchDeviceControl; DriverObject->MajorFunction[IRP_MJ_READ] = PmemRead; #if PMEM_WRITE_ENABLED == 1 { // Make sure that the drivers with write support are clearly marked as such. static char TAG[] = "Write Supported"; } // Support writing. DriverObject->MajorFunction[IRP_MJ_WRITE] = PmemWrite; #endif DriverObject->DriverUnload = IoUnload; // Use buffered IO - a bit slower but simpler to implement, and more // efficient for small reads. SetFlag(DeviceObject->Flags, DO_BUFFERED_IO ); ClearFlag(DeviceObject->Flags, DO_DIRECT_IO ); ClearFlag(DeviceObject->Flags, DO_DEVICE_INITIALIZING); RtlInitUnicodeString (&DeviceLink, L"\\DosDevices\\" PMEM_DEVICE_NAME); NtStatus = IoCreateSymbolicLink (&DeviceLink, &DeviceName); if (!NT_SUCCESS(NtStatus)) { WinDbgPrint ("IoCreateSymbolicLink failed. => %08X\n", NtStatus); IoDeleteDevice (DeviceObject); } // Populate globals in kernel context. CR3.QuadPart = __readcr3(); // Initialize the device extension with safe defaults. extension = DeviceObject->DeviceExtension; extension->mode = ACQUISITION_MODE_PHYSICAL_MEMORY; extension->MemoryHandle = 0; #if _WIN64 // Disable pte mapping for 32 bit systems. extension->pte_mmapper = pte_mmap_windows_new(); extension->pte_mmapper->loglevel = PTE_ERR; extension->mode = ACQUISITION_MODE_PTE_MMAP; #else extension->pte_mmapper = NULL; #endif WinDbgPrint("Driver intialization completed."); return NtStatus; error: return STATUS_UNSUCCESSFUL; }
NTSTATUS wddDispatchDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { UNICODE_STRING DestinationPath; PIO_STACK_LOCATION IrpStack; NTSTATUS status = STATUS_INVALID_PARAMETER; ULONG IoControlCode; PVOID IoBuffer; PULONG OutputBuffer; PDEVICE_EXTENSION ext; ULONG InputLen, OutputLen; ULONG Level; ULONG Type; // We must be running in PASSIVE_LEVEL or we bluescreen here. We // theoretically should always be running at PASSIVE_LEVEL here, but // in case we ended up here at the wrong IRQL its better to bail // than to bluescreen. if(KeGetCurrentIrql() != PASSIVE_LEVEL) { status = STATUS_ABANDONED; goto exit; }; ext = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; Irp->IoStatus.Information = 0; IrpStack = IoGetCurrentIrpStackLocation(Irp); IoBuffer = Irp->AssociatedIrp.SystemBuffer; OutputLen = IrpStack->Parameters.DeviceIoControl.OutputBufferLength; InputLen = IrpStack->Parameters.DeviceIoControl.InputBufferLength; IoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode; switch ((IoControlCode & 0xFFFFFF0F)) { // The old deprecated ioctrl interface for backwards // compatibility. Do not use for new code. case IOCTL_GET_INFO_DEPRECATED: { char *buffer = ExAllocatePoolWithTag(NonPagedPoolNx, 0x1000, PMEM_POOL_TAG); if (buffer) { struct DeprecatedPmemMemoryInfo *info = (void *)IoBuffer; struct PmemMemoryInfo *memory_info = (void *)buffer; status = AddMemoryRanges(memory_info, 0x1000); if (status != STATUS_SUCCESS) { ExFreePoolWithTag(buffer, PMEM_POOL_TAG); goto exit; }; info->CR3.QuadPart = CR3.QuadPart; info->NumberOfRuns = (unsigned long)memory_info->NumberOfRuns.QuadPart; // Is there enough space in the user supplied buffer? if (OutputLen < (info->NumberOfRuns * sizeof(PHYSICAL_MEMORY_RANGE) + sizeof(struct DeprecatedPmemMemoryInfo))) { status = STATUS_INFO_LENGTH_MISMATCH; ExFreePoolWithTag(buffer, PMEM_POOL_TAG); goto exit; }; // Copy the runs over. RtlCopyMemory(&info->Run[0], &memory_info->Run[0], info->NumberOfRuns * sizeof(PHYSICAL_MEMORY_RANGE)); // This is the total length of the response. Irp->IoStatus.Information = sizeof(struct DeprecatedPmemMemoryInfo) + info->NumberOfRuns * sizeof(PHYSICAL_MEMORY_RANGE); WinDbgPrint("Returning info on the system memory using deprecated " "interface!\n"); ExFreePoolWithTag(buffer, PMEM_POOL_TAG); status = STATUS_SUCCESS; }; }; break; // Return information about memory layout etc through this ioctrl. case IOCTL_GET_INFO: { struct PmemMemoryInfo *info = (void *)IoBuffer; if (OutputLen < sizeof(struct PmemMemoryInfo)) { status = STATUS_INFO_LENGTH_MISMATCH; goto exit; }; // Ensure we clear the buffer first. RtlZeroMemory(IoBuffer, sizeof(struct PmemMemoryInfo)); // Get the memory ranges according to the mode. if (ext->mode == ACQUISITION_MODE_PTE_MMAP_WITH_PCI_PROBE) { status = PCI_AddMemoryRanges(info, OutputLen); } else { status = AddMemoryRanges(info, OutputLen); } if (status != STATUS_SUCCESS) { goto exit; }; WinDbgPrint("Returning info on the system memory.\n"); // We are currently running in user context which means __readcr3() will // return the process CR3. So we return the kernel CR3 we found // when loading. info->CR3.QuadPart = CR3.QuadPart; info->NtBuildNumber.QuadPart = *NtBuildNumber; info->NtBuildNumberAddr.QuadPart = (uintptr_t)NtBuildNumber; info->KernBase.QuadPart = (uintptr_t)KernelGetModuleBaseByPtr( NtBuildNumber, "NtBuildNumber"); // Fill in KPCR. GetKPCR(info); // This is the length of the response. Irp->IoStatus.Information = sizeof(struct PmemMemoryInfo) + info->NumberOfRuns.LowPart * sizeof(PHYSICAL_MEMORY_RANGE); status = STATUS_SUCCESS; }; break; case IOCTL_SET_MODE: { WinDbgPrint("Setting Acquisition mode.\n"); /* First u32 is the acquisition mode. */ if (InputLen >= sizeof(u32)) { enum PMEM_ACQUISITION_MODE mode = *(u32 *)IoBuffer; switch(mode) { case ACQUISITION_MODE_PHYSICAL_MEMORY: // These are all the requirements for this method. if (!Pmem_KernelExports.MmGetPhysicalMemoryRanges) { WinDbgPrint("Kernel APIs required for this method are not " "available."); status = STATUS_UNSUCCESSFUL; } else { WinDbgPrint("Using physical memory device for acquisition.\n"); status = STATUS_SUCCESS; ext->mode = mode; }; break; case ACQUISITION_MODE_MAP_IO_SPACE: if (!Pmem_KernelExports.MmGetPhysicalMemoryRanges || !Pmem_KernelExports.MmMapIoSpace || !Pmem_KernelExports.MmUnmapIoSpace) { WinDbgPrint("Kernel APIs required for this method are not " "available."); status = STATUS_UNSUCCESSFUL; } else { WinDbgPrint("Using MmMapIoSpace for acquisition.\n"); status = STATUS_SUCCESS; ext->mode = mode; }; break; case ACQUISITION_MODE_PTE_MMAP: if (!Pmem_KernelExports.MmGetVirtualForPhysical || !Pmem_KernelExports.MmGetPhysicalMemoryRanges || !ext->pte_mmapper) { WinDbgPrint("Kernel APIs required for this method are not " "available."); status = STATUS_UNSUCCESSFUL; } else { WinDbgPrint("Using PTE Remapping for acquisition.\n"); status = STATUS_SUCCESS; ext->mode = mode; }; break; case ACQUISITION_MODE_PTE_MMAP_WITH_PCI_PROBE: if (!Pmem_KernelExports.MmGetVirtualForPhysical || !ext->pte_mmapper) { WinDbgPrint("Kernel APIs required for this method are not " "available."); status = STATUS_UNSUCCESSFUL; } else { WinDbgPrint("Using PTE Remapping with PCI probe for acquisition.\n"); status = STATUS_SUCCESS; ext->mode = mode; }; break; default: WinDbgPrint("Invalid acquisition mode %d.\n", mode); status = STATUS_INVALID_PARAMETER; }; } else { status = STATUS_INFO_LENGTH_MISMATCH; }; }; break; #if PMEM_WRITE_ENABLED == 1 case IOCTL_WRITE_ENABLE: { ext->WriteEnabled = !ext->WriteEnabled; WinDbgPrint("Write mode is %d. Do you know what you are doing?\n", ext->WriteEnabled); status = STATUS_SUCCESS; }; break; #endif default: { WinDbgPrint("Invalid IOCTRL %d\n", IoControlCode); status = STATUS_INVALID_PARAMETER; }; } exit: Irp->IoStatus.Status = status; IoCompleteRequest(Irp,IO_NO_INCREMENT); return status; }
// Traverses the page tables to find the pte for a given virtual address. // // Args: // self: The this pointer for the object calling this function. // vaddr: The virtual address to resolve the pte for // pte: A pointer to a pointer which will be set with the address of the pte, // if found. // // Returns: // PTE_SUCCESS or PTE_ERROR // static PTE_STATUS virt_find_pte(PTE_MMAP_OBJ *self, void *addr, PTE **pte) { PTE_CR3 cr3; PML4E *pml4; PML4E *pml4e; PDPTE *pdpt; PDPTE *pdpte; PDE *pd; PDE *pde; PTE *pt; VIRT_ADDR vaddr; PTE_STATUS status = PTE_ERROR; vaddr.pointer = addr; WinDbgPrint("Resolving PTE for Address:%#016llx", vaddr); // Get contents of cr3 register to get to the PML4 cr3 = self->get_cr3_(); WinDbgPrint("Kernel CR3 is %p", cr3); WinDbgPrint("Kernel PML4 is at %p physical", PFN_TO_PAGE(cr3.pml4_p)); // Resolve the PML4 pml4 = (PML4E *)self->phys_to_virt_(PFN_TO_PAGE(cr3.pml4_p)); WinDbgPrint("kernel PML4 is at %p virtual", pml4); // Resolve the PDPT pml4e = (pml4 + vaddr.pml4_index); WinDbgPrint("PML4 entry %d is at %p", vaddr.pml4_index, pml4e); if (!pml4e->present) { WinDbgPrint("Error, address %#016llx has no valid mapping in PML4:", vaddr.value); self->print_pte_(self, PTE_ERR, (PTE *)pml4e); goto error; } WinDbgPrint("PML4[%#010x]: %p)", vaddr.pml4_index, pml4e); pdpt = (PDPTE *)self->phys_to_virt_(PFN_TO_PAGE(pml4e->pdpt_p)); WinDbgPrintDebug("Points to PDPT: %p)", pdpt); // Resolve the PDT pdpte = (pdpt + vaddr.pdpt_index); if (!pdpte->present) { WinDbgPrint("Error, address %#016llx has no valid mapping in PDPT:", vaddr.value); self->print_pte_(self, PTE_ERR, (PTE *)pdpte); goto error; } if (pdpte->page_size) { WinDbgPrint("Error, address %#016llx belongs to a 1GB page:", vaddr.value); self->print_pte_(self, PTE_ERR, (PTE *)pdpte); goto error; } WinDbgPrint("PDPT[%#010x]: %p)", vaddr.pdpt_index, pdpte); pd = (PDE *)self->phys_to_virt_(PFN_TO_PAGE(pdpte->pd_p)); WinDbgPrint("Points to PD: %p)", pd); // Resolve the PT pde = (pd + vaddr.pd_index); if (!pde->present) { WinDbgPrint("Error, address %#016llx has no valid mapping in PD:", vaddr.value); self->print_pte_(self, PTE_ERR, (PTE *)pde); goto error; } if (pde->page_size) { WinDbgPrint("Error, address %#016llx belongs to a 2MB page:", vaddr.value); self->print_pte_(self, PTE_ERR, (PTE *)pde); goto error; } WinDbgPrint("PD [%#010x]: %p)", vaddr.pd_index, pde); pt = (PTE *)self->phys_to_virt_(PFN_TO_PAGE(pde->pt_p)); WinDbgPrint("Points to PT: %p)", pt); // Get the PTE and Page Frame *pte = (pt + vaddr.pt_index); if (! (*pte)->present) { WinDbgPrint("Error, address %#016llx has no valid mapping in PT:", vaddr.value); self->print_pte_(self, PTE_ERR, (*pte)); goto error; } WinDbgPrint("PT [%#010x]: %p)", vaddr.pt_index, *pte); status = PTE_SUCCESS; error: return status; }