系統呼叫篇——0環層面呼叫過程(上)

寂靜的羽夏發表於2021-11-14

寫在前面

  此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。

  看此教程之前,問一個問題,你明確學系統呼叫的目的了嗎? 沒有的話就不要繼續了,請重新學習 羽夏看Win系統核心——系統呼叫篇 裡面的內容。


? 華麗的分割線 ?


練習及參考

本次答案均為參考,可以與我的答案不一致,但必須成功通過。

1️⃣ 自己編寫WriteProcessMemory函式(不使用任何DLL,直接呼叫0環函式)並在程式碼中使用。

? 點選檢視答案 ?


  效果和上篇教程一樣。不過這個函式比起ReadProcessMemory稍微複雜一點點,這個函式會先使用NtProtectVirtualMemory判斷是否可寫,然後呼叫NtWriteVirtualMemory寫。因為我直接提供可寫的變數,故這一步就可以省了,程式碼見摺疊。


? 點選檢視程式碼 ?
#include "stdafx.h"
#include <windows.h>
#include <iostream>

int test=0;

BOOL __declspec(naked) __stdcall WriteProcMem0(DWORD handle,DWORD addr,unsigned char* buffer,DWORD len,DWORD sizewrite)
{
    _asm
    {
        mov eax, 115h ;
        mov edx, 7FFE0300h;
        call dword ptr [edx];
        retn 14h;
    }
}

BOOL __declspec(naked) __stdcall WriteProcMem1(DWORD handle,DWORD addr,unsigned char* buffer,DWORD len,DWORD sizewrite)
{
    _asm
    {
        mov eax, 115h;
        lea edx, [esp+4];
        int 2Eh;
        retn 14h;
    }
}

int main(int argc, char* argv[])
{
    int buffer = 0x1234;
    WriteProcMem0((DWORD)GetCurrentProcess(),(DWORD)&test,(unsigned char*)&buffer,4,NULL);
    printf("第一次的值為:%x\n",test);

    buffer=0x5678;
    WriteProcMem1((DWORD)GetCurrentProcess(),(DWORD)&test,(unsigned char*)&buffer,4,NULL);

    printf("第二次的值為:%x\n",test);

    system("pause");
    return 0;
}

詳解 sysenter

  上一篇介紹了Windows API在0環層面呼叫過程,並且只是提了一嘴sysenter,下面來詳細介紹一下這條彙編指令的功能。
  在執行sysenter指令之前,作業系統必須指定0環的CSSSEIP以及ESP。而它們的值儲存在MSR暫存器中,它的具體結構未公開。每個MSR暫存器都會有一個相應的ID,即MSR Index。當執行RDMSR或者WRMSR指令的時候,只要提供MSR Index就能讓CPU知道目標MSR暫存器。儲存上述暫存器的MSR Index如下表格所示:

MSR Index
IA32_SYSENTER_CS 174H
IA32_SYSENTER_ESP 175H
IA32_SYSENTER_EIP 176H

  看完表格後,你會發現沒有儲存SSMSR暫存器,它是經過計算得到:CS的值+8就是SS的值。我們測試一下它們的讀取:

