首页 电脑 电脑学堂 查看内容

获取Windows 系统的内核变量

2004-9-29 20:05 762 0

摘要: 作  者:于旸邮  件:tombkeeper[0x40]nsfocus[0x2e]com      ...
关键词: nbsp PsLoadedModuleLi Windows KdVersionBlock DWORD NtSystemDebugCon 变量 内核 KdDebuggerDataBl 代码

作  者:于旸邮  件:tombkeeper[0x40]nsfocus[0x2e]com        tombkeeper[0x40]xfocus[0x2e]org完成于:2004.07.30关键字:PsLoadedModuleList、PsActiveProcessHead、NtSystemDebugControl        PsNtosImageBase、KdVersionBlock、KdDebuggerDataBlock、内核变量    PsLoadedModuleList等重要内核变量并未被ntoskrnl.exe导出,也没有公开的函数可以获取。而这些内核变量对于Rootkit、Anti-Rootkit 以及内核溢出的利用等都是至关重要的。    下面我们以PsLoadedModuleList、PsActiveProcessHead 等为例,介绍得到这些变量的方法。    对于Windows NT 4.0和Windows 2000,尚没有“温柔”的办法可以获取这些变量,比较理想的办法也就是特征代码搜索,这种方法虽然暴力,但通常都很有效,一般也不会出问题;对于Windows XP和Windows 2003,我们找到了一些更加优雅的选择。    下面首先介绍特征代码搜索的方法。[DWORD KernelBase]    要进行特征代码搜索,首先要定位ntoskrnl.exe在内核的加载地址KernelBase。这个地址可以通过ZwQuerySystemInformation Class 10的SystemModuleInformation来得到。参考资源[1]中给出了相关代码。事实上,KernelBase 这个值对同一操作系统来说非常固定,可以作为常量来用:Windows NT:  0x80100000Windows 2000:0x80400000Windows XP:  0x804d1000Windows 2003: 0x804e0000    Windows NT 4.0 ntoskrnl.exe 的OptionalHeader->ImageBase = 0x80100000,ntldr 也会按照这个值来加载内核,但是从Windows 2000开始就不是这样了。可能基于这个历史原因,各系统的*(DWORD *)PsNtosImageBase始终初始化为0x80100000。    另外,内核变量PsNtosImageBase、KdpNtosImageBase等也指向KernelBase:KernelBase = *(DWORD *)PsNtosImageBaseKernelBase = *(DWORD *)KdpNtosImageBase[LIST_ENTRY PsLoadedModuleList]    PsLoadedModuleList这个全局变量指向一个保存着所加载驱动信息的双向链表。通过它可以枚举系统中所有的驱动模块。    虽然很多内核函数都用到了PsLoadedModuleList,但是大部分并没有被导出,而从基址开始搜会很花时间。对于Windows 2000来说,从下面这个地方入手是个好主意:nt!MmGetSystemRoutineAddress+0x66:804f0ed0 8b35f0e84680     mov     esi,[nt!PsLoadedModuleList (8046e8f0)]804f0ed6 81fef0e84680     cmp     esi,0x8046e8f0流程如下:1、ImageBase = LoadLibraryA("ntoskrnl.exe")2、GetProcAddress(ImageBase,"MmGetSystemRoutineAddress")3、搜索特征代码:*(WORD *)(MmGetSystemRoutineAddress + i) = 0x358b && \*(WORD *)(MmGetSystemRoutineAddress + i + 6) = 0xfe81&&*(DWORD *)(MmGetSystemRoutineAddress + i + 2) == \*(DWORD *)(MmGetSystemRoutineAddress + i + 8)4、定位内核中的地址:PsLoadedModuleList = \*(DWORD *)(MmGetSystemRoutineAddress + i + 2) + (KernelBase - ImageBase)    从SP0到SP4,i值并不相同,但是肯定不大于0x100。    对Windows NT来说,就没这么好运气了,没有理想的可用于定位的API,只能从头开始搜索。下面这段代码至少对SP1~SP6a的来说都具有很好的稳定性和唯一性:801CEB1C: 8B 4D 08           mov       ecx,dword ptr [ebp+8]801CEB1F: 89 01              mov       dword ptr [ecx],eax801CEB21: 8B 45 0C           mov       eax,dword ptr [ebp+0Ch]801CEB24: 89 10              mov       dword ptr [eax],edx801CEB26: 8B 36              mov       esi,dword ptr [esi]801CEB28: 81 FE 70 0B 15 80  cmp       esi,80150B70h  //PsLoadedModuleList    如果是用驱动做这件事情,就不必暴力搜索了,fuzen_op([email protected])在FU_Rootkit2.0(参考资源[2])中使用了一段比较巧妙的代码:DWORD FindPsLoadedModuleList (IN PDRIVER_OBJECT DriverObject){    PMODULE_ENTRY pm_current;    if (DriverObject == NULL)        return 0;    pm_current = *((PMODULE_ENTRY*)((DWORD)DriverObject + 0x14));    if (pm_current == NULL)        return 0;    gul_PsLoadedModuleList = pm_current;    while ((PMODULE_ENTRY)pm_current->le_mod.Flink != gul_PsLoadedModuleList)    {        if ((pm_current->unk1 == 0x00000000) && \        (pm_current->driver_Path.Length == 0))        {            return (DWORD) pm_current;        }        pm_current = (MODULE_ENTRY*)pm_current->le_mod.Flink;    }    return 0;}[LIST_ENTRY PsActiveProcessHead]    理论上PsActiveProcessHead 也可以用搜索代码的方法来取,但是还有更简单的方法。    ntoskrnl.exe导出的PsInitialSystemProcess 是一个PEPROCESS,指向system进程的EPROCESS。这个EPROCESS的结构成员EPROCESS.ActiveProcessLinks.Blink 就是PsActiveProcessHead:kd> dt _EPROCESS ActiveProcessLinks.Blink poi(PsInitialSystemProcess)   +0x0a0 ActiveProcessLinks  :  [ 0x81356900 - 0x8046e728 ]      +0x004 Blink               : 0x8046e728  [ 0x81a2fb00 - 0xff5a4ce0 ]kd> ? PsActiveProcessHeadEvaluate expression: -2142836952 = 8046e728    EPROCESS这个结构在不同的操作系统上各不相同,需要分别对待。[struct _KDDEBUGGER_DATA64 KdDebuggerDataBlock]    Windows 2000 开始,系统引入了变量KdDebuggerDataBlock。其中包含了大量的内核变量。如果能够获取到的话,可以解决许多问题。遗憾的是,Windows NT上没有这个变量。WinDBG SDK的wdbgexts.h中包含了它的结构:    typedef struct _KDDEBUGGER_DATA64因为比较长,这里就不引用了。    从对5.0.2195.6902版本ntoskrnl.exe 的逆向工程结果来看,只有两个函数使用了该变量,并且,两个函数都未导出,且代码前后没有明显特征,无法靠直接搜索代码来获取。    但是,我们发现,ntoskrnl.exe导出了KdEnableDebugger,KdEnableDebugger会调用KdInitSystem,而KdInitSystem 中引用了KdDebuggerDataBlock:n < 100Windows 2000:KdEnableDebugger + n:6A 00                 push    06A 00                 push    0C6 05 28 41 48 00 01  mov     _PoHiberInProgress, 1E8 1C DC 10 00        call    _KdInitSystem@8 ; KdInitSystem(x,x)KdInitSystem + n:68 70 02 00 00        push    270h      // sizeof(KdDebuggerDataBlock)B9 50 D1 54 00        mov     ecx, offset _KdpDebuggerDataListHead68 D8 FA 46 00        push    offset KdDebuggerDataBlock8B 40 18              mov     eax, [eax+18h]68 4B 44 42 47        push    4742444Bh // "KDBG",可以用作搜索的定位标志A3 3C D1 54 00        mov     ds:_KdpNtosImageBase, eax89 0D 54 D1 54 00     mov     ds:dword_54D154, ecx89 0D 50 D1 54 00     mov     ds:_KdpDebuggerDataListHead, ecxWindows XPKdEnableDebugger + n:6A 00                 push    06A 00                 push    0C6 05 8C 98 47 00 01  mov     _PoHiberInProgress, 1E8 2B 17 13 00        call    _KdInitSystem@8 ; KdInitSystem(x,x)KdInitSystem + n:68 90 02 00 00        push    290h68 E0 9D 46 00        push    offset KdDebuggerDataBlockBE 74 96 59 00        mov     esi, offset _KdpDebuggerDataListHead68 4B 44 42 47        push    4742444Bh89 35 78 96 59 00     mov     ds:dword_599678, esi89 35 74 96 59 00     mov     ds:_KdpDebuggerDataListHead, esiWindows 2003KdEnableDebugger + n:56                    push    esi56                    push    esiC6 05 0C 08 49 00 01  mov     PoHiberInProgres, 1E8 CB AD 15 00        call    _KdInitSystem@8 ; KdInitSystem(x,x)KdInitSystem + n:68 18 03 00 00        push    318h68 D0 A3 47 00        push    offset KdDebuggerDataBlockBE 18 10 5D 00        mov     esi, offset _KdpDebuggerDataListHead68 4B 44 42 47        push    4742444Bh89 35 1C 10 5D 00     mov     ds:dword_5D101C, esi89 35 18 10 5D 00     mov     ds:_KdpDebuggerDataListHead, esi    可以看出,上面代码特征的唯一性很好。用于搜索是没有问题的。我在上面同时列出了三个系统的代码,仅仅只是为了比较,事实上,对Windows XP和Windows 2003是完全没有必要采取如此暴力手段的。    下面介绍针对Windows XP和Windows 2003的方法。[struct _DBGKD_GET_VERSION64 KdVersionBlock]    Opc0de和Edgar Barbosa在参考资源[3]中提到,Windows XP和Windows 2003引入的一个新内核变量:KdVersionBlock,其结构中包含了PsLoadedModuleList。    WinDBG SDK的wdbgexts.h中有KdVersionBlock的结构:typedef struct _DBGKD_GET_VERSION64 {    USHORT  MajorVersion;    USHORT  MinorVersion;    USHORT  ProtocolVersion;    USHORT  Flags;    USHORT  MachineType;    UCHAR   MaxPacketType;    UCHAR   MaxStateChange;    UCHAR   MaxManipulate;    UCHAR   Simulation;    USHORT  Unused[1];    ULONG64 KernBase;    ULONG64 PsLoadedModuleList;    ULONG64 DebuggerDataList;} DBGKD_GET_VERSION64, *PDBGKD_GET_VERSION64;KdVersionBlock是KPCR的一个成员:lkd> dt _kpcr ffdff000nt!_KPCR   +0x000 NtTib            : _NT_TIB   +0x000 Used_ExceptionList : 0xf717dbcc   +0x004 Used_StackBase   : (null)   +0x008 PerfGlobalGroupMask : (null)   +0x00c TssCopy          : 0x80042000   +0x010 ContextSwitches  : 0x1f8b07a   +0x014 SetMemberCopy    : 1   +0x018 Used_Self        : 0x7ffde000   +0x01c SelfPcr          : 0xffdff000   +0x020 Prcb             : 0xffdff120   +0x024 Irql             : 0x2 ''   +0x028 IRR              : 0   +0x02c IrrActive        : 0   +0x030 IDR              : 0xffff24e0   +0x034 KdVersionBlock   : 0x8055a3a8     <--   +0x038 IDT              : 0x8003f400   +0x03c GDT              : 0x8003f000   +0x040 TSS              : 0x80042000   +0x044 MajorVersion     : 1   +0x046 MinorVersion     : 1   +0x048 SetMember        : 1   +0x04c StallScaleFactor : 0x64   +0x050 SpareUnused      : 0 ''   +0x051 Number           : 0 ''   +0x052 Spare0           : 0 ''   +0x053 SecondLevelCacheAssociativity : 0x8 ''   +0x054 VdmAlert         : 0   +0x058 KernelReserved   : [14] 0   +0x090 SecondLevelCacheSize : 0x80000   +0x094 HalReserved      : [16] 0   +0x0d4 InterruptMode    : 0   +0x0d8 Spare1           : 0 ''   +0x0dc KernelReserved2  : [17] 0   +0x120 PrcbData         : _KPRCBWindows 2000和NT的KPCR是没有这个成员的:kd> dt _kpcr ffdff000nt!_KPCR   +0x000 NtTib            : _NT_TIB   +0x01c SelfPcr          : 0xffdff000   +0x020 Prcb             : 0xffdff120   +0x024 Irql             : 0 ''   +0x028 IRR              : 0   +0x02c IrrActive        : 0   +0x030 IDR              : 0xffffffff   +0x034 Reserved2        : 0              <--   +0x038 IDT              : 0x80036400   +0x03c GDT              : 0x80036000   +0x040 TSS              : 0x802a4000   +0x044 MajorVersion     : 1   +0x046 MinorVersion     : 1   +0x048 SetMember        : 1   +0x04c StallScaleFactor : 0x64   +0x050 DebugActive      : 0 ''   +0x051 Number           : 0 ''   +0x052 VdmAlert         : 0 ''   +0x053 Reserved         : [1]  ""   +0x054 KernelReserved   : [15] 0   +0x090 SecondLevelCacheSize : 0   +0x094 HalReserved      : [16] 0   +0x0d4 InterruptMode    : 0   +0x0d8 Spare1           : 0 ''   +0x0dc KernelReserved2  : [17] 0   +0x120 PrcbData         : _KPRCB    KPCR 在各版本Windows系统上的值都是固定的0xffdff000,这就给我们提供了另一个获得PsLoadedModuleList的方法:#define KPCR 0xffdff000PsLoadedModuleList = *(DWORD *)( *(DWORD *)(KPCR+0x34)+0x18 )    KdVersionBlock的结构成员DebuggerDataList实际就是KdpDebuggerDataListHead,而:KdpDebuggerDataListHead.Flink = KdDebuggerDataBlockKdpDebuggerDataListHead.Blink = KdDebuggerDataBlock        也就是说,得到KdVersionBlock也就获得了KdDebuggerDataBlock。[利用NtSystemDebugControl获取KdVersionBlock]    Windows XP 和Windows 2003上,kd在使用“-kl”参数本地运行的时候,即使没有加载符号表,依然能够给出正确的PsLoadedModuleList:Windows Server 2003 Kernel Version 3790 UP Free x86 compatibleProduct: Server, suite: TerminalServer SingleUserTSBuilt by: 3790.srv03_rtm.030324-2048Kernel base = 0x804e0000 PsLoadedModuleList = 0x8056ac08    显然,Windows 5.1 以上版本提供了获取PsLoadedModuleList和KernelBase的机制。用WinDBG对kd进行调试(煮豆燃豆萁……)发现,dbgeng.dll调用一个未文档化的Native API NtSystemDebugControl,获取了上文提到的 KdVersionBlock。调用过程如下:dbgeng!DebugClient::WaitForEventdbgeng!RawWaitForEventdbgeng!WaitForAnyTargetdbgeng!LocalLiveKernelTargetInfo::WaitForEventdbgeng!LiveKernelTargetInfo::InitFromKdVersiondbgeng!LocalLiveKernelTargetInfo::GetTargetKdVersionntdll!NtSystemDebugControl    对于NtSystemDebugControl,除了BUGTRAQ ID 9694 的一个漏洞报告外,在互联网上找不到任何相关信息。(事实上,作者报告的问题是不能称之为漏洞的,因为,要执行这个API,必须拥有SeDebugPrivilege 特权,而正常情况下,只有管理员用户才有该特权。关于该漏洞,见参考资源[4])。    逆向工程的结果显示,在Windows XP和Windows 2003上,NtSystemDebugControl的功能号7将调用内部函数KdpSysGetVersion:; __stdcall KdpSysGetVersion(x)arg_0           = dword ptr  0Ch                push    esi                push    edi                mov     edi, [esp+arg_0]                push    0Ah                pop     ecx                mov     esi, offset _KdVersionBlock                rep movsd                pop     edi                pop     esi                retn    4    利用NtSystemDebugControl,可以非常优雅地得到KdVersionBlock:typedef enum _DEBUG_CONTROL_CODE {    DebugGetKdVersionBlock = 7} DEBUG_CONTROL_CODE;EnablePrivilege(SE_DEBUG_NAME);ZwSystemDebugControl(    DebugGetKdVersionBlock,    NULL,    0,    &KdVersionBlock,    sizeof(KdVersionBlock),    NULL    );printf ("KernBase:           0x%.8x\n",KdVersionBlock.KernBase);printf ("PsLoadedModuleList: 0x%.8x\n",KdVersionBlock.PsLoadedModuleList);printf ("DebuggerDataList:   0x%.8x\n",KdVersionBlock.DebuggerDataList);    除了获取KdVersionBlock之外,NtSystemDebugControl还有很多强大的功能,我将在另外一篇文档中详细介绍。    现在总结一下。        对Windows 2000来说,最重要的是搜索代码,得到 KdDebuggerDataBlock,得到了这个,实际上也就得到了PsLoadedModuleList、PsActiveProcessHead等。    对Windows XP和Windows 2003,最佳的办法是直接利用NtSystemDebugControl得到KdVersionBlock,然后取得KdDebuggerDataBlock。参考资源:[1]内核级HOOK的几种实现与应用http://www.xfocus.net/articles/200303/499.html[2]FU_Rootkit 2.0https://www.rootkit.com/vault/fuzen_op/FU_Rootkit.zip[3] Finding some non-exported kernel variables in Windows XPhttp://www.rootkit.com/vault/Opc0de/GetVarXP.pdf[4]Microsoft Windows NtSystemDebugControl() Kernel API Function Privilege   Escalation Vulnerabilityhttp://www.securityfocus.com/bid/9694
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部