获取操作系统的内核基地址
操作系统的内核模块根据处理器的个数和是否支持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层