系统调用篇——0环层面调用过程(上)

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看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,如所示:

.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 PreviousMode : 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环层面调用过程(下)

posted @ 2021-11-14 14:56  寂静的羽夏  阅读(1440)  评论(0编辑  收藏  举报