进程线程篇——总结与提升

写在前面

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

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?系统调用篇学会了吗?练习做完了吗?没有的话就不要继续了。


🔒 华丽的分割线 🔒


模拟线程切换分析

  之前我们详细分析了模拟线程切换的本质,里面模拟了两处线程切换,一处模拟了时钟切换,一处模拟了主动切换,也就是通过API的方式进行的切换。
  我们来看一下模拟时钟切换的部分:

while(TRUE) {
    Sleep(20);
    Scheduling();
}

  上面的Sleep(20)就是代表我每20ms继续调用Scheduling()来实现线程切换,也就是说时钟周期是20ms
  模拟通过API的方式进行的主动切换的就是如下函数:

void GMSleep(int MilliSeconds)
{
   GMThread_t* GMThreadp;
   GMThreadp = &GMThreadList[CurrentThreadIndex];
   if (GMThreadp->Flags != 0) {
      GMThreadp->Flags = GMTHREAD_SLEEP;
      GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;
   }

   Scheduling();
   return;
}

  而我们的每一个线程,都会调用GMSleep这个函数,这个函数模拟的就是模拟调用WinAPI

void Thread1(void*) {
    while(1){
        printf("Thread1\n");
        GMSleep(500);
    }
}

  我假设上面的你都搞明白了。那么,接着上篇的课后练习,怎样实现线程的挂起和恢复呢?这个问题你思考了吗?没思考的话就不要继续了。

线程挂起恢复分析实现

  为了实现线程的挂起恢复函数,我们应该把思路放在它是如何实现线程调度上。让线程挂起,无非就是不让给这个线程CPU时间了,看看下面负责调度的函数是怎样找到线程的:

void Scheduling(void)
{
    int i;
    int TickCount;
    GMThread_t* SrcGMThreadp;
    GMThread_t* DstGMThreadp;
    TickCount = GetTickCount();
    SrcGMThreadp = &GMThreadList[CurrentThreadIndex];
    DstGMThreadp = &GMThreadList[0];    
    for (i = 1; GMThreadList[i].name; i++) {
        if (GMThreadList[i].Flags & GMTHREAD_SLEEP) {
            if (TickCount > GMThreadList[i].SleepMillsecondDot) {
                GMThreadList[i].Flags = GMTHREAD_READY;
            }
        }
        if (GMThreadList[i].Flags & GMTHREAD_READY) {
            DstGMThreadp = &GMThreadList[i];
            break;
        }
    }   
    CurrentThreadIndex = DstGMThreadp - GMThreadList;
    SwitchContext(SrcGMThreadp, DstGMThreadp);
    return;
}

  可以看到,只要模拟线程是GMTHREAD_READY状态,它就会调换线程,也就是说我们改一改这个标志线程状态的参数一改,就能实现我想要的挂起和恢复,我们来实现它。
  首先,我们在ThreadSwitch.h来添加两处声明:

bool SyspendThread(char* Name);
bool ResumeThread(char* Name);

  然后我们对它进行实现:

bool SyspendThread(char* Name)
{
    for (int i=1;i<MAXGMTHREAD;i++)
    {
        if (!strcmp(Name,GMThreadList[i].name))
        {
            GMThreadList[i].Flags = GMTHREAD_EXIT;
            return true;
        }
    }
    return false;
}

bool ResumeThread(char* Name)
{
    for (int i=1;i<MAXGMTHREAD;i++)
    {
        if (!strcmp(Name,GMThreadList[i].name))
        {
            GMThreadList[i].Flags = GMTHREAD_READY;
            return true;
        }
    }
    return false;
}

  为了方便观察,我们只保留了Thread1Thread2,最终main.cpp的内容如下:

#include "stdafx.h"
#include <windows.h>
#include "ThreadSwitch.h"
 
extern int CurrentThreadIndex;
 
extern GMThread_t GMThreadList[MAXGMTHREAD];
void Thread1(void*) {
    while(1){
        printf("Thread1\n");
        GMSleep(500);
    }
}
void Thread2(void*) {
    while (1) {
        printf("Thread2\n");
        GMSleep(500);
    }
}

int main()
{
    RegisterGMThread("Thread1", Thread1, NULL);
    RegisterGMThread("Thread2", Thread2, NULL);

    //SyspendThread("Thread2");
    //ResumeThread("Thread2");

    while(TRUE) {
        Sleep(20);
        Scheduling();
    }

    return 0;
}

  我们接下来以动图的形式进行演示:

  上一篇留下的第0题到此就解决完毕了。

SwapContext 分析

  本分析对于逆向水平有一定的要求,如果不行的话建议做多一些有关IDACrakeMe练习,分析流程以做熟悉和练习。不过没有经验也无所谓,仔细看看本部分,回去重新做一遍。
  当你找到这个函数的是哦胡,你看到的应该是下面的情况:

SwapContext     proc near               ; CODE XREF: KiUnlockDispatcherDatabase(x)+72↑p
                                        ; KiSwapContext(x)+29↑p ...
                or      cl, cl
                mov     byte ptr es:[esi+2Dh], 2
                pushf

loc_46A8E8:                             ; CODE XREF: KiIdleLoop()+5A↓j
                mov     ecx, [ebx]      ; ebx = KPCR
                cmp     dword ptr [ebx+994h], 0
                push    ecx
                jnz     loc_46AA2D
                cmp     ds:_PPerfGlobalGroupMask, 0
                jnz     loc_46AA04

loc_46A905:                             ; CODE XREF: SwapContext+12C↓j
                                        ; SwapContext+13D↓j ...
                mov     ebp, cr0
                mov     edx, ebp
                mov     cl, [esi+2Ch]
                mov     [ebx+50h], cl

  既然分析它的参数是什么,首先我们得知道它们是如何传参的,传的是什么,这样我们才能解决上一篇留下的思考题。
  然后我们找到它的来自KiSwapContext的一个引用,结果如下:

; __fastcall KiSwapContext(x)
@KiSwapContext@4 proc near              ; CODE XREF: KiSwapThread()+41↑p

var_200FE4      = dword ptr -200FE4h
var_10          = dword ptr -10h
var_C           = dword ptr -0Ch
var_8           = dword ptr -8
var_4           = dword ptr -4

                sub     esp, 10h
                mov     [esp+10h+var_4], ebx
                mov     [esp+10h+var_8], esi
                mov     [esp+10h+var_C], edi
                mov     [esp+10h+var_10], ebp
                mov     ebx, ds:0FFDFF01Ch ; ebx = &_KPCR
                mov     esi, ecx        ; esi = ecx = NextReadyThread
                mov     edi, [ebx+124h]
                mov     [ebx+124h], esi
                mov     cl, [edi+58h]
                call    SwapContext
                mov     ebp, [esp+10h+var_10]
                mov     edi, [esp+10h+var_C]
                mov     esi, [esp+10h+var_8]
                mov     ebx, [esp+10h+var_4]
                add     esp, 10h
                retn
@KiSwapContext@4 endp

  但是这样也看不出来参数是什么,我们再往上找一级:

@KiSwapThread@0 proc near               ; CODE XREF: KiAttachProcess(x,x,x,x)+F2↑p
                                        ; KeDelayExecutionThread(x,x,x):loc_42279A↑p ...
                mov     edi, edi
                push    esi
                push    edi
                db      3Eh
                mov     eax, ds:0FFDFF020h
                mov     esi, eax

  我们找到了KiSwapThread这个函数,明显望文生义就是用来切换线程用的内核函数。前面我们知道0xFFDFF000这个地址存放的就是KPCR结构体的地址,那么根据结构体可以知道0xFFDFF020存放的就是KPRCB这个结构体的首地址,最终分析得到如下结果:

@KiSwapThread@0 proc near               ; CODE XREF: KiAttachProcess(x,x,x,x)+F2↑p
                                        ; KeDelayExecutionThread(x,x,x):loc_42279A↑p ...
                mov     edi, edi
                push    esi
                push    edi
                db      3Eh
                mov     eax, ds:0FFDFF020h
                mov     esi, eax
                mov     eax, [esi+_KPRCB.NextThread] ; eax = NextThread
                test    eax, eax        ; 测一测有没有
                mov     edi, [esi+_KPRCB.CurrentThread] ; edi = CurrentThread
                jz      short loc_429CAC ; 没有 NextThread 就跳
                and     [esi+_KPRCB.NextThread], 0 ; 把 NextThread 清零
                jmp     short loc_429CCF
; ---------------------------------------------------------------------------

loc_429CAC:                             ; CODE XREF: KiSwapThread()+14↑j
                push    ebx
                movsx   ebx, [esi+_KPRCB.Number]
                xor     edx, edx
                mov     ecx, ebx
                call    @KiFindReadyThread@8 ; KiFindReadyThread(x,x)
                test    eax, eax
                jnz     short loc_429CCE ; 如果找到了下一个线程就跳走
                mov     eax, [esi+_KPRCB.IdleThread]
                xor     edx, edx
                inc     edx
                mov     ecx, ebx
                shl     edx, cl
                or      _KiIdleSummary, edx

loc_429CCE:                             ; CODE XREF: KiSwapThread()+2C↑j
                pop     ebx

loc_429CCF:                             ; CODE XREF: KiSwapThread()+1A↑j
                mov     ecx, eax        
                call    @KiSwapContext@4 ; KiSwapContext(x)

  经过简单的分析,我们很容易地判断出执行到KiSwapContext前的ecx为一个线程结构体,如下图所示:

  根据符号显示KiSwapContext是只有一个参数,经过简单的分析可以得到下面的结果:

; __fastcall KiSwapContext(x)
@KiSwapContext@4 proc near              ; CODE XREF: KiSwapThread()+41↑p

var_200FE4      = dword ptr -200FE4h
var_10          = dword ptr -10h
var_C           = dword ptr -0Ch
var_8           = dword ptr -8
var_4           = dword ptr -4

                sub     esp, 10h
                mov     [esp+10h+var_4], ebx
                mov     [esp+10h+var_8], esi
                mov     [esp+10h+var_C], edi
                mov     [esp+10h+var_10], ebp
                mov     ebx, ds:0FFDFF01Ch ; ebx = &_KPCR
                mov     esi, ecx        ; esi = ecx = NextReadyThread
                mov     edi, [ebx+_KPCR.PrcbData.CurrentThread]
                mov     [ebx+_KPCR.PrcbData.CurrentThread], esi
                mov     cl, [edi+_KTHREAD.WaitIrql]
                call    SwapContext
                mov     ebp, [esp+10h+var_10]
                mov     edi, [esp+10h+var_C]
                mov     esi, [esp+10h+var_8]
                mov     ebx, [esp+10h+var_4]
                add     esp, 10h
                retn
@KiSwapContext@4 endp

  执行到SwapContext这个函数前,esi成了下一个切换新线程,而edi成了需要被切换的老线程,而ebxKPCR结构体,也就是说,改函数一共有3个参数,每一个参数的含义我们都已经知道了,我们利用IDAF5也可以得到一定的验证:

char __usercall SwapContext@<al>(int *a1@<ebx>, int a2@<edi>, int a3@<esi>)

  接下来我们分析一下在哪里实现了线程切换。既然操作系统线程切换是基于堆栈的,esp换了,必然导致线程的切换,我们很容易跟到下面的汇编:

loc_46A94C:                             ; CODE XREF: SwapContext+67↑j
                mov     ecx, [ebx+_KPCR.TSS]
                mov     [ecx+_KTSS.Esp0], eax
                mov     esp, [esi+_KTHREAD.KernelStack]
                mov     eax, [esi+_KTHREAD.Teb]
                mov     [ebx+_KPCR.NtTib.Self], eax

  经历过堆栈的弹出恢复操作,再调用retn,即可完成线程的切换。接下来我们看看什么时候切换的CR3

                mov     eax, [edi+_KTHREAD.ApcState.Process]
                cmp     eax, [esi+_KTHREAD.ApcState.Process]
                mov     [edi+_KTHREAD.IdleSwapBlock], 0
                jz      short loc_46A994
                mov     edi, [esi+_KTHREAD.ApcState.Process]
                test    word ptr [edi+_KTHREAD.Teb], 0FFFFh
                jnz     short loc_46A9CE
                xor     eax, eax

loc_46A975:                             ; CODE XREF: SwapContext+117↓j
                lldt    ax
                xor     eax, eax
                mov     gs, eax
                assume gs:GAP
                mov     eax, [edi+_EPROCESS.Pcb.DirectoryTableBase]
                mov     ebp, [ebx+_KPCR.TSS]
                mov     ecx, dword ptr [edi+_KTHREAD.Iopl]
                mov     [ebp+_KTSS.CR3], eax
                mov     cr3, eax
                mov     [ebp+_KTSS.IoMapBase], cx
                jmp     short loc_46A994
; ---------------------------------------------------------------------------
                align 4

loc_46A994:                             ; CODE XREF: SwapContext+86↑j
                                        ; SwapContext+AF↑j
                mov     eax, [ebx+_KPCR.NtTib.Self]

  经过分析,切换CR3是需要条件的,它会判断新的线程和老的线程的Process是不是一样的,然后决定是否处理:

