The idea of this article is to map a continuous virtual address to a new address by filling the page table entry, and at the same time map the read-only memory to be modified to the Dirty position of the page table entry. In the Windows operating system, write protection is achieved by protecting a specific virtual address. If a new mapping is not established, even if Dirty is set, an attempt to write to read-only memory will still trigger BugCheck. If a new mapping is established but Dirty is not set To trigger the BugCheck of PAGE_FAULT, two steps are indispensable.
To fill page table entries, PTEBase needs to be dynamically located first. The international practice is to use the page table self-mapping method. The code is as follows:
Here I also give another acquisition plan by the way, the basic idea is to export the function through NT
The 0x40000 size data under Dump contains MmPfnDataBase. MmPfnDataBase is an array of memory information indexed by the frame number of the physical address page. Among them is the address PteAddress of the PTE item corresponding to the physical page. We pass a valid physical address into it. (Such as the current CR3: __readcr3() & 0xFFFFFFFFF000) Take out the PteAddress, because PTEBase must be a multiple of 0x8000000000, so PteAddress can be directly calculated from PteBase. In addition, starting from the Win10 RS1 anniversary preview, KeCapturePersistentThreadState is controlled by the global variable ForceDumpDisabled. If the relevant subkeys in the registry “HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\CrashControl” meet the conditions, this variable will be set to 1 at startup , Causing the KeCapturePersistentThreadState call to fail. In summary, we get the second code for obtaining PTEBase as follows:
After obtaining PTEBase, now enter the topic, the main idea of the following code is: first start from a valid 512G-aligned kernel virtual address, until 0xFFFFFFFFFFFFFFFF, find the unoccupied PML4T (hereinafter collectively referred to as PXE) sub-items, namely The Valid bit of PXE is a sub-item of 0.
For the effective starting kernel virtual address, I chose MmSystemRangeStart at the beginning. The virtual machine test found that the mapping was successful for 8.1/10, but the CPU under Vista/7/8 did not recognize the validity of the mapped address and triggered the BugCheck, debugger When it is found that MmSystemRangeStart=0xFFFF080000000000 under Vista/7/8, MmSystemRangeStart=0xFFFF800000000000 under 8.1/10, and the mapped address range under Vista/7/8 is [0xFFFF080000000000, 0xFFFF800000000000), the debugger CrashDump prompts Noncanonical Virtual Address. After consulting the Intel manual, the author found that the maximum number of bits for virtual address addressing supported by the current Intel CPU is limited to 48 bits. For 64-bit Windows, the 47th bit is used to distinguish between user layer virtual addresses and kernel layer virtual addresses. That is, the kernel layer address actually has only 47 effective bits, so the effective starting kernel virtual address is 0xFFFF800000000000. Of course, for the sake of rigor, you can use the 0x80000008 function of the CPUID. At this time, the ah of the eax register is the maximum effective number of digits of the virtual address supported by the processor. Set it to x, then for the 64-bit address, as long as the highest (65-x ) Bits are all set to 1, and the remaining (x-1) bits are all set to 0, that is, the effective starting kernel virtual address is obtained.
After finding the unused PXE sub-item, apply for a continuous physical memory. The initial size is the page size described by the PXE sub-item and 512 PPE items of the PXE sub-item, namely (1 + 0x200) * PAGE_SIZE. If the application fails, Then halve the applied PPE item, and so on… Because it is continuous physical memory, the best solution is to apply through MmAllocateContiguousMemory. If ExAllocatePool is used, when the requested page is not a 2M large page, its virtual address corresponds to The physical addresses are probably not continuous, which will add a lot of trouble to the physical page frame numbers of 512 PPE items. After applying for a continuous physical address, the first page is filled into the target PXE sub-item, and the physical page frame numbers of the second to 513 pages are sequentially filled into the 512 PPE items described in the PXE sub-item page. Then, given any virtual address that needs to be mapped, we first align it to 0x8000000000 (512G), and then retrieve its PXE, PPE, and PDE items in turn. If the PXE item Valid is 0 or LargePage is 1, then it will not be mapped, otherwise it will start Search PPE and PDE in turn. If the PPE item Valid is 0, the PPE page corresponding to the mapped address is cleared. If Valid is 1, it will handle the LargePage or not. If it is a 1G large page, divide it into 512 2M large pages, and then fill the corresponding physical page frame numbers into the 512 PDE items described by the PPE in order; If it is not a large page, copy all the page corresponding to the PPE item of the mapped address to the PPE page corresponding to the mapped address. From here, the basic address mapping has been completed, and then for the page to be modified, just set the Dirty position of the corresponding PTE or large page PDE or large page PPE item to 1.
Test code and results on Win10 18362.207 x64:
NTKERNELAPI NTSTATUS ObReferenceObjectByName(IN PUNICODE_STRING ObjectName, IN
Attributes, IN PACCESS_STATE PassedAccessState OPTIONAL, IN ACCESS_MASK DesiredAccess OPTIONAL, IN POBJECT_TYPE ObjectType, IN KPROCESSOR_MODE AccessMode, IN OUT
ParseContext OPTIONAL, OUT
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString)
PagePointer = 0;
BaseAddress = 0;
SizeOfValidPPEPages = 0;
PDRIVER_OBJECT DrvObj = 0;
)) && NT_SUCCESS(ObReferenceObjectByName(&UDrvName, OBJ_CASE_INSENSITIVE, 0, 0, *IoDriverObjectType, KernelMode, 0, (
PagePointer = AllocateSinglePXEDirectory(&BaseAddress, &SizeOfValidPPEPages);
MappedKbdClassBase = FillPDEArrayForAllValidPPEs(PagePointer, BaseAddress, SizeOfValidPPEPages, (
)(MappedKbdClassBase + 4) = 0x78563412;
Also attach Win10’s _MMPTE_HARDWARE for reference
To sum up, the code implemented above is probably similar to MmBuildMdlForNonPagedPool and MmMapLockedPagesSpecifyCache. The difference is that the code in this article is more equivalent to a snapshot of the physical memory distribution of a 512G virtual address space. Some of the page memory may be placed during the snapshot process. In the case of entering the physical memory and being distributed back to the hard disk after the snapshot, the use of MmIsAddressValid in the mapped address space is far less reliable than the use in the original address space.