擷取Windows系統的核心變數

來源:互聯網
上載者:User

作   者:於暘
郵   件: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:   0x80100000
Windows 2000:0x80400000
Windows XP:   0x804d1000
Windows 2003: 0x804e0000

     Windows NT 4.0 ntoskrnl.exe 的OptionalHeader->ImageBase = 0x80100000,
ntldr 也會按照這個值來載入核心,但是從Windows 2000開始就不是這樣了。可能基
於這個曆史原因,各系統的*(DWORD *)PsNtosImageBase始終初始化為0x80100000。

     另外,核心變數PsNtosImageBase、KdpNtosImageBase等也指向KernelBase:

KernelBase = *(DWORD *)PsNtosImageBase
KernelBase = *(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],eax
801CEB21: 8B 45 0C            mov        eax,dword ptr [ebp+0Ch]
801CEB24: 89 10               mov        dword ptr [eax],edx
801CEB26: 8B 36               mov        esi,dword ptr [esi]
801CEB28: 81 FE 70 0B 15 80   cmp        esi,80150B70h   //PsLoadedModuleList

     如果是用驅動做這件事情,就不必暴力搜尋了,fuzen_op(fuzen_op@yahoo.com)
在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> ? PsActiveProcessHead
Evaluate 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 < 100

Windows 2000:

KdEnableDebugger + n:
6A 00                  push     0
6A 00                  push     0
C6 05 28 41 48 00 01   mov      _PoHiberInProgress, 1
E8 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 _KdpDebuggerDataListHead
68 D8 FA 46 00         push     offset KdDebuggerDataBlock
8B 40 18               mov      eax, [eax+18h]
68 4B 44 42 47         push     4742444Bh // "KDBG",可以用作搜尋的定位標誌
A3 3C D1 54 00         mov      ds:_KdpNtosImageBase, eax
89 0D 54 D1 54 00      mov      ds:dword_54D154, ecx
89 0D 50 D1 54 00      mov      ds:_KdpDebuggerDataListHead, ecx

Windows XP

KdEnableDebugger + n:
6A 00                  push     0
6A 00                  push     0
C6 05 8C 98 47 00 01   mov      _PoHiberInProgress, 1
E8 2B 17 13 00         call     _KdInitSystem@8 ; KdInitSystem(x,x)

KdInitSystem + n:
68 90 02 00 00         push     290h
68 E0 9D 46 00         push     offset KdDebuggerDataBlock
BE 74 96 59 00         mov      esi, offset _KdpDebuggerDataListHead
68 4B 44 42 47         push     4742444Bh
89 35 78 96 59 00      mov      ds:dword_599678, esi
89 35 74 96 59 00      mov      ds:_KdpDebuggerDataListHead, esi

Windows 2003

KdEnableDebugger + n:
56                     push     esi
56                     push     esi
C6 05 0C 08 49 00 01   mov      PoHiberInProgres, 1
E8 CB AD 15 00         call     _KdInitSystem@8 ; KdInitSystem(x,x)

KdInitSystem + n:
68 18 03 00 00         push     318h
68 D0 A3 47 00         push     offset KdDebuggerDataBlock
BE 18 10 5D 00         mov      esi, offset _KdpDebuggerDataListHead
68 4B 44 42 47         push     4742444Bh
89 35 1C 10 5D 00      mov      ds:dword_5D101C, esi
89 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 ffdff000
nt!_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          : _KPRCB

Windows 2000和NT的KPCR是沒有這個成員的:

kd> dt _kpcr ffdff000
nt!_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 0xffdff000
PsLoadedModuleList = *(DWORD *)( *(DWORD *)(KPCR+0x34)+0x18 )

     KdVersionBlock的結構成員DebuggerDataList實際就是KdpDebuggerDataListHead,
而:
KdpDebuggerDataListHead.Flink = KdDebuggerDataBlock
KdpDebuggerDataListHead.Blink = KdDebuggerDataBlock
    
     也就是說,得到KdVersionBlock也就獲得了KdDebuggerDataBlock。

[利用NtSystemDebugControl擷取KdVersionBlock]

     Windows XP 和Windows 2003上,kd在使用“-kl”參數本地啟動並執行時候,即使沒
有載入符號表,依然能夠給出正確的PsLoadedModuleList:

Windows Server 2003 Kernel Version 3790 UP Free x86 compatible
Product: Server, suite: TerminalServer SingleUserTS
Built by: 3790.srv03_rtm.030324-2048
Kernel base = 0x804e0000 PsLoadedModuleList = 0x8056ac08

     顯然,Windows 5.1 以上版本提供了擷取PsLoadedModuleList和KernelBase的機
制。用WinDBG對kd進行調試(煮豆燃豆萁……)發現,dbgeng.dll調用一個未文檔化
的Native API NtSystemDebugControl,擷取了上文提到的 KdVersionBlock。調用過
程如下:

dbgeng!DebugClient::WaitForEvent
dbgeng!RawWaitForEvent
dbgeng!WaitForAnyTarget
dbgeng!LocalLiveKernelTargetInfo::WaitForEvent
dbgeng!LiveKernelTargetInfo::InitFromKdVersion
dbgeng!LocalLiveKernelTargetInfo::GetTargetKdVersion
ntdll!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。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.