mov     eax, [edi+_KTHREAD.ApcState.Process]
cmp     eax, [esi+_KTHREAD.ApcState.Process]
mov     [edi+_KTHREAD.IdleSwapBlock], 0
jz      short loc_46A994

  如果不相同的话,就会执行下面的代码切换CR3

mov     eax, [edi+_EPROCESS.Pcb.DirectoryTableBase]
mov     ebp, [ebx+_KPCR.TSS]
mov     ecx, dword ptr [edi+_KTHREAD.Iopl]
mov     [ebp+_KTSS.CR3], eax
mov     cr3, eax

  看明白到这个地方的时候,第2题就解决了。一个CPU一套寄存器,也就说明里面只能存储一个TSS地址的寄存器,那么中断门提权时TSS中存储的一定是当前线程的ESP0SS0吗?我们接下来分析一下:

                mov     eax, [esi+_KTHREAD.InitialStack]
                mov     ecx, [esi+_KTHREAD.StackLimit]
                sub     eax, 210h
                mov     [ebx+_KPCR.NtTib.StackLimit], ecx
                mov     [ebx+_KPCR.NtTib.StackBase], eax
                xor     ecx, ecx
                mov     cl, [esi+_KTHREAD.NpxState]
                and     edx, 0FFFFFFF1h
                or      ecx, edx
                or      ecx, [eax+20Ch]
                cmp     ebp, ecx
                jnz     loc_46A9FC
                lea     ecx, [ecx+0]

loc_46A940:                             ; CODE XREF: SwapContext+11F↓j
                test    dword ptr [eax-1Ch], 20000h ; 检查是否为虚拟8086模式
                jnz     short loc_46A94C
                sub     eax, 10h

loc_46A94C:                             ; CODE XREF: SwapContext+67↑j
                mov     ecx, [ebx+_KPCR.TSS]
                mov     [ecx+_KTSS.Esp0], eax ; 将修正好的栈顶放入到 TSS 中
                mov     esp, [esi+_KTHREAD.KernelStack]
                mov     eax, [esi+_KTHREAD.Teb]

  可以看出在mov esp, [esi+_KTHREAD.KernelStack]之前,上面的代码已经处理好并修正ESP0,所以中断门提权时TSS中存储的一定是当前线程的ESP0SS0。至此,第3题解答完毕。
  在我测试的虚拟机中,fs的段选择子都是0x3B,但为什么不同的线程段选择子指向的TEB却不一样呢?是因为它直接修改了GDT表的内容,使它指向的地址是我们现在线程的TEB,代码如下所示:

mov     eax, [ebx+_KPCR.NtTib.Self]
mov     ecx, [ebx+_KPCR.GDT]
mov     [ecx+3Ah], ax
shr     eax, 10h
mov     [ecx+3Ch], al
mov     [ecx+3Fh], ah

  至此,第4题解答完毕。我们来看看0环的ExceptionList在哪里备份的:

pop     ecx
mov     [ebx+_KPCR.NtTib.ExceptionList], ecx

  第5题也就解决了,它把新线程的ExceptionList存于KPCR中。下面我们来看看IdleThread如何查找:
  我们知道IdleThread存储于KPCR之中,我们通过结构体的方式进行查询,找到该成员存储的地址。

kd> dt _KPCR  0xffdff000 
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

kd> dx -id 0,0,805539a0 -r1 ((ntkrnlpa!_KPRCB *)0xffdff120)
((ntkrnlpa!_KPRCB *)0xffdff120)                 : 0xffdff120 [Type: _KPRCB *]
    [+0x000] MinorVersion     : 0x1 [Type: unsigned short]
    [+0x002] MajorVersion     : 0x1 [Type: unsigned short]
    [+0x004] CurrentThread    : 0x80553740 [Type: _KTHREAD *]
    [+0x008] NextThread       : 0x0 [Type: _KTHREAD *]
    [+0x00c] IdleThread       : 0x80553740 [Type: _KTHREAD *]
    [+0x010] Number           : 0 [Type: char]
    [+0x011] Reserved         : 0 [Type: char]

  然后我们dt一下这个结构体:

kd> dt _ETHREAD 0x80553740
ntdll!_ETHREAD
   +0x000 Tcb              : _KTHREAD
   +0x1c0 CreateTime       : _LARGE_INTEGER 0x0
   +0x1c0 NestedFaultCount : 0y00
   +0x1c0 ApcNeeded        : 0y0
   +0x1c8 ExitTime         : _LARGE_INTEGER 0x0
   +0x1c8 LpcReplyChain    : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x1c8 KeyedWaitChain   : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x1d0 ExitStatus       : 0n0
   +0x1d0 OfsChain         : (null) 
   +0x1d4 PostBlockList    : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x1dc TerminationPort  : (null) 
   +0x1dc ReaperLink       : (null) 
   +0x1dc KeyedWaitValue   : (null) 
   +0x1e0 ActiveTimerListLock : 0
   +0x1e4 ActiveTimerListHead : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x1ec Cid              : _CLIENT_ID
   +0x1f4 LpcReplySemaphore : _KSEMAPHORE
   +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
   +0x208 LpcReplyMessage  : (null) 
   +0x208 LpcWaitingOnPort : (null) 
   +0x20c ImpersonationInfo : (null) 
   +0x210 IrpList          : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x218 TopLevelIrp      : 0
   +0x21c DeviceToVerify   : (null) 
   +0x220 ThreadsProcess   : (null) 
   +0x224 StartAddress     : (null) 
   +0x228 Win32StartAddress : (null) 
   +0x228 LpcReceivedMessageId : 0
   +0x22c ThreadListEntry  : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x234 RundownProtect   : _EX_RUNDOWN_REF
   +0x238 ThreadLock       : _EX_PUSH_LOCK
   +0x23c LpcReplyMessageId : 0
   +0x240 ReadClusterSize  : 0
   +0x244 GrantedAccess    : 0x1f03ff
   +0x248 CrossThreadFlags : 0
   +0x248 Terminated       : 0y0
   +0x248 DeadThread       : 0y0
   +0x248 HideFromDebugger : 0y0
   +0x248 ActiveImpersonationInfo : 0y0
   +0x248 SystemThread     : 0y0
   +0x248 HardErrorsAreDisabled : 0y0
   +0x248 BreakOnTermination : 0y0
   +0x248 SkipCreationMsg  : 0y0
   +0x248 SkipTerminationMsg : 0y0
   +0x24c SameThreadPassiveFlags : 0
   +0x24c ActiveExWorker   : 0y0
   +0x24c ExWorkerCanWaitUser : 0y0
   +0x24c MemoryMaker      : 0y0
   +0x250 SameThreadApcFlags : 0
   +0x250 LpcReceivedMsgIdValid : 0y0
   +0x250 LpcExitThreadCalled : 0y0
   +0x250 AddressSpaceOwner : 0y0
   +0x254 ForwardClusterOnly : 0 ''
   +0x255 DisablePageFaultClustering : 0 ''

  在上面的结构体中+0x224 StartAddress存储的就是线程开始执行的地址,也就是我们在3环调用CreateThread传递的函数地址。但是对于这个线程比较特殊,直接全为空,那么我们如何找到函数地址呢?
  程序执行的时候,一定会用到堆栈。我们可以通过堆栈就可以定位程序的行为。我们把关注点放到KTHREADKernelStack上。涉及该成员的操作存在于线程切换中,我们来看看与堆栈操作相关的局部汇编代码:

pushf
mov     ecx, [ebx]      ; ebx = KPCR
cmp     [ebx+_KPCR.PrcbData.DpcRoutineActive], 0
push    ecx ;KPCR.NtTib.ExceptionList
……
mov     esp, [esi+_KTHREAD.KernelStack]
……
pop     ecx
xor     eax, eax
retn

  先看看堆栈地址的地址是啥:

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

  再看看堆栈长什么样子:

kd> dd 0x8054ac4c
8054ac4c  00000000 ffdff980 80542af0 00000000
8054ac5c  0000000e 00000000 00000000 00000000
8054ac6c  00000000 00000000 00000000 00000000
8054ac7c  00000000 00000000 00000000 00000000
8054ac8c  00000000 00000000 00000000 00000000
8054ac9c  00000000 00000000 00000000 00000000
8054acac  00000000 00000000 00000000 00000000
8054acbc  00000000 00000000 00000000 00000000

  也就是说,第一个就是ExceptionList,第二个就是Eflag,第三个就是切换线程后跳转的地址,在这里也就是IdleThread继续走的地址。这个地址肯定就在IdleThread当中。我们u一下:

kd> u 80542af0
ReadVirtual: 80542af0 not properly sign extended
80542af0 fb              sti
ReadVirtual: 80542b00 not properly sign extended
80542af1 90              nop
ReadVirtual: 80542b01 not properly sign extended
80542af2 90              nop
ReadVirtual: 80542b02 not properly sign extended
80542af3 fa              cli
ReadVirtual: 80542b03 not properly sign extended
80542af4 3b6d00          cmp     ebp,dword ptr [ebp]
80542af7 740d            je      nt!KiIdleLoop+0x26 (80542b06)
80542af9 b102            mov     cl,2
80542afb ff15a8864d80    call    dword ptr [nt!_imp_HalClearSoftwareInterrupt (804d86a8)]

  也就是函数是KiIdleLoop,我们通过IDA看看该函数:

; _DWORD __cdecl KiIdleLoop()
@KiIdleLoop@0   proc near               ; CODE XREF: KiSystemStartup(x)+2E2↓j
                lea     ebp, [ebx+980h]
                jmp     short loc_46AAF0
; ---------------------------------------------------------------------------

loc_46AAE8:                             ; CODE XREF: KiIdleLoop()+2D↓j
                lea     ecx, [ebx+0C50h]
                call    dword ptr [ecx]

loc_46AAF0:                             ; CODE XREF: KiIdleLoop()+6↑j
                                        ; KiIdleLoop()+65↓j
                sti
                nop
                nop
                cli
                cmp     ebp, [ebp+0]
                jz      short loc_46AB06
                mov     cl, 2
                call    ds:__imp_@HalClearSoftwareInterrupt@4 ; HalClearSoftwareInterrupt(x)
                call    KiRetireDpcList

loc_46AB06:                             ; CODE XREF: KiIdleLoop()+17↑j
                cmp     dword ptr [ebx+128h], 0
                jz      short loc_46AAE8
                sti
                mov     esi, [ebx+128h]
                mov     edi, [ebx+124h]
                or      ecx, 1
                mov     [ebx+124h], esi
                mov     byte ptr es:[esi+2Dh], 2
                mov     dword ptr [ebx+128h], 0
                push    offset loc_46AB3F
                pushf
                jmp     loc_46A8E8
; ---------------------------------------------------------------------------

loc_46AB3F:                             ; DATA XREF: KiIdleLoop()+54↑o
                lea     ebp, [ebx+980h]
                jmp     short loc_46AAF0
@KiIdleLoop@0   endp

  这个函数没有任何意义,就是让CPU别闲着,执行一波无任何意义的代码。至此第6题解决。

  至此第7题解决。
  模拟线程切换与Windows的线程切换有哪些区别?真正的线程有两个栈,一个内核0环的栈,一个是3环的栈,发生线程切换在0环;模拟线程切换没用到FS、异常列表之类的东西,其他的区别可以自行总结。
  接下来是最后一题,我们走一下时钟中断的流程,中断都是在IDT表中的,首先我们跟着走一下,首先定位该表,只需g_IDT,效果如下所示:

_IDT            dd offset _KiTrap00     ; DATA XREF: KiSystemStartup(x)+1D5↑o
                db 0, 8Eh
word_5B8B02     dw 8                    ; DATA XREF: KiSwapIDT()↓o
                dd offset _KiTrap01
                dd 88E00h
                dd offset _KiTrap02
                dd 88E00h
                dd offset _KiTrap03
                dd 8EE00h
                dd offset _KiTrap04
                dd 8EE00h
                dd offset _KiTrap05
                dd 88E00h
                dd offset _KiTrap06
                dd 88E00h
                dd offset _KiTrap07
                dd 88E00h
                dd offset _KiTrap08
                dd 88E00h
                dd offset _KiTrap09

  时钟中断的中断号是0x30,我们定位到这个函数,如何定位呢?看下面的图:

  然后跳转到这个函数:

; _DWORD __stdcall KiStartUnexpectedRange()
_KiStartUnexpectedRange@0 proc near     ; DATA XREF: KiGetVectorInfo(x,x)+68↑o
                                        ; INIT:005B8C7C↓o
                push    30h ; '0'
                jmp     _KiEndUnexpectedRange@0 ; KiEndUnexpectedRange()
_KiStartUnexpectedRange@0 endp

  我们看看跳转到哪里:

; _DWORD __stdcall KiEndUnexpectedRange()
_KiEndUnexpectedRange@0 proc near       ; CODE XREF: KiStartUnexpectedRange()+5↑j
                                        ; _KiUnexpectedInterrupt1+5↑j ...
                jmp     cs:off_46632E
