创建进程的过程就是构建一个环境,这个环境包含了很多的机制 (比如自我保护, 与外界通信等等)。 构建这个环境需要两种“人”来协调完成(用户态和内核态),他们各有分工,其中用户态提供原料(提供创建的那些参数), 内核态负责来构建这个环境,由于环境是由内核态构建的,因此他持有这个环境的控制权, 而用户由于提供了原料, 因此他具有使用权。 内核态开始构建环境中的基础设施(进程对象,等等),在构建完基础设施后,内核态通知用户态基础设施构建已经完成,是否需要继续构建其他设施,于是用户态通知内核态继续构建一条通道(既创建线程),方便两边的通信,当用户态接收到线程创建完毕的信息后,便可以开始使用这个环境(投入生产),以后缺啥补啥 。
二. 3环进程的创建分析:
CreateProcesssA的分析:
在CreateProcessA函数中调用了CreateProcessInternalA,调用该函数时,增加了两个参数(一头一尾加了个零参数),在该函数里面也没看到对这两个参数的引用,而是直接传递给CreateProcessInternalW函数。
在CreateProcessInternalA中,首先检查了参数 lpCmdLine, 判断是否为0,不为0则将lpCmdLine初始化为UNICODE字符串, 为0则做了几个局部变量赋值后跳过初始化为UNICODE字符串。接着继续检查参数,先将lpStartupInfor的内容拷贝到一个局部变量, 然后判断参数lpApplicationName是否为0,不为0则参数转换为UNICODE字符串,为0则跳过转换继续判断参数lpCurrentDirectory是否为0,这个和参数lpAppLicationName的判断逻辑是一样的。接着判断STARTUPINFOA.lpReserved域是否为0(MSDN上说在调用CreateProcess前,要把lpReserved设置为NULL ,不过我试了下,不为0也没问题。),该域不为0则和其他字符串参数一样做UNICODE的转换,后面还判断STARTUPINFOA中的几个字符串的域,不为0的都作了下转换。最后调用了CreateProcesssW函数, 看上面的步骤大家应该知道了其实CreateProcessInternalA函数只是对字符串参数或者结构体中包含字符串类型的域的作了检查和转换工作,然后就调用了下层函数。(附图)
分析CreateProcesssW的大概流程:
1. 将参数 保存到局部变量中。
2. dwCreationFlags 的值至少由一个标志组合成(一些创建标志和优先级类型),首先屏蔽 CREATE_NO_WINDOW 标志,代码如下:
代码:
mov eax, [ebp+20h] ; ebp+20h = dwCreationFlags and eax, 0F7FFFFFFh ; 屏蔽CREATE_NO_WINDOW标志。 mov [ebp+20h], eax mov ecx, eax and ecx, 18h ;18h = DETACHED_PROCESS | CREATE_NEW_CONSOLE cmp cl, 18h jz loc_7C8427DE ;如果相等,说明创建标志不合法
3. 在2中说到dwCreationFlags中也包含优先级类型的组合, 接着就判断优先级,判断的顺序依次是IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS, 只要满足其中一个优先级,就跳过其他优先级的判断,如果都不满足, 将权限级置为0.
(当满足IDLE_PRIORITY_CLASS时),置优先级标志为1.
(当满足NORMAL_PRIORITY_CLASS时),置优先级标志为2.
(当满足HIGH_PRIORITY_CLASS时),置优先级标志3.
(当满足REALTIME_PRIORITY_CLASS时),由于该优先级别很高,比操作系统优先级还高(参考MSDN),作了如下操作, 申请堆空间 -->打开一个令牌对象与线程关联,并返回一个句柄可用于访问该令牌。-->调整优先级令牌。 置优先级标志4。
代码:
mov eax, [ebp+20h] ; ebp+20h = dwCreationFlagsand eax, 0F7FFFFFFh ; 屏蔽CREATE_NO_WINDOW标志mov [ebp+20h], eaxmov ecx, eaxand ecx, 18hcmp cl, 18h ; 判断dwCreationFlags 是否为CREATE_NEW_CONSOLE |DETACHED_PROCESSjz loc_7C8427DE ; 标志组合不合法, 进行错误处理并退出mov [ebp+var_6D4], ebxmov [ebp+var_6DC], ebxtest al, 40hjnz IsIdlePriority ; 优先级为IDLE_PRIORITY_CLASStest ah, 40hjnz loc_7C8427F6test al, 20hjnz IsNormalPriority ; 优先级为NORMAL_PRIORITY_CLASStest ah, ahjs loc_7C842802test al, aljs IsHighPriotity ; 优先级为HIGH_PRIORITY_CLASStest ah, 1jnz IsRealTimePriority ; 优先级为REALTIME_PRIORITY_CLASS
代码:
mov [ebp+var_668], bland word ptr [ebp+dwCreateFlag], 3E1Fh ; 屏蔽权限级表示的位mov edi, 800hmov esi, 1000htest [ebp+dwCreateFlag], edijnz loc_7C842832 ; dwCreationFlag = CREATE_SEPARATE_WOW_VDM test [ebp+dwCreateFlag], esijnz short loc_7C819A33 ; dwCreationFlag = CREATE_SHARED_WOW_VDMmov eax, _BaseStaticServerDatacmp [eax+19F4h], bljnz loc_7C84283C
7. 接着调用NtOpenFile()得到文件句柄
代码:
push 60h ;打开选项push 5 ;共享模式lea eax, [ebp+pIoStatusBlock]push eax ; I/0状态块lea eax, [ebp+pObjAttribute] push eax ; 对象属性push 1000A1h ;SYNCHRONIZE |FILE_ATTRIBUTE_NORMAL| ;FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLYlea eax, [ebp+pHandleOfFile]push eax ; 文件句柄 mov esi, ds:__imp__NtOpenFile@24 ; NtOpenFile(x,x,x,x,x,x)call esi ; NtOpenFile(x,x,x,x,x,x) ; NtOpenFile(x,x,x,x,x,x)
代码:
push [ebp+pHandleOfFile] ; 文件句柄push 1000000hpush 10h ; 内存区页面保护属性push ebx ; SETION大小push ebx ; 对象属性push 0F001Fh ; 访问掩码lea eax, [ebp+pSectionHandle]push eax ; 指向内存区对象的指针(传出参数)call ds:__imp__NtCreateSection@28 ; NtCreateSection(x,x,x,x,x,x,x)
在得到内存区对象句柄后调用了NtQuerySection函数,返回后得到节的基本信息(节基地址,大小,属性)
代码:
push ebx ; 接受返回的大小push 30h ; 内存区信息的长度lea eax, [ebp+OepAddress]push eax ; 接受内存区信息Bufferxor edi, ediinc edipush edi ; edi = 1 = SectionImageInformationpush [ebp+pSectionHandle] ; 内存区句柄call ds:__imp__NtQuerySection@20 ; 将区对象作为镜像执行文件来查询信息
下面检查镜像文件的部分信息的有效性:
代码:
mov ax, [ebp+MachineType] ; MachineType为机器类型,cmp ax, ds:7FFE002Ch ; ds:7FFE002Ch中的内容为0x014cjb loc_7C84329Ecmp ax, ds:7FFE002Ehja loc_7C84329Ecmp dword ptr [ebp+ SubSystemVersion], 2jz short loc_7C8191C4 ; 子系统次版本号cmp dword ptr [ebp+ SubSystemVersion], 3 ; 子系统的比较版本(PE信息中包含)jnz loc_7C842D0C
代码:
movzx eax, [ebp+pImageFileMinorVersion] ; 子系统次版本号push eaxmovzx eax, [ebp+pImageFileMajorVersion] ; phSection->GpValuepush eax ; 子系统的主版本号call _BasepIsImageVersionOk@8 ; 判断镜像文件的版本是否合法。
代码:
push offset LibFileName ; "advapi32.dll"call _LoadLibraryA@4 ; LoadLibraryA(x)mov esi, eaxmov [ebp+phModuleOfAdvapi32.dll], esi ; 保存模块基地址cmp esi, ebxjz short loc_7C81923Cpush offset ProcName ; "CreateProcessAsUserSecure"push esi ; hModulecall _GetProcAddress@8 ; 获得指定模块中指定函数的地址
然后调用BaseFormatObjectAttributes将安全属性结构格式为NT对象属性结构(得到了对象属性)。 接着调用了_DbgUiConnectToDbg在实现通过调用NtCreateDebugObject函数来创建调试对象,调用DbgUiGetThreadDebugObject来获得调试对象(作为参数传递到0环)。
代码:
call _DbgUiConnectToDbg@0 ; 实现调用中NtCreateDebugObjectmov [ebp+nReturnStatus], eaxcmp eax, ebxjl loc_7C82DF58call _DbgUiGetThreadDebugObject@0 ; 获得线程调试对象teb->0xF24,mov [ebp+phandleOfDebugObj], eax
代码:
push [ebp+JobLevel] ; 作业级别push ebx ; 异常端口对象句柄push [ebp+phandleOfDebugObj] ; 调试对象句柄push [ebp+pSectionHandle] ; 内存区对象句柄push [ebp+CreateFlag] ; 创建标志or esi, 0FFFFFFFFhpush esi ; 父进程句柄(FFFFFFFF)push [ebp+ObjAttibute] ; 对象属性push 1F0FFFh ; (访问掩码)lea eax, [ebp+pHandleOfProcess]push eax ; 保留进程句柄值(传出参数)call ds:__imp__NtCreateProcessEx@36 ; 创建进程(x,x,x,x,x,x,x,x,x)
三. 0环创建进程的分析:
在NtCreateProcessEx中, 首先是判断父进程是否存在,如果不存在,就返回一个STATUS_INVALID_PARAMETER错误码(即无效参数),否则调用PspCreateProcess, 参数都没有变,直接传递给该函数。
1. 在PspCreateProcess函数中,首先保存当前线程运行的前一个模式(r0/r3), 通过KTHREAD->PreviousMode可以得到前一个模式。
代码:
mov eax, large fs:124h ; eax保存了指向KTHREAD结构的指针mov [ebp+pKthread], eaxmov cl, [eax+_KTHREAD.PreviousMode] ; 线程的前一个模式mov [ebp+PreviousMode], cl
2. 通过参数ParentProcess调用ObReferenceObjectByHandle()函数得到父进程对象的指针)
代码:
push esi ; HandleInformationlea eax, [ebp+pParentObject] ; eax 为EPROCESS局部变量的地址(作传出参数)push eax ; Objectpush dword ptr [ebp+PreviousMode] ; AccessModepush _PsProcessType ; ObjectTypepush 80h ; DesiredAccesspush [ebp+ParentProcessHandle] ; Handlecall _ObReferenceObjectByHandle@24 ; 通过句柄得到父进程对象指针
3. 通过ObCreateObject函数创建新进程对象并将对象内容初始化为0.
代码:
lea eax, [ebp+pNewProcessObj]push eax ; 新对象指针push esi ; intpush esi ; 对象的大小push 260h ; paseContextpush esi ; intpush dword ptr [ebp+PreviousMode] ; intpush [ebp+ObjectAttributes] ; 对象属性push _PsProcessType ; 对象类型push dword ptr [ebp+PreviousMode] ; 处理器模式call _ObCreateObject@36 ; 创建进程对象 mov edi, eaxcmp edi, esijl loc_4AAB6B ; 判断ObCreateObject函数调用是否成功mov ecx, 98hxor eax, eaxmov ebx, [ebp+pNewProcessObj]mov edi, ebxrep stosd ; 将新建对象初始化为0
4. 通过参数SectionHandle调用ObReferenceObjectByHandle函数得到区对象指针,当然前提是SectionHandle有效,然后将区对象指针赋值给新进程EPROCESS的相应域中,接着就判断参数DebugPort是否为0, 当不为0时:
代码:
cmp [ebp+SectionHandle], esijz loc_4F449F ; 判断区对象句柄是否为0push esi ; HandleInformationeax, [ebp+pSectionObj]push eax ; 内存区对象指针push dword ptr [ebp+PreviousMode] ; 处理器模式push _MmSectionObjectType ; 对象类型push 8 ; 访问掩码push [ebp+SectionHandle] ; 内存去句柄call _ObReferenceObjectByHandle@24 ; 通过区对象句柄得到区对象指针mov eax, [ebp+HandleInformation]mov [ebx+_EPROCESS.SectionObject], eax ; pNewProcessObject->SectionObject = 内存区对象指针cmp [ebp+DebugPort], esijnz IsDebugger ; 判断参数DebugPort是否为0IsDebugger:(DebugPort不为0时)push esi ; HandleInformationlea eax, [ebp+pDebugObj]push eax ; 新调试对象指针push dword ptr [ebp+PreviousMode] ; 处理器模式push _DbgkDebugObjectType ; 对象类型push 2 ; 访问模式push [ebp+DebugPort] ; 调试端口句柄call _ObReferenceObjectByHandle@24 ; 通过调试对象句柄得到调试对象指针
以上部分就是通过参数来得到所需要的对象指针。
5. 接着调用PspInitializeProcessSecurity函数来设置新进程的安全属性, 主要是设置新进程的安全令牌对象(从父进程拷贝):
代码:
push ebx ; 新进程对象push [ebp+ParentObj] ; 父进程对象call _PspInitializeProcessSecurity@8 ; 初始进程的安全属性(设置新进程对象的令牌对象)
代码:
push 1 ; TOKEN是否为活动lea eax, [ebp+8]push eax ; eax指向父进程对象的指针push edi ; 父进程对象中的令牌对象call _SeSubProcessToken@12 ; 将父进程的令牌对象赋值给新进程对象
代码:
lea eax, [ebp+pDirTableBase]push eax ; 页目录表基地指针push ebx ; 新进程对象push [ebp+MinimumWorkingSet] ; 最小工作集call _MmCreateProcessAddressSpace@12 ; 创建进程地址空间(并构建页目录表,页表 及物理页的关系)
代码:
push eaxlea eax, [ebp+pDirTableBase]push eax ; 页目录表基地址push [ebp+Affinity] ; 亲和性push 8 ; 进程基本优先级push ebx ; 新进程对象call _KeInitializeProcess@20 ;
代码:
push ebx ; 新进程对象mov eax, [ebp+CreateFlag]and al, 4neg alsbb eax, eaxand eax, [ebp+ParentObj]push eax ; 父进程对象call _ObInitProcess@8 ; 初始化新进程对象的对象表(如果父进程被指定,父进程的对象表拷贝到新进程中, 对象表 的每个对象中HandleCount域都+1)
该函数的实现中调用了KiAttachProcess函数来实现进程的切换(将当前线程挂靠到新进程中),以及初始化EPROCESS中的部分域和PFN,工作集列表等。
代码:
lea eax, [ebx+_EPROCESS.SeAuditProcessCreationInfo]push eax ; 对象名称信息push [ebp+HandleInformation] ; 映射节信息push esi ; 克隆进程push ebx ; 需要被初始化的新进程对象call _MmInitializeProcessAddressSpace@16 ; 这个函数WRK和XP的不一样,XP的只有4个参数,WRK中有5个参数
代码:
push esi ; 需要映射DLL的基地址push ebx ; 新进程对象call _PspMapSystemDll@8 ; 映射新进程对象的系统DLL(NTDLL.DLL)
代码:
lea eax, [ebp+pMapAddress]push eax ; 映射到那个地址push [ebp+pNewObj] ; 进程对象(映射到那个进程)push ebx ; 系统DLL映射节的地址call _MmMapViewOfSection@40 ; 映射节区
代码:
mov edi, [ebx+_EPROCESS.Token] ; 取得pNewProcessObject->Tokenand edi, 0FFFFFFF8h ; 取消Token中的低3位push ebx ; 新进程对象call _MmGetSessionId@4 ; 返回指定进程的会话IDpush eax ; 新进程对象的会话IDpush edi ; 新进程令牌对象call _SeSetSessionIdToken@8 ; 设置指定Token->SessionIdmov [ebp+var_70], ebxmov [ebp+var_6C], esilea eax, [ebp+var_70]push eax ; eax = pNewProcessObjectpush _PspCidTable ; 该表保存了所有进程和线程对象的指针call _ExCreateHandle@8 ; 在PspCidTable中添加一项
接着调用MiCreatePebOrTeb创建PEB/TEB,(该函数通过ExAllocatePoolWithTag来申请_MMVAD结构大小的空间,通过MiFindEmptyAddressRangeDownTree函数在VAD树中查找一块未被使用的地址空间范围,并返回该范围的起始地址, 最后通过MiInsertVad函数将申请的地址空间插入到VAD树中。), 在创建PEB结构后,初始化PEB中部分域的值(镜像基地址,操作系统编译号等域),最后调用KeDetachProcess函数使线程回到原来的线程中。到此创建PEB完成。
代码:
lea eax, [ebx+_EPROCESS.Peb]push eax ; 指向PEB结构的指针(传出参数)lea eax, [ebp+pInitPeb]push eax ; 初始PEBpush ebx ; 新进程对象call MmCreatePeb@12; 创建PEB(将线程切换到目标进程后,创建PEB结构及初始化部分域, mov esi, [ebp+pNewObj]push esi ; 新进程对象(附加到那个进程)call _KeAttachProcess@4 ; KeAttachProcess(x)lea eax, [ebp+pPeb]push eax ; 指向PEB 对象的地址(传出参数)push 210h ; 创建PEB/TEB的大小push esi ; 进程对象结构EPROCESS的指针call _MiCreatePebOrTeb@12 ; 创建lea eax, [ebp+Size]push eax ; 数据目录大小push 0Ah ; 目录表项索引push 1 ; 如果不为0, 作为镜像映射mov eax, [ebp+pPeb]push dword ptr [eax+8] ; ImageBase(镜像文件或者数据文件的基地址)call _RtlImageDirectoryEntryToData@16 ; 将一个镜像文件中IMAGE_DIRECTORY_LOAD_CONFIG节, 在该文件的那个节区,返回该节区对象指针。
代码:
mov ecx, _PsProcessTypeadd ecx, 68hpush ecx ; GenericMappingpush [ebp+AccessMask] ; 访问掩码lea ecx, [ebp+var_B8]push ecx ; 访问辅助结构指针PAUX_ACCESS_DATAlea ecx, [ebp+var_12C]push ecx ; 访问状态(传出参数)push eax ; 进程对象push esi ; 线程对象call _SeCreateAccessStateEx@24lea eax, [ebp+pHandle]push eax ; 接受返回新对象的句柄push esi ; 接受返回新对象的的指针push 1 ; 对象指针计数push [ebp+AccessMask] ; 访问掩码lea eax, [ebp+AccessState]push eax ; 访问状态push ebx ; 新进程对象call _ObInsertObject@24 ; 插入一个对象到进程的句柄表,并返回该对象的句柄值
最后分析下KeAttachProcess:
KeAttachProcess的实现,KeAttachProcess函数实际上是对KiAttachProcess函数的封装,只是在该函数总作了些参数的检查,首先判断当前线程挂接的进程是否为当前进程,如果是退出该函数。接着 判断当前线程的APC与DPC状态是否为活动的, 如果存在上述的情况的话,会系统崩溃然后蓝屏, 我认为这应该为了防止在切换进程时被其他更高级的优先级抢占。
代码:
mov eax, large fs:124hmov edi, [ebp+pNewProcessObj]mov esi, eax ; KTHREADcmp [esi+_KTHREAD.ApcState.Process], edi ; 判断是否为当前进程jz short Exitcmp [esi+_KTHREAD.ApcStateIndex], 0jnz loc_445F94 ; 判断APC是否为活动mov eax, large fs:994h ; _KPRCB->DpcRoutineActivetest eax, eaxjnz loc_445F94 ; 判断DPC状态是否为活动
代码:
push eaxpush [ebp+pNewProcessObj]push edi ; 进程对象push esi ; 线程对象call _KiAttachProcess@16 ; 将当前线程附加到目标进程的地址空间中
代码:
lea edi, [esi+_KTHREAD.ApcState.ApcListHead.Flink]push edi ;原始APCcall _KiMoveApcState@8 ; 保存当前线程的APC状态到ApcSaveState
最后附上PspCreateProcess函数的执行流程图:
四. 3环创建线程的分析:
在创建进程返回后,此时EPROCESS,PEB结构已经创建,进程地址空间也已经创建并初始化了。接下处理下创建进程后的残余工作,调用NtSetInformationProcess函数设置进程的优先级和默认处理模式.
代码:
push 2 ; 进程信息的的大小lea eax, [ebp+pProcessInforBuffer]push eax ; 进程信息push 12h ; 进程信息类型索引(12 = ProcessPriorityClass)push [ebp+pHandleOfProcess] ; 进程句柄call ds:__imp__NtSetInformationProcess@16 ; 设置进程信息mov [ebp+pInforBuffer], edipush 4 ; 进程信息的长度lea eax, [ebp+pInforBuffer]push eax ; 进程信息的Bufferpush 0Ch ; 设置那个进程信息的索引(0c = ProcessDefaultHardErrorMode)push [ebp+pHandleOfProcess] ; 进程句柄call ds:__imp__NtSetInformationProcess@16 ; 设置进程信息
接下来要做的就是创建线程, 不过在在此之前还要构建线程的环境,调用BaseCreateStack函数创建栈:
代码:
lea ecx, [ebp+InitialTeb]push ecx ; 初始TEBpush eax ; 栈的最大值push [ebp+StackSize] ; 栈大小push [ebp+pHandleOfProcess] ; 进程句柄call _BaseCreateStack@16 ; 创建栈
代码:
push ebx ; 基本上下文类型push [ebp+InitSp] ; 初始化栈指针值push dword ptr [ebp-834h] ; [ebp-834] = InitPc(初始化计数值)push [ebp+Param] ; teblea eax, [ebp+pThreadContext]push eax ; eax为指向CONTEXT的指针call _BaseInitializeContext@20
代码:
BaseProcessStart@4: ; CODE XREF: BaseProcessStartThunk(x,x)+5 j7C817054 push 0Ch7C817056 push offset stru_7C8170807C81705B call __SEH_prolog ;安装异常注册项7C817060 and dword ptr [ebp-4], 07C817064 push 47C817066 lea eax, [ebp+8]7C817069 push eax7C81706A push 97C81706C push 0FFFFFFFEh7C81706E call ds:__imp__NtSetInformationThread@16 ; 7C817074 call dword ptr [ebp+8] ; 调用启动函数(CALL OEP)7C817077 push eax ; dwExitCode7C817078 call _ExitThread@4 ; ExitThread(x)
最后调用NtCreateThread创建线程:
代码:
push 1 ; 是否创建后挂起lea eax, [ebp+InitialTeb]push eax ; 初始化TEBlea eax, [ebp+pThreadContext]push eax ; 线程上下文结构指针lea eax, [ebp+pClientId]push eax ; 客户端ID结构指针push [ebp+pHandleOfProcess] ; 进程句柄push [ebp+ObjAttibute] ; 对象属性push 1F03FFh ; 访问掩码lea eax, [ebp+pThreadHandle]push eax ; 线程句柄指针call ds:__imp__NtCreateThread
五. 0环创建线程的分析:
0环中NtCreateThread函数直接调用pspCreateThread函数:
在pspCreateThread函数中,首先检查参数StartRoutine(系统线程)是否为0, 如果为0, 将PreviousMode值为1, 否则值为0。接着调用ObCreateObject函数创建线程对象并初始化为0.
代码:
lea eax, [ebp+pThreadObj]push eax ; 新对象指针push esi ; 不可分页面大小push esi ; 可分页面的大小push 258h ; 对象体的大小push esi ; intpush dword ptr [ebp+PreviousMode] ; intpush [ebp+ObjAttribute] ; 对象属性push _PsThreadType ; 对象类型push dword ptr [ebp+PreviousMode] ; 处理器模式call _ObCreateObject@36 ; 创建线程对象mov ecx, 96hxor eax, eaxmov esi, [ebp+pThreadObj]mov edi, esirep stosd ; 初始化线程对象为0
代码:
lea eax, [ebp+pEthread]50 push eax ; 线程对象push _PspCidTable ; CID表指针call _ExCreateHandle@8 ; 在指定的句柄表中添加一个表项
代码:
lea eax, [ebp+pTeb]push eax ; 指向TEB指针(传出参数)push edi ; 客户端ID结构的指针push [ebp+InitTeb] ; 初始TEBpush ebx ; 目标进程对象call _MmCreateTeb@16 ; 创建TEB
代码:
push ebx ; 进程对象(EPROCESS)push ecx ; tebpush ecx ; 线程上下文指针push [ebp+pStartContext] ; 启动线程上下文push eax ; 启动线程函数指针push offset _PspSystemThreadStartup@8 ; 系统线程函数指针 , 当创建用户线程时,参数为PspUserThreadStartuppush ecx ; 内核栈指针push esi ; 线程对象(ETHREAD)call _KeInitThread@32 ; 初始化线程对象(初始化APC, 信号量对象,创建内核线程栈 ;初始化线程上下文)
代码:
push [ebp+pContext] ; pContext 上下文记录push [ebp+pContextStart] ; 启动线程函数上下文push [ebp+pThreadStart] ; 启动线程函数地址push [ebp+pKernelFunStart] ; 系统线程函数push esi ; 内核线程对象(KTHREAD)call _KiInitializeContextThread@20
然后调用KeStartThread函数,该函数除初始化线程对象的部分域外,还将进程对象中的线程列表中的线程函数放到线程对象中的线程队列中。接着调用了SeCreateAccessStateEx函数来创建线程的访问状态,ObInsertObject函数来将线程对象添加到进程中的对象表中.
然后调用ObGetObjectSecurity,PsReferencePrimaryToken设置进程对象的安全属性和令牌对象,以及调用SeAccessCheck函数设置线程的访问权限, 最后调用KeReadyThread函数将线程加入到进程对象中的线程就绪队列中(kprocess->ReadyListHead)
在以上调用完后,会调用内核中线程的启动函数KiThreadStartup, 该函数的实现中调用了PspUserThreadStartup,该函数初始化用户APC,将LdrInitializeThunk函数作为APC函数挂入APC队列中,最后调用KiDeliverApc发生APC, 通过中断返回3环。
切换到3环KiUserApcDispatcher,实现中调用LdrInitializeThunk来加载需要的DLL,
代码:
KiUserApcDispatcherlea edi, [esp+arg_C]pop eaxcall eax //LdrInitializeThunkpush 1push edicall _ZwContinue@8 ;加载完DLL后,调用该函数继续返回到内核
线程创建完成后调用CsrClientCallServer通知CRSS 线程创建成功,其中调用NtRequestWaitReplyPort()来等待回应,最后调用NtResumeThread()函数恢复线程的执行。
在用户空间线程启动时调用BaseProcessStartThunk, 该函数调用了BaseProcessStart函数, 这部分代码在上面已经贴出来了。
附上线程创建的流程图:
到此, 程序分析完毕,谢谢大家看完 。