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