kd> rdmsr 174
msr[174] = 00000000`00000008
kd> rdmsr 175
msr[175] = 00000000`bacd0000
kd> rdmsr 176
msr[176] = 00000000`8053e540

  如果呼叫sysenter進入核心,前往的地址就是8053e540,我們看看函式是什麼:

kd> uf 8053e540
Flow analysis was incomplete, some code may be missing
8053e540 b923000000      mov     ecx,23h
8053e545 6a30            push    30h
8053e547 0fa1            pop     fs
8053e549 8ed9            mov     ds,cx
8053e54b 8ec1            mov     es,cx
8053e54d 8b0d40f0dfff    mov     ecx,dword ptr ds:[0FFDFF040h]
8053e553 8b6104          mov     esp,dword ptr [ecx+4]
8053e556 6a23            push    23h
8053e558 52              push    edx
8053e559 9c              pushfd
8053e55a 6a02            push    2
8053e55c 83c208          add     edx,8
8053e55f 9d              popfd
8053e560 804c240102      or      byte ptr [esp+1],2
8053e565 6a1b            push    1Bh
8053e567 ff350403dfff    push    dword ptr ds:[0FFDF0304h]
8053e56d 6a00            push    0
8053e56f 55              push    ebp
8053e570 53              push    ebx
8053e571 56              push    esi
8053e572 57              push    edi
8053e573 8b1d1cf0dfff    mov     ebx,dword ptr ds:[0FFDFF01Ch]
8053e579 6a3b            push    3Bh
8053e57b 8bb324010000    mov     esi,dword ptr [ebx+124h]
8053e581 ff33            push    dword ptr [ebx]
8053e583 c703ffffffff    mov     dword ptr [ebx],0FFFFFFFFh
8053e589 8b6e18          mov     ebp,dword ptr [esi+18h]
8053e58c 6a01            push    1
8053e58e 83ec48          sub     esp,48h
8053e591 81ed9c020000    sub     ebp,29Ch
8053e597 c6864001000001  mov     byte ptr [esi+140h],1
8053e59e 3bec            cmp     ebp,esp
8053e5a0 759a            jne     nt!KiFastCallEntry2+0x47 (8053e53c)  Branch

  雖然沒有顯示出函式是什麼,我們可以通過PE的知識進行定位,經過工具檢視它的檔案對齊和記憶體對齊是一樣的,那就相當的方便了:
  首先,我們得知道核心檔案的地址:

kd> dd PsLoadedModuleList
80554fc0  89e033a0 898dbd70 00000000 00000000
80554fd0  00000000 00000000 00000000 00000000
80554fe0  8055c460 80559680 00000000 00000000
80554ff0  00000000 00000000 00000000 00000000
80555000  00000000 00000000 00000000 00000000
80555010  00000000 00000000 80545500 80544e00
80555020  80544e00 80543c00 80546800 80545500
80555030  00000000 00000011 00000011 89bbfb50
kd> dt _LDR_DATA_TABLE_ENTRY 89e033a0
nt!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x89e03338 - 0x80554fc0 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x018 DllBase          : 0x804d8000 Void
   +0x01c EntryPoint       : 0x806907e0 Void
   +0x020 SizeOfImage      : 0x1f8480
   +0x024 FullDllName      : _UNICODE_STRING "\WINDOWS\system32\ntkrnlpa.exe"
   +0x02c BaseDllName      : _UNICODE_STRING "ntoskrnl.exe"
   +0x034 Flags            : 0xc004000
   +0x038 LoadCount        : 1
   +0x03a TlsIndex         : 0
   +0x03c HashLinks        : _LIST_ENTRY [ 0x0 - 0x2050d3 ]
   +0x03c SectionPointer   : (null)
   +0x040 CheckSum         : 0x2050d3
   +0x044 TimeDateStamp    : 0
   +0x044 LoadedImports    : (null)
   +0x048 EntryPointActivationContext : (null)
   +0x04c PatchInformation : 0x0074006e Void

  這個東西在核心篇的總結與提升講到過,我就不詳細講解了。既然得到了目標地址,模組的首地址也都得到了。經過相減得到0x66540,由於IDA預設是以ImageBase為首地址載入,故為0x466540,如所示:

  核心篇的總結與提升暫未釋出,由於其細節和內容很多,釋出後本段文字將會刪除。這裡透露一下里面將會介紹什麼:核心檔案載入和解除安裝過程;進一步隱藏核心檔案繞過PCHunter檢測;使用程式碼實現驅動檔案的載入,同時還會把PCHunter的驅動載入過程(經過逆向所得)進行介紹;跨程式監控MessageBox專案的實現。不知這些東西何時能夠完成釋出,敬請期待。

.text:00466540 _KiFastCallEntry proc near              ; DATA XREF: KiLoadFastSyscallMachineSpecificRegisters(x)+24↑o
.text:00466540                                         ; _KiTrap01+72↓o
.text:00466540
.text:00466540 var_B           = byte ptr -0Bh
.text:00466540
.text:00466540 ; FUNCTION CHUNK AT .text:00466519 SIZE 00000025 BYTES
.text:00466540 ; FUNCTION CHUNK AT .text:004667DC SIZE 00000014 BYTES
.text:00466540
.text:00466540                 mov     ecx, 23h ; '#'
.text:00466545                 push    30h ; '0'
.text:00466547                 pop     fs
.text:00466549                 mov     ds, ecx
.text:0046654B                 mov     es, ecx
.text:0046654D                 mov     ecx, ds:0FFDFF040h
.text:00466553                 mov     esp, [ecx+4]
.text:00466556                 push    23h ; '#'
.text:00466558                 push    edx
.text:00466559                 pushf

  然後看看我們之前的uf的結果,是不是一模一樣?
  接下來繼續分析通過中斷門0x2E的進入核心這道門的過程,先找到對應的中斷門:

kd> dq 8003f400+2e*8
ReadVirtual: 8003f570 not properly sign extended
8003f570  8053ee00`0008e481 80548e00`00081780
8003f580  806d8e00`00087d54 89db8e00`00082784
8003f590  80538e00`0008db54 80538e00`0008db5e
8003f5a0  80538e00`0008db68 80538e00`0008db72
8003f5b0  80538e00`0008db7c 80538e00`0008db86
8003f5c0  806d8e00`00081ef0 89d88e00`0008e41c
8003f5d0  89ab8e00`0008815c 89d88e00`0008add4
8003f5e0  89ab8e00`00088584 80538e00`0008dbc2

  我們找到了中斷門對應的地址為8053e481,如法炮製,我們定位到IDA

.text:00466481 _KiSystemService proc near              ; CODE XREF: ZwAcceptConnectPort(x,x,x,x,x,x)+C↑p
.text:00466481                                         ; ZwAccessCheck(x,x,x,x,x,x,x,x)+C↑p ...
.text:00466481
.text:00466481 arg_0           = dword ptr  4
.text:00466481
.text:00466481                 push    0
.text:00466483                 push    ebp
.text:00466484                 push    ebx
.text:00466485                 push    esi
.text:00466486                 push    edi
.text:00466487                 push    fs
.text:00466489                 mov     ebx, 30h ; '0'
.text:0046648E                 mov     fs, bx
.text:00466491                 assume fs:nothing
.text:00466491                 push    dword ptr ds:0FFDFF000h
.text:00466497                 mov     dword ptr ds:0FFDFF000h, 0FFFFFFFFh
.text:004664A1                 mov     esi, ds:0FFDFF124h
.text:004664A7                 push    dword ptr [esi+140h]
.text:004664AD                 sub     esp, 48h
.text:004664B0                 mov     ebx, [esp+68h+arg_0]
.text:004664B4                 and     ebx, 1
.text:004664B7                 mov     [esi+140h], bl
.text:004664BD                 mov     ebp, esp
.text:004664BF                 mov     ebx, [esi+134h]
.text:004664C5                 mov     [ebp+3Ch], ebx
.text:004664C8                 mov     [esi+134h], ebp
.text:004664CE                 cld

  進入0環的過程我們可以可以總結一下:
  通過中斷門進0環時,固定中斷號為0x2E,它的CS/EIP由門描述符提供,ESP/SSTSS提供;進入0環後執行的核心函式為NT!KiSystemService
  通過sysenter指令進0環時,CS/ESP/EIPMSR暫存器提供,SS是計算得到的;進入0環後執行的核心函式為NT!KiFastCallEntry

進入0環之後

  我們通過int 2E或者sysenter進入核心後,我們的流程會怎樣繼續進行,首先我們得知道個結構體,它就是KPCRTrap_Frame

Trap_Frame

  棧幀結構體,用於Windows API儲存現場。經過提權進入0環的時候,Windows就會遵守這個結構體儲存一系列的資料,它的結構體如下所示:

kd> dt _KTrap_Frame
nt!_KTRAP_FRAME
   +0x000 DbgEbp           : Uint4B
   +0x004 DbgEip           : Uint4B
   +0x008 DbgArgMark       : Uint4B
   +0x00c DbgArgPointer    : Uint4B
   +0x010 TempSegCs        : Uint4B
   +0x014 TempEsp          : Uint4B
   +0x018 Dr0              : Uint4B
   +0x01c Dr1              : Uint4B
   +0x020 Dr2              : Uint4B
   +0x024 Dr3              : Uint4B
   +0x028 Dr6              : Uint4B
   +0x02c Dr7              : Uint4B
   +0x030 SegGs            : Uint4B
   +0x034 SegEs            : Uint4B
   +0x038 SegDs            : Uint4B
   +0x03c Edx              : Uint4B
   +0x040 Ecx              : Uint4B
   +0x044 Eax              : Uint4B
   +0x048 PreviousPreviousMode : Uint4B
   +0x04c ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x050 SegFs            : Uint4B
   +0x054 Edi              : Uint4B
   +0x058 Esi              : Uint4B
   +0x05c Ebx              : Uint4B
   +0x060 Ebp              : Uint4B
   +0x064 ErrCode          : Uint4B
   +0x068 Eip              : Uint4B
   +0x06c SegCs            : Uint4B
   +0x070 EFlags           : Uint4B
   +0x074 HardwareEsp      : Uint4B
   +0x078 HardwareSegSs    : Uint4B
   +0x07c V86Es            : Uint4B
   +0x080 V86Ds            : Uint4B
   +0x084 V86Fs            : Uint4B
   +0x088 V86Gs            : Uint4B

  下面幾個成員用於虛擬8086模式下,由於我們們研究是保護模式,不使用,故不贅述:

   +0x07c V86Es            : Uint4B
   +0x080 V86Ds            : Uint4B
   +0x084 V86Fs            : Uint4B
   +0x088 V86Gs            : Uint4B

  中斷髮生時,若發生許可權變換,則要儲存舊堆疊,由CPU壓入的。如下兩個成員儲存:

   +0x074 HardwareEsp      : Uint4B
   +0x078 HardwareSegSs    : Uint4B

  中斷髮生時,儲存被中斷的程式碼段和iret要返回的地址,由CPU壓入的:

   +0x068 Eip              : Uint4B
   +0x06c SegCs            : Uint4B
   +0x070 EFlags           : Uint4B

  Windows中非易失性暫存器需要在中斷例程中先儲存(ErrCode有時由作業系統壓入,有時由CPU壓入,詳情請參考本篇教程的中斷門涉及的中斷號部分):

   +0x048 PreviousPreviousMode : Uint4B
   +0x04c ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x050 SegFs            : Uint4B
   +0x054 Edi              : Uint4B
   +0x058 Esi              : Uint4B
   +0x05c Ebx              : Uint4B
   +0x060 Ebp              : Uint4B
   +0x064 ErrCode          : Uint4B

  用於除錯以及其他用途:

   +0x000 DbgEbp           : Uint4B
   +0x004 DbgEip           : Uint4B
   +0x008 DbgArgMark       : Uint4B
   +0x00c DbgArgPointer    : Uint4B
   +0x010 TempSegCs        : Uint4B
   +0x014 TempEsp          : Uint4B
   +0x018 Dr0              : Uint4B
   +0x01c Dr1              : Uint4B
   +0x020 Dr2              : Uint4B
   +0x024 Dr3              : Uint4B
   +0x028 Dr6              : Uint4B
   +0x02c Dr7              : Uint4B
   +0x030 SegGs            : Uint4B
   +0x034 SegEs            : Uint4B
   +0x038 SegDs            : Uint4B
   +0x03c Edx              : Uint4B
   +0x040 Ecx              : Uint4B
   +0x044 Eax              : Uint4B

  由於裡面涉及的知識點比較廣,故不詳細講解,之後的教程將會逐個體現出來。

KPCR

  我們先從WinDbg檢視一下這個結構體:

kd> dt _KPCR
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x01c SelfPcr          : Ptr32 _KPCR
   +0x020 Prcb             : Ptr32 _KPRCB
   +0x024 Irql             : UChar
   +0x028 IRR              : Uint4B
   +0x02c IrrActive        : Uint4B
   +0x030 IDR              : Uint4B
   +0x034 KdVersionBlock   : Ptr32 Void
   +0x038 IDT              : Ptr32 _KIDTENTRY
   +0x03c GDT              : Ptr32 _KGDTENTRY
   +0x040 TSS              : Ptr32 _KTSS
   +0x044 MajorVersion     : Uint2B
   +0x046 MinorVersion     : Uint2B
   +0x048 SetMember        : Uint4B
   +0x04c StallScaleFactor : Uint4B
   +0x050 DebugActive      : UChar
   +0x051 Number           : UChar
   +0x052 Spare0           : UChar
   +0x053 SecondLevelCacheAssociativity : UChar
   +0x054 VdmAlert         : Uint4B
   +0x058 KernelReserved   : [14] Uint4B
   +0x090 SecondLevelCacheSize : Uint4B
   +0x094 HalReserved      : [16] Uint4B
   +0x0d4 InterruptMode    : Uint4B
   +0x0d8 Spare1           : UChar
   +0x0dc KernelReserved2  : [17] Uint4B
   +0x120 PrcbData         : _KPRCB

  可以看出KPCR裡面還巢狀著兩個結構體TIBKPRCB,我們dt一下:

kd> dt _NT_TIB
nt!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   +0x008 StackLimit       : Ptr32 Void
   +0x00c SubSystemTib     : Ptr32 Void
   +0x010 FiberData        : Ptr32 Void
   +0x010 Version          : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB
kd> dt _KPRCB
nt!_KPRCB
   +0x000 MinorVersion     : Uint2B
   +0x002 MajorVersion     : Uint2B
   +0x004 CurrentThread    : Ptr32 _KTHREAD
   +0x008 NextThread       : Ptr32 _KTHREAD
   +0x00c IdleThread       : Ptr32 _KTHREAD
   +0x010 Number           : Char
   +0x011 Reserved         : Char
   +0x012 BuildType        : Uint2B
   +0x014 SetMember        : Uint4B
   +0x018 CpuType          : Char
   +0x019 CpuID            : Char
   +0x01a CpuStep          : Uint2B
   +0x01c ProcessorState   : _KPROCESSOR_STATE
   +0x33c KernelReserved   : [16] Uint4B
   +0x37c HalReserved      : [16] Uint4B
   +0x3bc PrcbPad0         : [92] UChar
   +0x418 LockQueue        : [16] _KSPIN_LOCK_QUEUE
   +0x498 PrcbPad1         : [8] UChar
   +0x4a0 NpxThread        : Ptr32 _KTHREAD
   +0x4a4 InterruptCount   : Uint4B
   +0x4a8 KernelTime       : Uint4B
   +0x4ac UserTime         : Uint4B
   +0x4b0 DpcTime          : Uint4B
   +0x4b4 DebugDpcTime     : Uint4B
   +0x4b8 InterruptTime    : Uint4B
   +0x4bc AdjustDpcThreshold : Uint4B
   +0x4c0 PageColor        : Uint4B
   +0x4c4 SkipTick         : Uint4B
   +0x4c8 MultiThreadSetBusy : UChar
   +0x4c9 Spare2           : [3] UChar
   +0x4cc ParentNode       : Ptr32 _KNODE
   +0x4d0 MultiThreadProcessorSet : Uint4B
   +0x4d4 MultiThreadSetMaster : Ptr32 _KPRCB
   +0x4d8 ThreadStartCount : [2] Uint4B
   +0x4e0 CcFastReadNoWait : Uint4B
   +0x4e4 CcFastReadWait   : Uint4B
   +0x4e8 CcFastReadNotPossible : Uint4B
   +0x4ec CcCopyReadNoWait : Uint4B
   +0x4f0 CcCopyReadWait   : Uint4B
   +0x4f4 CcCopyReadNoWaitMiss : Uint4B
   +0x4f8 KeAlignmentFixupCount : Uint4B
   +0x4fc KeContextSwitches : Uint4B
   +0x500 KeDcacheFlushCount : Uint4B
   +0x504 KeExceptionDispatchCount : Uint4B
   +0x508 KeFirstLevelTbFills : Uint4B
   +0x50c KeFloatingEmulationCount : Uint4B
   +0x510 KeIcacheFlushCount : Uint4B
   +0x514 KeSecondLevelTbFills : Uint4B
   +0x518 KeSystemCalls    : Uint4B
   +0x51c SpareCounter0    : [1] Uint4B
   +0x520 PPLookasideList  : [16] _PP_LOOKASIDE_LIST
   +0x5a0 PPNPagedLookasideList : [32] _PP_LOOKASIDE_LIST
   +0x6a0 PPPagedLookasideList : [32] _PP_LOOKASIDE_LIST
   +0x7a0 PacketBarrier    : Uint4B
   +0x7a4 ReverseStall     : Uint4B
   +0x7a8 IpiFrame         : Ptr32 Void
   +0x7ac PrcbPad2         : [52] UChar
   +0x7e0 CurrentPacket    : [3] Ptr32 Void
   +0x7ec TargetSet        : Uint4B
   +0x7f0 WorkerRoutine    : Ptr32     void 
   +0x7f4 IpiFrozen        : Uint4B
   +0x7f8 PrcbPad3         : [40] UChar
   +0x820 RequestSummary   : Uint4B
   +0x824 SignalDone       : Ptr32 _KPRCB
   +0x828 PrcbPad4         : [56] UChar
   +0x860 DpcListHead      : _LIST_ENTRY
   +0x868 DpcStack         : Ptr32 Void
   +0x86c DpcCount         : Uint4B
   +0x870 DpcQueueDepth    : Uint4B
   +0x874 DpcRoutineActive : Uint4B
   +0x878 DpcInterruptRequested : Uint4B
   +0x87c DpcLastCount     : Uint4B
   +0x880 DpcRequestRate   : Uint4B
   +0x884 MaximumDpcQueueDepth : Uint4B
   +0x888 MinimumDpcRate   : Uint4B
   +0x88c QuantumEnd       : Uint4B
   +0x890 PrcbPad5         : [16] UChar
   +0x8a0 DpcLock          : Uint4B
   +0x8a4 PrcbPad6         : [28] UChar
   +0x8c0 CallDpc          : _KDPC
   +0x8e0 ChainedInterruptList : Ptr32 Void
   +0x8e4 LookasideIrpFloat : Int4B
   +0x8e8 SpareFields0     : [6] Uint4B
   +0x900 VendorString     : [13] UChar
   +0x90d InitialApicId    : UChar
   +0x90e LogicalProcessorsPerPhysicalProcessor : UChar
   +0x910 MHz              : Uint4B
   +0x914 FeatureBits      : Uint4B
   +0x918 UpdateSignature  : _LARGE_INTEGER
   +0x920 NpxSaveArea      : _FX_SAVE_AREA
   +0xb30 PowerState       : _PROCESSOR_POWER_STATE

  KPCR叫做CPU控制區,英文全稱Processor Control Region,每一個CPU都有一個這樣的結構體,我們如何知道我們有幾個CPU,也就是幾個核呢?我們可以用下面的指令得到:

kd> dd KeNumberProcessors
8054d4e0  00000001 00000006 00009e0a 20013fff
8054d4f0  806bb7c0 00000000 00000000 00000061
8054d500  8003f0e0 00000000 00000000 00000000
8054d510  00000001 00000000 00000001 00000000
8054d520  00000000 00000000 00000000 00000000
8054d530  00000000 00000000 00000000 00000000
8054d540  00000000 00000000 00000000 00000000
8054d550  00000000 00000000 00000000 00000000

  可以看出我們只有一個CPU,那麼如何檢視KPCR呢?我們可以用下面的指令得到:

kd> dd KiProcessorBlock
80553e40  ffdff120 00000000 00000000 00000000
80553e50  00000000 00000000 00000000 00000000
80553e60  00000000 00000000 00000000 00000000
80553e70  00000000 00000000 00000000 00000000
80553e80  00000000 00000000 00000000 00000000
80553e90  00000000 00000000 00000000 00000000
80553ea0  00000000 00000000 00000000 00000000
80553eb0  00000000 00000000 00000000 00000000

  KiProcessorBlock是一個陣列,它儲存著KPRCB的地址。既然我知道KPRCB的地址,通過這個減去偏移我們就可以找到KPCR這個結構:

kd> dt _KPCR ffdff000
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x01c SelfPcr          : 0xffdff000 _KPCR
   +0x020 Prcb             : 0xffdff120 _KPRCB
   +0x024 Irql             : 0x1c ''
   +0x028 IRR              : 4
   +0x02c IrrActive        : 0
   +0x030 IDR              : 0xffff20f8
   +0x034 KdVersionBlock   : 0x80546ab8 Void
   +0x038 IDT              : 0x8003f400 _KIDTENTRY
   +0x03c GDT              : 0x8003f000 _KGDTENTRY
   +0x040 TSS              : 0x80042000 _KTSS
   +0x044 MajorVersion     : 1
   +0x046 MinorVersion     : 1
   +0x048 SetMember        : 1
   +0x04c StallScaleFactor : 0x64
   +0x050 DebugActive      : 0 ''
   +0x051 Number           : 0 ''
   +0x052 Spare0           : 0 ''
   +0x053 SecondLevelCacheAssociativity : 0 ''
   +0x054 VdmAlert         : 0
   +0x058 KernelReserved   : [14] 0
   +0x090 SecondLevelCacheSize : 0
   +0x094 HalReserved      : [16] 0
   +0x0d4 InterruptMode    : 0
   +0x0d8 Spare1           : 0 ''
   +0x0dc KernelReserved2  : [17] 0
   +0x120 PrcbData         : _KPRCB

  接下來看看比較重要的成員:

ExceptionList

  錯誤連結串列,指向EXCEPTION_REGISTRATION_RECORD的列表,用於SEH,即為結構化異常處理,裡面記錄了異常處理函式。有人會稱它為SEH鏈入口。

CurrentThread

  當前CPU所執行執行緒的ETHREAD結構體。

NextThread

  下一個CPU所執行執行緒的ETHREAD結構體。

IdleThread

  當所有的執行緒都執行完了CPU就執行這個執行緒。

Number

  CPU編號。

CpuStep

  CPU子版本號。

ProcessorState

  CPU狀態,是_KPROCESSOR_STATE結構體。

NpxThread

  Npx浮點處理器,最後一次用過浮點的執行緒。

InterruptCount

  中斷計數,統計資訊,沒什麼實際意義。

KernelTime

  核心時間,統計資訊,沒什麼實際意義。

UserTime

  使用者層時間,,統計資訊,沒什麼實際意義。

LogicalProcessorsPerPhysicalProcessor

  指明每個物理處理器有幾個邏輯處理器。

MHz

  CPU的頻率。

ETHREAD

  介紹KPCR涉及了KTHREAD結構體,故把這個ETHREAD結構體進行介紹一下:

kd> dt _ETHREAD
nt!_ETHREAD
   +0x000 Tcb              : _KTHREAD
   +0x1c0 CreateTime       : _LARGE_INTEGER
   +0x1c0 NestedFaultCount : Pos 0, 2 Bits
   +0x1c0 ApcNeeded        : Pos 2, 1 Bit
   +0x1c8 ExitTime         : _LARGE_INTEGER
   +0x1c8 LpcReplyChain    : _LIST_ENTRY
   +0x1c8 KeyedWaitChain   : _LIST_ENTRY
   +0x1d0 ExitStatus       : Int4B
   +0x1d0 OfsChain         : Ptr32 Void
   +0x1d4 PostBlockList    : _LIST_ENTRY
   +0x1dc TerminationPort  : Ptr32 _TERMINATION_PORT
   +0x1dc ReaperLink       : Ptr32 _ETHREAD
   +0x1dc KeyedWaitValue   : Ptr32 Void
   +0x1e0 ActiveTimerListLock : Uint4B
   +0x1e4 ActiveTimerListHead : _LIST_ENTRY
   +0x1ec Cid              : _CLIENT_ID
   +0x1f4 LpcReplySemaphore : _KSEMAPHORE
   +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
   +0x208 LpcReplyMessage  : Ptr32 Void
   +0x208 LpcWaitingOnPort : Ptr32 Void
   +0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
   +0x210 IrpList          : _LIST_ENTRY
   +0x218 TopLevelIrp      : Uint4B
   +0x21c DeviceToVerify   : Ptr32 _DEVICE_OBJECT
   +0x220 ThreadsProcess   : Ptr32 _EPROCESS
   +0x224 StartAddress     : Ptr32 Void
   +0x228 Win32StartAddress : Ptr32 Void
   +0x228 LpcReceivedMessageId : Uint4B
   +0x22c ThreadListEntry  : _LIST_ENTRY
   +0x234 RundownProtect   : _EX_RUNDOWN_REF
   +0x238 ThreadLock       : _EX_PUSH_LOCK
   +0x23c LpcReplyMessageId : Uint4B
   +0x240 ReadClusterSize  : Uint4B
   +0x244 GrantedAccess    : Uint4B
   +0x248 CrossThreadFlags : Uint4B
   +0x248 Terminated       : Pos 0, 1 Bit
   +0x248 DeadThread       : Pos 1, 1 Bit
   +0x248 HideFromDebugger : Pos 2, 1 Bit
   +0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
   +0x248 SystemThread     : Pos 4, 1 Bit
   +0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
   +0x248 BreakOnTermination : Pos 6, 1 Bit
   +0x248 SkipCreationMsg  : Pos 7, 1 Bit
   +0x248 SkipTerminationMsg : Pos 8, 1 Bit
   +0x24c SameThreadPassiveFlags : Uint4B
   +0x24c ActiveExWorker   : Pos 0, 1 Bit
   +0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit
   +0x24c MemoryMaker      : Pos 2, 1 Bit
   +0x250 SameThreadApcFlags : Uint4B
   +0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit
   +0x250 LpcExitThreadCalled : Pos 1, 1 Bit
   +0x250 AddressSpaceOwner : Pos 2, 1 Bit
   +0x254 ForwardClusterOnly : UChar
   +0x255 DisablePageFaultClustering : UChar

  ETHREAD是執行緒結構體,接觸過PEBTEB可能有所瞭解。但PEBTEB是3環的結構體,ETHREAD是0環的結構體。由於不瞭解這東西可能對後面的知識瞭解不了,這東西將會在程式執行緒篇進行詳細講解,故在此進行了解性的學習。
  ETHREAD裡面巢狀了一個結構體KTHREAD,我們來看看它的結構體:

kd> dt _KTHREAD
nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY
   +0x018 InitialStack     : Ptr32 Void
   +0x01c StackLimit       : Ptr32 Void
   +0x020 Teb              : Ptr32 Void
   +0x024 TlsArray         : Ptr32 Void
   +0x028 KernelStack      : Ptr32 Void
   +0x02c DebugActive      : UChar
   +0x02d State            : UChar
   +0x02e Alerted          : [2] UChar
   +0x030 Iopl             : UChar
   +0x031 NpxState         : UChar
   +0x032 Saturation       : Char
   +0x033 Priority         : Char
   +0x034 ApcState         : _KAPC_STATE
   +0x04c ContextSwitches  : Uint4B
   +0x050 IdleSwapBlock    : UChar
   +0x051 Spare0           : [3] UChar
   +0x054 WaitStatus       : Int4B
   +0x058 WaitIrql         : UChar
   +0x059 WaitMode         : Char
   +0x05a WaitNext         : UChar
   +0x05b WaitReason       : UChar
   +0x05c WaitBlockList    : Ptr32 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x068 WaitTime         : Uint4B
   +0x06c BasePriority     : Char
   +0x06d DecrementCount   : UChar
   +0x06e PriorityDecrement : Char
   +0x06f Quantum          : Char
   +0x070 WaitBlock        : [4] _KWAIT_BLOCK
   +0x0d0 LegoData         : Ptr32 Void
   +0x0d4 KernelApcDisable : Uint4B
   +0x0d8 UserAffinity     : Uint4B
   +0x0dc SystemAffinityActive : UChar
   +0x0dd PowerState       : UChar
   +0x0de NpxIrql          : UChar
   +0x0df InitialNode      : UChar
   +0x0e0 ServiceTable     : Ptr32 Void
   +0x0e4 Queue            : Ptr32 _KQUEUE
   +0x0e8 ApcQueueLock     : Uint4B
   +0x0f0 Timer            : _KTIMER
   +0x118 QueueListEntry   : _LIST_ENTRY
   +0x120 SoftAffinity     : Uint4B
   +0x124 Affinity         : Uint4B
   +0x128 Preempted        : UChar
   +0x129 ProcessReadyQueue : UChar
   +0x12a KernelStackResident : UChar
   +0x12b NextProcessor    : UChar
   +0x12c CallbackStack    : Ptr32 Void
   +0x130 Win32Thread      : Ptr32 Void
   +0x134 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   +0x140 PreviousMode     : Char
   +0x141 EnableStackSwap  : UChar
   +0x142 LargeStack       : UChar
   +0x143 ResourceIndex    : UChar
   +0x144 KernelTime       : Uint4B
   +0x148 UserTime         : Uint4B
   +0x14c SavedApcState    : _KAPC_STATE
   +0x164 Alertable        : UChar
   +0x165 ApcStateIndex    : UChar
   +0x166 ApcQueueable     : UChar
   +0x167 AutoAlignment    : UChar
   +0x168 StackBase        : Ptr32 Void
   +0x16c SuspendApc       : _KAPC
   +0x19c SuspendSemaphore : _KSEMAPHORE
   +0x1b0 ThreadListEntry  : _LIST_ENTRY
   +0x1b8 FreezeCount      : Char
   +0x1b9 SuspendCount     : Char
   +0x1ba IdealProcessor   : UChar
   +0x1bb DisableBoost     : UChar

  可以看到KTHREAD裡面有一個你眼熟的TEB結構體,這個暫且不說,我們看看幾個必要的成員:
  PreviousMode:先前模式,如果0環呼叫的,值為0。如果為3環呼叫的,值為1
  DebugActive:除錯活動狀態,指示當前執行緒是否處於除錯狀態。如果這個成員被設成0,則會影響硬體斷點無法斷下。其他的知識點和細節將會在程式執行緒篇進行介紹。
  TrapFrame:棧幀,熟不熟悉?這就是我們前面講解的Trap_Frame結構體。一個執行緒一個棧幀結構體。

本節練習

本節的答案將會在下一節的正文給出,務必把本節練習做完後看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。

  俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習不多,但難度較大,請保質保量的完成。

1️⃣ 逆向分析KiSystemServiceKiFastCallEntry函式。

下一篇

  系統呼叫篇——0環層面呼叫過程(下)

相關文章