_KiEndUnexpectedRange@0 endp

; ---------------------------------------------------------------------------
off_46632E      dd offset _KiUnexpectedInterruptTail
                                        ; DATA XREF: KiEndUnexpectedRange()↑r

  继续跟着,为了节约篇幅,只保留调用流程部分:

_KiUnexpectedInterruptTail proc near    ; CODE XREF: KiEndUnexpectedRange()↑j
                                        ; DATA XREF: .text:off_46632E↑o

……

loc_466E7E:                             ; CODE XREF: Dr_kui_a+10↑j
                                        ; Dr_kui_a+7C↑j
                inc     dword ptr ds:0FFDFF5C4h
                mov     ebx, [esp+68h+var_68]
                sub     esp, 4
                push    esp
                push    ebx
                push    1Fh
                call    ds:__imp__HalBeginSystemInterrupt@12 ; HalBeginSystemInterrupt(x,x,x)
                or      eax, eax
                jnz     short loc_466E9D
                add     esp, 8
                jmp     short loc_466EEC
; ---------------------------------------------------------------------------

loc_466E9D:                             ; CODE XREF: _KiUnexpectedInterruptTail+BF↑j
                cli
                call    ds:__imp__HalEndSystemInterrupt@8 ; HalEndSystemInterrupt(x,x)
                jmp     short Kei386EoiHelper@0 ; Kei386EoiHelper()
_KiUnexpectedInterruptTail endp
                public HalEndSystemInterrupt
HalEndSystemInterrupt proc near         ; CODE XREF: sub_80010EF0+E8↑p
                                        ; sub_80017144+B3↓p
                                        ; DATA XREF: ...

arg_0           = byte ptr  4

                movzx   ecx, [esp+arg_0]
                cmp     byte ptr ds:0FFDFF024h, 2
                jbe     short loc_8001123E
                mov     eax, ds:dword_800176EC[ecx*4]
                or      eax, ds:0FFDFF030h
                out     21h, al         ; Interrupt controller, 8259A.
                shr     eax, 8
                out     0A1h, al        ; Interrupt Controller #2, 8259A

loc_8001123E:                           ; CODE XREF: HalEndSystemInterrupt+C↑j
                mov     ds:0FFDFF024h, cl
                mov     eax, ds:0FFDFF028h
                mov     al, ds:byte_80017784[eax]
                cmp     al, cl
                ja      short loc_80011256
                retn    8
; ---------------------------------------------------------------------------

loc_80011256:                           ; CODE XREF: HalEndSystemInterrupt+35↑j
                add     esp, 0Ch
                jmp     ds:pKiUnexpectedInterrupt[eax*4]
HalEndSystemInterrupt endp ; sp-analysis failed
pKiUnexpectedInterrupt dd offset KiUnexpectedInterrupt
                                        ; DATA XREF: HalEndSystemInterrupt+3D↑r
                                        ; sub_80011260+3D↑r
                dd offset sub_80016BDD
                dd offset sub_80016A45
sub_80016A45    proc near               ; CODE XREF: KfLowerIrql:loc_800110AC↑p
                                        ; KfReleaseSpinLock:loc_8001111C↑p ...
                push    dword ptr ds:0FFDFF024h
                mov     byte ptr ds:0FFDFF024h, 2
                and     dword ptr ds:0FFDFF028h, 0FFFFFFFBh
                sti
                call    ds:KiDispatchInterrupt
                cli
                call    sub_80011260
                jmp     ds:Kei386EoiHelper
sub_80016A45    endp
; _DWORD __stdcall KiDispatchInterrupt()
                 public _KiDispatchInterrupt@0
 _KiDispatchInterrupt@0 proc near        ; DATA XREF: .edata:off_58D2A8↓o

 var_C           = dword ptr -0Ch
 var_8           = dword ptr -8
 var_4           = dword ptr -4

                 mov     ebx, ds:0FFDFF01Ch ; a1
                 lea     eax, [ebx+980h]
                 cli
                 cmp     eax, [eax]
                 jz      short loc_46A85E
                 push    ebp
                 push    dword ptr [ebx]
                 mov     dword ptr [ebx], 0FFFFFFFFh
                 mov     edx, esp
                 mov     esp, [ebx+988h]
                 push    edx
                 mov     ebp, eax
                 call    KiRetireDpcList
                 pop     esp
                 pop     dword ptr [ebx]
                 pop     ebp

 loc_46A85E:                             ; CODE XREF: KiDispatchInterrupt()+F↑j
                 sti
                 cmp     dword ptr [ebx+9ACh], 0
                 jnz     short loc_46A8BE
                 cmp     dword ptr [ebx+128h], 0
                 jz      short locret_46A8BD
                 mov     eax, [ebx+128h]

 loc_46A877:                             ; CODE XREF: KiDispatchInterrupt()+9F↓j
                 sub     esp, 0Ch
                 mov     [esp+0Ch+var_4], esi
                 mov     [esp+0Ch+var_8], edi
                 mov     [esp+0Ch+var_C], ebp
                 mov     esi, eax        ; NewThread
                 mov     edi, [ebx+124h] ; oldThread
                 mov     dword ptr [ebx+128h], 0
                 mov     [ebx+124h], esi
                 mov     ecx, edi
                 mov     byte ptr [edi+50h], 1
                 call    @KiReadyThread@4 ; KiReadyThread(x)
                 mov     cl, 1
                 call    SwapContext
                 mov     ebp, [esp+0Ch+var_C]
                 mov     edi, [esp+0Ch+var_8]
                 mov     esi, [esp+0Ch+var_4]
                 add     esp, 0Ch

 locret_46A8BD:                          ; CODE XREF: KiDispatchInterrupt()+3F↑j
                 retn
 ; ---------------------------------------------------------------------------

 loc_46A8BE:                             ; CODE XREF: KiDispatchInterrupt()+36↑j
                 mov     dword ptr [ebx+9ACh], 0
                 call    _KiQuantumEnd@0 ; KiQuantumEnd()
                 or      eax, eax
                 jnz     short loc_46A877
                 retn
 _KiDispatchInterrupt@0 endp

进程挂靠

  在讲编程的时候,我们都听过:一个进程可以包含多个线程,一个进程至少要有一个线程。进程为线程提供资源,也就是提供Cr3的值,Cr3中存储的是页目录表基址,Cr3确定了,线程能访问的内存也就确定了。
  对于这一行代码:mov eax,dword ptr ds:[0x12345678]CPU如何解析这个地址呢?CPU解析线性地址时要通过页目录表来找对应的物理页,页目录表基址存在Cr3寄存器中。当前的Cr3的值来源于当前的进程结构体的_KPROCESS.DirectoryTableBase当中。那么进程挂靠又是怎么回事呢?我们先来看个结构体:

kd> dt _KTHREAD
ntdll!_KTHREAD
   ……
   +0x032 Saturation       : Char
   +0x033 Priority         : Char
   +0x034 ApcState         : _KAPC_STATE
   +0x04c ContextSwitches  : Uint4B
   +0x050 IdleSwapBlock    : UChar
   ……

kd> dt _KAPC_STATE
ntdll!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY
   +0x010 Process          : Ptr32 _KPROCESS
   +0x014 KernelApcInProgress : UChar
   +0x015 KernelApcPending : UChar
   +0x016 UserApcPending   : UChar

  ApcState这个成员我们在逆向线程切换的时候遇到过,也就是解决我们第2题的时候,这个结构体的Process成员就是存储的进程挂靠上的进程CR3。可以打一个比方,EPROCESSDirectoryTableBase存储的是亲父母,而ApcState存储的是养父母,我想要资源时从养父母来拿。正常情况下,CR3的值是由养父母提供的,但CR3的值也可以改成和当前线程毫不相干的其他进程的DirectoryTableBase。将当前CR3的值改为其他进程,称为“进程挂靠”。

跨进程内存读写

  跨进程内存读写根据之前所学肯定必须切换CR3,并且读取内存肯定会落实到类似如下汇编:

mov eax,dword ptr ds:[0x12345678]
mov dword ptr ds:[0x00401234],eax

  我们自己实现一个跨进程内存读写一个int还好说,如果是一个指定长度的Buffer,那咋办呢?
  我们都知道,应用程序的高2G的内核空间是共用的,也就是说,无论是哪个应用程序,高2G的内容寻址都能寻到的。那么我把读取进程的内存写到高2G的空间,然后切换CR3回去,然后重新把高2G缓存的东西写到指定Buffer中,我们就完成了。上面的读的操作,写得操作也是类似的。

跨进程读

跨进程写

  我们下面来简单分析一下Windows实现的跨进程读内存的函数NtReadVirtualMemory和跨进程写NtWriteVirtualMemory的函数,NtWriteVirtualMemoryNtReadVirtualMemory实现十分相似,我就只分析前者,下面的自行分析。为什么说是浅析是因为里面有大量的其他前置知识,比如APC和内存管理。三环怎么进内核的我就不再赘述了,为了方便。为了缩短篇幅增加可读性,我会尽可能使用IDA翻译的伪代码,你的伪代码结果应该和我的不一样,因为我进行了一些重命名操作。

NtReadVirtualMemory 浅析

  我们先定位到NtReadVirtualMemory这个伪代码:

NTSTATUS __stdcall NtReadVirtualMemory(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToRead, PSIZE_T NumberOfBytesRead)
{
  _KTHREAD *v5; // edi
  PSIZE_T v6; // ebx
  int a7; // [esp+10h] [ebp-28h] BYREF
  PRKPROCESS PROCESS; // [esp+14h] [ebp-24h] BYREF
  KPROCESSOR_MODE AccessMode[4]; // [esp+18h] [ebp-20h]
  NTSTATUS res; // [esp+1Ch] [ebp-1Ch]
  CPPEH_RECORD ms_exc; // [esp+20h] [ebp-18h]

  v5 = KeGetCurrentThread();
  AccessMode[0] = v5->PreviousMode;
  if ( AccessMode[0] )
  {
    if ( BaseAddress + NumberOfBytesToRead < BaseAddress
      || Buffer + NumberOfBytesToRead < Buffer
      || BaseAddress + NumberOfBytesToRead > MmHighestUserAddress
      || Buffer + NumberOfBytesToRead > MmHighestUserAddress )
    {
      return 0xC0000005;
    }
    v6 = NumberOfBytesRead;
    if ( NumberOfBytesRead )
    {
      ms_exc.registration.TryLevel = 0;
      if ( NumberOfBytesRead >= MmUserProbeAddress )
        *MmUserProbeAddress = 0;
      *NumberOfBytesRead = *NumberOfBytesRead;
      ms_exc.registration.TryLevel = -1;
    }
  }
  else
  {
    v6 = NumberOfBytesRead;
  }
  a7 = 0;
  res = 0;
  if ( NumberOfBytesToRead )
  {
    res = ObReferenceObjectByHandle(ProcessHandle, 0x10u, PsProcessType, AccessMode[0], &PROCESS, 0);
    if ( !res )
    {
      res = MmCopyVirtualMemory(
              PROCESS,
              BaseAddress,
              v5->ApcState.Process,
              Buffer,
              NumberOfBytesToRead,
              AccessMode[0],
              &a7);
      ObfDereferenceObject(PROCESS);
    }
  }
  if ( v6 )
  {
    *v6 = a7;
    ms_exc.registration.TryLevel = -1;
  }
  return res;
}

  我们可以看到,该函数实现内存拷贝是通过MmCopyVirtualMemory这个函数实现的,我们点击去看看:

NTSTATUS __stdcall MmCopyVirtualMemory(PRKPROCESS PROCESS, PVOID BaseAddress, PRKPROCESS KPROCESS, char *buffer, SIZE_T Length, KPROCESSOR_MODE AccessMode, int *a7)
{
  PRKPROCESS process; // ebx
  PRKPROCESS kprocess; // ecx
  NTSTATUS res; // esi
  struct _EX_RUNDOWN_REF *RunRefa; // [esp+8h] [ebp+8h]

  if ( !Length )
    return 0;
  process = PROCESS;
  kprocess = PROCESS;
  if ( PROCESS == KeGetCurrentThread()->ApcState.Process )
    kprocess = KPROCESS;
  RunRefa = &kprocess[1].ProfileListHead.Blink;
  if ( !ExAcquireRundownProtection(&kprocess[1].ProfileListHead.Blink) )
    return STATUS_PROCESS_IS_TERMINATING;
  if ( Length <= 0x1FF )
    goto LABEL_10;
  res = MiDoMappedCopy(process, BaseAddress, KPROCESS, buffer, Length, AccessMode, a7);
  if ( res == STATUS_WORKING_SET_QUOTA )
  {
    *a7 = 0;
LABEL_10:
    res = MiDoPoolCopy(process, BaseAddress, KPROCESS, buffer, Length, AccessMode, a7);
  }
  ExReleaseRundownProtection(RunRefa);
  return res;
}

  你可能看到一个新奇的函数ExAcquireRundownProtection,这个函数是申请一个锁,从网上查阅翻译过来是停运保护(RundownProtection)锁,名字怪怪的听起来怪怪的。
  这个不涉及我们的核心,我们继续分析,发现它内部又是通过MiDoMappedCopy实现进程内存读取的:

NTSTATUS __stdcall MiDoMappedCopy(PRKPROCESS PROCESS, char *src, PRKPROCESS process, char *buffer, SIZE_T Length, KPROCESSOR_MODE AccessMode, int *a7)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v13 = 0;
  src_0 = src;
  buffer_1 = buffer;
  v7 = 0xE000;
  if ( Length <= 0xE000 )
    v7 = Length;
  v16 = &MemoryDescriptorList;
  Length_1 = Length;
  v19 = v7;
  v20 = 0;
  v14 = 0;
  v15 = 0;
  while ( Length_1 )
  {
    if ( Length_1 < v19 )
      v19 = Length_1;
    KeStackAttachProcess(PROCESS, &ApcState);
    BaseAddress = 0;
    v12 = 0;
    v11 = 0;
    ms_exc.registration.TryLevel = 0;
    if ( src_0 == src && AccessMode )
    {
      v20 = 1;
      if ( Length && (&src[Length] < src || &src[Length] > MmUserProbeAddress) )
        ExRaiseAccessViolation();
      v20 = 0;
    }
    MemoryDescriptorList.Next = 0;
    MemoryDescriptorList.Size = 4 * (((src_0 & 0xFFF) + v19 + 0xFFF) >> 12) + 28;
    MemoryDescriptorList.MdlFlags = 0;
    MemoryDescriptorList.StartVa = (src_0 & 0xFFFFF000);
    MemoryDescriptorList.ByteOffset = src_0 & 0xFFF;
    MemoryDescriptorList.ByteCount = v19;
    MmProbeAndLockPages(&MemoryDescriptorList, AccessMode, IoReadAccess);
    v12 = 1;
    BaseAddress = MmMapLockedPagesSpecifyCache(&MemoryDescriptorList, 0, MmCached, 0u, 0u, 0x20u);
    if ( !BaseAddress )
    {
      v13 = 1;
      ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
    }
    KeUnstackDetachProcess(&ApcState);
    KeStackAttachProcess(process, &ApcState);
    if ( src_0 == src )
    {
      if ( AccessMode )
      {
        v20 = 1;
        ProbeForWrite(buffer, Length, 1u);
        v20 = 0;
      }
    }
    v11 = 1;
    qmemcpy(buffer_1, BaseAddress, v19);
    ms_exc.registration.TryLevel = -1;
    KeUnstackDetachProcess(&ApcState);
    MmUnmapLockedPages(BaseAddress, &MemoryDescriptorList);
    MmUnlockPages(&MemoryDescriptorList);
    Length_1 -= v19;
    src_0 += v19;
    buffer_1 += v19;
  }
  *a7 = Length;
  return STATUS_SUCCESS;
}

  KeStackAttachProcessKeUnstackDetachProcess这俩函数与APC相关,在这里你可以简单理解就是切换CR3,实现进程挂靠和解除挂靠。我们注意一下下面的伪代码:

MemoryDescriptorList.Next = 0;
MemoryDescriptorList.Size = 4 * (((src_0 & 0xFFF) + v19 + 0xFFF) >> 12) + 28;
MemoryDescriptorList.MdlFlags = 0;
MemoryDescriptorList.StartVa = (src_0 & 0xFFFFF000);
MemoryDescriptorList.ByteOffset = src_0 & 0xFFF;
MemoryDescriptorList.ByteCount = v19;
MmProbeAndLockPages(&MemoryDescriptorList, AccessMode, IoReadAccess);
v12 = 1;
BaseAddress = MmMapLockedPagesSpecifyCache(&MemoryDescriptorList, 0, MmCached, 0u, 0u, 0x20u);

  MmMapLockedPagesSpecifyCache这个函数就是映射里面描述的物理页,下面是微软对该函数的描述:

The MmMapLockedPagesSpecifyCache routine maps the physical pages that are described by an MDL to a virtual address, and enables the caller to specify the cache attribute that is used to create the mapping.

  上面的操作就算锁住物理页,并把它重新映射到高2G地址,我们直接写到里面,就少了重新把高2G的内容重新写到程序空间的步骤了。

进程创建浅析

  同理上面的浅析,我同样使用伪代码。进程创建浅析是为了知道内核是怎样创建进程的流程,具体细节请自行挖掘,我们定位到其内核函数NtCreateProcess

NTSTATUS __stdcall NtCreateProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, BOOLEAN InheritObjectTable, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort)
{
  ULONG v8; // eax

  v8 = (SectionHandle & 1) != 0;
  if ( (DebugPort & 1) != 0 )
    v8 |= 2u;
  if ( InheritObjectTable )
    v8 |= 4u;
  return NtCreateProcessEx(
           ProcessHandle,
           DesiredAccess,
           ObjectAttributes,
           ParentProcess,
           v8,
           SectionHandle,
           DebugPort,
           ExceptionPort,
           0);
}

  这个内核函数又会调用NtCreateProcessEx实现功能,我们点进去看看:

NTSTATUS __stdcall NtCreateProcessEx(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, BOOLEAN InJob)
{
  PHANDLE v9; // ecx
  NTSTATUS result; // eax

  if ( KeGetCurrentThread()->PreviousMode )
  {
    v9 = ProcessHandle;
    if ( ProcessHandle >= MmUserProbeAddress )
      *MmUserProbeAddress = 0;
    *ProcessHandle = *ProcessHandle;
  }
  else
  {
    v9 = ProcessHandle;
  }
  if ( ParentProcess )
    result = PspCreateProcess(
               v9,
               DesiredAccess,
               ObjectAttributes,
               ParentProcess,
               Flags,
               SectionHandle,
               DebugPort,
               ExceptionPort,
               InJob);
  else
    result = STATUS_INVALID_PARAMETER;
  return result;
}

  这个函数优惠调用PspCreateProcess实现创建进程的任务,继续点击去看看:

🔒 点击查看伪代码 🔒
NTSTATUS __stdcall PspCreateProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, BOOLEAN InJob)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v41 = KeGetCurrentThread();
  AccessMode[0] = v41->PreviousMode;
  process = v41->ApcState.Process;
  v64 = 0;
  v55[0] = 0;
  v55[1] = 0;
  if ( (Flags & 0xFFFFFFF0) != 0 )
    return STATUS_INVALID_PARAMETER;
  if ( ParentProcess )
  {
    result = ObReferenceObjectByHandle(ParentProcess, 0x80u, PsProcessType, AccessMode[0], &eprocess, 0);
    ParentProcess_1 = eprocess;
    if ( result < 0 )
      return result;
    if ( InJob && !eprocess[2].Affinity )
    {
      ObfDereferenceObject(eprocess);
      return STATUS_INVALID_PARAMETER;
    }
    ActiveProcessors = eprocess->Affinity;
  }
  else
  {
    ParentProcess_1 = 0;
    ActiveProcessors = KeActiveProcessors;
  }
  v48 = ActiveProcessors;
  *NewIrql = PsMinimumWorkingSet;
  v47 = PsMaximumWorkingSet;
  v11 = ObCreateObject(AccessMode[0], PsProcessType, ObjectAttributes, *AccessMode, 0, 608, 0, 0, &Process);
  if ( v11 < 0 )
    goto LABEL_97;
  NewProcess = Process;
  memset(Process, 0, 0x260u);
  NewProcess[1].ProfileListHead.Blink = 0;
  *&NewProcess[1].Header.Type = 0;
  NewProcess[3].ThreadListHead.Flink = &NewProcess[3].VdmTrapcHandler;
  NewProcess[3].VdmTrapcHandler = &NewProcess[3].VdmTrapcHandler;
  PspInheritQuota(NewProcess, ParentProcess_1);
  ObInheritDeviceMap(NewProcess, ParentProcess_1);
  v13 = ParentProcess_1;
  if ( ParentProcess_1 )
  {
    *&NewProcess[3].AutoAlignment = *&ParentProcess_1[3].AutoAlignment;
    NewProcess[3].Header.WaitListHead.Flink = v13[1].DirectoryTableBase[0];
  }
  else
  {
    *&NewProcess[3].AutoAlignment = 1;
    NewProcess[3].Header.WaitListHead.Flink = 0;
  }
  if ( SectionHandle )
  {
    v14 = ObReferenceObjectByHandle(SectionHandle, 8u, MmSectionObjectType, AccessMode[0], &v40, 0);
    v61 = v40;
    v11 = v14;
    if ( v14 < 0 )
      goto LABEL_96;
    v13 = ParentProcess_1;
  }
  else
  {
    v61 = 0;
    if ( v13 != PsInitialSystemProcess )
    {
      if ( ExAcquireRundownProtection(&v13[1].ProfileListHead.Blink) )
      {
        v15 = *&v13[2].StackCount;
        v61 = v15;
        if ( v15 )
          ObfReferenceObject(v15);
        ExReleaseRundownProtection(&v13[1].ProfileListHead.Blink);
      }
      if ( !v61 )
      {
        v11 = STATUS_PROCESS_IS_TERMINATING;
        goto LABEL_96;
      }
    }
  }
  *&NewProcess[2].StackCount = v61;
  if ( DebugPort )
  {
    v11 = ObReferenceObjectByHandle(DebugPort, 2u, DbgkDebugObjectType, AccessMode[0], &v44, 0);
    if ( v11 < 0 )
      goto LABEL_96;
    NewProcess[1].ThreadListHead.Flink = v44;
    if ( (Flags & 2) != 0 )
      _InterlockedOr(&NewProcess[5].Int21Descriptor.Access, 2u);
  }
  else if ( v13 )
  {
    DbgkCopyProcessDebugPort(NewProcess, v13);
  }
  if ( ExceptionPort )
  {
    v11 = ObReferenceObjectByHandle(ExceptionPort, 0, LpcPortObjectType, AccessMode[0], &v45, 0);
    if ( v11 < 0 )
      goto LABEL_96;
    NewProcess[1].ThreadListHead.Blink = v45;
  }
  *&NewProcess[5].IopmOffset = 259;
  v11 = PspInitializeProcessSecurity(ParentProcess_1, NewProcess);
  if ( v11 < 0 )
    goto LABEL_96;
  v16 = ParentProcess_1;
  if ( ParentProcess_1 )
  {
    if ( !MmCreateProcessAddressSpace(NewIrql[0], NewProcess, v55) )
      goto LABEL_59;
  }
  else
  {
    NewProcess[1].ProcessLock = process[49].Count;
    MmInitializeHandBuiltProcess(NewProcess, v55);
  }
  _InterlockedOr(&NewProcess[5].Int21Descriptor.Access, 0x40000u);
  *&NewProcess[4].AutoAlignment = v47;
  KeInitializeProcess(NewProcess, 8, v48, v55, NewProcess[3].AutoAlignment & 4);
  NewProcess->ThreadQuantum = PspForegroundQuantum;
  LOBYTE(NewProcess[5].KernelTime) = 2;
  if ( v16 )
  {
    v17 = v16[5].KernelTime;
    if ( v17 == 1 || v17 == 5 )
      LOBYTE(NewProcess[5].KernelTime) = v17;
    v18 = ObInitProcess((Flags & 4) != 0 ? ParentProcess_1 : 0, NewProcess);
  }
  else
  {
    v18 = MmInitializeHandBuiltProcess2(NewProcess);
  }
  v11 = v18;
  if ( v18 < 0 )
    goto LABEL_96;
  v58 = 0;
  if ( SectionHandle )
  {
    v19 = MmInitializeProcessAddressSpace(NewProcess, 0, v61, &NewProcess[4].ReadyListHead.Blink);
    v11 = v19;
    if ( v19 < 0 )
      goto LABEL_96;
    v58 = v19;
    v11 = PspMapSystemDll(NewProcess, 0);
    if ( v11 < 0 )
      goto LABEL_96;
    v64 = 1;
    goto LABEL_58;
  }
  v20 = ParentProcess_1;
  if ( !ParentProcess_1 )
    goto LABEL_58;
  if ( ParentProcess_1 == PsInitialSystemProcess )
  {
    v11 = MmInitializeProcessAddressSpace(NewProcess, 0, 0, 0);
    if ( v11 >= 0 )
    {
      v24 = ExAllocatePoolWithTag(PagedPool, 8u, 0x61506553u);
      NewProcess[4].ReadyListHead.Blink = v24;
      if ( v24 )
      {
        v24->Flink = 0;
        v24->Blink = 0;
        goto LABEL_58;
      }
      goto LABEL_59;
    }
LABEL_96:
    ObfDereferenceObject(NewProcess);
    goto LABEL_97;
  }
  *&NewProcess[2].AutoAlignment = *&ParentProcess_1[2].AutoAlignment;
  v11 = MmInitializeProcessAddressSpace(NewProcess, v20, 0, 0);
  v64 = 1;
  if ( v11 < 0 )
    goto LABEL_96;
  v21 = ParentProcess_1[4].ReadyListHead.Blink;
  if ( v21 )
  {
    v22 = HIWORD(v21->Flink) + 8;
    v23 = ExAllocatePoolWithTag(PagedPool, v22, 0x61506553u);
    NewProcess[4].ReadyListHead.Blink = v23;
    if ( v23 )
    {
      qmemcpy(v23, ParentProcess_1[4].ReadyListHead.Blink, v22);
      NewProcess[4].ReadyListHead.Blink->Blink = NewProcess[4].ReadyListHead.Blink + 1;
      goto LABEL_58;
    }
LABEL_59:
    v11 = STATUS_INSUFFICIENT_RESOURCES;
    goto LABEL_96;
  }
