diff --git a/CHANGELOG.md b/CHANGELOG.md index f73b376..c880790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +### 17. Nov 2024 + +* Refactored device io control, adopted for nail first, ask later. This is because it is hard not to lose the overview of the numerous ioctl codes. Each case may or may not require an input or/and outputbuffer that needs to be secured properly. Now it's nail first - ask later. +* Added support for detecting a level 5 paging system. They are still rare, but they exist. Azure machines and modern Windows servers with top tier hardware might have level 5. If level 5 is detected, the PTE method will be disabled. It should still work with the other provided methods, but will take an eternity. +* Made DbgPrints (mostly) toggleable again. (Toggle is in precompiler.h, *not* toggled via the vcxproj debug switch. Being able to test the release version with verbose debug print on is important.) +* Made Winpmem rogue PTE TLB-uncached (cache disable bit). No TLB entry, no flushing, no nailing to one processor. (This is the most critical change!) +* Improvements on the usermode program bulk reading logic. +* Increased version number to 4.0.1, BETA, for now. + ### 18. May 2024 * Updated vcxproj file. @@ -17,7 +26,7 @@ Needed: there are no build instruction in readme and Winpmem usage is a bit outd ### 27. Nov 2022, 3.0.3 *alpha* -No bugs detected anymore, only small improvements, testing (and knowledge increase). +No bugs detected anymore, only small improvements, testing (and knowledge increase). Tried to resolve an issue with not being able to read certain pages inside the normal physical ranges, KD fails at these, too. It's not a Winpmem error. * **Important note**: the iospace method cannot be used as general purpose physical reading method. (For details, please refer to my comments in read.c.) In particular, it should urgently be thrown out of the mini tool. (Applicable also for the 2.0.1 version in the master branch.) It can cause hazard to use it in this purpose. diff --git a/src/executable/winpmem.cpp b/src/executable/winpmem.cpp index a4e3f07..22fb552 100644 --- a/src/executable/winpmem.cpp +++ b/src/executable/winpmem.cpp @@ -18,7 +18,7 @@ #include "winpmem.h" #include -constexpr auto BUFF_SIZE = (4096 * 4096); +constexpr auto MAXIMUM_BULK_READ = (4096 * 4096); // 16 MB bulk read /** * Pad file in pad range with zeros. @@ -27,35 +27,26 @@ __int64 WinPmem::pad(unsigned __int64 start, unsigned __int64 length) { DWORD bytes_written = 0; BOOL result = FALSE; - unsigned __int64 count = 0; - unsigned char * paddingbuffer = (unsigned char * ) malloc(BUFF_SIZE); + unsigned char * paddingbuffer = (unsigned char * ) malloc(MAXIMUM_BULK_READ); if (!paddingbuffer) { return 0; }; - ZeroMemory(paddingbuffer, BUFF_SIZE); + ZeroMemory(paddingbuffer, MAXIMUM_BULK_READ); + + // More noisy than helpful perhaps? + Log(TEXT("\n(Omitting & padding reserved block 0x%llX - 0x%llX, length 0x%llx.) \n"), start, start+length, length); + // Seriously not that interesting watching us writing lots of zeros. - printf("pad\n"); - printf(" - length: 0x%llx\n", length); - fflush(stdout); - - while (length > 0) { - DWORD to_write = (DWORD)min((BUFF_SIZE), length); + while (length > 0) + { + DWORD to_write = (DWORD)min((MAXIMUM_BULK_READ), length); // Maximum buffer could be less than required pad length... that's why it's in a loop. result = WriteFile(out_fd_, paddingbuffer, to_write, &bytes_written, NULL); if ((!(result)) || (bytes_written != to_write)) { - LogLastError(TEXT("Failed to write padding")); + LogLastError(TEXT("Failed to write padding.\n")); goto error; } - // === Progress printing === - if ((count % 50) == 0) { - Log(TEXT("\n%02lld%% 0x%08llX "), (start * 100) / max_physical_memory_, - start); - } - - Log(TEXT(".")); - - count++; out_offset += bytes_written; length -= bytes_written; }; @@ -68,82 +59,16 @@ __int64 WinPmem::pad(unsigned __int64 start, unsigned __int64 length) return 0; } -// Copy memory from start to end in 1 page increments. -__int64 WinPmem::copy_memory_small(unsigned __int64 start, unsigned __int64 end) { - LARGE_INTEGER large_start; - - // Total number of pages written. - unsigned __int64 count = 0; - - BOOL result = FALSE; - const int buff_size = 0x1000; - unsigned char* largebuffer = (unsigned char*)malloc(buff_size); - unsigned char* nullbuffer = (unsigned char*)calloc(buff_size, 1); - - if (start > max_physical_memory_) { - return 0; - } - - // Clamp the region to the top of physical memory. - if (end > max_physical_memory_) { - end = max_physical_memory_; - } - - while (start < end) { - DWORD to_write = (DWORD)min((buff_size), end - start); - DWORD bytes_read = 0; - DWORD bytes_written = 0; - - large_start.QuadPart = start; - - // seek input stream to the required offset. - result = SetFilePointerEx(fd_, large_start, NULL, FILE_BEGIN); - - if (!(result)) { - LogLastError(TEXT("Failed to seek in the pmem device.\n")); - goto error; - } - - // read from memory stream - result = ReadFile(fd_, largebuffer, to_write, &bytes_read, NULL); - - // Page read failed - pad with nulls and keep going. - if ((!(result)) || (bytes_read != to_write)) - { - result = WriteFile(out_fd_, nullbuffer, buff_size, &bytes_written, NULL); - } - else - { - result = WriteFile(out_fd_, largebuffer, bytes_read, &bytes_written, NULL); - } - - if (!result) - { - LogLastError(TEXT("Failed to write image file")); - goto error; - } - start += to_write; - count++; - } - if (largebuffer) free(largebuffer); - if (nullbuffer) free(nullbuffer); - return 1; - -error: - if (largebuffer) free(largebuffer); - if (nullbuffer) free(nullbuffer); - return 0; -} __int64 WinPmem::copy_memory(unsigned __int64 start, unsigned __int64 end) { LARGE_INTEGER large_start; - // Total number of pages written. - unsigned __int64 count = 0; + unsigned __int64 dotCounter = 0; // how much dots were already drawn. BOOL result = FALSE; - unsigned char * largebuffer = (unsigned char *) malloc(BUFF_SIZE); // ~ 16 MB + unsigned char * largebuffer = (unsigned char *) malloc(MAXIMUM_BULK_READ); // ~ 16 MB + unsigned char * nullbuffer = (unsigned char*)calloc(PAGE_SIZE, 1); // One "padding" page, zeroed already. if (start > max_physical_memory_) { @@ -153,17 +78,15 @@ __int64 WinPmem::copy_memory(unsigned __int64 start, unsigned __int64 end) { // Clamp the region to the top of physical memory. if (end > max_physical_memory_) { - end = max_physical_memory_; + end = max_physical_memory_; } - printf("\ncopy_memory\n"); - printf(" - start: 0x%llx\n", start); - printf(" - end: 0x%llx\n", end); - fflush(stdout); + // More noisy than helpful perhaps? + Log(TEXT("\nWrite 0x%llx - 0x%llx, length: 0x%llx.\n"), start, end, (end-start)); while(start < end) { - DWORD to_write = (DWORD) min((BUFF_SIZE), end - start); // ReadFile wants a DWORD, for whatever reason. + DWORD to_write = (DWORD) min((MAXIMUM_BULK_READ), end - start); // ReadFile wants a DWORD, for whatever reason. DWORD bytes_read = 0; DWORD bytes_written = 0; @@ -185,47 +108,108 @@ __int64 WinPmem::copy_memory(unsigned __int64 start, unsigned __int64 end) { // read result = ReadFile(fd_, largebuffer, to_write, &bytes_read, NULL); - - if (!result || (bytes_read != to_write)) // reading from winpmem (using large reads) failed in first instance. - { - // Indicates this 16mb buffer was read slowly. - indicator = TEXT("x"); - result = copy_memory_small( start, start + to_write); - // the second no-fail slow but sturdy approach, reads in tiny portions. - // Any stubborn unreadable pages will be padded with zeros. - // Does the writes on its own. - } - else // reading from winpmem (using large reads) went fine at first try. + + if (bytes_read) // either Winpmem could read some bytes already ... { + // If Winpmem managed to read some bytes in the bulk read, + // Write them to file now. + // This is even true if ReadFile returns an error, which means that Winpmem could not read all the requested bytes. + // But if winpmem indicates it could read n bytes, these n bytes should be considered "good". + // Don't throw away 15.7 MB bytes of your 16 MB request. result = WriteFile(out_fd_, largebuffer, bytes_read, &bytes_written, NULL); + + // Progress report, with '.' + if ((dotCounter % 50) == 0) + { + Log(TEXT("\n%02lld%% 0x%08llX "), + (start * 100) / max_physical_memory_, + start); + } + + Log(indicator); + dotCounter++; // one dot was drawn + + // First, update the byte-counting variables already. + out_offset += bytes_written; // Less than 16 MB if driver had to return earlier (and could not read the full 16 MB). + start += bytes_written; + + if (!result) + { + // Also, Winpmem encountered a read error. There is no point of trying exactly that location again. + // Instead, advance immediately one additional page, padded with zeros, and re-enter the loop with read pointer set on next page. + + // Progress report, with 'x', indicating exact position of the brick wall. + + indicator = TEXT("x"); + + if ((dotCounter % 50) == 0) + { + Log(TEXT("\n%02lld%% 0x%08llX "), + (start * 100) / max_physical_memory_, + start); + } + + Log(indicator); + dotCounter++; // one dot was drawn + + // Now write one zero-padded page which was reported by Winpmem at this position. + result = WriteFile(out_fd_, nullbuffer, PAGE_SIZE, &bytes_written, NULL); + + if ((!result) || (bytes_written != PAGE_SIZE)) // ASSERT that 4096 have been written by WriteFile API. + { + LogLastError(TEXT("WriteFile API failed when writing bytes to disk.\n")); + goto error; + } + + out_offset += PAGE_SIZE; + start += PAGE_SIZE; + + // We hereby advance by a page. + + } } - - if (!result) + else // the else case, no bytes read at all. { - LogLastError(TEXT("Failed to write image file")); - goto error; - } - - out_offset += bytes_written; - - // === Progress printing === - if ((count % 50) == 0) { - Log(TEXT("\n%02lld%% 0x%08llX "), (start * 100) / max_physical_memory_, - start); + // That's the case when you currently try-walk over an unreadable physical memory location. + // Proceed yourself by another +1 page and try again. Write down the unreadable page as zero-padded page. + + indicator = TEXT("x"); + + if ((dotCounter % 50) == 0) + { + Log(TEXT("\n%02lld%% 0x%08llX "), + (start * 100) / max_physical_memory_, + start); + } + + Log(indicator); + dotCounter++; // one dot was drawn + + result = WriteFile(out_fd_, nullbuffer, PAGE_SIZE, &bytes_written, NULL); + + if ((!result) || (bytes_written != PAGE_SIZE)) // ASSERT that 4096 have been written by WriteFile API. + { + LogLastError(TEXT("WriteFile API failed when writing bytes to disk.\n")); + goto error; + } + + out_offset += PAGE_SIZE; + start += PAGE_SIZE; + + // We hereby advance by a page. } - - Log(indicator); - - start += to_write; - count ++; + } Log(TEXT("\n")); if (largebuffer) free(largebuffer); + if (nullbuffer) free(nullbuffer); return 1; error: + Log(TEXT("\n")); if (largebuffer) free(largebuffer); + if (nullbuffer) free(nullbuffer); return 0; } @@ -373,7 +357,7 @@ __int64 WinPmem::write_raw_image() BOOL result = FALSE; __int64 i; __int64 status = -1; - SYSTEMTIME st, lt; + SYSTEMTIME st; BYTE infoBuffer[sizeof(WINPMEM_MEMORY_INFO) + sizeof(LARGE_INTEGER) * 32] = { 0 }; if(out_fd_==INVALID_HANDLE_VALUE) @@ -441,9 +425,7 @@ __int64 WinPmem::write_raw_image() { if(info.Run[i].BaseAddress.QuadPart > current) { - // pad zeros from current until begin of next RAM memory region. - printf("Padding from 0x%08llX to 0x%08llX\n", current, info.Run[i].BaseAddress.QuadPart); - fflush(stdout); + // pad zeros from current until begin of next RAM memory region. if (!pad(current, info.Run[i].BaseAddress.QuadPart - current)) { printf("padding went terribly wrong! Cancelling & terminating. \n"); @@ -455,7 +437,7 @@ __int64 WinPmem::write_raw_image() // write next RAM memory region to file. - result = copy_memory(info.Run[i].BaseAddress.QuadPart, info.Run[i].BaseAddress.QuadPart + info.Run[i].NumberOfBytes.QuadPart); + result = (BOOL) copy_memory(info.Run[i].BaseAddress.QuadPart, info.Run[i].BaseAddress.QuadPart + info.Run[i].NumberOfBytes.QuadPart); if (!result) { diff --git a/src/executable/winpmem.vcxproj b/src/executable/winpmem.vcxproj index 59dbd95..e65809a 100644 --- a/src/executable/winpmem.vcxproj +++ b/src/executable/winpmem.vcxproj @@ -31,28 +31,28 @@ true Unicode false - v142 + v143 Application false true Unicode - v142 + v143 Application true Unicode false - v142 + v143 Application false true Unicode - v142 + v143 diff --git a/src/precompiler.h b/src/precompiler.h index 1f855ae..df3c382 100644 --- a/src/precompiler.h +++ b/src/precompiler.h @@ -3,7 +3,11 @@ // Important switches for compilation flavors. -// Control whether there should be DbgPrint or not. +// DBGPRINT 1/0: Controls the DbgPrint verbosity level. +// DBGPRINT (0) is commonly the most useful and prints *only* unexpected critical/important DbgPrints. +// Not being able to read from a (VSM protected) physical address is not considered a real error, and not printed with "DBGPRINT (0)" setting. +// These are technically read errors, but uncritical expected ones. +// DBGPRINT (1) might be a bit spammy. #define DBGPRINT (1) // Set to 1 for generic DbgPrints! // Beware, if reading the whole RAM this will spam you! diff --git a/src/pte_mmap.c b/src/pte_mmap.c index db6224f..0cfa98b 100644 --- a/src/pte_mmap.c +++ b/src/pte_mmap.c @@ -93,7 +93,8 @@ PTE_STATUS pte_remap_rogue_page(_Inout_ PPTE_METHOD_DATA pPtedata, _In_ PHYS_ADD pPtedata->rogue_pte->page_frame = PAGE_TO_PFN(Phys_addr); // Flush the old pte from the tlbs in the system. - __invlpg(pPtedata->page_aligned_rogue_ptr.pointer); + // __invlpg(pPtedata->page_aligned_rogue_ptr.pointer); + // Not needed anymore, PTE has cache disable bit set. Changes to PTE have immediate result. return PTE_SUCCESS; } @@ -313,6 +314,12 @@ BOOLEAN setupBackupForOriginalRoguePage(_Inout_ PPTE_METHOD_DATA pPtedata) pPtedata->pte_method_is_ready_to_use = FALSE; return FALSE; } + + // Set cache disable bit. + pPtedata->rogue_pte->cache_disable = 1; + + // Flush the old pte from TLB. + __invlpg(pPtedata->page_aligned_rogue_ptr.pointer); pPtedata->pte_method_is_ready_to_use = TRUE; diff --git a/src/pte_mmap.h b/src/pte_mmap.h index c96bbb0..50d115f 100644 --- a/src/pte_mmap.h +++ b/src/pte_mmap.h @@ -26,7 +26,8 @@ #if defined(_WIN64) // Section for PTE remapping maneuvers. Fulfills special requirements. -#pragma section(".roguepage",read,write,nopage) +#pragma section(".roguepage",read,write,nopage, nocache) +// nocache definitely doesn't work. The cache disable bit won't be set. #endif diff --git a/src/read.c b/src/read.c index 100d67e..747c929 100644 --- a/src/read.c +++ b/src/read.c @@ -47,7 +47,7 @@ BOOLEAN setupPhysMemSectionHandle(_Out_ PHANDLE pMemoryHandle) if (!NT_SUCCESS(ntstatus)) { - DbgPrint("Error: failed ZwOpenSection(MemoryHandle) => %08X\n", ntstatus); + WinDbgPrint("Error: failed ZwOpenSection(MemoryHandle) => %08X\n", ntstatus); return FALSE; } @@ -84,7 +84,7 @@ ULONG PhysicalMemoryPartialRead(_In_ HANDLE memoryHandle, if ((ntstatus != STATUS_SUCCESS) || (!mapped_buffer)) { - DbgPrint("Error %08x (method: phys mem device): ZwMapViewOfSection failed. physAddr was 0x%llX.\n", ntstatus, physAddr.QuadPart); // real error + WinDbgPrint("Error %08x (method: phys mem device): ZwMapViewOfSection failed. physAddr was 0x%llX.\n", ntstatus, physAddr.QuadPart); // real error return 0; } @@ -109,7 +109,7 @@ ULONG PhysicalMemoryPartialRead(_In_ HANDLE memoryHandle, except(EXCEPTION_EXECUTE_HANDLER) { ntstatus = GetExceptionCode(); - DbgPrint("Warning: read error %08x (method: phys mem device): unable to read %u bytes from %p.\n", ntstatus, to_read, mapped_buffer+page_offset); + WinDbgPrint("Warning: read error %08x (method: phys mem device): unable to read %u bytes from %p.\n", ntstatus, to_read, mapped_buffer+page_offset); goto error; } @@ -176,14 +176,14 @@ ULONG MapIOPagePartialRead(_In_ LARGE_INTEGER physAddr, _Inout_ unsigned char * } else { - DbgPrint("Error (method: map I/O): zero buffer returned from MmMapIoSpace (wanted to read %u bytes on %p).\n", to_read, mapped_buffer+page_offset); // real error + WinDbgPrint("Error (method: map I/O): zero buffer returned from MmMapIoSpace (wanted to read %u bytes on %p).\n", to_read, mapped_buffer+page_offset); // real error return 0; } } except(EXCEPTION_EXECUTE_HANDLER) { ntStatus = GetExceptionCode(); - DbgPrint("Warning: read error %08x (method: map I/O): unable to read %u bytes from %p.\n", ntStatus, to_read, mapped_buffer+page_offset); + WinDbgPrint("Warning: read error %08x (method: map I/O): unable to read %u bytes from %p.\n", ntStatus, to_read, mapped_buffer+page_offset); return 0; } @@ -239,7 +239,7 @@ ULONG PTEMmapPartialRead(_Inout_ PPTE_METHOD_DATA pPtedata, _In_ LARGE_INTEGER p } except(EXCEPTION_EXECUTE_HANDLER) { ntStatus = GetExceptionCode(); - DbgPrint("Warning: read error %08x (method: PTE remap): unable to read %u bytes from %llx.\n", ntStatus, to_read, viewPage.QuadPart); + WinDbgPrint("Warning: read error %08x (method: PTE remap): unable to read %u bytes from %llx.\n", ntStatus, to_read, viewPage.QuadPart); return 0; } result = to_read; @@ -273,17 +273,6 @@ NTSTATUS DeviceRead(_In_ PDEVICE_EXTENSION extension, if (extension->mode == PMEM_MODE_PTE) // The PTE method is not thread-safe. { ExAcquireFastMutex(&extension->mu); // Don't forget to always free the Mutex! - - // For PTE remap method, it might happen that at any point in time, - // we are thrown off from the current CPU core, and later we continue - // on another CPU core that has another private CPU cache. - // This does not seem to happen often, but it may happen. The read would return wrong data. - // What needs to be done is to nail the current process/thread to one specific CPU core (and its cache). - // To keep the code simple, we choose the first core (there is always one at least) and - // we don't revert, because this would do no favor in this special case. - // In context of Winpmem, the caller probably will call multiple times. - KeSetSystemAffinityThreadEx(1); // the formula is (1 << i), where i starts at 0, and maximum is i=n-1. - // In short: we make the caller stay on this processor core (and its cache) and expect further incoming calls. } #endif @@ -364,7 +353,7 @@ NTSTATUS DeviceRead(_In_ PDEVICE_EXTENSION extension, // Thus, on read error, the usermode buffer will be left unscathed. // As a usermode program author, on read error, please remember this, especially when using uninitialized malloc'ed buffers! - DbgPrint("Device read: an error occurred: no bytes read.\n"); + WinDbgPrint("Device read: an error occurred: no bytes read.\n"); MmUnlockPages(mdl); IoFreeMdl(mdl); status = STATUS_IO_DEVICE_ERROR; // The reading method failed. @@ -433,12 +422,7 @@ BOOLEAN pmemFastIoRead ( goto bail_out; } - // DbgPrint("pmemFastIoRead\n"); - - - // Do security checkings now. - // The usermode program is not allowed to order more than ONE PAGE_SIZE at a time. - // But the arbitrary read/write allows up to 1-PAGESIZE bytes. So... + // WinDbgPrint("pmemFastIoRead\n"); if (!toxic_buffer) { @@ -491,8 +475,8 @@ BOOLEAN pmemFastIoRead ( // All things considered, the buffer looked good, of right size, and was accessible for write. // It's accepted for the next stage. Don't touch, it's still considered poisonous. - //DbgPrint("PmemRead\n"); - //DbgPrint("Buffer: %llx, BufLen: 0x%x\n", physAddr, BufLen); + //WinDbgPrint("PmemRead\n"); + //WinDbgPrint("Buffer: %llx, BufLen: 0x%x\n", physAddr, BufLen); // ASSERTION: this has been set by SET MODE IOCTL and was very carefully checked. (It is also prevented from being changed.) ASSERT((extension->mode == PMEM_MODE_IOSPACE) || @@ -507,12 +491,12 @@ BOOLEAN pmemFastIoRead ( // As it is now, the issue is that we do not know whether a real error happened or 'only' a VSM/Hyper-v induced read error. // The read handler function returns either the number of bytes or 0 (but no status). // We could avoid that by giving a ULONG * bytes_read to the read handler function and have a NTSTATUS returned instead. - DbgPrint("Error in pmemFastIoRead: read error occurred: no bytes read.\n"); + WinDbgPrint("Error in pmemFastIoRead: read error occurred: no bytes read.\n"); status = STATUS_IO_DEVICE_ERROR; goto bail_out; } - // DbgPrint("Fast I/O read status on return: %08x.\n",status); + // WinDbgPrint("Fast I/O read status on return: %08x.\n",status); IoStatus->Status = status; IoStatus->Information = total_read; @@ -606,7 +590,7 @@ NTSTATUS PmemRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { status = GetExceptionCode(); - DbgPrint("Error 0x%08x, write-probe on usermode buffer failed in PmemRead. Bad/nonexisting buffer.\n", status); + WinDbgPrint("Error 0x%08x, write-probe on usermode buffer failed in PmemRead. Bad/nonexisting buffer.\n", status); goto bail_out; } @@ -614,8 +598,8 @@ NTSTATUS PmemRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) // It's accepted for the next stage. Don't touch, it's still considered poisonous. - //DbgPrint("PmemRead:\n"); - //DbgPrint("Buffer: %llx, BufLen: 0x%x\n", physAddr, BufLen); + //WinDbgPrint("PmemRead:\n"); + //WinDbgPrint("Buffer: %llx, BufLen: 0x%x\n", physAddr, BufLen); // ASSERTION: this has been set by SET MODE IOCTL and was very carefully checked. (It is also prevented from being changed.) ASSERT((extension->mode == PMEM_MODE_IOSPACE) || @@ -630,7 +614,7 @@ NTSTATUS PmemRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) // As it is now, the issue is that we do not know whether a real error happened or 'only' a VSM/Hyper-v induced read error. // The read handler function returns either the number of bytes or 0 (but no status). // We could avoid that by giving a ULONG * bytes_read to the read handler function and have a NTSTATUS returned instead. - DbgPrint("Error in PmemRead: read error occurred: no bytes read.\n"); + WinDbgPrint("Error in PmemRead: read error occurred: no bytes read.\n"); status = STATUS_IO_DEVICE_ERROR; goto bail_out; } @@ -752,7 +736,7 @@ NTSTATUS PmemWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) if (!extension->MemoryHandle) { - DbgPrint("Error (PmemWrite): physical device handle not available!\n"); // real error + WinDbgPrint("Error (PmemWrite): physical device handle not available!\n"); // real error written = 0; status = STATUS_IO_DEVICE_ERROR; goto exit; @@ -765,7 +749,7 @@ NTSTATUS PmemWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) if ((status != STATUS_SUCCESS) || (!mapped_buffer)) { - DbgPrint("Error %08x (PmemWrite): ZwMapViewOfSection failed (address %llx, offset %llx).\n", status, ViewSize.QuadPart, offset.QuadPart); // real error + WinDbgPrint("Error %08x (PmemWrite): ZwMapViewOfSection failed (address %llx, offset %llx).\n", status, ViewSize.QuadPart, offset.QuadPart); // real error written = 0; status = STATUS_IO_DEVICE_ERROR; goto exit; @@ -787,7 +771,7 @@ NTSTATUS PmemWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); - DbgPrint("Error 0x%08x (PmemWrite): writing %u bytes to %p failed.\n", status, BufLen, (mapped_buffer + page_offset)); + WinDbgPrint("Error 0x%08x (PmemWrite): writing %u bytes to %p failed.\n", status, BufLen, (mapped_buffer + page_offset)); written = 0; goto exit; } diff --git a/src/userspace_interface/winpmem_shared.h b/src/userspace_interface/winpmem_shared.h index ce57b7a..9536500 100644 --- a/src/userspace_interface/winpmem_shared.h +++ b/src/userspace_interface/winpmem_shared.h @@ -4,7 +4,7 @@ // Contains all the data shared between the driver and the usermode part. -#define PMEM_DRIVER_VERSION "3.0.3" +#define PMEM_DRIVER_VERSION "4.0.1" #define PMEM_DEVICE_NAME_ASCII "pmem" // the name for normal userspace usage. #define PMEM_DEVICE_NAME L"pmem" // preferred by the driver. #define PMEM_SERVICE_NAME TEXT("winpmem") // and this is finally the service/display name. diff --git a/src/winpmem.c b/src/winpmem.c index c7c72b0..24e1706 100644 --- a/src/winpmem.c +++ b/src/winpmem.c @@ -154,6 +154,11 @@ NTSTATUS wddDispatchDeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP PVOID outBuffer; PDEVICE_EXTENSION ext; ULONG InputLen, OutputLen; + + unsigned char * mdl_inbuffer = NULL; + unsigned char * mdl_outbuffer = NULL; + PMDL mdl_in = NULL; // The KM VA for the nailed UM PA. + PMDL mdl_out = NULL; // The KM VA for the nailed UM PA. PAGED_CODE(); @@ -172,48 +177,126 @@ NTSTATUS wddDispatchDeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP inBuffer = IrpStack->Parameters.DeviceIoControl.Type3InputBuffer; outBuffer = Irp->UserBuffer; - OutputLen = IrpStack->Parameters.DeviceIoControl.OutputBufferLength; InputLen = IrpStack->Parameters.DeviceIoControl.InputBufferLength; IoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode; + + // Point 1: the inputbuffer. + + // Not every ioctl uses an inputbuffer, but if there is one, nail it. + + if ((inBuffer) && ((UINT64)inBuffer < MM_USER_PROBE_ADDRESS)) // pre-check, just for shortening the fail path. + { + mdl_in = IoAllocateMdl(inBuffer, InputLen, FALSE, TRUE, NULL); + if (!mdl_in) + { + status = STATUS_INSUFFICIENT_RESOURCES; + goto exit; + } + + try + { + MmProbeAndLockPages(mdl_in, UserMode, IoReadAccess); + } + except(EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + DbgPrint("Error %08x: exception while locking UM inbuffer.\n", status); + IoFreeMdl(mdl_in); + goto exit; + } + + // If we got here, MmProbeAndLockPages probed + // and we have the physical pages resident. + // The UM VA address might still become invalid + // (only if the Usermode program is malicious). + // Thus, we must get our own VA for the PA to be actually safe. + + mdl_inbuffer = MmGetSystemAddressForMdlSafe(mdl_in, NormalPagePriority ); + + if (!mdl_inbuffer) + { + // According to current MSDN, when MmGetSystemAddressForMdlSafe was used, + // these following steps are not explicitly required. + // Not trusting this and prefer doing it explicitly. + + MmUnlockPages(mdl_in); + IoFreeMdl(mdl_in); + status = STATUS_INSUFFICIENT_RESOURCES; + goto exit; + } + } + // inputbuffer, if any, is now accessible via KM VA. + + // Point 2: the outputbuffer. + + // Not every ioctl uses an outputbuffer, but if there is one, we nail it, too. + + // Allocate an mdl. + if ((outBuffer) && ((UINT64)outBuffer < MM_USER_PROBE_ADDRESS)) // pre-check, for fail fast + { + mdl_out = IoAllocateMdl(outBuffer, OutputLen, FALSE, TRUE, NULL); + if (!mdl_out) + { + status = STATUS_INSUFFICIENT_RESOURCES; + goto exit; + } + + try + { + MmProbeAndLockPages(mdl_out, UserMode, IoWriteAccess); + } + except(EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + DbgPrint("Error %08x: exception while locking UM outbuffer.\n", status); + IoFreeMdl(mdl_out); + goto exit; + } + + mdl_outbuffer = MmGetSystemAddressForMdlSafe(mdl_out, NormalPagePriority ); + + if (!mdl_outbuffer) + { + // According to current MSDN, when MmGetSystemAddressForMdlSafe was used, + // these following steps are not explicitly required. + // Not trusting this and prefer doing it explicitly. + + MmUnlockPages(mdl_out); + IoFreeMdl(mdl_out); + status = STATUS_INSUFFICIENT_RESOURCES; + goto exit; + } + + // Now we have an own KM VA for the UM PA stuff. + + } + + // outputbuffer, if any, is now accessible via KM VA, using R+W PTE. switch (IoControlCode) { - - // Return information about memory layout etc through this ioctrl. + case IOCTL_GET_INFO: { PWINPMEM_MEMORY_INFO pInfo = NULL; - if (!outBuffer) + if (!mdl_outbuffer) { - DbgPrint("Error: outbuffer invalid in device io dispatch.\n"); + DbgPrint("Error: no outbuffer in IOCTL_GET_INFO.\n"); status = STATUS_INVALID_PARAMETER; goto exit; } + // Check if the acquired space accomodates for the WINPMEM_MEMORY_INFO struct. if (OutputLen < sizeof(WINPMEM_MEMORY_INFO)) { - DbgPrint("Error: outbuffer too small for the info struct!\n"); + DbgPrint("Error: outbuffersize too small for the info struct!\n"); status = STATUS_INFO_LENGTH_MISMATCH; goto exit; } - try - { - ProbeForRead( outBuffer, sizeof(WINPMEM_MEMORY_INFO), sizeof( UCHAR ) ); - ProbeForWrite( outBuffer, sizeof(WINPMEM_MEMORY_INFO), sizeof( UCHAR ) ); - } - except(EXCEPTION_EXECUTE_HANDLER) - { - status = GetExceptionCode(); - DbgPrint("Error: 0x%08x, probe in Device io dispatch, outbuffer. A naughty process sent us a bad/nonexisting buffer.\n", status); - - status = STATUS_INVALID_PARAMETER; // Naughty usermode process, this is your fault. - goto exit; - } - - pInfo = (PWINPMEM_MEMORY_INFO) outBuffer; + pInfo = (PWINPMEM_MEMORY_INFO) mdl_outbuffer; // Ensure we clear the buffer first. RtlZeroMemory(pInfo, sizeof(WINPMEM_MEMORY_INFO)); @@ -237,7 +320,7 @@ NTSTATUS wddDispatchDeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP pInfo->KernBase.QuadPart = ext->kernelbase.QuadPart; // Fill in KPCR. - GetKPCR(pInfo); + GetKPCR(pInfo); // This is the length of the response. Irp->IoStatus.Information = sizeof(WINPMEM_MEMORY_INFO); @@ -259,26 +342,14 @@ NTSTATUS wddDispatchDeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP goto exit; } - if ((!(inBuffer)) || (InputLen < sizeof(ULONG))) + if ((!mdl_inbuffer) || (InputLen < sizeof(ULONG))) { - DbgPrint("Error: InBuffer in device io dispatch was invalid.\n"); + DbgPrint("Error: no (adequate) inbuffer in IOCTL_SET_MODE.\n"); status = STATUS_INFO_LENGTH_MISMATCH; goto exit; } - try - { - ProbeForRead( inBuffer, sizeof(ULONG), sizeof( UCHAR ) ); - mode = *(PULONG)inBuffer; - } - except(EXCEPTION_EXECUTE_HANDLER) - { - status = GetExceptionCode(); - DbgPrint("Error: 0x%08x, IOCTL_SET_MODE, Inbuffer. A naughty process sent us a bad/nonexisting buffer.\n", status); - - status = STATUS_INVALID_PARAMETER; // Naughty usermode process, this is your fault. - goto exit; - } + mode = *(PULONG)mdl_inbuffer; switch(mode) { @@ -314,12 +385,12 @@ NTSTATUS wddDispatchDeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP } else { - DbgPrint("Error: the acquisition mode PTE failed setup and is not available.\n"); + DbgPrint("Error: the acquisition mode PTE is not available for your system.\n"); status = STATUS_NOT_SUPPORTED; } #else - WinDbgPrint("PTE Remapping has not been implemented on 32 bit OS.\n"); + DbgPrint("PTE Remapping has not been implemented on 32 bit OS.\n"); status = STATUS_NOT_IMPLEMENTED; #endif @@ -327,7 +398,7 @@ NTSTATUS wddDispatchDeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP break; default: - WinDbgPrint("Invalid acquisition mode %u.\n", mode); + DbgPrint("Invalid acquisition mode %u.\n", mode); status = STATUS_INVALID_PARAMETER; }; // switch mode @@ -337,11 +408,11 @@ NTSTATUS wddDispatchDeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP #if PMEM_WRITE_ENABLED == 1 - case IOCTL_WRITE_ENABLE: // Actually this is a switch. You can turn write support off again. + case IOCTL_WRITE_ENABLE: // Actually this is a switch. You can turn write support off/on again. { // No in/outbuffers needed. ext->WriteEnabled = !ext->WriteEnabled; - WinDbgPrint("Write mode is %u. Do you know what you are doing?\n", ext->WriteEnabled); + DbgPrint("Write mode is %u. Do you know what you are doing?\n", ext->WriteEnabled); status = STATUS_SUCCESS; }; break; // end of IOCTL_WRITE_ENABLE @@ -359,91 +430,73 @@ NTSTATUS wddDispatchDeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP if (!ext->pte_data.pte_method_is_ready_to_use) { - DbgPrint("Error: the acquisition mode PTE failed setup and is not available.\n"); + DbgPrint("Error: the acquisition mode PTE is not available for your system.\n"); status = STATUS_NOT_SUPPORTED; } - else + + if ((!mdl_inbuffer) || (InputLen < sizeof(UINT64))) { - try - { - ProbeForRead( inBuffer, sizeof(UINT64), sizeof( UCHAR ) ); - ProbeForWrite( outBuffer, sizeof(UINT64), sizeof( UCHAR ) ); - - In_VA.value = *(PUINT64) inBuffer; - - } - except(EXCEPTION_EXECUTE_HANDLER) - { - status = GetExceptionCode(); - DbgPrint("Error: 0x%08x, inbuffer in reverse query. A naughty process sent us a bad/nonexisting buffer.\n", status); - - status = STATUS_INVALID_PARAMETER; // Naughty usermode process, this is your fault. - goto exit; - } - - WinDbgPrint("REVERSE SEARCH QUERY for: VA %llx.\n", In_VA.value); - - page_offset = (ULONG) In_VA.offset; - In_VA.value -= page_offset; - - ASSERT(!In_VA.offset); - - // At least one sanity check. - if (!In_VA.value) - { - DbgPrint("Error: invoker specified 0 as virtual address. Mistake?\n"); - status = STATUS_ACCESS_DENIED; - goto exit; - } - - pte_status = virt_find_pte(In_VA, &pPTE); - - if (pte_status != PTE_SUCCESS) - { - // Remember todo: reverse search currently returns error on large pages (because not implemented). - DbgPrint("Reverse search found nothing: no present page for %llx. Sorry.\n", In_VA.value); - Out_PhysAddr = 0; - } - else - { - if (pPTE->present) // But virt_find_pte checked that already. - { - if (!pPTE->large_page) - { - Out_PhysAddr = ( PFN_TO_PAGE(pPTE->page_frame) ) + page_offset; // normal calculation. - } - else - { - Out_PhysAddr = ( PFN_TO_PAGE(( pPTE->page_frame + In_VA.pt_index)) ) + page_offset; // Large page calculation. - } - } - else - { - DbgPrint("Valid bit not set in PTE. Sorry.\n"); - Out_PhysAddr = 0; - } - } - - // Because NEITHER buffer wasn't locked down: - try - { - ProbeForWrite( outBuffer, sizeof(UINT64), sizeof( UCHAR ) ); - - *(PUINT64) outBuffer = Out_PhysAddr; - - } - except(EXCEPTION_EXECUTE_HANDLER) - { - status = GetExceptionCode(); - DbgPrint("Error: 0x%08x, probe in reverse search query. A naughty process sent us a bad/nonexisting buffer.\n", status); - - status = STATUS_INVALID_PARAMETER; // Naughty usermode process, this is your fault. - goto exit; - } - - Irp->IoStatus.Information = sizeof(UINT64); - - } // end of else pte_data.pte_method_is_ready_to_use=yes + DbgPrint("Error: no (adequate) inbuffer in IOCTL_REVERSE_SEARCH_QUERY.\n"); + status = STATUS_INFO_LENGTH_MISMATCH; + goto exit; + } + + if ((!mdl_outbuffer) || (OutputLen < sizeof(UINT64))) + { + DbgPrint("Error: no (adequate) outbuffer in IOCTL_REVERSE_SEARCH_QUERY.\n"); + status = STATUS_INFO_LENGTH_MISMATCH; + goto exit; + } + + In_VA.value = *(PUINT64) mdl_inbuffer; + + WinDbgPrint("REVERSE SEARCH QUERY for: VA %llx.\n", In_VA.value); + + page_offset = (ULONG) In_VA.offset; + In_VA.value -= page_offset; + + ASSERT(!In_VA.offset); + + // At least one sanity check. + if (!In_VA.value) + { + DbgPrint("Error: invoker specified 0 as virtual address. Mistake?\n"); + status = STATUS_ACCESS_DENIED; + goto exit; + } + + pte_status = virt_find_pte(In_VA, &pPTE); + + if (pte_status != PTE_SUCCESS) + { + // Remember todo: reverse search currently returns error on large pages (because not implemented). + WinDbgPrint("Reverse search found nothing: no present page for %llx. Sorry.\n", In_VA.value); + Out_PhysAddr = 0; + } + else + { + if (pPTE->present) // But virt_find_pte checked that already. + { + if (!pPTE->large_page) + { + Out_PhysAddr = ( PFN_TO_PAGE(pPTE->page_frame) ) + page_offset; // normal calculation. + } + else + { + Out_PhysAddr = ( PFN_TO_PAGE(( pPTE->page_frame + In_VA.pt_index)) ) + page_offset; // Large page calculation. + } + } + else + { + WinDbgPrint("Valid bit not set in PTE. Sorry.\n"); + Out_PhysAddr = 0; + } + } + + *(PUINT64) mdl_outbuffer = Out_PhysAddr; + + Irp->IoStatus.Information = sizeof(UINT64); + status = STATUS_SUCCESS; #else @@ -462,9 +515,27 @@ NTSTATUS wddDispatchDeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP } exit: - Irp->IoStatus.Status = status; - IoCompleteRequest(Irp,IO_NO_INCREMENT); - return status; + +// unlock & free all mdls we might have. +// According to current MSDN, when MmGetSystemAddressForMdlSafe was used, +// unlock & freeing is not explicitly required. +// Not trusting this and prefer doing it explicitly. + + if (mdl_outbuffer) + { + MmUnlockPages(mdl_out); + IoFreeMdl(mdl_out); + } + + if (mdl_inbuffer) + { + MmUnlockPages(mdl_in); + IoFreeMdl(mdl_in); + } + + Irp->IoStatus.Status = status; + IoCompleteRequest(Irp,IO_NO_INCREMENT); + return status; } @@ -477,6 +548,7 @@ NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT DeviceObject = NULL; PDEVICE_EXTENSION extension; ULONG FastioTag = 0x54505346; + UINT64 cr4 = 0; // for level 5 check. UNREFERENCED_PARAMETER(RegistryPath); @@ -502,7 +574,7 @@ NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, if (!NT_SUCCESS(ntstatus)) { - WinDbgPrint ("IoCreateDevice failed. => %08X\n", ntstatus); + DbgPrint ("IoCreateDevice failed. => %08X\n", ntstatus); // nothing to free until here. return ntstatus; } @@ -555,7 +627,7 @@ NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, if (!NT_SUCCESS(ntstatus)) { - WinDbgPrint("IoCreateSymbolicLink failed. => %08X\n", ntstatus); + DbgPrint("IoCreateSymbolicLink failed. => %08X\n", ntstatus); // The unload routine will take care of deleting things and we should not double free things. IoUnload(DriverObject); // Calling the Unload Routine or freeing all things that happened until here is actually required. @@ -584,17 +656,31 @@ NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, } #if defined(_WIN64) - // setup PTE mode - if (!setupBackupForOriginalRoguePage(&extension->pte_data)) - { - extension->pte_data.pte_method_is_ready_to_use = FALSE; - DbgPrint("Warning: PTE method failed! (You will not be able to use this method).\n"); - } - else - { - // Indicate it is usable now. - extension->pte_data.pte_method_is_ready_to_use = TRUE; - } + // setup PTE mode part. + + // Check if level 5 present (la57). + // We only support level 4. + cr4 = __readcr4(); + if (cr4 & (1 << 12)) // la57 bit set? + { + DbgPrint("Warning: level 5 paging system found.\n"); + DbgPrint("You can try the physical memory device method, but the PTE method is not implemented for level 5.\n"); + DbgPrint("Warning: Winpmem has never been tested on level 5 paging systems.\n"); + extension->pte_data.pte_method_is_ready_to_use = FALSE; + } + else // PTE method setup. + { + if (!setupBackupForOriginalRoguePage(&extension->pte_data)) + { + extension->pte_data.pte_method_is_ready_to_use = FALSE; + DbgPrint("Warning: PTE method failed (unknown reason)!\n(You will not be able to use this method).\n"); + } + else + { + // Indicate it is usable now. + extension->pte_data.pte_method_is_ready_to_use = TRUE; + } + } #endif ExInitializeFastMutex(&extension->mu); diff --git a/src/winpmem.vcxproj b/src/winpmem.vcxproj index c0bc455..cf642de 100644 --- a/src/winpmem.vcxproj +++ b/src/winpmem.vcxproj @@ -40,7 +40,7 @@ Desktop DbgengKernelDebugger - Windows7 + Windows10 diff --git a/versioninfo.md b/versioninfo.md index 237032d..50c5e6e 100644 --- a/versioninfo.md +++ b/versioninfo.md @@ -1,3 +1,7 @@ +### 4.0.1 BETA version + +Significant performance improvements, new features (level 5 paging detection), possible bug fixes. + ### 3.0.1 stable version