Windows NT系统在创建进程之后,紧接着创建线程。本文以反汇编wrkx86.exe为主,来分析线程创建流程!

引用:
工作环境: Windows Server 2003 R2
调试软件: WinDbg , IDA
在OD中调试到如下地址:

 
然后在WinDbg下断bp nt!ntCreateThread即进入内核线程创建开始位置。

一.线程创建流程分析:
引用:
 1.NtCreateThread函数中检查地址参数是否合法和可写,保存Teb作为PspCreateThread传入参数。
 2.StartRoutine是否有值来决定当前模式是内核/用户模式。为NULL,通过ETHREAD->Tcb获得运行模式。
 3.由ProcessHandle参数获得相应的进程对象,保存在局部变量Process。
 4.调用ObCreateObject 创建ETHREAD的一个对象。
 5.初始线程的停止保护锁(&Thread->RundownProtect), 用于跨线程初始化TEB,挂起线程。
 6.设置线程的进程CID, 线程的CID句柄。函数在PspCidTable句柄表创建句柄表项。
 7.初始读取的族大小          
   初始化LPC信号量对象        
   初始化跨进程通信LPC        
   初始化所有正在处理单尚末完成的I/O请求<IRP对象> 
   初始化配置管理器等级注册表的变化通知
   初始化线程锁/时间旋转锁/当前线程的所有定时器
 8.根据ThreadContext的值来确认此次创建是用户模式线程<非NULL>,或者系统线程<NULL>.
     用户模式线程: 创建一个TEB,并用InitialTeb初始化,接着初始线程的启动地址,WINDOWS子系统的启动地址。
 9.调用KeInitThread函数 <继续初始新线程属性。> 同步Header, WaitBlock,系统服务表 ,APC ,定时器, 线程的内核栈等。
10.禁用当前线程内核APC的。且锁定进程。 确保当前进程的状态不是退出或正在终止。进程中Flags标记位判断当前进程是否是死进程。CrossThreadFlags跨线程访问的标志位。 
   包括Terminated 线程已执行终止操作 创建失败 等信息。
11.进程的活动线程数+1。挂入目标进程(EPROCESS中)的线程队列。
12.启动该线程运行 KeStartThread函数; 并再次初始化末完成的域,设置线程的优先级, 时限设置等。
13.局部变量OldActiveThreads  判断当前创建的线程是否是第一个线程。当为第一个线程:  通知线程创建标注的注册程序.
14.检测当前新创建线程的进程是否正处于在一个作业中。
15.线程对象引用数+2, 一个是当前创建的操作, 另一个返回线程的句柄。
16.CreateSuspended为TURE   挂起目标线程 不让其参与调度。KeSuspendThread挂起目标线程 , KeForceResumeThread 线程唤醒。
17.SeCreateAccessStateEx 创建ACCESS_STATE结构 用来插入进程的句柄表中,通过ObInsertObject函数将新线程对象插入。
18.KeQuerySystemTime 查询线程创建的时间。      PS_SET_THREAD_CREATE_TIME 设置线程创建的时间。
19.目标线程需要根据安全属性描述块确定其允许的访问权限.ObGetObjectSecurity 得到线程SD 。成员GrantedAccess赋值。
    注: 已被挂起的线程即使处于就绪状态也不会被调度运行,而要到被解除挂起时才能被调度运行   KeReadyThread函数将线程进入就绪状态。
20.最后ObDereferenceObject将引用计数-1,操作完成。线程创建结束。
    
二.  线程创建总体流程图:

 
三.  NtCreateThread函数分析:

代码:
NTSTATUS
NtCreateThread(                     // <相关参数说明>
    __out PHANDLE ThreadHandle,     //返回创建线程的句柄
    __in ACCESS_MASK DesiredAccess, //对新线程的访问权限
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes, //指定了线程对象的属性
    __in HANDLE ProcessHandle,      //进程句柄
    __out PCLIENT_ID ClientId,      //返回新线程的ClientId 结构
    __in PCONTEXT ThreadContext,    //新线程的执行环境
    __in PINITIAL_TEB InitialTeb,   //提供新线程的TEB初始值
    __in BOOLEAN CreateSuspended    //新创建的线程是否要先被挂起
)
    这个函数主要检查传入参数是否可写, <即验证传入的参数是否合法>。如果不能,则直接返回异常值<STATUS_INVALID_PARAMETER>。 完成参数的检测后,此函数调用创建线程的函数PspCreateThread。
IDA关键分析点:
  1.验证用户模式和内核模式下,地址的合法性。
    32系统中,用户模式的2GB和内核模式的4GB虚拟内存地址空间中,其地址位置0x7fff0000~0x7fffffff <容量为64KB>不能访问。 
IDA处理思路 :
   定义全局变量_MmUserProbeAddress = 0x7fff0000;对变量地址相减如发生借位操作,即合法。 代码如下:
代码:
mov     eax, _MmUserProbeAddress 
; 用户使允许用的最高断地址  = 7FFF0000 检测标记
mov     ecx, [ebp+ThreadHandle]
cmp     ecx, eax
jb      short ThreadHandleProc 
; 判断threadhand传出参数的地址是否在用户所允许申请的空间
;通过相减 得出的结果是否进位来判断是否越界
  2.结构体的对齐。
   结构体的开始地址判断必须是4的倍数开始,有利于对齐操作,因为此函数参数结构体成员占4个字节。
IDA处理思路 :
对地址的低2位进行检测,即test bl,3。代码如下:
代码:
mov     [ebp+SaveClientID], ebx 
;保存传入进程结构体地址指针  ClientId
test    bl, 3          
; 等于0 bl=4   对ClientId这个结构体进行对齐判断标记
           ; 结构体的开始地址末尾从00 04 08偏移 方便成员对齐
           ; 用TEST bl,3起到对齐作用 因为这个结构体成员是以4字节对齐
jnz     short MisalignmentProc 
; 结构体首地址如果不是从00 04 08开始
            ; 导致成员变量对齐问题  因为系统遵循对齐规则
            ;  进入末对齐异常处理函数
参数处理后,进入PspCreateThread线程创建.
四.  PspCreateThread函数分析:

代码:
NTSTATUS
PspCreateThread(                       //<相关参数说明>
    OUT PHANDLE ThreadHandle,          //返回创建线程的句柄
    IN ACCESS_MASK DesiredAccess,     //对新线程的访问权限
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,  
                     //指定了线程对象的属性
    IN HANDLE ProcessHandle,       //进程句柄
    IN PEPROCESS ProcessPointer,     //指向所属进程的EPROCESS对象         
    OUT PCLIENT_ID ClientId OPTIONAL,   //返回新线程的ClientId 结构
    IN PCONTEXT ThreadContext OPTIONAL, //新线程的执行环境
    IN PINITIAL_TEB InitialTeb OPTIONAL,//提供新线程的TEB初始值
    IN BOOLEAN CreateSuspended,         //新创建的线程是否要先被挂起
    IN PKSTART_ROUTINE StartRoutine OPTIONAL, //系统线程启动函数地址
    IN PVOID StartContext               //系统线程启动函数的执行环境
)
参数其它说明:
   ProcessPointer 创建系统线程时指向全局的PsInitialSystemProcess 其余情况为NULL.ThreadContext 为NULL,表示将创建一个系统线程。
  IDA反汇编分析:
1.  PreviousMode分析:进行创建时,首先判断当前运行的模式。
代码:
cmp   [ebp+StartRoutine], esi 
; 判断是否是系统线程启动函数的地址
jz    short NoKelnelModeProc 
; StartRoutine有值代表线程当前是内核模式
mov   byte ptr [ebp+PreviousMode], 0 
; 0代表内核模式
jmp   short GetCurModeProc
NoKelnelModeProc:
; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x)+1D j
mov     al, [eax+_ETHREAD.Tcb.___u33._s2.PreviousMode] 
; StartRoutine为空  则通过当前ETHREAD->Tcb的值获得前运行模式
mov     byte ptr [ebp+PreviousMode], al
2.  获得进程对象
代码:
xor     ebx, ebx                    ;清零
mov     [ebp+Process], ebx          ;初始化为零
cmp     [ebp+ProcessHandle], esi    ;比较值
jz      short NoProcessHandleProc
push    esi             ; HandleInformation
lea     eax, [ebp+TargetProcess] ; 保存进程对象的地址
push    eax             ; Object
push    [ebp+PreviousMode] ; AccessMode
push    _PsProcessType     ; ObjectType
push    2                  ; DesiredAccess
push    [ebp+ProcessHandle] ; ProcessHandle保存从3环传入的进程句柄
call    _ObReferenceObjectByHandle@24  ; ObReferenceObjectByHandle 
;在当前进程的句柄表或内核句柄表中找到相应的表项,并通过参数返回指向目标对象<数据结构>的指针 
mov     ebx, [ebp+TargetProcess]
mov     [ebp+Process], ebx    ;另保存通过函数获得进程对象
3.  当模式为内核,且当前进程为系统初始进程,退出创建并返回
代码:
cmp     byte ptr [ebp+PreviousMode], 0 ;判断是否内核模式
jz      short ModeAndObject_OK   
cmp     ebx, _PsInitialSystemProcess   ;全局变量,记录系统的初始进程
jnz     short ModeAndObject_OK 
mov     esi, STATUS_INVALID_HANDLE     ;满足条件即返回错误码
4.  初始线程保护锁,线程的进程CID,获取PspCidTable中CID句柄表.
代码:
and     dword ptr [esi+_ETHREAD.RundownProtect.___u0], eax 
; 变量L_EThreadRundPtOffset     线程的停止保护锁 用于跨线程初始化TEB,挂起线程
mov     [esi+_ETHREAD.ThreadsProcess], ebx 
; 变量L_EThreadProcessOffset   设置线程的进程CID。
mov     eax, [ebx+_EPROCESS.UniqueProcessId] 
; 变量L_EProcessUniqueProcessIdOffset
mov     [esi+_ETHREAD.Cid.UniqueProcess], eax 
; 变量L_ETreadCidUPSOffset  == Thread->Cid.UniqueProcess 的偏移地址
mov     dword ptr [ebp+CidEntry.___u0], esi 
; 相当于___u0保存的线程对象<CidEntry.Object> 创建线程的CID句柄。
and     dword ptr [ebp+CidEntry.___u1], 0 ; 赋值0
lea     eax, [ebp+CidEntry]
push    eax                               ; HandleTableEntry
push    _PspCidTable                      ; HandleTable  句柄表
; PspCidTable是个独立的句柄表,CID即进程号+线程号是这个句柄表中的句柄
call    _ExCreateHandle@8 
; ExCreateHandle(x,x)通过句柄表创建句柄的入口地址       
mov     [esi+_ETHREAD.Cid.UniqueThread], eax       ; 保存句柄
5.  链表成员的初始化. PLIST_ENTRY链表结构体包括2个节点成员.Flink, 指向下一个成员节点。Blink 指向头一个成员节点。
代码:
lea     eax, [esi+_ETHREAD.___u2.LpcReplyChain.Flink] 
; _EThread结构体中一个共用体结构的首地址
; 1._ETHREAD.ExitTime
; 2._ETHREAD.LpcReplyChain   //用于跨进程通信<lpc>
; 3._ETHREAD.KeyedWaitChain
mov     [eax+4], eax   
; _EThread.LpcReplyChain.Blink指向_EThread共用体中的LpcReplyChain首地址
mov     [eax], eax     
; _EThread.LpcReplyChain.Flink 指向_EThread共用体中的LpcReplyChain首地址
lea     eax, [esi+_ETHREAD.IrpList] 
;  _ETHREAD.IrpList的地址
// 初始化所有正在处理单尚末完成的I/O请求<IRP对象>
mov     [eax+4], eax   
;  _EThread.IrpList.Blink  链表地址指向_EThread.IrpList
mov     [eax], eax     
       ;  _EThread.IrpList.Flink 链表地址指向_EThread.IrpList
lea     eax, [esi+_ETHREAD.PostBlockList] 
;  _ETHREAD.PostBlockList的地址   
// 初始化配置管理器等级注册表的变化通知
mov     [eax+4], eax    
;  _EThread.PostBlockList.Blink  链表地址指向_EThread.PostBlockList
mov     [eax], eax      
;  _EThread.PostBlockList.Flink  链表地址指向_EThread.PostBlockList
mov     dword ptr [esi+_ETHREAD.ThreadLock.___u0], edi 
; _EThread.ThreadLock  // 初始化线程锁
mov     [esi+_ETHREAD.ActiveTimerListLock], edi 
; _EThread.ActiveTimerListLock   初始化时间旋转锁
lea     eax, [esi+_ETHREAD.ActiveTimerListHead] 
; 初始化当前线程的所有定时器
mov     [eax+4], eax    
; _EThread.ActiveTimerListHead.Blink 指向_EThread.ActiveTimerListHead
mov     [eax], eax      
;_EThread.ActiveTimerListHead.Flink 指向_EThread.ActiveTimerListHead
6.  RundownProtection 线程保护锁
  当进行跨线程初始化TEB,挂起线程等操作. 通过RundownProtection值锁定线程以确保当前线程不是处在退出或正终止的状态中。
代码:
lea     edi, [ebx+_EPROCESS.RundownProtect] 
; RundownProtection跨进程<线程>访问标志
mov     ecx, [edi]
and     ecx, 0FFFFFFFEh      ; 提取最低位
lea     edx, [ecx+2]       
mov     eax, ecx
lock cmpxchg [edi], edx 
; 检测RundownProtect是否为0或1  如果不是则调用ExfAcquireRundownProtection求值
;  X不为0或1(>=2)           eax = X的最地位<0或1>        ECX = X的值<最低位为0>    最后值 = EAX   不一定等于0
;  X为0或1(0 1)     eax = X                ECX = X                   最后的值为X的第2位 = 0
; <ExfAcquireRundownProtection == X>
7.  通过检测ThreadContext来判断是创建用户系统还是系统线程。
代码:
cmp     [ebp+ThreadContext], 0 ;  线程上下文  3环传入参数
jz      THreadContext_NoExist 
; ThreadContext是否存在值  存在即用此值创建内核态的对象<用户模式线程>
; 否则用内核态Context创建内核对象<系统线程>
lea     eax, [ebp+Teb]
push    eax             ; Base
lea     eax, [esi+_ETHREAD.Cid]
push    eax             ; ClientId
push    [ebp+InitialTeb] ; InitialTeb
push    ebx             ; TargetProcess
call    _MmCreateTeb@16 ; MmCreateTeb(x,x,x,x)  创建对象TEB
mov     [ebp+Status], eax ; 此例程将创建一个TEB加入它的目标进程内页并复制初始TEB
8.  初始化线程启动地址和Windows子系统的启动地址
代码:
mov     eax, [ebp+ThreadContext] 
; 通过用户模式的线程来初始化内核线程对象
mov     ecx, [eax+_CONTEXT._Eip]
; ThreadContext->Eip  初始化线程的启动地址
mov     [esi+_ETHREAD.StartAddress], ecx
mov     ecx, [eax+_CONTEXT._Eax] 
; ThreadContext->Eax  初始化WINDOWS子系统的启动地址
mov     [esi+_ETHREAD.___u17.Win32StartAddress], ecx
9.  通过KeInitThread初始线程对象 
  当创建用户线程赋值PspUserThreadStartup 当线程创建完毕后,在3环下启动线程时,即通过此函数入口点执行。
当创建系统线程赋值PspSystemThreadStartup
代码:
push    ebx             ; Process
push    [ebp+Teb]       ; Teb
push    eax             ; ThreadContext
push    [esi+_ETHREAD.StartAddress]
push    ecx             ; NULL
push    offset _PspUserThreadStartup@8 
; PspUserThreadStartup(x,x) 启动调用用户态的线程
push    ecx             ; NULL
jmp     short Call_KeInitThread
THreadContext_NoExist:                  
; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x)+1A3 j
xor     eax, eax        
; 进入没有创建新线程的情况下处理流程  即用内核线程创建系统线程
     mov     [ebp+Teb], eax  ; Teb = NULL;
     push    10h
     pop     ecx             ; PS_CROSS_THREAD_FLAGS_SYSTEM
     lea     edx, [esi+_ETHREAD.CrossThreadFlags]
     lock or [edx], ecx     
; 永远受系统线程的CrossThreadFlags标志位的控制
     mov     ecx, [ebp+StartRoutine] 
; 从这里开始为KeInitThread传参   用内核线程来创建内核对象
     mov     [esi+_ETHREAD.StartAddress], ecx
     push    ebx             ; Process
     push    eax             ; Teb
     push    eax             ; ContextFrame
     push    [ebp+StartContext] ; StartContext
     push    ecx             ; StartRoutine
     push    offset _PspSystemThreadStartup@8 ; SystemRoutine
     push    eax             ; KernelStack
Call_KeInitThread:                      
; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x)+24E j
     push    esi             ; Thread
     call    _KeInitThread@32 ; KeInitThread(x,x,x,x,x,x,x,x)
; 初始化线程对象 <继续初始新线程属性。> 同步Header, WaitBlock,
; 系统服务表 ,APC ,定时器, 线程的内核栈等
10.  Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD 跨线程访问的标志位 包括Terminated 线程已执行终止操作 创建失败 等.
IDA 主要代码:
代码:
dec     [edi+_KTHREAD.___u29._s0.KernelApcDisable] 
; KeEnterCriticalRegionThread (&CurrentThread->Tcb);
           ; 将此值设为-1   关闭当前线程的内核APC
       lea     eax, [ebx+_EPROCESS.ProcessLock.___u0]
       mov     [ebp+PushLock], eax
       mov     eax, 0
       mov     ecx, [ebp+PushLock] ; 锁定进程
           ; 确保当前进程的状态不是退出或正在终止。
           ; Process->Flags 判断进程是否是死进程。
           ; CurrentThread->CrossThreadFlags跨线程访问的标志位
           ; 包括Terminated 线程已执行终止操作 创建失败 等。
       lock bts [ecx], eax
       setb    al
       test    al, al
       jz      short PushLock_OK ; 跳转成功
       mov     ecx, [ebp+PushLock] ; PushLock
       call @ExfAcquirePushLockExclusive@4 
; ExfAcquirePushLockExclusive(x) 获得PUSHLOCK值

PushLock_OK:                            
; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x)+2DB j
        test    byte ptr [ebx+_EPROCESS.Flags], 8 
; PS_PROCESS_FLAGS_PROCESS_DELETE 检测Flags标记位
        jnz     NoRightFlagsExitProc
 ; 进程和线程的标志位至少一个必须为0  否则引发错误
        test    byte ptr [edi+_ETHREAD.CrossThreadFlags], 1
;PS_CROSS_THREAD_FLAGS_TERMINATED 检测CrossThreadFlags位
11.  挂入目标进程(EPROCESS中)的线程队列 并调用KeStartThread设置线程的优先级等,启动该线程运行。
代码:
lea     eax, [ebx+_EPROCESS.ActiveThreads] ;记录进程的活动线程数
mov     ecx, [eax]
mov     [ebp+L_OldActiveThreads], ecx ; 保存原ActiveThreads
inc     ecx             ; 进程的活动线程数+1
mov     [eax], ecx
lea     eax, [esi+_ETHREAD.ThreadListEntry]
lea     ecx, [ebx+_EPROCESS.ThreadListHead]
mov     edx, [ecx+4]    ; _EPROCESS.ThreadListHead.blink
mov     [eax], ecx      ; _EPROCESS.ThreadListEntry.Flink
mov     [eax+4], edx    ; _ETHREAD.ThreadListEntry.blink
mov     [edx], eax
mov     [ecx+4], eax    
; 1.从_EPROCESS.ThreadListHead => _ETHREAD.ThreadListHead
; 2._EPROCESS.ThreadListHead .blink =>_EPROCESS.ThreadListHead .Flink
; 挂入目标进程(EPROCESS中)的线程队列
push    esi             ; Thread
call    _KeStartThread@4 ; KeStartThread  启动线程
; 并再次初始化末完成的域,设置线程的优先级, 时限设置等
12.  回调处理 当创建的是第一个线程,通知线程创建标注的注册程序.
代码:
cmp     [ebp+L_OldActiveThreads], 0
; 局部变量OldActiveThreads  判断当前创建的线程是否是第一个线程。
;当为第一个线程:  通知线程创建标注的注册程序
; 线程创建成功时得到通报内核成员 在数组登记一个通知函数 以新线程CID(进程号和线程号)为参数
jnz     short ProcessAndThreads_OK ; 跳转成功
mov     dl, 1                     ; Create
mov     ecx, ebx                  ; Process
call    @WmiTraceProcess@8 ; WmiTraceProcess  鉴定当前进程的通知例程
cmp     _PspCreateProcessNotifyRoutineCount, 0 ; 创建进程的个数
jz      short ProcessAndThreads_OK
mov     [ebp+l_CPNotifyRoutineAddr],
 offset _PspCreateProcessNotifyRoutine ; 保存首地址
mov     [ebp+L_nCount], 8 ; 循环总次数
repetitionProc_P:                       
; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x)+3F4 j
push    [ebp+l_CPNotifyRoutineAddr]     ; CallBack
call    _ExReferenceCallBackBlock@4     ; ExReferenceCallBackBlock
;_PspCreateProcessNotifyRoutine[L_nCount]每个成员的信号是否被阻止回调
mov     [ebp+CallBack], eax
test    eax, eax
jz      short BlockCall_P ; 当为NULL 阻止
push    eax                       ; CallBackBlock
call    _ExGetCallBackBlockRoutine@4 ; ExGetCallBackBlockRoutine(x)
; 当不阻止时  关联起信号
push    1
push    [ebx+_EPROCESS.UniqueProcessId]
push    [ebx+_EPROCESS.InheritedFromUniqueProcessId]
call    eax
push    [ebp+CallBack]            ; CallBackBlock
push    [ebp+l_CPNotifyRoutineAddr] ; CallBack
call    _ExDereferenceCallBackBlock@8 
; ExDereferenceCallBackBlock(x,x)       恢复先前的
BlockCall_P:                           
; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x)+3CA j
add     [ebp+l_CPNotifyRoutineAddr], 4   ; 依次遍历数组中每个成员
dec     [ebp+L_nCount]             ; 总数决定循环的中止
jnz     short repetitionProc_P         ; 重复处理  for();
13.  创建ACCESS_STATE结构  插入对象目录以及当前进程的句柄表
代码:
mov     eax, _PsThreadType  获得线程类型结构
add     eax, 68h        ; &PsThreadType->TypeInfo.GenericMapping
push    eax             ; GenericMapping
push    [ebp+DesiredAccess] ; DesiredAccess
lea     eax, [ebp+AuxData]
push    eax             ; AuxData
lea     eax, [ebp+LocalAccessState]
push    eax             ; AccessState
push    edi             ; Process
push    0               ; Thread
call    _SeCreateAccessStateEx@24 
; SeCreateAccessStateEx(x,x,x,x,x,x) 初始化ACCESS_STATE结构体
        ; 创建ACCESS_STATE结构 用来插入进程的句柄表中
        ; 通过ObInsertObject函数将新线程对象插入
mov     edi, eax
test    edi, edi
jge     short NOAccessState
push    2
pop     eax
lea     ecx, [esi+_ETHREAD.CrossThreadFlags] ; 失败后恢复先前设置
lock or [ecx], eax      ; 失败,设置标记位表示该线程死亡
cmp     [ebp+CreateSuspended], 0
jz      short CurNOSuspendedStatus ; 是否是挂起状态下
push    esi             ; Thread
call    _KeResumeThread@4 ;  KeResumeThread(x) 恢复挂起的线程
CurNOSuspendedStatus:                   
; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x)+596 j
push    esi             ; Thread
call    _KeReadyThread@4 ; KeReadyThread(x)  准备执行线程
       ; Dispatch 调度线程  解除挂起以后,出于就绪状态 当该线程被调度运行时 自行退出
       ; 将目标线程挂入就绪线程队列
14.  查询系统当前时间并得到SD。
代码:
lea     eax, [ebp+CreateTime] 
push    eax                 ; CurrentTime
call    _KeQuerySystemTime@4 ; KeQuerySystemTime(x) 查询当前系统时间
mov     eax, dword ptr [ebp+CreateTime]
mov     dword ptr [esi+_ETHREAD.CreateTime], eax 
; 初始化赋值_Ethread.Creatime结构体  当前创建线程的时间
mov eax, dword ptr [ebp+CreateTime+4] ; (Thread)->CreateTime.QuadPart
mov     [esi+_ETHREAD.CreateTime.u.HighPart], eax
lea     edi, [esi+_ETHREAD.CrossThreadFlags]
test    byte ptr [edi], 2 ; PS_CROSS_THREAD_FLAGS_DEADTHREAD
jnz     DropThreadFlagProc 
; (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD) == 0
lea     eax, [ebp+MemoryAllocated]
push    eax             ; MemoryAllocated
lea     eax, [ebp+SecurityDescriptor]
push    eax             ; SecurityDescriptor
push    esi             ; Object
call    _ObGetObjectSecurity@12 
; ObGetObjectSecurity(x,x,x)  得到对象的安全属性//得到线程SD
15.  目标线程根据安全属性描述块确定其允许的访问权限
代码:
mov     [ebp+SubjectContext.ProcessAuditId], ebx; 保存进程
push    ebx             ; Process
call    _PsReferencePrimaryToken@4 
; PsReferencePrimaryToken(x) 返回指向主进程的标记  且保护指针的标记数目++
mov     [ebp+SubjectContext.PrimaryToken], eax    ;保存保护指针的标记
and     [ebp+SubjectContext.ClientToken], 0      
lea     edi, [esi+_ETHREAD.GrantedAccess]    ;获取GranteAccess地址
lea     eax, [ebp+accesst]                  ;获取accesst地址
push    eax             ; AccessStatus
push    edi             ; GrantedAccess
push    [ebp+PreviousMode] ; AccessMode
mov     eax, _PsThreadType                  ;线程类型数组首地址
add     eax, 68h ;求取偏移68H值&PsThreadType->TypeInfo.GenericMapping
push    eax             ; GenericMapping
xor     eax, eax        ;清零 传参
push    eax             ; Privileges
push    eax             ; PreviouslyGrantedAccess
push    2000000h        ; DesiredAccess
push    eax             ; SubjectContextLocked
lea     eax, [ebp+SubjectContext]
push    eax             ; SubjectSecurityContext
push    [ebp+SecurityDescriptor] ; SecurityDescriptor
call    _SeAccessCheck@40
; SeAccessCheck(x,x,x,x,x,x,x,x,x,x) 入口进行检查
mov     [ebp+AccessCheck], al
lea     ecx, [ebx+_EPROCESS.Token] ; FastRef
mov     edx, [ebp+SubjectContext.PrimaryToken] ; Object
call    @ObFastDereferenceObject@8 
; ObFastDereferenceObject         快速解除对象的引用
push    [ebp+MemoryAllocated] ; MemoryAllocated
push    [ebp+SecurityDescriptor] ; SecurityDescriptor
call    _ObReleaseObjectSecurity@8 
; ObReleaseObjectSecurity(x,x) 安全的释放对象
cmp     [ebp+AccessCheck], 0
jnz     short AccessCheckValue
and     dword ptr [edi], 0 ; Thread->GrantedAccess = 0
AccessCheckValue:                       
; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x)+6FD j
or      dword ptr [edi], 61h 
; Thread->GrantedAccess |= (THREAD_TERMINATE | THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION)   ; 设置最小权限
五.KeInitThread函数关键点分析:
     此函数根据进程对象中的信息来初始化新线程的一些属性。根据ThreadContext的值来决定是创建用户模式线程还是系统线程。如果创建用户线程,则传参PspUserThreadStartup.系统线程,传参PspSystemThreadStartup.
另外此函数根据所提供的参数信息调用KiInitializeContextThread.完成于特定处理器相关执行环境的初始化。
1.  WaitListHead的初始化:
代码:
mov    [esi+_KTHREAD.Header.___u0._s0.Type], 6   ; ThreadObject
mov    [esi+_KTHREAD.Header.___u0._s0.Size], 6Eh 
; sizeof(KTHREAD) / sizeof(LONG)
lea     eax, [esi+_KTHREAD.Header.WaitListHead]
; 初始化WaitListHead  当一个线程正在等待执行时  加入此链表
mov     [eax+_LIST_ENTRY.Blink], eax   ;初始链表头
mov     [eax+_LIST_ENTRY.Flink], eax   ;初始链表尾
2.  内存对齐算法 <AutoAlignment>
通过移位操作来设置线程的标记位<内存访问对齐> 。
代码:
lea     eax, [esi+_KTHREAD.ThreadFlags]    取得线程首地址
mov     ebx, [ebp+Process]                 取得进程地址
mov     ecx, [ebx+_KPROCESS.ProcessFlags]  进程标志位
shl     ecx, 1Fh     ;左移31位 
sar     ecx, 1Fh    ;求出最低位
xor     ecx, [eax]
and     ecx, 1
xor     [eax], ecx    
; Thread->AutoAlignment = Process->AutoAlignment;
; 位段  最低位段为AutoAlignment
; AutoAlignment 内存访问对齐标志 继承自EPROCESS
3.  此函数主要也是初始化线程的成员。<Header, WaitBlock,系统服务表 ,APC ,定时器, 线程的内核栈等>  具体赋值部分请查看wrkx86.idb中KeInitThread函数。


六.PspUserThreadStartup函数关键点函数分析
  当创建的线程是用户模式线程时,在调用KeInitThread函数时,作为参数进行传入,在三环下启动线程时,调用应用程序指定PspUserThreadStartup<初始线程启动函数>,将此启动函数地址压入用户栈开始执行。
1.  调试线程的创建部分
代码:
test    byte ptr [esi+_ETHREAD.CrossThreadFlags], 6
;此标记位用来检测调试状态
jnz     short jump_ok_1 
; 如果当前新创建线程的进程是在调试状态或有调试通告 则须创建调试线程
push    [ebp+StartContext] ; StartAddress
push    esi             ; Thread
call    _DbgkCreateThread@8 ; DbgkCreateThread(x,x)   创建调试线程

2.  获取TrapFrame陷阱框架地址
代码:
call    ds:__imp_@KfRaiseIrql@4
 ; KeRaiseIrql  这个函数的IRQL降低到指定的值
push    ebx             ; SystemArgument2
push    ds:SystemArgument1 ; SystemArgument1
push    ebx             ; NormalContext
push    ds:NormalRoutine ; NormalRoutine
mov     eax, [esi+_KTHREAD.InitialStack]
sub     eax, 29Ch       
; PSPALIGN_UP(sizeof(KTRAP_FRAME),4) + sizeof(FX_SAVE_AREA)
; => sizeof(KTRAP_FRAME)+sizeof(FX_SAVE_AREA) => 29Ch
; TrapFrame陷阱框架地址
push    eax             ; TrapFrame
push    ebx             ; ExceptionFrame
call    _KiInitializeUserApc@24 
; KiInitializeUserApc(x,x,x,x,x,x) 初始化一个用户模式APC的背景

3.  在系统中填写的cookie处理。由SharedUserData->Cookie来决定
代码:
CooikeZero:                             
; CODE XREF: PspUserThreadStartup(x,x)+143 j
       lea     eax, [ebp+Time] ; 获取保存时间的地址
       push    eax             ; CurrentTime
       call    _KeQuerySystemTime@4 
; KeQuerySystemTime(x)  获取系统时间
       mov     eax, large fs:20h ; //获取当前处理器块地址
       mov     ecx, [eax+_KPRCB.MmPageFaultCount]
       xor     ecx, [eax+_KPRCB.InterruptTime]
       xor     ecx, dword ptr [ebp+Time+4] ; Time.HighPart
       xor     ecx, dword ptr [ebp+Time] ; Time.LowPart
       lea     eax, [ebp+Time] ; 获取保存时间的地址
       xor     ecx, eax        
       mov     edx, 0FFDF0330h
;Time.LowPart^Time.HighPart^Prcb->InterruptTime^Prcb->MmPageFaultCount ^ (ULONG)(ULONG_PTR)&Time = 0FFDF0330h;
       xor     eax, eax
       lock cmpxchg [edx], ecx ; 交换指令

ExitFun: 
       cmp     ds:0FFDF0330h, ebx ; SharedUserData->Cookie   ;判断此值来决定是否填写Cookie
七.KeStartThread整体分析
   功能:  KeStartThread函数启动该线程运行;插入线程到进程的线程链表中。并再次初始化末完成的域,<设置线程的优先级, 时限设置等>   
函数初始流程IDA分析:
代码:
mov     esi, [edi+_ETHREAD.Tcb.___u6.ApcState.Process] ; 得到进程
mov     ecx, [esi+60h]        ; Process->DisableBoost
shl     ecx, 1Eh                  ;获取标志位
lea     eax, [edi+0A0h]      ; Thread->DisableBoost
sar     ecx, 1Eh                  ;获取标志位
xor     ecx, [eax]      ;  Thread->DisableBoost = Process->DisableBoost;
                      ; 线程调度过程中优先级的提升
    lea     edx, [ebp+LockHandle] ; 调用函数的第二个参数
and     ecx, 2
xor     [eax], ecx
mov     al, [esi+_EPROCESS.Pcb.Iopl]
mov     [edi+_ETHREAD.Tcb.Iopl], al ; Thread->Iopl = Process->Iopl;   I/O优先级
mov     al, [esi+_KPROCESS.QuantumReset] 
; // Initialize the thread quantum and set system affinity false.
mov     [edi+_ETHREAD.Tcb.___u57._s1.Quantum], al
 ; Thread->Quantum = Process->QuantumReset; 初始化线程量子数量
mov     al, [esi+_KPROCESS.QuantumReset]
lea     ecx, [esi+_KPROCESS.ProcessLock] ; 调用函数的第一个参数
mov     [edi+_ETHREAD.Tcb.___u57._s2.QuantumReset], al
     ; Thread->QuantumReset = Process->QuantumReset;  线程的基本时间重置值
mov     [edi+_KTHREAD.___u33._s3.SystemAffinityActive], 0 
; Thread->SystemAffinityActive = FALSE; 系统亲和力设置
call    ds:__imp_@KeAcquireInStackQueuedSpinLockRaiseToSynch@8 
; KeAcquireInStackQueuedSpinLockRaiseToSynch(x,x)
    ; 提高的IRQL到SYNCH_LEVEL获得栈队列中的旋转锁
mov     al, [esi+_KPROCESS.BasePriority] ; 设置线程的基本优先级
mov     [edi+_KTHREAD.BasePriority], al
mov     [edi+_KTHREAD.Priority], al ; 设置线程的优先级 动态微调
mov     eax, [esi+_EPROCESS.Pcb.Affinity]
mov     [edi+_KTHREAD.Affinity], eax
mov     eax, [esi+_EPROCESS.Pcb.Affinity]
mov     [edi+_KTHREAD.UserAffinity], eax ; 设置线程的亲和力
movzx   eax, [esi+_EPROCESS.Pcb.IdealNode] ; 进程选择优先的处理器节点
movzx   edx, [esi+_EPROCESS.Pcb.ThreadSeed] ; 进程选择理想的处理器
mov     eax, _KeNodeBlock[eax*4] ; =>  KeNodeBlock[Process->IdealNode]
mov     ecx, _KiProcessorBlock[edx*4] ; => KiProcessorBlock[Process->ThreadSeed];
mov     eax, [eax+10h]  ; KeNodeBlock[Process->IdealNode]->ProcessorMask
mov     ecx, [ecx+550h] ; KiProcessorBlock[IdealProcessor]->MultiThreadProcessorSet
and     eax, [esi+_EPROCESS.Pcb.Affinity]
not     ecx
and     ecx, eax        ; 2个数组的值相与
jz      short ValueExist
mov     eax, ecx

ValueExist:                             ; CODE XREF: KeStartThread(x)+97 j
push    eax             ; Set
push    edx             ; Number
call    _KeFindNextRightSetAffinity@8 ; KeFindNextRightSetAffinity(x,x)
mov     ecx, large fs:20h
mov     [esi+_EPROCESS.Pcb.ThreadSeed], al
mov     ebx, 418h
add     ecx, ebx        ; LockQueue
mov     [edi+_ETHREAD.Tcb.UserIdealProcessor], al
mov     [edi+_ETHREAD.Tcb.IdealProcessor], al
call    @KeAcquireQueuedSpinLockAtDpcLevel@4 
; KeAcquireQueuedSpinLockAtDpcLevel(x)
; 在当前IRQL级别中选择指定栈队列里自旋锁
lea     ecx, [esi+_EPROCESS.Pcb.ThreadListHead]
mov     edx, [ecx+_LIST_ENTRY.Blink]
lea     eax, [edi+_ETHREAD.Tcb.ThreadListEntry]
mov     [eax+_LIST_ENTRY.Flink], ecx
mov     [eax+_LIST_ENTRY.Blink], edx
mov     [edx], eax
mov     [ecx+4], eax    ; 从尾部插入链表  插入线程到进程列表和增加内核线程
mov     ecx, large fs:20h
inc     [esi+_EPROCESS.Pcb.StackCount] 
; //解锁调度数据库,释放锁的过程,降低IRQL到其以前的值
add     ecx, ebx        ; LockQueue
call    @KeReleaseQueuedSpinLockFromDpcLevel@4 
; KeReleaseQueuedSpinLockFromDpcLevel(x)


八.关系图
 

相关参考资料:
引用:
Windows 内核原理与实现  潘爱民
Windows 内核情景分析    毛德操


注: 因本人水平有限,加上时间仓促,难免存在错误与纰漏之处,恳请各位高手给予指正!
科锐五期 tariq