LABEL_58:
  v25 = (NewProcess[1].Affinity & 0xFFFFFFF8);
  v26 = MmGetSessionId(NewProcess);
  SeSetSessionIdToken(v25, v26);
  v46[0] = NewProcess;
  v46[1] = 0;
  v27 = ExCreateHandle(PspCidTable, v46);
  NewProcess[1].DirectoryTableBase[0] = v27;
  if ( !v27 )
    goto LABEL_59;
  *(NewProcess[1].ProcessLock + 8) = v27;
  if ( SeDetailedAuditingWithToken(0) )
    SeAuditProcessCreation(NewProcess);
  if ( ParentProcess_1 )
  {
    v28 = ParentProcess_1[2].Affinity;
    if ( v28 )
    {
      if ( (v28[38] & 0x1000) == 0 )
      {
        if ( (Flags & 1) != 0 )
        {
          v11 = (v28[38] & 0x800) != 0 ? 0 : STATUS_ACCESS_DENIED;
        }
        else
        {
          v11 = PspGetJobFromSet(v28, InJob, &NewProcess[2].Affinity);
          if ( v11 < 0 )
            goto LABEL_96;
          v43 = NewProcess[2].Affinity;
          v11 = PspAddProcessToJob(v43, NewProcess);
          v29 = v43[9].Header.SignalState;
          if ( v29 )
          {
            v11 = SeSubProcessToken(v29, &v50, 0);
            if ( v11 < 0 )
              goto LABEL_96;
            SeAssignPrimaryToken(NewProcess, v50);
            ObfDereferenceObject(v50);
          }
        }
        if ( v11 < 0 )
          goto LABEL_96;
      }
    }
  }
  if ( ParentProcess_1 && v64 )
  {
    BaseAddress[0] = 0;
    BaseAddress[1] = -1;
    if ( SectionHandle )
    {
      v11 = MmCreatePeb(NewProcess, BaseAddress, &NewProcess[4]);
      if ( v11 < 0 )
      {
        *&NewProcess[4].Header.Type = 0;
        goto LABEL_96;
      }
    }
    else
    {
      LOBYTE(BaseAddress[0]) = 1;
      v30 = *&ParentProcess_1[4].Header.Type;
      *&NewProcess[4].Header.Type = v30;
      MmCopyVirtualMemory(process, BaseAddress, NewProcess, v30, 8u, 0, &a7a);
    }
  }
  v31 = v41;
  --v41->KernelApcDisable;
  ExAcquireFastMutexUnsafe(&PspActiveProcessMutex);
  v32 = dword_48315C;
  NewProcess[1].DirectoryTableBase[1] = &PsActiveProcessHead;
  *&NewProcess[1].LdtDescriptor.LimitLow = v32;
  *v32 = NewProcess + 136;
  dword_48315C = &NewProcess[1].DirectoryTableBase[1];
  ExReleaseFastMutexUnsafe(&PspActiveProcessMutex);
  v34 = (*(v31 + 212))++ == -1;
  if ( v34 && *(v31 + 52) != v31 + 52 )
  {
    *(v31 + 73) = 1;
    LOBYTE(v33) = 1;
    HalRequestSoftwareInterrupt(v33);
  }
  if ( !ParentProcess_1 || (v35 = PsInitialSystemProcess, ParentProcess_1 != PsInitialSystemProcess) )
    v35 = *(v31 + 68);
  v11 = SeCreateAccessStateEx(0, v35, &PassedAccessState, v37, DesiredAccess, (PsProcessType + 26));
  if ( v11 < 0 )
    goto LABEL_96;
  v11 = ObInsertObject(NewProcess, &PassedAccessState, DesiredAccess, 1u, 0, &Handle);
  v53 = v11;
  SeDeleteAccessState(&PassedAccessState);
  if ( v11 >= 0 )
  {
    *&NewProcess[3].StackCount = 1;
    PsSetProcessPriorityByClass(NewProcess, 0);
    if ( !ParentProcess_1 || ParentProcess_1 == PsInitialSystemProcess )
    {
      *&NewProcess[3].StackCount = 2035711;
    }
    else
    {
      v11 = ObGetObjectSecurity(NewProcess, &SecurityDescriptor, MemoryAllocated);
      v53 = v11;
      if ( v11 < 0 )
      {
        ObCloseHandle(Handle, AccessMode[0]);
        goto LABEL_96;
      }
      SubjectSecurityContext.ProcessAuditId = NewProcess;
      SubjectSecurityContext.PrimaryToken = PsReferencePrimaryToken(NewProcess);
      SubjectSecurityContext.ClientToken = 0;
      v62 = SeAccessCheck(
              SecurityDescriptor,
              &SubjectSecurityContext,
              0,
              0x2000000u,
              0,
              0,
              (PsProcessType + 26),
              AccessMode[0],
              &NewProcess[3].StackCount,
              &AccessStatus);
      ObFastDereferenceObject(&NewProcess[1].Affinity, SubjectSecurityContext.PrimaryToken);
      ObReleaseObjectSecurity(SecurityDescriptor, MemoryAllocated[0]);
      if ( !v62 )
        *&NewProcess[3].StackCount = 0;
      *&NewProcess[3].StackCount |= 0x1F07FBu;
    }
    KeQuerySystemTime(&NewProcess[1].Header.SignalState);
    *ProcessHandle = Handle;
    ms_exc.registration.TryLevel = -1;
    if ( v58 )
      v11 = v58;
    goto LABEL_96;
  }
LABEL_97:
  if ( ParentProcess_1 )
    ObfDereferenceObject(ParentProcess_1);
  return v11;
}

  我们都知道,创建进程的时候一定会创建一个线程,被称之为主线程。上面都是初始化进程结构体,创建TEB等操作,线程在哪里创建的呢?是因为这个是在3环调用的,我们来看看3环长啥样子:

BOOL __stdcall CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
{
  return CreateProcessInternalW(
           0,
           lpApplicationName,
           lpCommandLine,
           lpProcessAttributes,
           lpThreadAttributes,
           bInheritHandles,
           dwCreationFlags,
           lpEnvironment,
           lpCurrentDirectory,
           lpStartupInfo,
           lpProcessInformation,
           0);
}

  这个函数又调用了CreateProcessInternalW这个函数,我们来继续点击去看看,创建线程的函数就在这里面,但是为了节省篇幅,我们就给出了局部代码:

BaseInitializeContext(&ThreadContext, v157, SectionInformation, UserStack.StackBase, 0);
v161 = BaseFormatObjectAttributes(&ObjectAttributes, v141, 0);
if ( v184 && v160 && v141 )
{
  DirectoryInfo.CurDirRef = v141->nLength;
  v200 = v141->lpSecurityDescriptor;
  v201 = v141->bInheritHandle;
  v200 = 0;
  v161 = BaseFormatObjectAttributes(&ObjectAttributes, &DirectoryInfo.CurDirRef, 0);
}
v20 = NtCreateThread(
        &ThreadHandle,
        0x1F03FFu,
        v161,
        ProcessHandle,
        &ClientId,
        &ThreadContext,
        &UserStack,
        1u);

进程结束浅析

  结束函数没啥可分析了,给出如下IDA伪代码:

NTSTATUS __stdcall NtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v2 = KeGetCurrentThread();
  v3 = v2;
  v8 = v2->ApcState.Process;
  if ( ProcessHandle )
  {
    v11 = 1;
  }
  else
  {
    ProcessHandle = -1;
    v11 = 0;
  }
  LOBYTE(AccessMode) = v2->PreviousMode;
  result = ObReferenceObjectByHandle(ProcessHandle, 1u, PsProcessType, AccessMode, &AccessMode, 0);
  v5 = AccessMode;
  v6 = AccessMode;
  if ( result >= 0 )
  {
    ProcessHandlea = &AccessMode[146];
    if ( (AccessMode[146].Count & 0x2000) != 0 )
      PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n", AccessMode, &AccessMode[93]);
    RunRef = v5 + 32;
    if ( ExAcquireRundownProtection(v5 + 32) )
    {
      if ( v11 )
        _InterlockedOr(ProcessHandlea, 8u);
      ProcessHandleb = 290;
      v7 = PsGetNextProcessThread(v6, 0);
      if ( v7 )
      {
        ProcessHandleb = 0;
        do
        {
          if ( v7 != v3 )
            PspTerminateThreadByPointer(v7, ExitStatus);
          v7 = PsGetNextProcessThread(v6, v7);
        }
        while ( v7 );
      }
      ExReleaseRundownProtection(RunRef);
      if ( v6 == v8 )
      {
        if ( v11 )
        {
          ObfDereferenceObject(v6);
          PspTerminateThreadByPointer(v3, ExitStatus);
        }
      }
      else if ( ExitStatus == DBG_TERMINATE_PROCESS )
      {
        DbgkClearProcessDebugObject(v6, 0);
      }
      if ( ProcessHandleb == 290 || v6[1].ThreadListHead.Flink && v11 )
      {
        ObClearProcessHandleTable(v6);
        ProcessHandleb = 0;
      }
      ObfDereferenceObject(v6);
      result = ProcessHandleb;
    }
    else
    {
      ObfDereferenceObject(v5);
      result = STATUS_PROCESS_IS_TERMINATING;
    }
  }
  return result;
}

  可以说,结束进程,也就是把它的所有的线程全部干掉,删掉进程相关记录,进程就被杀死了。

进程线程结构体扩展

  之前介绍进程线程相关结构体的时候主要介绍关键的成员,但是有些成员是在说明信息的时候还是比较重要的,或者没啥用处做个记录的,这里再重新补充一下,仅供了解:

KPROCESS

  其结构体如下所示:

kd> dt _KPROCESS
ntdll!_KPROCESS
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 ProfileListHead  : _LIST_ENTRY
   +0x018 DirectoryTableBase : [2] Uint4B
   +0x020 LdtDescriptor    : _KGDTENTRY
   +0x028 Int21Descriptor  : _KIDTENTRY
   +0x030 IopmOffset       : Uint2B
   +0x032 Iopl             : UChar
   +0x033 Unused           : UChar
   +0x034 ActiveProcessors : Uint4B
   +0x038 KernelTime       : Uint4B
   +0x03c UserTime         : Uint4B
   +0x040 ReadyListHead    : _LIST_ENTRY
   +0x048 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x04c VdmTrapcHandler  : Ptr32 Void
   +0x050 ThreadListHead   : _LIST_ENTRY
   +0x058 ProcessLock      : Uint4B
   +0x05c Affinity         : Uint4B
   +0x060 StackCount       : Uint2B
   +0x062 BasePriority     : Char
   +0x063 ThreadQuantum    : Char
   +0x064 AutoAlignment    : UChar
   +0x065 State            : UChar
   +0x066 ThreadSeed       : UChar
   +0x067 DisableBoost     : UChar
   +0x068 PowerState       : UChar
   +0x069 DisableQuantum   : UChar
   +0x06a IdealNode        : UChar
   +0x06b Flags            : _KEXECUTE_OPTIONS
   +0x06b ExecuteOptions   : UChar

ProfileListHead

  性能分析相关,一般操作系统会自动处理,如下图所示,没啥用处:

ActiveProcessors

  当前活动的处理器,表示在哪个核运行。

ReadyListHead

  该进程处于就绪状态的所有进程链表。

SwapListEntry

  被交换到磁盘上的进程的链表,如果进程被交换出去就会挂到这里。

ThreadListHead

  当前进程下的所有线程的链表。

ProcessLock

  进程锁,用于同步,防止被同时修改用的,给操作系统用的。

ThreadQuantum

  线程默认的时间碎片。

State

  表示进程交换到磁盘和内存的状态。

ThreadSeed

  指示Affinity陈述的亲核性最理想亲的核。

EPROCESS

  其结构体如下所示:

kd> dt _EPROCESS
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER
   +0x078 ExitTime         : _LARGE_INTEGER
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : Ptr32 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY
   +0x090 QuotaUsage       : [3] Uint4B
   +0x09c QuotaPeak        : [3] Uint4B
   +0x0a8 CommitCharge     : Uint4B
   +0x0ac PeakVirtualSize  : Uint4B
   +0x0b0 VirtualSize      : Uint4B
   +0x0b4 SessionProcessLinks : _LIST_ENTRY
   +0x0bc DebugPort        : Ptr32 Void
   +0x0c0 ExceptionPort    : Ptr32 Void
   +0x0c4 ObjectTable      : Ptr32 _HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
   +0x0ec WorkingSetPage   : Uint4B
   +0x0f0 AddressCreationLock : _FAST_MUTEX
   +0x110 HyperSpaceLock   : Uint4B
   +0x114 ForkInProgress   : Ptr32 _ETHREAD
   +0x118 HardwareTrigger  : Uint4B
   +0x11c VadRoot          : Ptr32 Void
   +0x120 VadHint          : Ptr32 Void
   +0x124 CloneRoot        : Ptr32 Void
   +0x128 NumberOfPrivatePages : Uint4B
   +0x12c NumberOfLockedPages : Uint4B
   +0x130 Win32Process     : Ptr32 Void
   +0x134 Job              : Ptr32 _EJOB
   +0x138 SectionObject    : Ptr32 Void
   +0x13c SectionBaseAddress : Ptr32 Void
   +0x140 QuotaBlock       : Ptr32 _EPROCESS_QUOTA_BLOCK
   +0x144 WorkingSetWatch  : Ptr32 _PAGEFAULT_HISTORY
   +0x148 Win32WindowStation : Ptr32 Void
   +0x14c InheritedFromUniqueProcessId : Ptr32 Void
   +0x150 LdtInformation   : Ptr32 Void
   +0x154 VadFreeHint      : Ptr32 Void
   +0x158 VdmObjects       : Ptr32 Void
   +0x15c DeviceMap        : Ptr32 Void
   +0x160 PhysicalVadList  : _LIST_ENTRY
   +0x168 PageDirectoryPte : _HARDWARE_PTE_X86
   +0x168 Filler           : Uint8B
   +0x170 Session          : Ptr32 Void
   +0x174 ImageFileName    : [16] UChar
   +0x184 JobLinks         : _LIST_ENTRY
   +0x18c LockedPagesList  : Ptr32 Void
   +0x190 ThreadListHead   : _LIST_ENTRY
   +0x198 SecurityPort     : Ptr32 Void
   +0x19c PaeTop           : Ptr32 Void
   +0x1a0 ActiveThreads    : Uint4B
   +0x1a4 GrantedAccess    : Uint4B
   +0x1a8 DefaultHardErrorProcessing : Uint4B
   +0x1ac LastThreadExitStatus : Int4B
   +0x1b0 Peb              : Ptr32 _PEB
   +0x1b4 PrefetchTrace    : _EX_FAST_REF
   +0x1b8 ReadOperationCount : _LARGE_INTEGER
   +0x1c0 WriteOperationCount : _LARGE_INTEGER
   +0x1c8 OtherOperationCount : _LARGE_INTEGER
   +0x1d0 ReadTransferCount : _LARGE_INTEGER
   +0x1d8 WriteTransferCount : _LARGE_INTEGER
   +0x1e0 OtherTransferCount : _LARGE_INTEGER
   +0x1e8 CommitChargeLimit : Uint4B
   +0x1ec CommitChargePeak : Uint4B
   +0x1f0 AweInfo          : Ptr32 Void
   +0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
   +0x1f8 Vm               : _MMSUPPORT
   +0x238 LastFaultCount   : Uint4B
   +0x23c ModifiedPageCount : Uint4B
   +0x240 NumberOfVads     : Uint4B
   +0x244 JobStatus        : Uint4B
   +0x248 Flags            : Uint4B
   +0x248 CreateReported   : Pos 0, 1 Bit
   +0x248 NoDebugInherit   : Pos 1, 1 Bit
   +0x248 ProcessExiting   : Pos 2, 1 Bit
   +0x248 ProcessDelete    : Pos 3, 1 Bit
   +0x248 Wow64SplitPages  : Pos 4, 1 Bit
   +0x248 VmDeleted        : Pos 5, 1 Bit
   +0x248 OutswapEnabled   : Pos 6, 1 Bit
   +0x248 Outswapped       : Pos 7, 1 Bit
   +0x248 ForkFailed       : Pos 8, 1 Bit
   +0x248 HasPhysicalVad   : Pos 9, 1 Bit
   +0x248 AddressSpaceInitialized : Pos 10, 2 Bits
   +0x248 SetTimerResolution : Pos 12, 1 Bit
   +0x248 BreakOnTermination : Pos 13, 1 Bit
   +0x248 SessionCreationUnderway : Pos 14, 1 Bit
   +0x248 WriteWatch       : Pos 15, 1 Bit
   +0x248 ProcessInSession : Pos 16, 1 Bit
   +0x248 OverrideAddressSpace : Pos 17, 1 Bit
   +0x248 HasAddressSpace  : Pos 18, 1 Bit
   +0x248 LaunchPrefetched : Pos 19, 1 Bit
   +0x248 InjectInpageErrors : Pos 20, 1 Bit
   +0x248 VmTopDown        : Pos 21, 1 Bit
   +0x248 Unused3          : Pos 22, 1 Bit
   +0x248 Unused4          : Pos 23, 1 Bit
   +0x248 VdmAllowed       : Pos 24, 1 Bit
   +0x248 Unused           : Pos 25, 5 Bits
   +0x248 Unused1          : Pos 30, 1 Bit
   +0x248 Unused2          : Pos 31, 1 Bit
   +0x24c ExitStatus       : Int4B
   +0x250 NextPageColor    : Uint2B
   +0x252 SubSystemMinorVersion : UChar
   +0x253 SubSystemMajorVersion : UChar
   +0x252 SubSystemVersion : Uint2B
   +0x254 PriorityClass    : UChar
   +0x255 WorkingSetAcquiredUnsafe : UChar
   +0x258 Cookie           : Uint4B

RundownProtect

  进程停运保护,可以防止他人杀死进程。

  进程的会话子系统相关。

Token

  该进程的令牌,与安全相关。

InheritedFromUniqueProcessId

  指示被谁创建该进程的pid,这东西比较有用,可以找到父进程。

SeAuditProcessCreationInfo

  通过这个可以获取进程的全路径,我们举个例子:

kd> dt _EPROCESS 89a9b648
ntdll!_EPROCESS
   ……
   +0x1f0 AweInfo          : (null) 
   +0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
   +0x1f8 Vm               : _MMSUPPORT
   ……

kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_SE_AUDIT_PROCESS_CREATION_INFO *)0x89a9b83c))
(*((ntdll!_SE_AUDIT_PROCESS_CREATION_INFO *)0x89a9b83c))                 [Type: _SE_AUDIT_PROCESS_CREATION_INFO]
    [+0x000] ImageFileName    : 0x89cc8a18 [Type: _OBJECT_NAME_INFORMATION *]
kd> dx -id 0,0,805539a0 -r1 ((ntdll!_OBJECT_NAME_INFORMATION *)0x89cc8a18)
((ntdll!_OBJECT_NAME_INFORMATION *)0x89cc8a18)                 : 0x89cc8a18 [Type: _OBJECT_NAME_INFORMATION *]
    [+0x000] Name             : "\Device\HarddiskVolume1\Program Files\PalmInput\Extensions\Guard\2.6.0.49\PalmInputGuard.exe" [Type: _UNICODE_STRING]

Flags

  指示进程的状态,比较有用。

SubSystemMinorVersion / SubSystemMajorVersion

  指示支持的子系统版本,在PE文件的信息中是有描述的。

ExitStatus

  进程退出状态。

ReadOperationCount

  调用ReadFile的次数。

WriteOperationCount

  调用WriteFile的次数。

OtherOperationCount

  调用其他与IO读写文件相关的API次数。

KTHREAD

  其结构体如下所示:

kd> dt _KTHREAD
ntdll!_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

MutantListHead

  互斥体链表相关,具体细节在同步篇进行讲解。

Alertable

  指示线程是否被唤醒,具体细节在APC篇进行讲解。

Priority

  线程优先级。

EnableStackSwap

  指示是否能将堆栈最为文件交换

ETHREAD

  结构体如下所示:

kd> dt _ETHREAD
ntdll!_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

CrossThreadFlags

  表示线程的状态和身份,可以设置状态为系统线程可以使普通权限杀不死。

线程创建浅析

  之前我们分析创建进程的时候,是通过NtCreateThread这个函数进行创建主线程,我们来看看里面有什么:

NTSTATUS __stdcall NtCreateThread(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, PCLIENT_ID ClientId, PCONTEXT ThreadContext, PINITIAL_TEB UserStack, BOOLEAN CreateSuspended)
{
  struct _INITIAL_TEB *v8; // eax
  int *v9; // ebx
  int v11; // ecx
  int v12[6]; // [esp+Ch] [ebp-38h] BYREF
  _KTHREAD *v13; // [esp+24h] [ebp-20h]
  CPPEH_RECORD ms_exc; // [esp+2Ch] [ebp-18h]

  ms_exc.registration.TryLevel = 0;
  v13 = KeGetCurrentThread();
  if ( v13->PreviousMode )
  {
    if ( ThreadHandle >= MmUserProbeAddress )
      *MmUserProbeAddress = 0;
    *ThreadHandle = *ThreadHandle;
    if ( ClientId )
    {
      v12[5] = ClientId;
      if ( ClientId >= MmUserProbeAddress )
        *MmUserProbeAddress = 0;
      if ( (ClientId & 3) != 0 )
        ExRaiseDatatypeMisalignment();
      LOBYTE(ClientId->UniqueProcess) = ClientId->UniqueProcess;
      LOBYTE(ClientId->UniqueThread) = ClientId->UniqueThread;
    }
    if ( !ThreadContext )
    {
      ms_exc.registration.TryLevel = -1;
      return STATUS_INVALID_PARAMETER;
    }
    if ( (ThreadContext & 3) != 0 )
      ExRaiseDatatypeMisalignment();
    v8 = MmUserProbeAddress;
    if ( ThreadContext >= MmUserProbeAddress )
    {
      *MmUserProbeAddress = 0;
      v8 = MmUserProbeAddress;
    }
    v9 = UserStack;
    if ( (UserStack & 3) != 0 )
      ExRaiseDatatypeMisalignment();
    if ( UserStack >= v8 )
      v8->PreviousStackBase = 0;
  }
  else
  {
    v9 = UserStack;
  }
  v12[0] = *v9;
  v11 = v9[1];
  v12[1] = v11;
  if ( !v12[0] && !v11 )
    qmemcpy(v12, v9, 0x14u);
  ms_exc.registration.TryLevel = -1;
  return PspCreateThread(
           ThreadHandle,
           DesiredAccess,
           ObjectAttributes,
           ProcessHandle,
           0,
           ClientId,
           ThreadContext,
           v12,
           CreateSuspended,
           0,
           0);
}

  浏览一遍,发现最终是通过PspCreateThread这个函数进行创建线程实现,点击去看看:

NTSTATUS __stdcall PspCreateThread(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, PVOID a5, PCLIENT_ID ClientId, PCONTEXT ThreadContext, int a8, BOOLEAN CreateSuspended, int a10, int a11)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  CurrentThread = KeGetCurrentThread();
  CurrentThread_1 = CurrentThread;
  if ( a10 )
    AccessMode[0] = 0;
  else
    AccessMode[0] = CurrentThread->PreviousMode;
  v70 = 0;
  v12 = 0;
  v56 = 0;
  if ( ProcessHandle )
  {
    result = ObReferenceObjectByHandle(ProcessHandle, 2u, PsProcessType, AccessMode[0], &Object, 0);
    v12 = Object;
    v56 = Object;
  }
  else if ( a10 )
  {
    v12 = a5;
    ObfReferenceObject(a5);
    v56 = a5;
    result = 0;
  }
  else
  {
    result = STATUS_INVALID_HANDLE;
  }
  if ( result >= 0 )
  {
    if ( AccessMode[0] && v12 == PsInitialSystemProcess )
    {
      v14 = STATUS_INVALID_HANDLE;
LABEL_15:
      ObfDereferenceObject(v12);
      return v14;
    }
    v15 = ObCreateObject(AccessMode[0], PsThreadType, ObjectAttributes, *AccessMode, 0, 600, 0, 0, &v59);
    if ( v15 < 0 )
    {
      v14 = v15;
      goto LABEL_15;
    }
    thread = v59;
    memset(v59, 0, 0x258u);
    thread[1].WaitBlock[0].WaitListEntry.Blink = 0;
    thread[1].WaitListEntry.Flink = v12;
    *&thread[1].DebugActive = v12[33];
    v60[0] = thread;
    v60[1] = 0;
    v17 = ExCreateHandle(PspCidTable, v60);
    *&thread[1].Iopl = v17;
    if ( !v17 )
    {
      v18 = STATUS_INSUFFICIENT_RESOURCES;
LABEL_38:
      ObfDereferenceObject(thread);
      return v18;
    }
    thread[1].WaitBlock[0].NextWaitBlock = MmReadClusterSize;
    KeInitializeSemaphore(&thread[1].ApcState, 0, 1);
    thread[1].Header.WaitListHead.Blink = &thread[1].Header.WaitListHead;
    thread[1].Header.WaitListHead.Flink = &thread[1].Header.WaitListHead;
    thread[1].WaitStatus = &thread[1].IdleSwapBlock;
    *&thread[1].IdleSwapBlock = thread + 528;
    thread[1].InitialStack = &thread[1].MutantListHead.Blink;
    thread[1].MutantListHead.Blink = &thread[1].MutantListHead.Blink;
    thread[1].WaitBlock[0].Thread = 0;
    KeInitializeSpinLock(&thread[1].Teb);
    thread[1].KernelStack = &thread[1].TlsArray;
    thread[1].TlsArray = &thread[1].TlsArray;
    v45 = v12 + 32;
    if ( !ExAcquireRundownProtection(v12 + 32) )
    {
      v18 = STATUS_PROCESS_IS_TERMINATING;
      goto LABEL_38;
    }
    if ( ThreadContext )
    {
      v18 = MmCreateTeb(v12, a8, &thread[1].DebugActive, &v70);
      if ( v18 < 0 )
      {
        v19 = v45;
LABEL_37:
        ExReleaseRundownProtection(v19);
        goto LABEL_38;
      }
      ms_exc.registration.TryLevel = 0;
      thread[1].WaitListEntry.Blink = ThreadContext->Eip;
      thread[1].WaitTime = ThreadContext->Eax;
      ms_exc.registration.TryLevel = -1;
      v20 = KeInitThread(thread, 0, PspUserThreadStartup, 0, thread[1].WaitListEntry.Blink, ThreadContext, v70, v12);
    }
    else
    {
      v70 = 0;
      _InterlockedOr(&thread[1].WaitBlock[1], 0x10u);
      thread[1].WaitListEntry.Blink = a10;
      v20 = KeInitThread(thread, 0, PspSystemThreadStartup, a10, a11, 0, 0, v12);
    }
    v18 = v20;
    if ( v20 < 0 )
    {
      if ( v70 )
      {
        MmDeleteTeb(v12, v70);
        thread->Teb = 0;
      }
LABEL_36:
      v19 = v12 + 32;
      goto LABEL_37;
    }
    v21 = CurrentThread_1;
    --CurrentThread_1->KernelApcDisable;
    v53 = v12 + 27;
    _ECX = v12 + 27;
    _EDX = 2;
    __asm { cmpxchg [ecx], edx }
    if ( (v12[146].Count & 8) != 0 )
    {
      v50 = v12 + 27;
      _ECX = v12 + 27;
      _EDX = 0;
      __asm { cmpxchg [ecx], edx }
      v26 = (*(v21 + 212))++ == -1;
      if ( v26 && *(v21 + 52) != v21 + 52 )
      {
        *(v21 + 73) = 1;
        LOBYTE(_ECX) = 1;
        HalRequestSoftwareInterrupt(_ECX);
      }
      KeUninitThread(thread);
      if ( v70 )
        MmDeleteTeb(v12, v70);
      v18 = STATUS_PROCESS_IS_TERMINATING;
      goto LABEL_36;
    }
    v27 = v12[104].Count;
    v12[104].Count = v27 + 1;
    v28 = v12[101].Count;
    *&thread[1].BasePriority = v12 + 100;
    thread[1].WaitBlock[0].WaitListEntry.Flink = v28;
    v28->Flink = &thread[1].BasePriority;
    v12[101].Count = &thread[1].BasePriority;
    KeStartThread(thread);
    v48 = v12 + 27;
    _ECX = v12 + 27;
    _EDX = 0;
    __asm { cmpxchg [ecx], edx }
    v31 = CurrentThread_1;
    v26 = CurrentThread_1->KernelApcDisable++ == -1;
    if ( v26 && *(v31 + 52) != v31 + 52 )
    {
      CurrentThread_1->ApcState.KernelApcPending = 1;
      LOBYTE(_ECX) = 1;
      HalRequestSoftwareInterrupt(_ECX);
    }
    ExReleaseRundownProtection(v12 + 32);
    if ( !v27 )
    {
      WmiTraceProcess(v12);
      if ( PspCreateProcessNotifyRoutineCount )
      {
        v58 = &PspCreateProcessNotifyRoutine;
        v51 = 8;
        do
        {
          v32 = ExReferenceCallBackBlock(v58);
          v33 = v32;
          if ( v32 )
          {
            v34 = ExGetCallBackBlockRoutine(v32);
            v34(v12[83].Count, v12[33].Count, 1);
            ExDereferenceCallBackBlock(v58, v33);
          }
          v58 += 4;
          --v51;
        }
        while ( v51 );
      }
    }
    v35 = v12[77].Count;
    if ( v35 && *(v35 + 196) && (v12[145].Count & 5) == 0 )
    {
      _InterlockedOr(&v12[145], 4u);
      --CurrentThread_1->KernelApcDisable;
      Resource = (v35 + 32);
      ExAcquireResourceSharedLite((v35 + 32), 1u);
      v36 = *(v35 + 196);
      if ( v36 )
        IoSetIoCompletion(v36, *(v35 + 200), v12[33].Count, 0, 6, 0);
      ExReleaseResourceLite(Resource);
      v37 = CurrentThread_1;
      v26 = CurrentThread_1->KernelApcDisable++ == -1;
      if ( v26 && *(v37 + 52) != v37 + 52 )
      {
        *(v37 + 73) = 1;
        LOBYTE(v37) = 1;
        HalRequestSoftwareInterrupt(v37);
      }
    }
    WmiTraceThread(thread, a8, 1);
    if ( PspCreateThreadNotifyRoutineCount )
    {
      v57 = &PspCreateThreadNotifyRoutine;
      v49 = 8;
      do
      {
        v38 = ExReferenceCallBackBlock(v57);
        RunRef = v38;
        if ( v38 )
        {
          v39 = ExGetCallBackBlockRoutine(v38);
          v39(*&thread[1].DebugActive, *&thread[1].Iopl, 1);
          ExDereferenceCallBackBlock(v57, RunRef);
        }
        v57 += 4;
        --v49;
      }
      while ( v49 );
    }
    ObReferenceObjectEx(thread, 2);
    if ( ThreadContext )
    {
      P = ExAllocatePoolWithTag(NonPagedPool, 0x30u, 'aCsP');
      if ( !P )
      {
        _InterlockedOr(&thread[1].WaitBlock[1], 2u);
LABEL_64:
        v18 = STATUS_INSUFFICIENT_RESOURCES;
LABEL_77:
        KeReadyThread(thread);
        ObDereferenceObjectEx(thread, 2);
        return v18;
      }
      KeInitializeApc(P, thread, 0, IopDeallocateApc, 0, dword_598B5C, 1, 0);
      if ( !KeInsertQueueApc(P, BaseAddress, 0, 0) )
      {
        _InterlockedOr(&thread[1].WaitBlock[1], 2u);
        ExFreePoolWithTag(P, 0);
        goto LABEL_64;
      }
    }
    if ( CreateSuspended )
    {
      ms_exc.registration.TryLevel = 1;
      KeSuspendThread(thread);
      ms_exc.registration.TryLevel = -1;
      if ( (thread[1].WaitBlock[1].WaitListEntry.Flink & 1) != 0 )
        KeForceResumeThread(thread);
    }
    if ( ThreadContext )
      v40 = CurrentThread_1->ApcState.Process;
    else
      v40 = v12;
    v63 = SeCreateAccessStateEx(0, v40, &PassedAccessState, v44, DesiredAccess, (PsThreadType + 26));
    if ( v63 < 0 )
    {
      _InterlockedOr(&thread[1].WaitBlock[1], 2u);
      if ( CreateSuspended )
        KeResumeThread(thread);
      v18 = v63;
      goto LABEL_77;
    }
    v63 = ObInsertObject(thread, &PassedAccessState, DesiredAccess, 0, 0, &Handle);
    SeDeleteAccessState(&PassedAccessState);
    if ( v63 >= 0 )
    {
      ms_exc.registration.TryLevel = 2;
      *ThreadHandle = Handle;
      if ( ClientId )
        *ClientId = *&thread[1].DebugActive;
      ms_exc.registration.TryLevel = -1;
    }
    else
    {
      _InterlockedOr(&thread[1].WaitBlock[1], 2u);
      if ( CreateSuspended )
        KeResumeThread(thread);
    }
    KeQuerySystemTime(&CurrentTime);
    v41 = CurrentTime.QuadPart >> 29;
    *&thread[1].Header.Type = 8 * CurrentTime.LowPart;
    thread[1].Header.SignalState = v41;
    if ( (thread[1].WaitBlock[1].WaitListEntry.Flink & 2) != 0 )
    {
      *&thread[1].WaitBlock[0].WaitKey = 2032639;
    }
    else
    {
      v63 = ObGetObjectSecurity(thread, &SecurityDescriptor, MemoryAllocated);
      if ( v63 < 0 )
      {
        _InterlockedOr(&thread[1].WaitBlock[1], 2u);
        if ( CreateSuspended )
          KeResumeThread(thread);
        KeReadyThread(thread);
        ObfDereferenceObject(thread);
        ObCloseHandle(Handle, AccessMode[0]);
        goto LABEL_95;
      }
      SubjectSecurityContext.ProcessAuditId = v12;
      SubjectSecurityContext.PrimaryToken = PsReferencePrimaryToken(v12);
      SubjectSecurityContext.ClientToken = 0;
      v42 = &thread[1].WaitBlock[0].WaitKey;
      v64 = SeAccessCheck(
              SecurityDescriptor,
              &SubjectSecurityContext,
              0,
              0x2000000u,
              0,
              0,
              (PsThreadType + 26),
              AccessMode[0],
              &thread[1].WaitBlock[0].WaitKey,
              &AccessStatus);
      ObFastDereferenceObject(&v12[50], SubjectSecurityContext.PrimaryToken);
      ObReleaseObjectSecurity(SecurityDescriptor, MemoryAllocated[0]);
      if ( !v64 )
        *v42 = 0;
      *v42 |= 0x61u;
    }
    KeReadyThread(thread);
    ObfDereferenceObject(thread);
LABEL_95:
    result = v63;
  }
  return result;
}

  流程和进程线程创建了流程套路几乎一样,初始化线程结构体,创建TEB,插入结构体,但里面有APC相关的知识,具体细节就不再赘述了。

DPC

  DPC是什么?它的英文全称为Deferred Procedure Call,即延迟过程调用。它最初作用是设计为中断服务程序的一部分,用来解决中断服务处理时间过长的问题。因为每次触发中断,都会关中断,然后执行中断服务例程。由于关中断了,所以中断服务例程必须短小精悍,不能消耗过多时间,否则会导致系统丢失大量其他中断。但是有的中断,其中断服务例程要做的事情本来就很多,那怎么办?于是,可以在中断服务例程中先执行最紧迫的那部分工作,然后把剩余的相对来说不那么重要的工作移入到DPC函数中去执行。
  每当触发一个中断时,中断服务例程可以在当前CPU中插入一个DPC,当执行完ISR,退出ISR后, CPU就会扫描它的DPC队列,依次执行里面的每个DPC,当执行完DPC后,才又回到当前线程的中断处继续执行。
  既然提到ISR,那么它是什么?它的英文全称为Interrupt Service Routines,即中断服务处理。在Windows中如果有认为不太重要的操作会被打包成一个KDPC结构体,如下所示:

kd> dt _KDPC
ntdll!_KDPC
   +0x000 Type             : Int2B
   +0x002 Number           : UChar
   +0x003 Importance       : UChar
   +0x004 DpcListEntry     : _LIST_ENTRY
   +0x00c DeferredRoutine  : Ptr32     void 
   +0x010 DeferredContext  : Ptr32 Void
   +0x014 SystemArgument1  : Ptr32 Void
   +0x018 SystemArgument2  : Ptr32 Void
   +0x01c Lock             : Ptr32 Uint4B

  打包完毕后,会插入到KPCRBDpcListHead成员中,等待触发调用时机。KPCRB结构如下所示,只展示与DPC相关的成员:

kd> dt _KPRCB
ntdll!_KPRCB
   ……
   +0x4b0 DpcTime          : Uint4B
   +0x4b4 DebugDpcTime     : Uint4B
   ……
   +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
   ……
   +0x8a0 DpcLock          : Uint4B
   ……
   +0x8c0 CallDpc          : _KDPC
   ……

  非时钟中断的中断、时钟中断、主动调用API,相关函数会去轮询DPC链表进行回调。对于用户或者驱动创建的DPC,还可以加定时器,到指定时间进行触发,但不会挂到KPCR当中,会挂到时钟任务当中。
  介绍了上面的概念,我们来介绍结构体的相关成员含义:

KDPC

Type

  指明该结构体的类型,值为0x13

Number

  指明属于哪个KPCR

Importance

  优先级,取值0-2,0最低,2最高,优先级越高越优先执行,初始化默认值为1。

DpcListEntry

  DPC链表,和进程线程链表一样,挂到腰上。

DeferredRoutine

  DPC的回调函数地址。

DeferredContext

  回调函数上下文,非必须。

SystemArgument1 / SystemArgument2

  回调函数的参数,非必须。

Lock

  DPC结构体的锁。

DPC 的初始化

  学习DPC,首先我们了解一下它的初始化,我们用IDA定位到该函数:

; void __stdcall KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext)
                public _KeInitializeDpc@12
_KeInitializeDpc@12 proc near           ; CODE XREF: IopInitializeIrpStackProfiler()+29↑p
                                        ; VdmpDelayInterrupt(x)+26B↓p ...

Dpc             = dword ptr  8
DeferredRoutine = dword ptr  0Ch
DeferredContext = dword ptr  10h

                mov     edi, edi
                push    ebp
                mov     ebp, esp
                mov     eax, [ebp+Dpc]
                mov     ecx, [ebp+DeferredRoutine]
                and     dword ptr [eax+1Ch], 0
                mov     [eax+0Ch], ecx
                mov     ecx, [ebp+DeferredContext]
                mov     word ptr [eax], 13h
                mov     byte ptr [eax+2], 0
                mov     byte ptr [eax+3], 1
                mov     [eax+10h], ecx
                pop     ebp
                retn    0Ch
_KeInitializeDpc@12 endp

  这个函数十分简单,用伪代码展示如下:

void __stdcall KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext)
{
  Dpc->Lock = 0;
  Dpc->DeferredRoutine = DeferredRoutine;
  Dpc->Type = 19;
  Dpc->Number = 0;
  Dpc->Importance = 1;
  Dpc->DeferredContext = DeferredContext;
}

DPC 的插入

  然后我们来看看DPC的插入流程,由于涉及IRQL提权操作,我们先看一下枚举:

  为了增强可读性,如下是我重命名好的IDA的伪代码:

BOOLEAN __stdcall KeInsertQueueDpc(PRKDPC Dpc, PVOID SystemArgument1, PVOID SystemArgument2)
{
  _KPRCB *kprcb; // esi
  bool Importance; // zf
  _LIST_ENTRY *DpcListHead; // ecx
  _LIST_ENTRY *DpcListEntry; // eax
  _LIST_ENTRY *v9; // edx
  _LIST_ENTRY *v10; // edx
  KIRQL NewIrql; // [esp+Fh] [ebp-1h]

  NewIrql = KfRaiseIrql(0x1Fu);
  kprcb = MEMORY[0xFFDFF020];
  _ECX = &Dpc->Lock;
  _EDX = MEMORY[0xFFDFF020] + 0x8A0;            // DpcLock
  __asm { cmpxchg [ecx], edx }
  ++kprcb->DpcCount;
  ++kprcb->DpcQueueDepth;
  Importance = Dpc->Importance == 2;
  Dpc->SystemArgument1 = SystemArgument1;
  Dpc->SystemArgument2 = SystemArgument2;
  DpcListHead = &kprcb->DpcListHead;
  DpcListEntry = &Dpc->DpcListEntry;
  if ( Importance )
  {
    v9 = DpcListHead->Flink;
    DpcListEntry->Flink = DpcListHead->Flink;
    Dpc->DpcListEntry.Blink = DpcListHead;
    v9->Blink = DpcListEntry;
    DpcListHead->Flink = DpcListEntry;
  }
  else
  {
    v10 = kprcb->DpcListHead.Blink;
    DpcListEntry->Flink = DpcListHead;
    Dpc->DpcListEntry.Blink = v10;
    v10->Flink = DpcListEntry;
    kprcb->DpcListHead.Blink = DpcListEntry;
  }
  if ( !kprcb->DpcRoutineActive
    && !kprcb->DpcInterruptRequested
    && (Dpc->Importance
     || kprcb->DpcQueueDepth >= kprcb->MaximumDpcQueueDepth
     || kprcb->DpcRequestRate < kprcb->MinimumDpcRate) )
  {
    LOBYTE(DpcListHead) = 2;
    kprcb->DpcInterruptRequested = 1;
    HalRequestSoftwareInterrupt(DpcListHead);
  }
  KfLowerIrql(NewIrql);
  return 1;
}

  可以看出,如果DPC的优先级高,就会插到DPC链表的前面,如果低就会插到末尾。在多核的情况下,其伪代码会有所不一样,如下所示:

BOOLEAN __stdcall KeInsertQueueDpc(PRKDPC Dpc, PVOID SystemArgument1, PVOID SystemArgument2)
{
  unsigned __int8 Number; // al
  _KPRCB *kpcrb; // esi
  char v5; // bl
  bool v6; // zf
  _LIST_ENTRY *v7; // ecx
  _LIST_ENTRY *v8; // eax
  _LIST_ENTRY *v9; // edx
  _LIST_ENTRY *v10; // edx
  signed __int32 v12; // [esp+Ch] [ebp-8h]
  KIRQL NewIrql; // [esp+13h] [ebp-1h]

  NewIrql = KfRaiseIrql(0x1Fu);
  Number = Dpc->Number;
  if ( Number < 0x20u )
  {
    v5 = Dpc;
    kpcrb = KeGetPcr()->Prcb;
  }
  else
  {
    kpcrb = KiProcessorBlock[Number];
    v5 = Number - 32;
  }
  KiAcquireSpinLock();
  v12 = _InterlockedCompareExchange(&Dpc->Lock, &kpcrb->DpcLock, 0);
  if ( !v12 )
  {
    ++kpcrb->DpcCount;
    ++kpcrb->DpcQueueDepth;
    v6 = Dpc->Importance == 2;
    Dpc->SystemArgument1 = SystemArgument1;
    Dpc->SystemArgument2 = SystemArgument2;
    v7 = &kpcrb->DpcListHead;
    v8 = &Dpc->DpcListEntry;
    if ( v6 )
    {
      v9 = v7->Flink;
      v8->Flink = v7->Flink;
      Dpc->DpcListEntry.Blink = v7;
      v9->Blink = v8;
      v7->Flink = v8;
    }
    else
    {
      v10 = kpcrb->DpcListHead.Blink;
      v8->Flink = v7;
      Dpc->DpcListEntry.Blink = v10;
      v10->Flink = v8;
      kpcrb->DpcListHead.Blink = v8;
    }
    if ( !kpcrb->DpcRoutineActive && !kpcrb->DpcInterruptRequested )
    {
      if ( kpcrb == KeGetPcr()->Prcb )
      {
        if ( Dpc->Importance
          || kpcrb->DpcQueueDepth >= kpcrb->MaximumDpcQueueDepth
          || kpcrb->DpcRequestRate < kpcrb->MinimumDpcRate )
        {
          LOBYTE(v7) = 2;
          kpcrb->DpcInterruptRequested = 1;
          HalRequestSoftwareInterrupt(v7); //处理 DPC
        }
      }
      else if ( Dpc->Importance == 2 || kpcrb->DpcQueueDepth >= kpcrb->MaximumDpcQueueDepth )
      {
        kpcrb->DpcInterruptRequested = 1;
        KiIpiSend(1 << v5, 2u);
      }
    }
  }
  KiReleaseSpinLock(&kpcrb->DpcLock);
  KfLowerIrql(NewIrql);
  return v12 == 0;
}

  这里提一句,想要获取多核调试环境极其内核文件,必须把虚拟机的配置配置成多核再重新安装操作系统。但是我给的符号是单核的,多核的我也踏破铁鞋也找不到,上面的伪代码一些结构体我是通过单核的生成的,至于函数是参考资料命名的。如果您有多核的符号,希望您能够提供,感谢支持。
  对于多核的情况,具体细节将会在同步篇进行讲解。含有SpinLock函数的这个东西成为自旋锁。发现这份代码和单核的会多一些判断和一个函数KiIpiSend,这个函数作用是通知另一个KPCR执行DPC,了解即可。

DPC 的移除

  对于移除DPC代码很简单,如下是其伪代码:

BOOLEAN __stdcall KeRemoveQueueDpc(PRKDPC Dpc)
{
  unsigned int *Lock; // esi

  _disable();                                   // cli
  Lock = Dpc->Lock;
  if ( Lock )
  {
    --*(Lock - 12);
    RemoveEntryList(&Dpc->DpcListEntry);
    Dpc->Lock = 0;
  }
  _enable();                                    // sti
  return Lock != 0;
}

  对于多核下,代码如下,和单核区别不大,只是加入了自旋锁:

BOOLEAN __stdcall KeRemoveQueueDpc(PRKDPC Dpc)
{
  unsigned int *Lock; // edi

  _disable();
  Lock = Dpc->Lock;
  if ( Lock )
  {
    KiAcquireSpinLock(Dpc->Lock);
    if ( Lock == Dpc->Lock )
    {
      --*(Lock - 12);
      RemoveEntryList(&Dpc->DpcListEntry);
      Dpc->Lock = 0;
    }
    KiReleaseSpinLock(Lock);
  }
  _enable();
  return Lock != 0;
}

DPC 的执行

  执行DPC是通过KiRetireDpcList执行的,而调用该函数的是通过KiDispatchInterruptKiIdleLoop执行的,我们通过伪代码进行简单了解:

int __usercall KiRetireDpcList@<eax>(_KPCR *kpcr@<ebx>, _LIST_ENTRY *DPCListHead@<ebp>)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v13 = 0;
  if ( PPerfGlobalGroupMask && (*(PPerfGlobalGroupMask + 4) & 0x80) != 0 )
    v13 = 1;
  do
  {
    MEMORY[0xFFDFFBC4] = &v11;                  // 这里有错误,原汇编:
                                                // mov ds:0FFDFF994h, esp
                                                // 可能是插件的 Bug
                                                // 指向:DpcRoutineActive
    do
    {
      dpc = DPCListHead->Flink;
      v3 = DPCListHead->Flink->Flink;
      DPCListHead->Flink = v3;
      v3->Blink = DPCListHead;
      dpc = (dpc - 4);                          // 挂到腰上了,把指针指到头部
      DeferredRoutine = dpc->DeferredRoutine;
      SystemArgument1 = *&dpc->SystemArgument1;
      DeferredContext = dpc->DeferredContext;
      dpc_1 = dpc;
      dpc->Lock = 0;
      --kpcr->PrcbData.DpcQueueDepth;
      _enable();
      if ( v13 )                                // 日志性能分析,忽略
      {
        v6 = WmiGetCpuClock(DeferredRoutine);
        DeferredRoutine = v7;
        v11 = v6;
        v12 = v7;
      }
      result = (DeferredRoutine)(dpc_1, DeferredContext, SystemArgument1, HIDWORD(SystemArgument1));// 执行 DPC
      if ( v13 )
        result = PerfInfoLogDpc(v12, v11, SHIDWORD(v11));
      _disable();
    }
    while ( !IsListEmpty(DPCListHead) );
    kpcr->PrcbData.DpcRoutineActive = 0;
    kpcr->PrcbData.DpcInterruptRequested = 0;
  }
  while ( !IsListEmpty(DPCListHead) );
  return result;
}

DPC 的使用

  既然学习了DPC,没有应用肯定不行,我们做几个实验体验一下DPC。我们新建一个驱动项目,怎么配置我就不说了,代码如下:

#include <ntifs.h>
#include <ntddk.h>  

KDPC dpc = { 0 };

VOID DPCRoutine(_In_ struct _KDPC* Dpc, _In_opt_ PVOID DeferredContext,
 _In_opt_ PVOID SystemArgument1, _In_opt_ PVOID SystemArgument2)
{
    DbgPrint("DPC Running……\n");
}

NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
    DbgPrint("Unloaded Successfully!");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DbgPrint("Loaded Successfully!");
    DriverObject->DriverUnload = UnloadDriver;

    KeInitializeDpc(&dpc, DPCRoutine, NULL);
    KeInsertQueueDpc(&dpc, NULL, NULL);

    return STATUS_SUCCESS;
}

  编译然后加载后,就会出现如下结果:

  那么继续搞得高级点,不是驱动或者用户创建的DPC支持定时器吗?我们来搞一下:

#include <ntifs.h>
#include <ntddk.h>  

KDPC dpc = { 0 };
KTIMER timer = { 0 };
LARGE_INTEGER duringtime = { 0 };

VOID DPCRoutine(_In_ struct _KDPC* Dpc, _In_opt_ PVOID DeferredContext,
 _In_opt_ PVOID SystemArgument1, _In_opt_ PVOID SystemArgument2)
{
    DbgPrint("DPC Running……\n");
    KeSetTimer(&timer, duringtime, &dpc);
}

VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
    KeCancelTimer(&timer);
    DbgPrint("Unloaded Successfully!");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DbgPrint("Loaded Successfully!");
    DriverObject->DriverUnload = UnloadDriver;

    KeInitializeTimer(&timer);
    KeInitializeDpc(&dpc, DPCRoutine, NULL);    
    duringtime.QuadPart = -30 * 1000 * 1000; //负号表示相对时间,此处间隔为3秒
    KeSetTimer(&timer, duringtime, &dpc);

    return STATUS_SUCCESS;
}

  实现的效果如下:

  上面实现的就是DPC定时器。

Windows 架构

  学习系统内核,最好了解一些该系统的系统架构,示意图如下,来自潘爱民的《Windows内核原理与实现》:

  如有兴趣了解其细节,请自行翻找,注意该书是基于WRK的,里面的结构体相关或者代码相关的请以逆向结果为准。

下一篇

  羽夏看Win系统内核——句柄表篇