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
【看雪培训】《Adroid高级研修班》2022年夏季班招生中!
上传的附件: