Я пишу драйвер режима ядра (Windows 10 64-бит), где мне требуется выполнение кода по виртуальному адресу в виртуальном адресном пространстве другого драйвера, который в настоящее время не используется (MmIsAddressValid
возвращает false).
Что я пробовал:
- вызов недокументированной функции ntoskrnl
MiFillPteHierarchy
. Эта функция возвращает 4 указателя, один из которых должен быть указателем на структуру PTE, которую мне нужно изменить, чтобы дать привилегии выполнения кода странице. Я обнаружил, что когда я обращаю внимание на эти указатели, я получаю ошибку сегментации страницы BSOD. Функция работает нормально, когда я даю ей адрес, для которого MmIsAddressValid возвращает true (что и следовало ожидать).
Функция MIFILLPTEHIERARCHY в ntoskrnl возвращает 4 виртуальных адреса, соответствующих PTE, PDE, PDPTE и PML4E (насколько мне известно). Вот функция sig, которую я имею:
typedef uint64_t(__fastcall* MIFILLPTEHIERARCHY)(void* virtualaddy, PTE_HIERARCHY* hierarchy);
Структура PTE_HIERARCHY выглядит следующим образом
struct PTE_HIERARCHY {
PTE* pte;
uint64_t* PDE;
uint64_t* PDPTE;
uint64_t* PML4E;
};
(единственная созданная мной структура PTE)
согласно инструкции INTEL, которую я читал https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf Структура PTE выглядит следующим образом:
union PTE{
uint64_t raw;
struct {
uint64_t present : 1; //Present; must be 1 to map a 4-KByte pag
uint64_t read_write : 1; //Read/write; if 0, writes may not be allowed to the 4-KByte page referenced by this entry (see Section 4
uint64_t user_supervisor : 1; //User/supervisor; if 0, user-mode accesses are not allowed to the 4-KByte page referenced by this entry (see Section 4.6)
uint64_t PWT : 1; //Page-level write-through; indirectly determines the memory type used to access the 4-KByte page referenced by this entry (see Section 4.9.
uint64_t PCD : 1; //Page-level cache disable; indirectly determines the memory type used to access the 4-KByte page referenced by this entry (see Section 4.9.2
uint64_t accessed : 1; //Accessed; indicates whether software has accessed the 4-KByte page referenced by this entry (see Section 4.8
uint64_t dirty : 1; //Dirty; indicates whether software has written to the 4-KByte page referenced by this entry (see Section 4.8)
uint64_t PAT : 1; //Indirectly determines the memory type used to access the 4-KByte page referenced by this entry (see Section 4.9.2)
uint64_t global : 1; //Global; if CR4.PGE = 1, determines whether the translation is global (see Section 4.10); ignored otherwis
uint64_t ignored : 3;
uint64_t physical_page_address : 36; //Physical address of the 4-KByte page referenced by this entry
uint64_t reserved : 4;
uint64_t ignored2 : 7;
uint64_t protection_key : 4; //Protection key; if CR4.PKE = 1, determines the protection key of the page (see Section 4.6.2); ignored otherwise
uint64_t xd : 1; //if IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 4-KByte page controlled by this entry; see Section 4.6); otherwise, reserved (must be 0
}values;
};
Моя реализация:
//enumerate system modules
uint32_t buffersize=0;
PRTL_PROCESS_MODULES mods=nullptr;
ZwQuerySystemInformation(SystemModuleInformation,0,buffersize,&buffersize);
mods = (PRTL_PROCESS_MODULES)ExAllocatePool(NonPagedPool, buffersize);
ZwQuerySystemInformation(SystemModuleInformation, mods, buffersize, &buffersize);
//using ACPI as an example
const char* drivername = "ACPI.sys";
for (uint32_t i = 0; i < mods->NumberOfModules; i++) {
RTL_PROCESS_MODULE_INFORMATION mod = mods->Modules[i];
char currentdriver[0x100] = { 0 };
memcpy(¤tdriver[0], &mod.FullPathName[mod.OffsetToFileName], 8);
if (!strcmp(currentdriver, drivername)) {
imagebase = mod.ImageBase[i]
imagesize=mod.size[i]
break;
}
}
//I then find some unused virtual address in the drivers address space
//something like
for imagebase < address < infinity (just find some address that is not valid
if(!MmIsAddressValid(address))
targetaddress= address
else
address++
//query MiFillPteHierarchy for PTE hierarchy
PTE_HIERARCHY hierarchy = {0};
MiFillPteHierarchy(targetaddress, &hierarchy);
hierarchy.PTE->values.present //BSOD here
//However if I have a virtual address where MmIsAddressValid returns true;
hierarchy.PTE->values.present //NO BSOD
Моя идея состояла в том, чтобы получить структуру PTE и изменить ее так, чтобы у меня было выполнение кода привилегии и ОС / ЦП не перезаписывают эту область виртуальной памяти другой страницей.
MmMapLockedPagesSpecifyCache
. Когда я использую этот метод, указатель возврата находится совсем рядом с целевым виртуальным адресом.
Это мой код, использующий этот метод
//where MmIsAddressValid(targetvirtualaddress)=false
void* pool = ExAllocatePool(NonPagedPool/PagePool(tried both),50);
memset(pool, 0, 50);
PMDL MDL = IoAllocateMdl(pool, 50, false, false, nullptr);
MmBuildMdlForNonPagedPool(MDL);
void* resultantaddress = MmMapLockedPagesSpecifyCache(MDL, KernelMode, MmNonCached, targetvirtualaddress, false, HighPagePriority);
//resultantaddress is nowhere near targetvirtualaddress
В идеале то, что я хотел бы:
MmIsAddressValid(targetaddress) returns false //windows can overwrite
//do something to page here
MmIsAddressValid(targetaddress) returns true //windows wont overwrite
//modify PTE to gain code execution privileges
(Насколько мне известно, если MmIsAddressValid возвращает true, это означает, что windows знает, что виртуальный адрес используется, и не будет пытаться его перезаписать)
Я предполагаю, что мой вопрос Почему разыменование PTE / PDE для этого виртуального адреса, который не используется, приводит к BSOD (ошибка сегментации страницы)? И почему MmMapLockedPagesSpecifyCache
не размещает страницу где-либо рядом с целевым адресом? И если оба эти метода не будут работать для моих целей, что я могу сделать?