Windows NT系统在创建进程之后,紧接着创建线程。本文以反汇编wrkx86.exe为主,来分析线程创建流程!
然后在WinDbg下断bp nt!ntCreateThread即进入内核线程创建开始位置。
一.线程创建流程分析:
二. 线程创建总体流程图:
三. 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 //新创建的线程是否要先被挂起 )
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传出参数的地址是否在用户所允许申请的空间 ;通过相减 得出的结果是否进位来判断是否越界
结构体的开始地址判断必须是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线程创建.
代码:
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
代码:
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 ;另保存通过函数获得进程对象
代码:
cmp byte ptr [ebp+PreviousMode], 0 ;判断是否内核模式 jz short ModeAndObject_OK cmp ebx, _PsInitialSystemProcess ;全局变量,记录系统的初始进程 jnz short ModeAndObject_OK mov esi, STATUS_INVALID_HANDLE ;满足条件即返回错误码
代码:
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 ; 保存句柄
代码:
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
当进行跨线程初始化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>
代码:
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
代码:
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
当创建用户线程赋值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 ,定时器, 线程的内核栈等
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位
代码:
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 启动线程 ; 并再次初始化末完成的域,设置线程的优先级, 时限设置等
代码:
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();
代码:
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 调度线程 解除挂起以后,出于就绪状态 当该线程被调度运行时 自行退出 ; 将目标线程挂入就绪线程队列
代码:
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
代码:
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) ; 设置最小权限
此函数根据进程对象中的信息来初始化新线程的一些属性。根据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 ;初始链表尾
通过移位操作来设置线程的标记位<内存访问对齐> 。
代码:
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
六.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函数启动该线程运行;插入线程到进程的线程链表中。并再次初始化末完成的域,<设置线程的优先级, 时限设置等>
函数初始流程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)
八.关系图
相关参考资料:
注: 因本人水平有限,加上时间仓促,难免存在错误与纰漏之处,恳请各位高手给予指正!
科锐五期 tariq