操作系統的內核模塊根據處理器的個數和是否支持PAE(Physical Address Extension物理地址擴展)分為以下四種
ntoskrnl.exe ---Uniprocessor單處理器,不支持PAE
ntkrnlpa.exe ---Uniprocessor單處理器,支持PAE
ntkrnlmp.exe ---Multiprocessor多處理器,不支持PAE
ntkrpamp.exe ---Mulitiprocessor多處理器,支持PAE
操作系統實際上加載的內核模塊只能是上述四種中其中的一種
本文介紹的方法比較通用,沒有局限性,可以正確的獲得系統內核的基地址,主要方法有以下幾種:
- 通過ntdll.dll中未歸檔化的ZwQuerySystemInformation的11號調用得到系統的所有加載模塊,其中我們需要的操作系統內核模塊就位於第一個,此方法適用於ring0和ring3
- 通過KPCR結構中的KdVersionBlock成員結構得到KernelBase,此方法最為簡單但只適用於ring0
- 通過DriverEntry函數中的第一個參數DriverObject結構中的DriverSection成員,實際上是一個指向LDR_DATA_TABLE_ENTRY的結構指針,通過遍歷該表得到內核的基地址,此方法同樣只適用於ring0,不過需要知道內核模塊的名稱比如ntoskrnl.exe或者ntkrnlpa.exe不具有通用性,因此該方法下面就不討論了
1) ZwQuerySystemInformation方法
該未歸檔化的系統調用在ntdll.dll中,因此需要通過GetProcAddress來動態的獲得函數地址
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) typedef NTSTATUS (__stdcall *ZWQUERYSYSTEMINFORMATION)( IN ULONG SystemInformationClass,//SYSTEM_INFORMATION_CLASS IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL ); ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation; HMODULE hNtDll=LoadLibraryA("ntdll.dll"); if (!hNtDll) { printf("%s:LoadLibraryA failed,error=%d\n",__FUNCTION__,GetLastError()); return 0; } ZwQuerySystemInformation=(ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"ZwQuerySystemInformation"); if (!ZwQuerySystemInformation) { printf("%s:GetProcAddress failed,error=%d\n",__FUNCTION__,GetLastError()); return 0; }
取得地址之后就可以通過11號調用來得到SYSTEM_MODULE_INFORMATION,結構如下所示
typedef struct _SYSTEM_MODULE_INFORMATION { // Information Class 11 ULONG Reserved[2]; PVOID Base; ULONG Size; ULONG Flags; USHORT Index; USHORT Unknown; USHORT LoadCount; USHORT ModuleNameOffset; CHAR ImageName[256]; } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
這里緩沖區大小的取值步驟分為兩次,第一次傳遞進去的SystemInformation為NULL,SystemInformationLength為0,這樣返回的RenturnLength就是需要分配的緩沖區的大小,到時候malloc一下就可以了,然后再次調用ZwQuerySystemInformation即可
ULONG cbNeed; ZwQuerySystemInformation(11,NULL,0,&cbNeed); PSYSTEM_MODULE_INFORMATION Info=(PSYSTEM_MODULE_INFORMATION)malloc(cbNeed); NTSTATUS status=ZwQuerySystemInformation(11,Info,cbNeed,&cbNeed); if (!NT_SUCCESS(status)) { printf("%s:ZwQuerySystemInformation failed,error=0x%08x\n",__FUNCTION__,status); return 0; }
此時的Info的前四個字節是系統加載的所有模塊的個數,后面才是SYSTEM_MODULE_INFORMATION結構數組,因此將緩沖區稍微調整一下,然后內核基地址就是第一個SYSTEM_MODULE_INFORMATION結構中的Base
//ULONG ModuleCnt=*(PULONG)Info; PSYSTEM_MODULE_INFORMATION SystemModuleInfo=(PSYSTEM_MODULE_INFORMATION)((PULONG)Info+1); printf("ImageName=%s,ImageBase=0x%08x\n",SystemModuleInfo->ImageName,SystemModuleInfo->Base); free(Info);
2) KPCR方法
每個處理器核心都有一個KPCR結構(documented歸檔化),包含了該ProcessorCore的中斷向量表IDT,任務狀態段TSS,全局描述符表GDT等信息,當然還有KdVersionBlock,用windbg dt _kpcr發現KdVersionBlock的類型是PVOID,其實KeVersionBlock指向的是_DBGKD_GET_VERSION64結構體,這個結構體的大小只有0x28,這個結構信息還可以通過 IG_GET_KERNEL_VERSION的IOCTL操作來得到,緊跟在后面的是KDDEBUGGER_DATA64(不同版本的系統結構不一樣,但是新加入的成員都是放在結構體的后面,所以前面的變量位置並沒有改變),系統中很多未導出的重要變量都在此結構體中比如PsLoadedModuleList,PsActiveProcessHead,PspCidTable,ObpRootDirectoryObject,ObpTypeObjectType,KiProcessorBlock等,這些結構體都是從%WDKPATH%\inc\api\WDBGEXTS.H中獲得的,可以通過GetDebuggerData來得到,這里列出這些結構體吧
typedef struct _DBGKD_GET_VERSION64 { USHORT MajorVersion; USHORT MinorVersion; UCHAR ProtocolVersion; UCHAR KdSecondaryVersion; // Cannot be 'A' for compat with dump header USHORT Flags; USHORT MachineType; // // Protocol command support descriptions. // These allow the debugger to automatically // adapt to different levels of command support // in different kernels. // // One beyond highest packet type understood, zero based. UCHAR MaxPacketType; // One beyond highest state change understood, zero based. UCHAR MaxStateChange; // One beyond highest state manipulate message understood, zero based. UCHAR MaxManipulate; // Kind of execution environment the kernel is running in, // such as a real machine or a simulator. Written back // by the simulation if one exists. UCHAR Simulation; USHORT Unused[1]; ULONG64 KernBase; ULONG64 PsLoadedModuleList; // // Components may register a debug data block for use by // debugger extensions. This is the address of the list head. // // There will always be an entry for the debugger. // ULONG64 DebuggerDataList; } DBGKD_GET_VERSION64, *PDBGKD_GET_VERSION64;
typedef struct _DBGKD_DEBUG_DATA_HEADER64 { // // Link to other blocks // LIST_ENTRY64 List; // // This is a unique tag to identify the owner of the block. // If your component only uses one pool tag, use it for this, too. // ULONG OwnerTag; // // This must be initialized to the size of the data block, // including this structure. // ULONG Size; } DBGKD_DEBUG_DATA_HEADER64, *PDBGKD_DEBUG_DATA_HEADER64;
typedef struct _KDDEBUGGER_DATA64 { DBGKD_DEBUG_DATA_HEADER64 Header; // // Base address of kernel image // ULONG64 KernBase; // // DbgBreakPointWithStatus is a function which takes an argument // and hits a breakpoint. This field contains the address of the // breakpoint instruction. When the debugger sees a breakpoint // at this address, it may retrieve the argument from the first // argument register, or on x86 the eax register. // ULONG64 BreakpointWithStatus; // address of breakpoint // // Address of the saved context record during a bugcheck // // N.B. This is an automatic in KeBugcheckEx's frame, and // is only valid after a bugcheck. // ULONG64 SavedContext; // // help for walking stacks with user callbacks: // // // The address of the thread structure is provided in the // WAIT_STATE_CHANGE packet. This is the offset from the base of // the thread structure to the pointer to the kernel stack frame // for the currently active usermode callback. // USHORT ThCallbackStack; // offset in thread data // // these values are offsets into that frame: // USHORT NextCallback; // saved pointer to next callback frame USHORT FramePointer; // saved frame pointer // // pad to a quad boundary // USHORT PaeEnabled:1; // // Address of the kernel callout routine. // ULONG64 KiCallUserMode; // kernel routine // // Address of the usermode entry point for callbacks. // ULONG64 KeUserCallbackDispatcher; // address in ntdll // // Addresses of various kernel data structures and lists // that are of interest to the kernel debugger. // ULONG64 PsLoadedModuleList; ULONG64 PsActiveProcessHead; ULONG64 PspCidTable; ULONG64 ExpSystemResourcesList; ULONG64 ExpPagedPoolDescriptor; ULONG64 ExpNumberOfPagedPools; ULONG64 KeTimeIncrement; ULONG64 KeBugCheckCallbackListHead; ULONG64 KiBugcheckData; ULONG64 IopErrorLogListHead; ULONG64 ObpRootDirectoryObject; ULONG64 ObpTypeObjectType; ULONG64 MmSystemCacheStart; ULONG64 MmSystemCacheEnd; ULONG64 MmSystemCacheWs; ULONG64 MmPfnDatabase; ULONG64 MmSystemPtesStart; ULONG64 MmSystemPtesEnd; ULONG64 MmSubsectionBase; ULONG64 MmNumberOfPagingFiles; ULONG64 MmLowestPhysicalPage; ULONG64 MmHighestPhysicalPage; ULONG64 MmNumberOfPhysicalPages; ULONG64 MmMaximumNonPagedPoolInBytes; ULONG64 MmNonPagedSystemStart; ULONG64 MmNonPagedPoolStart; ULONG64 MmNonPagedPoolEnd; ULONG64 MmPagedPoolStart; ULONG64 MmPagedPoolEnd; ULONG64 MmPagedPoolInformation; ULONG64 MmPageSize; ULONG64 MmSizeOfPagedPoolInBytes; ULONG64 MmTotalCommitLimit; ULONG64 MmTotalCommittedPages; ULONG64 MmSharedCommit; ULONG64 MmDriverCommit; ULONG64 MmProcessCommit; ULONG64 MmPagedPoolCommit; ULONG64 MmExtendedCommit; ULONG64 MmZeroedPageListHead; ULONG64 MmFreePageListHead; ULONG64 MmStandbyPageListHead; ULONG64 MmModifiedPageListHead; ULONG64 MmModifiedNoWritePageListHead; ULONG64 MmAvailablePages; ULONG64 MmResidentAvailablePages; ULONG64 PoolTrackTable; ULONG64 NonPagedPoolDescriptor; ULONG64 MmHighestUserAddress; ULONG64 MmSystemRangeStart; ULONG64 MmUserProbeAddress; ULONG64 KdPrintCircularBuffer; ULONG64 KdPrintCircularBufferEnd; ULONG64 KdPrintWritePointer; ULONG64 KdPrintRolloverCount; ULONG64 MmLoadedUserImageList; // NT 5.1 Addition ULONG64 NtBuildLab; ULONG64 KiNormalSystemCall; // NT 5.0 hotfix addition ULONG64 KiProcessorBlock; ULONG64 MmUnloadedDrivers; ULONG64 MmLastUnloadedDriver; ULONG64 MmTriageActionTaken; ULONG64 MmSpecialPoolTag; ULONG64 KernelVerifier; ULONG64 MmVerifierData; ULONG64 MmAllocatedNonPagedPool; ULONG64 MmPeakCommitment; ULONG64 MmTotalCommitLimitMaximum; ULONG64 CmNtCSDVersion; // NT 5.1 Addition ULONG64 MmPhysicalMemoryBlock; ULONG64 MmSessionBase; ULONG64 MmSessionSize; ULONG64 MmSystemParentTablePage; // Server 2003 addition ULONG64 MmVirtualTranslationBase; USHORT OffsetKThreadNextProcessor; USHORT OffsetKThreadTeb; USHORT OffsetKThreadKernelStack; USHORT OffsetKThreadInitialStack; USHORT OffsetKThreadApcProcess; USHORT OffsetKThreadState; USHORT OffsetKThreadBStore; USHORT OffsetKThreadBStoreLimit; USHORT SizeEProcess; USHORT OffsetEprocessPeb; USHORT OffsetEprocessParentCID; USHORT OffsetEprocessDirectoryTableBase; USHORT SizePrcb; USHORT OffsetPrcbDpcRoutine; USHORT OffsetPrcbCurrentThread; USHORT OffsetPrcbMhz; USHORT OffsetPrcbCpuType; USHORT OffsetPrcbVendorString; USHORT OffsetPrcbProcStateContext; USHORT OffsetPrcbNumber; USHORT SizeEThread; ULONG64 KdPrintCircularBufferPtr; ULONG64 KdPrintBufferSize; ULONG64 KeLoaderBlock; USHORT SizePcr; USHORT OffsetPcrSelfPcr; USHORT OffsetPcrCurrentPrcb; USHORT OffsetPcrContainedPrcb; USHORT OffsetPcrInitialBStore; USHORT OffsetPcrBStoreLimit; USHORT OffsetPcrInitialStack; USHORT OffsetPcrStackLimit; USHORT OffsetPrcbPcrPage; USHORT OffsetPrcbProcStateSpecialReg; USHORT GdtR0Code; USHORT GdtR0Data; USHORT GdtR0Pcr; USHORT GdtR3Code; USHORT GdtR3Data; USHORT GdtR3Teb; USHORT GdtLdt; USHORT GdtTss; USHORT Gdt64R3CmCode; USHORT Gdt64R3CmTeb; ULONG64 IopNumTriageDumpDataBlocks; ULONG64 IopTriageDumpDataBlocks; // Longhorn addition ULONG64 VfCrashDataBlock; ULONG64 MmBadPagesDetected; ULONG64 MmZeroedPageSingleBitErrorsDetected; // Windows 7 addition ULONG64 EtwpDebuggerData; USHORT OffsetPrcbContext; } KDDEBUGGER_DATA64, *PKDDEBUGGER_DATA64;
因此要獲取的KernelBase只要通過KdVersionBlock->DBGKD_GET_VERSION64->KernBase或者KdVersionBlock->DBGKD_GET_VERSION64->KDDEBUGGER_DATA64->KernBase即可
在貼出代碼之前有個需要注意的地方就是之前說過每個ProcessorCore都有一個KPCR結構,如果你的操作系統是多核的話你可以觀察一下,只有CPU0的KdVersionBlock才有值,其他的都是NULL
怎么查看呢,通過windbg擴展命令!pcr [CPUID],如果沒有指定CPUID的話那么默認顯示的是CPU0的KPCR,通過!pcr 0 和!pcr 1……得到KPCR地址,然后dt再查看
如果碰巧當前線程正運行在CPU0那么算你幸運,如果是在別的核上運行的話會直接BSOD,那怎么辦呢,可以通過KeSetSystemAffinityThread來使該段代碼運行在CPU0,該函數請查看MSDN
現在可以放代碼了
//由於只用到了一個硬編碼,在不同的系統上面都是一樣的,所以可以在不同的環境中編譯
DRIVER_INITIALIZE DriverEntry; DRIVER_UNLOAD DriverUnload; NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegPath) { NTSTATUS status=STATUS_SUCCESS; PVOID KdVersionBlock; PKDDEBUGGER_DATA64 KdDebuggerData64; DbgPrint("DriverEntry!\n"); KeSetSystemAffinityThread(1); __asm{ mov eax,fs:[0x34]//KdVersionBlock’s offset in KPCR is 0x34 mov KdVersionBlock,eax } KdDebuggerData64=(PKDDEBUGGER_DATA64)((ULONG_PTR)KdVersionBlock+sizeof(DBGKD_GET_VERSION64));
//DbgPrint("KernelBase=0x%08x\n",(PDBGKD_GET_VERSION64)KdVersionBlock->KernBase);
DbgPrint("KernelBase=0x%08x\n",KdDebuggerData64->KernBase);
KeRevertToUserAffinityThread(); DriverObject->DriverUnload=DriverUnload; return status; } VOID DriverUnload(PDRIVER_OBJECT DriverObject) { DbgPrint("DriverUnload!\n"); }
通過KdVersionBlock->_DBGKD_GET_VERSION64->KDDEBUGGER_DATA64這條路徑可以獲得很多重要的沒導出的系統變量,KdVersionBlock真是個好東西,呵呵
BTW:fs寄存器在內核態的時候指向的是KPCR結構,用戶態下指向的是當前線程的TEB(Thread Environment Block線程環境塊),因此這種方法只適用於ring0層