Понякога за определени нужди е необходимо да се разбере адресът на ntoskrnl.exe. За целта могат да се направят няколко неща - като може би най-лесното е да се извика ZwQuerySystemInformation с information class ModuleQueryInformation. Така ще имате копие на PsLoadedModuleLIst, който може да се обходи и да разберете адресът, на който е зареден кърнъла. Разбира се това не е единственият начин и определено е най-скучният. Днес ще покажа един друг метод, който е "стабилен" - тоест едва дали в бъдеще ще бъде премахнат и като цяло не разчита на volatile структури, които могат да бъдат обект на промяна при даден service pack или update. И така, за какво става на въпрос.
PDRIVER_OBJECT
Всеки драйвър в entry point-а му бива подаден един pointer към структура от тип DRIVER_OBJECT. Тази структура е документирана в WDK-то и затова не смятам да я поствам тук, но щ обърна внимание на един интересен pointer :
struct _DRIVER_OBJECT {//ommited for brevityPVOID DriverSection;}
От декларацията му не става ясно към какво точно сочи, но всъщност ако човек си направи труда да се поразрови из сорсовете на WRK ще разбере, че всъщност това е pointer към структура от следният тип:
typedef struct _KLDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; PVOID ExceptionTable; ULONG ExceptionTableSize; // ULONG padding on IA64 PVOID GpValue; PVOID NonPagedDebugInfo; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT __Unused5; PVOID SectionPointer; ULONG CheckSum; // ULONG padding on IA64 PVOID LoadedImports; PVOID PatchInformation; } KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
Това, което трябва да се отбележи е, че всички нови променливи, които ще бъдат добавени в декларация на тази структура, винаги се добавят в края, което означава, че съвсем спокойно можем да я ползваме. Полетата, които ни интересуват са LIST_ENTRY InLoadOrderLinks, както лесно може да се досетим - това е linked-list, които съдържа всички заредени драйвъри. BaseDllName е името на даденият драйвър - за windows kernel-а, това винаги ще бъде ntoskrnl.exe, без значение коя версия на кърнъла сме заредили - ntoskrnl.exe или ntoskrnlpa. След като намерим entry-то, което ни трябва можем съвсем спокойно да вземем стойността на DllBase, която е адресът, на който е зареден кърнъла. От там нататък можем да парсваме този адрес като най-обикновен PE файл. Ето и малко код:
PVOID GetNtosBaseAddr(PDRIVER_OBJECT DriverObject) { PKLDR_DATA_TABLE_ENTRY driverSection; PKLDR_DATA_TABLE_ENTRY ldrDataTableEntry; PVOID kernelBase = NULL; PLIST_ENTRY headEntry; PLIST_ENTRY currentEntry; UNICODE_STRING ntosString = {0}; driverSection = (PKLDR_DATA_TABLE_ENTRY) DriverObject->DriverSection; RtlInitUnicodeString(&ntosString, L"ntoskrnl.exe"); headEntry = driverSection->InLoadOrderLinks.Blink; //header points to prev entry currentEntry = headEntry->Flink; while(currentEntry != headEntry) { ldrDataTableEntry = CONTAINING_RECORD(currentEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if(RtlCompareUnicodeString(&ntosString, &ldrDataTableEntry->BaseDllName, TRUE) == 0) { DbgPrint("Found base address of NTOSKRNL: 0x%p\n", ldrDataTableEntry->DllBase ); kernelBase = ldrDataTableEntry->DllBase; break; } currentEntry = currentEntry->Flink; } return kernelBase; }