获取操作系统的内核基地址

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

posted @ 2012-11-05 16:22  unixstudio  阅读(5836)  评论(1编辑  收藏  举报