獲取操作系統的內核基地址


操作系統的內核模塊根據處理器的個數和是否支持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層


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM