windows进程/线程创建过程 --- windows操作系统学习

有了之前的对进程和线程对象的学习的铺垫后,我们现在可以开始学习windows下的进程创建过程了,我将尝试着从源代码的层次来分析在windows下创建一个进程都要涉及到哪些步骤,都要涉及到哪些数据结构。

 

1. 相关阅读材料

《windows 内核原理与分析》 --- 潘爱民

《深入解析windows操作系统(第4版,中文版)》

http://bbs.pediy.com/showthread.php?p=819417#post819417      看雪上的精华贴

http://undoc.airesoft.co.uk/      查阅windows未公开的函数的网站

 

 

由于进程的创建可以在ring3模式下,也可以在ring0模式下,所以我们分两个部分来分别说明!!

 

一 . Ring3进程创建流程

1. 简介

当一个应用程序调用某个进程创建函数,比如CreateProcess、CreateProcessAsUser、CreateProcessWithTokenW、CreateProcessWithLogonW时,一个windows进程就被创建起来了。

创建一个windows进程的过程,是由操作系统的三个部分执行一些列步骤来完成的(之后会详细介绍):

1. 客户方的windows库Kernel32.dll
2. windows执行体
3. windows子系统进程(Csrss.exe)

由于windows是多环境子系统的体系结构,因此,创建一个windows执行体进程对象(其他的子系统也可以使用),与创建一个windows进程的工作是分离的。

也就是说windows在创建进程的过程中有两大类的工作要做:

1. windows系统加入的语义
2. 执行体/内核层对象等的创建

下面概括了一下在利用windows的CreateProcess函数来创建一个进程时所涉及的主要阶段:

1. 打开将要在该进程中执行的映像文件(.exe)
2. 创建windows执行体进程对象
3. 创建初始线程(栈、执行环境、windows执行体线程对象)
4. 通知windows子系统新进程创建了,所以它可以为新进程和线程做好准备
5. 开始执行初始线程(除非指定了CREATE_SUSPENDING标志)
6. 在新进程和线程的环境中,完成地址空间中的初始化(比如加载必要的DLL),并开始执行程序

windows进程/线程创建过程 --- windows操作系统学习_第1张图片

在开始学习进程创建的详细过程之前,有几点是我们要注意的:

1. 在CreateProcess中,新进程的优先级类别是在CreationFlags参数总由单独的位来决定的,因此,我们可以为单个CreateProcess调用多个优先级类别。
windows通过选取"最低优先级类别集合",来解决为进程分配优先级类别的问题 BOOL WINAPI CreateProcess( _In_opt_ LPCTSTR lpApplicationName, _Inout_opt_ LPTSTR lpCommandLine, _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ BOOL bInheritHandles, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCTSTR lpCurrentDirectory, _In_ LPSTARTUPINFO lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInformation ); 2. 如果创建新进程时没有指定优先级类别,那么,它的优先级类别默认被设置为Normal(进程/线程优先级的问题参考 学习笔记(2)),除非创建线程的优先级类别是Idle或者Below Normal,
在这种情况下,新进程的优先级类别与创建进程时指定的的优先级相同
3. 如果为新进程指定了Real-Time优先级类别,但是该进程的调用者没有"Increase Scheduling Priority(增加调度优先级)"特权,则使用High优先级类别。换句话说,CreateProcess
不会仅仅因为调用者没有足够的特权来创建Real-Time优先级类别的进程而失败,而是自动"降低"一点,新进程只是没有Real-Time那么高的优先级而已 4. 所有的窗口都与桌面有关联,从操作系统的角度理解,桌面是一个工作区的图形表示。如果在CreateProcess中没有指定桌面,那么,该进程就与调用者的当前桌面关联在一起

 

 

2. 详细分析(CreateProcess)

 

阶段一: 打开将要被执行的映像

从最开始的地方,那就是我们点击鼠标或者在命令行中输入可执行程序的路径来进行所谓的"打开"程序。
在CreateProcess中的第一个阶段是: 先找到适当的windows映像(.exe文件),它将运行由调用者指定的可执行文件(.exe)。然后创建一个内存区对象,以便稍后将它映射到新进程的地址空间中。如果没有指定映像名称,则该命令行的第一个符号(被定义成命令行字符串中第一个空格或制表符之前的、合乎文件规范的那部分)被用作映像文件名。

windows进程/线程创建过程 --- windows操作系统学习_第2张图片

在windows XP和windows Server 2003上,CreateProcess会首先检查机器上的"软件限制策略"是否允许该映像被运行起来(《深入解析windows操作系统(第4版)》8.6软件限制策略)

如果指定的可执行文件是一个windows.exe类型的文件,那么,它可被直接使用。如果它不是一个windows.exe(例如MS-DOS、Win16、POSIX应用程序),那么CreateProcess通过一系列的步骤来找到一个windows支持映像(support image),以便运行它。这个过程是必须的,因为"非windows"应用程序不能直接被运行,相反,windows使用少数几个特定的"支持映像"中的某一个,由它负责实际运行这一个"非windows"程序。
例如:

1. 如果你试图运行一个POSIX应用程序,那么CreateProcess把它识别出来,并改变该映像,改成可以在windows可执行文件Posix.exe上运行的新映像。
2. 如果你试图运行一个MS-DOS或者Win16可执行文件,那么,被运行的映像变成了windows的可执行文件Ntvdm.exe。

总之,我们不能直接创建一个非windows进程的进程,如果windows不能找到一种方法把要激活的映像解析成一个windows进程,那么CreateProcess就会"失败"。

windows进程/线程创建过程 --- windows操作系统学习_第3张图片

(这张图以辐射状展示windows对可执行文件的类型判断并自动进行映像转换)

windows进程/线程创建过程 --- windows操作系统学习_第4张图片

(这就是为什么我们运行windows的bash脚本.bat时弹出的是cmd的窗口的原因)

阶段一属于操作系统对的工作,和CreateProcess代码本身的关系并不是很大,但是却是操作系统很重要的一步

 

 

阶段二: 检查和转换工作

在CreateProcessA函数中调用了CreateProcessInternalA,调用该函数时,增加了两个参数(一头一尾加了个零参数),在该函数里面也没看到对这两个参数的引用,而是直接传递给CreateProcessInternalW函数。

IDA->Kernel32.dll:

BOOL __stdcall CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, 
      LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment,
      LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation) {
return CreateProcessInternalA( 0, lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation, 0); }

OD->动态调试(C代码会在文章的最后给出):

windows进程/线程创建过程 --- windows操作系统学习_第5张图片

进入CreateProcessInternalA的代码中,发现了大量的代码,下面请允许我一口气把代码都贴出来,因为我觉得说什么都不如原始的windows源代码来的实在,我们一行一行地来分析这些代码的逻辑,我会尽我的能力解析代码的意思:

int __stdcall CreateProcessInternalA(int var_0, int lpApplicationName, int lpCommandLine, int lpProcessAttributes, int lpThreadAttributes, 
    int bInheritHandles, int dwCreationFlags, int lpEnvironment, int lpCurrentDirectory, int lpStartupInfo, int lpProcessInformation,
    int var_0_) { int result; // eax@2 int v13; // eax@7 int v14; // eax@10 int v15; // eax@17 int v16; // eax@19 int v17; // eax@21 int v18; // eax@24 int v19; // eax@26 int v20; // eax@30 int v21; // eax@32 signed int v22; // [sp-4h] [bp-B8h]@20 char v23; // [sp+14h] [bp-A0h]@15 int v24; // [sp+18h] [bp-9Ch]@15 unsigned __int32 v25; // [sp+1Ch] [bp-98h]@32 char Dst; // [sp+20h] [bp-94h]@3 int v27; // [sp+24h] [bp-90h]@3 int v28; // [sp+28h] [bp-8Ch]@3 int v29; // [sp+2Ch] [bp-88h]@3 char *v30; // [sp+68h] [bp-4Ch]@15 int v31; // [sp+6Ch] [bp-48h]@21 char v32; // [sp+70h] [bp-44h]@4 int v33; // [sp+74h] [bp-40h]@3 char v34; // [sp+78h] [bp-3Ch]@6 int v35; // [sp+7Ch] [bp-38h]@3 char v36; // [sp+80h] [bp-34h]@2 int v37; // [sp+84h] [bp-30h]@10 int v38; // [sp+88h] [bp-2Ch]@11 char v39; // [sp+8Ch] [bp-28h]@21 __int16 v40; // [sp+8Eh] [bp-26h]@19 int v41; // [sp+90h] [bp-24h]@21 unsigned __int16 v42; // [sp+94h] [bp-20h]@16 CPPEH_RECORD ms_exc; // [sp+9Ch] [bp-18h]@3 if ( !lpCommandLine ) { v37 = 0; v30 = &v23; v24 = 0; LABEL_3: v33 = 0; v35 = 0; _memmove(&Dst, (const void *)lpStartupInfo, 0x44u); v27 = 0; v28 = 0; v29 = 0; ms_exc.disabled = 1; if ( lpApplicationName && !Basep8BitStringToDynamicUnicodeString(&v32, lpApplicationName) || lpCurrentDirectory && !Basep8BitStringToDynamicUnicodeString(&v34, lpCurrentDirectory) ) goto LABEL_14; v13 = *(_DWORD *)(lpStartupInfo + 4); if ( v13 ) { ms_exc.disabled = 2; RtlInitAnsiString(&v42, v13); ms_exc.disabled = 1; if ( (_BYTE)NlsMbCodePageTag ) LOWORD(v15) = RtlxAnsiStringToUnicodeSize(&v42); else v15 = 2 * v42 + 2; v40 = v15; v16 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), BaseDllTag, (unsigned __int16)v15); v27 = v16; if ( !v16 ) goto LABEL_20; v41 = v16; v17 = RtlAnsiStringToUnicodeString(&v39, &v42, 0); v31 = v17; if ( v17 < 0 ) goto LABEL_34; } if ( *(_DWORD *)(lpStartupInfo + 8) ) { RtlInitAnsiString(&v42, *(_DWORD *)(lpStartupInfo + 8)); if ( (_BYTE)NlsMbCodePageTag ) LOWORD(v18) = RtlxAnsiStringToUnicodeSize(&v42); else v18 = 2 * v42 + 2; v40 = v18; v19 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), BaseDllTag, (unsigned __int16)v18); v28 = v19; if ( !v19 ) goto LABEL_20; v41 = v19; v17 = RtlAnsiStringToUnicodeString(&v39, &v42, 0); v31 = v17; if ( v17 < 0 ) { LABEL_34: v22 = v17; goto LABEL_35; } } if ( !*(_DWORD *)(lpStartupInfo + 12) ) { LABEL_10: ms_exc.disabled = 0; v14 = v37; if ( !v37 ) v14 = *((_DWORD *)v30 + 1); v38 = CreateProcessInternalW( var_0, v33, v14, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, v35, &Dst, lpProcessInformation, var_0_); goto LABEL_12; } RtlInitAnsiString(&v42, *(_DWORD *)(lpStartupInfo + 12)); if ( (_BYTE)NlsMbCodePageTag ) LOWORD(v20) = RtlxAnsiStringToUnicodeSize(&v42); else v20 = 2 * v42 + 2; v40 = v20; v25 = __readfsdword(24); v21 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v25 + 48) + 24), BaseDllTag, (unsigned __int16)v20); v29 = v21; if ( v21 ) { v41 = v21; v17 = RtlAnsiStringToUnicodeString(&v39, &v42, 0); v31 = v17; if ( v17 >= 0 ) goto LABEL_10; goto LABEL_34; } LABEL_20: v22 = -1073741801; LABEL_35: BaseSetLastNTError(v22); LABEL_14: v38 = 0; ms_exc.disabled = 0; LABEL_12: ms_exc.disabled = -1; RtlFreeUnicodeString(&v36); RtlFreeUnicodeString(&v32); RtlFreeUnicodeString(&v34); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), 0, v27); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), 0, v28); return RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), 0, v29); } result = Basep8BitStringToDynamicUnicodeString(&v36, lpCommandLine); if ( result ) goto LABEL_3; return result; }

 

1. 函数的开始首先是一大段的临时栈区变量的申请。由于是IDA反编译出来的,名字都是这个递增名字,其中有一个结构体变量CPPEH_RECORD注意一下:

CPPEH_RECORD    struc ; (sizeof=0x18, standard type)
   old_esp         dd ?
   exc_ptr         dd ?                    ; offset
   prev_er         dd ?                    ; offset
   handler         dd ?                    ; offset
   msEH_ptr        dd ?                    ; offset
   disabled        dd ?
CPPEH_RECORD    ends

 

2. 判断lpCommandLine(就是我们输入的notepad.exe这个命令)是否为空。即判断是否为0,不为0则将lpCommandLine初始化为UNICODE字符串

if ( !lpCommandLine )
{
    ...
}
result = Basep8BitStringToDynamicUnicodeString(&lpCommandLine_UNICODE, lpCommandLine);
if ( result )
    goto LABEL_3;
...

这里的Basep8BitStringToDynamicUnicodeString()函数是windows的未公开undocumented函数(在文章的开头有给出网址),它的定义如下:

BOOL WINAPI Basep8BitStringToDynamicUnicodeString ( 
    PUNICODE_STRING pConvertedStr, 
    LPCSTR pszAnsiStr 
)

接着继续检查参数(lpApplicationName / lpCurrentDirectory),我们跟随代码来到LABEL_3:

1. 先将lpStartupInfor的内容拷贝到一个局部变量lpStartupInfo_buf
2. 然后判断参数lpApplicationName是否为0,不为0则参数转换为UNICODE字符串
3. 为0则跳过转换继续判断参数lpCurrentDirectory是否为0(思考"与运算符""或运算符"在汇编代码中的表现形式,即"与运算符"的提前退出性,"或运算符"的并行检查性),
不为0则参数转换为UNICODE字符串
4. lpApplicationName和lpCurrentDirectory中只有至少有一个不为0,则这个if判断成立,继续检查参数
LABEL_3:
    ..
    _memmove(&lpStartupInfo_buf, (const void *)lpStartupInfo, 0x44u);
    .. 
    if ( lpApplicationName && !Basep8BitStringToDynamicUnicodeString(&v32, lpApplicationName)
      || lpCurrentDirectory && !Basep8BitStringToDynamicUnicodeString(&v34, lpCurrentDirectory) ) 
    ..

 

3. 继续检查参数(lpStartupInfo.lpReserved / lpStartupInfo.lpDesktop / lpStartupInfo.lpTitle),接着判断STARTUPINFOA.lpReserved域是否为0,如果不为0,则申请了一个堆空间并将STARTUPINFOA.lpReserved的ASCII字符串转换成了UNICODE,接下来判断lpStartupInfo.lpDesktop、lpStartupInfo.lpTitle。代码模式都是一样的:

if ( STARTUPINFOA.lpReserved )  //STARTUPINFOA.lpReserved
{
      ms_exc.disabled = 2;
      RtlInitAnsiString(&STARTUPINFOA.lpReserved_buf, STARTUPINFOA.lpReserved);
      ms_exc.disabled = 1;
      if ( (_BYTE)NlsMbCodePageTag )
        LOWORD(STARTUPINFOA.lpReserved_buf_len) = RtlxAnsiStringToUnicodeSize(&STARTUPINFOA.lpReserved_buf);
      else
        STARTUPINFOA.lpReserved_buf_len = 2 * STARTUPINFOA.lpReserved_buf + 2;
      STARTUPINFOA.lpReserved_buf_len_ = STARTUPINFOA.lpReserved_buf_len;
      v16 = RtlAllocateHeap(
              *(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24),
              BaseDllTag,
              (unsigned __int16)STARTUPINFOA.lpReserved_buf_len);
      v27 = v16;
      if ( !v16 )
        goto LABEL_20;
      v41 = v16;
      v17 = RtlAnsiStringToUnicodeString(&v39, &STARTUPINFOA.lpReserved_buf, 0);
      v31 = v17;
      if ( v17 < 0 )
        goto LABEL_34;
    }
    if ( *(_DWORD *)(lpStartupInfo + 8) )  //STARTUPINFOA.lpDesktop 
    {
      RtlInitAnsiString(&STARTUPINFOA.lpReserved_buf, *(_DWORD *)(lpStartupInfo + 8));
      if ( (_BYTE)NlsMbCodePageTag )
        LOWORD(v18) = RtlxAnsiStringToUnicodeSize(&STARTUPINFOA.lpReserved_buf);
      else
        v18 = 2 * STARTUPINFOA.lpReserved_buf + 2;
      STARTUPINFOA.lpReserved_buf_len_ = v18;
      v19 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 24), BaseDllTag, (unsigned __int16)v18);
      v28 = v19;
      if ( !v19 )
        goto LABEL_20;
      v41 = v19;
      v17 = RtlAnsiStringToUnicodeString(&v39, &STARTUPINFOA.lpReserved_buf, 0);
      v31 = v17;
      if ( v17 < 0 )
      {
LABEL_34:
        v22 = v17;
        goto LABEL_35;
      }
    }
    if ( !*(_DWORD *)(lpStartupInfo + 12) )  //STARTUPINFOA.lpTitle 
    {
    ...

这里附上STARTUPINFOA数据结构的定义,这个数据结构负责在创建进程的时候给操作系统传递必要的和这个进程相关的信息,非常重要

typedef struct _STARTUPINFO 
{
    DWORD cb;
    LPTSTR lpReserved;
    LPTSTR lpDesktop;
    LPTSTR lpTitle;
    DWORD dwX;
    DWORD dwY;
    DWORD dwXSize;
    DWORD dwYSize;
    DWORD dwXCountChars;
    DWORD dwYCountChars;
    DWORD dwFillAttribute;
    DWORD dwFlags;
    WORD wShowWindow;
    WORD cbReserved2;
    LPBYTE lpReserved2;
    HANDLE hStdInput;
    HANDLE hStdOutput;
    HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO; 

4. 这些参数都判断完之后,就开始调用CreateProcessInternalW(),注意,这次传入的参数全部都是UNICODE字符串了,也就是符合NT式的标准,我们知道,windows在NT系统以后把所有的API(例如CreateProcessInternalW)实现都以UNICODE实现了,而保留原本的ASCII版本的API(例如CreateProcessInternalA)只是在中间做了一个"转阶层",最终都一定要调用UNICODE版本的API,从这个例子我们可以清除地看到这点。

...
 v38 = CreateProcessInternalW(
              var_0,
              v33,
              v14,
              lpProcessAttributes,
              lpThreadAttributes,
              bInheritHandles,
              dwCreationFlags,
              lpEnvironment,
              v35,
              &lpStartupInfo_buf,
              lpProcessInformation,
              var_0_);
...

windows进程/线程创建过程 --- windows操作系统学习_第6张图片

看上面的步骤大家应该知道了其实CreateProcessInternalA函数只是对字符串参数或者结构体中包含字符串类型的域的作了检查和转换工作,然后就调用了下层函数

windows进程/线程创建过程 --- windows操作系统学习_第7张图片

 

 

 

阶段三: CreateProcessInternalW(),ring3阶段进程创建流程

在开始分析之前,先明确一点:

我们现在还处在ring3层,也就是用户模式的代码空间中,我们之前和我们接下来分析的代码在kernel32.dll中是可以逆向出源代码的,到了ring0层我们就没法直接逆向出源代码了,
心理一定要明确这一点,因为进程的创建从大的分类来看是分ring3(用户模式)和ring0(内核模式)的。

我们从CreateProcessInternalA()进入到了kernel32.dll中的关于进程创建的更底层的函数CreateProcessInternalW(),接下来分析这个函数的源代码。

请原谅我又贴出一大段"杂乱"的代码,我思考了一下,为了保持思路的完整性,还是把原生的完整代码贴出来,然后再进行代码逻辑的分析,所以可能会造成朋友们看的不舒服,请多多包涵了。

int __stdcall CreateProcessInternalW(int var_0, size_t lpApplicationName, wchar_t *lpCommandLine, int lpProcessAttributes, int lpThreadAttributes, 
    int bInheritHandles, int dwCreationFlags, int lpEnvironment, const WCHAR *lpCurrentDirectory, const void *lpStartupInfo,
    int lpProcessInformation, int var_0_) { unsigned __int16 v12; // ax@22 int v13; // eax@22 int v14; // esi@23 size_t v15; // edi@33 int v16; // eax@34 int v17; // edi@39 int v18; // eax@40 signed int v19; // esi@42 int v20; // eax@47 int v21; // eax@50 signed int v22; // edi@55 int v23; // eax@55 HMODULE v24; // eax@65 HMODULE v25; // esi@65 signed int v26; // esi@76 int v27; // eax@76 int v28; // ecx@81 int v29; // eax@2 int v30; // eax@17 int v31; // edi@89 DWORD v32; // eax@91 int v33; // ecx@97 signed int v34; // eax@101 int v35; // eax@121 int v36; // esi@123 void (__stdcall *v37)(_DWORD, _DWORD, _DWORD); // esi@127 int *v38; // esi@131 int v39; // edi@131 int v40; // eax@132 int v41; // eax@135 int v42; // ecx@136 int v43; // esi@138 int result; // eax@154 int *v45; // esi@161 int v46; // edi@161 int v47; // ecx@161 int v48; // edx@162 int v49; // ecx@163 int v50; // eax@165 int v51; // esi@167 HMODULE v52; // eax@182 int v53; // eax@182 signed int v54; // esi@198 unsigned int v55; // eax@201 const wchar_t *v56; // edi@201 int v57; // edi@206 size_t v58; // eax@209 int v59; // eax@209 wchar_t *v60; // esi@209 wchar_t *v61; // edi@226 DWORD v62; // eax@259 int v63; // esi@259 DWORD v64; // eax@261 size_t v65; // eax@270 wchar_t v66; // ax@272 HANDLE v67; // eax@297 int *v68; // eax@329 int v69; // ecx@329 int v70; // ecx@330 size_t v71; // eax@358 int v72; // esi@358 unsigned __int32 v73; // eax@377 unsigned __int32 v74; // esi@377 size_t v75; // eax@377 wchar_t *v76; // eax@377 const wchar_t *v77; // edi@378 int v78; // eax@393 int v79; // eax@398 int v80; // esi@398 signed int v81; // [sp-4h] [bp-A28h]@173 signed int v82; // [sp-4h] [bp-A28h]@179 DWORD v83; // [sp-4h] [bp-A28h]@277 int v84; // [sp-4h] [bp-A28h]@289 signed int v85; // [sp-4h] [bp-A28h]@235 int v86; // [sp+18h] [bp-A0Ch]@22 unsigned __int32 v87; // [sp+28h] [bp-9FCh]@394 char v88; // [sp+2Ch] [bp-9F8h]@14 int v89; // [sp+30h] [bp-9F4h]@182 int v90; // [sp+34h] [bp-9F0h]@47 int v91; // [sp+38h] [bp-9ECh]@103 unsigned __int32 v92; // [sp+3Ch] [bp-9E8h]@140 unsigned __int32 v93; // [sp+40h] [bp-9E4h]@192 unsigned __int32 v94; // [sp+44h] [bp-9E0h]@171 unsigned __int32 v95; // [sp+48h] [bp-9DCh]@191 unsigned __int32 v96; // [sp+4Ch] [bp-9D8h]@409 unsigned __int32 v97; // [sp+50h] [bp-9D4h]@241 unsigned int v98; // [sp+54h] [bp-9D0h]@201 unsigned __int32 v99; // [sp+58h] [bp-9CCh]@190 unsigned __int32 v100; // [sp+5Ch] [bp-9C8h]@309 unsigned __int32 v101; // [sp+60h] [bp-9C4h]@247 HANDLE v102; // [sp+64h] [bp-9C0h]@297 unsigned __int32 v103; // [sp+68h] [bp-9BCh]@189 unsigned __int32 v104; // [sp+6Ch] [bp-9B8h]@220 unsigned __int32 v105; // [sp+70h] [bp-9B4h]@418 unsigned __int32 v106; // [sp+74h] [bp-9B0h]@144 unsigned __int32 v107; // [sp+78h] [bp-9ACh]@144 unsigned __int32 v108; // [sp+7Ch] [bp-9A8h]@377 unsigned int v109; // [sp+80h] [bp-9A4h]@209 DWORD v110; // [sp+84h] [bp-9A0h]@91 int v111; // [sp+88h] [bp-99Ch]@50 DWORD v112; // [sp+8Ch] [bp-998h]@90 unsigned __int32 v113; // [sp+90h] [bp-994h]@310 unsigned __int32 v114; // [sp+94h] [bp-990h]@89 unsigned __int32 v115; // [sp+98h] [bp-98Ch]@84 unsigned __int32 v116; // [sp+9Ch] [bp-988h]@158 int v117; // [sp+A0h] [bp-984h]@34 HMODULE v118; // [sp+A4h] [bp-980h]@65 int v119; // [sp+A8h] [bp-97Ch]@248 unsigned __int32 v120; // [sp+ACh] [bp-978h]@358 unsigned __int32 v121; // [sp+B0h] [bp-974h]@269 unsigned __int32 v122; // [sp+B4h] [bp-970h]@358 unsigned __int32 v123; // [sp+B8h] [bp-96Ch]@231 unsigned __int32 v124; // [sp+BCh] [bp-968h]@358 unsigned __int32 v125; // [sp+C0h] [bp-964h]@20 unsigned __int32 v126; // [sp+C4h] [bp-960h]@352 unsigned __int32 v127; // [sp+C8h] [bp-95Ch]@144 int v128; // [sp+CCh] [bp-958h]@209 unsigned __int32 v129; // [sp+D0h] [bp-954h]@144 unsigned __int32 v130; // [sp+D4h] [bp-950h]@209 unsigned __int32 v131; // [sp+D8h] [bp-94Ch]@181 char v132; // [sp+DCh] [bp-948h]@411 int v133; // [sp+E0h] [bp-944h]@411 DWORD v134; // [sp+E4h] [bp-940h]@261 char *v135; // [sp+E8h] [bp-93Ch]@224 char *v136; // [sp+ECh] [bp-938h]@224 char *v137; // [sp+F0h] [bp-934h]@224 char *v138; // [sp+F4h] [bp-930h]@1 char *v139; // [sp+F8h] [bp-92Ch]@1 char *v140; // [sp+FCh] [bp-928h]@1 char *v141; // [sp+100h] [bp-924h]@1 char *v142; // [sp+104h] [bp-920h]@1 int *v143; // [sp+108h] [bp-91Ch]@409 const void *v144; // [sp+110h] [bp-914h]@1 int v145; // [sp+114h] [bp-910h]@193 int v146; // [sp+118h] [bp-90Ch]@1 size_t v147; // [sp+11Ch] [bp-908h]@209 int v148; // [sp+120h] [bp-904h]@1 int v149; // [sp+124h] [bp-900h]@76 int v150; // [sp+128h] [bp-8FCh]@163 int *v151; // [sp+12Ch] [bp-8F8h]@86 int v152; // [sp+130h] [bp-8F4h]@198 int v153; // [sp+134h] [bp-8F0h]@81 int v154; // [sp+138h] [bp-8ECh]@22 int v155; // [sp+13Ch] [bp-8E8h]@17 int v156; // [sp+140h] [bp-8E4h]@1 char v157; // [sp+144h] [bp-8E0h]@23 __int16 v158; // [sp+146h] [bp-8DEh]@23 int v159; // [sp+148h] [bp-8DCh]@22 int v160; // [sp+14Ch] [bp-8D8h]@1 int v161; // [sp+150h] [bp-8D4h]@208 int v162; // [sp+154h] [bp-8D0h]@1 char v163[4]; // [sp+158h] [bp-8CCh]@1 int v164; // [sp+15Ch] [bp-8C8h]@1 char v165; // [sp+163h] [bp-8C1h]@33 int v166; // [sp+164h] [bp-8C0h]@1 int v167; // [sp+168h] [bp-8BCh]@232 int v168; // [sp+16Ch] [bp-8B8h]@409 int v169; // [sp+170h] [bp-8B4h]@1 int v170; // [sp+174h] [bp-8B0h]@25 DWORD dwErrCode; // [sp+178h] [bp-8ACh]@30 int v172; // [sp+17Ch] [bp-8A8h]@259 int v173; // [sp+180h] [bp-8A4h]@161 int v174; // [sp+184h] [bp-8A0h]@25 int v175; // [sp+18Ch] [bp-898h]@38 int v176; // [sp+1B0h] [bp-874h]@25 LPCWSTR lpFileName; // [sp+1C8h] [bp-85Ch]@1 size_t v178; // [sp+1CCh] [bp-858h]@356 wchar_t v179; // [sp+1D0h] [bp-854h]@252 wchar_t *v180; // [sp+1D4h] [bp-850h]@226 int v181; // [sp+1D8h] [bp-84Ch]@1 int v182; // [sp+1DCh] [bp-848h]@88 int v183; // [sp+1E0h] [bp-844h]@1 int v184; // [sp+1E4h] [bp-840h]@1 int v185; // [sp+1E8h] [bp-83Ch]@1 int v186; // [sp+1ECh] [bp-838h]@70 int v187; // [sp+1F0h] [bp-834h]@55 unsigned int v188; // [sp+1F8h] [bp-82Ch]@101 int v189; // [sp+1FCh] [bp-828h]@103 int v190; // [sp+200h] [bp-824h]@61 unsigned __int16 v191; // [sp+204h] [bp-820h]@63 unsigned __int16 v192; // [sp+206h] [bp-81Eh]@63 char v193; // [sp+20Dh] [bp-817h]@56 unsigned __int16 v194; // [sp+210h] [bp-814h]@59 int v195; // [sp+220h] [bp-804h]@72 wchar_t *Dest; // [sp+224h] [bp-800h]@25 int v197; // [sp+228h] [bp-7FCh]@1 int v198; // [sp+22Ch] [bp-7F8h]@25 char v199; // [sp+230h] [bp-7F4h]@87 int v200; // [sp+234h] [bp-7F0h]@88 wchar_t *v201; // [sp+248h] [bp-7DCh]@1 LPWSTR Str1; // [sp+24Ch] [bp-7D8h]@25 char v203; // [sp+253h] [bp-7D1h]@30 int v204; // [sp+254h] [bp-7D0h]@1 int v205; // [sp+258h] [bp-7CCh]@37 int v206; // [sp+25Ch] [bp-7C8h]@37 int *v207; // [sp+260h] [bp-7C4h]@37 int v208; // [sp+264h] [bp-7C0h]@37 int v209; // [sp+268h] [bp-7BCh]@37 int v210; // [sp+26Ch] [bp-7B8h]@37 char v211; // [sp+270h] [bp-7B4h]@103 int v212; // [sp+278h] [bp-7ACh]@104 int i; // [sp+284h] [bp-7A0h]@81 char v214; // [sp+28Ah] [bp-79Ah]@122 char v215; // [sp+28Bh] [bp-799h]@1 int v216; // [sp+28Ch] [bp-798h]@188 int v217; // [sp+290h] [bp-794h]@1 int v218; // [sp+294h] [bp-790h]@1 int v219; // [sp+298h] [bp-78Ch]@1 int v220; // [sp+29Ch] [bp-788h]@1 int v221; // [sp+2A0h] [bp-784h]@1 int v222; // [sp+2A4h] [bp-780h]@1 int v223; // [sp+2A8h] [bp-77Ch]@1 int v224; // [sp+2ACh] [bp-778h]@1 int v225; // [sp+2B0h] [bp-774h]@86 int v226; // [sp+2B4h] [bp-770h]@375 int v227; // [sp+2B8h] [bp-76Ch]@375 int v228; // [sp+2BCh] [bp-768h]@375 int v229; // [sp+2C0h] [bp-764h]@1 int v230; // [sp+2C4h] [bp-760h]@1 int v231; // [sp+2C8h] [bp-75Ch]@1 int v232; // [sp+2CCh] [bp-758h]@1 int v233; // [sp+2D0h] [bp-754h]@1 int v234; // [sp+2D4h] [bp-750h]@1 char v235; // [sp+2DBh] [bp-749h]@1 int v236; // [sp+2DCh] [bp-748h]@37 int v237; // [sp+2E0h] [bp-744h]@33 int v238; // [sp+2E4h] [bp-740h]@176 int v239; // [sp+2E8h] [bp-73Ch]@36 int v240; // [sp+2ECh] [bp-738h]@1 int v241; // [sp+2F0h] [bp-734h]@1 int v242; // [sp+2F4h] [bp-730h]@1 int v243; // [sp+2F8h] [bp-72Ch]@1 int v244; // [sp+2FCh] [bp-728h]@1 int v245; // [sp+300h] [bp-724h]@1 wchar_t *Source; // [sp+304h] [bp-720h]@1 size_t Size; // [sp+308h] [bp-71Ch]@1 char v248; // [sp+30Eh] [bp-716h]@25 char v249; // [sp+30Fh] [bp-715h]@1 char v250; // [sp+310h] [bp-714h]@37 char v251; // [sp+31Bh] [bp-709h]@30 char *v252; // [sp+31Ch] [bp-708h]@1 char *v253; // [sp+320h] [bp-704h]@1 char *v254; // [sp+324h] [bp-700h]@1 char *v255; // [sp+328h] [bp-6FCh]@1 int *v256; // [sp+32Ch] [bp-6F8h]@1 int *v257; // [sp+330h] [bp-6F4h]@1 int v258; // [sp+334h] [bp-6F0h]@1 int v259; // [sp+338h] [bp-6ECh]@35 int v260; // [sp+33Ch] [bp-6E8h]@106 int v261; // [sp+340h] [bp-6E4h]@107 char v262; // [sp+344h] [bp-6E0h]@324 int v263; // [sp+348h] [bp-6DCh]@3 char v264; // [sp+34Ch] [bp-6D8h]@324 int v265; // [sp+350h] [bp-6D4h]@3 char v266; // [sp+354h] [bp-6D0h]@99 wchar_t *v267; // [sp+358h] [bp-6CCh]@25 int v268; // [sp+35Ch] [bp-6C8h]@1 int v269; // [sp+360h] [bp-6C4h]@181 __int16 v270; // [sp+364h] [bp-6C0h]@154 __int16 v271; // [sp+366h] [bp-6BEh]@358 wchar_t *v272; // [sp+368h] [bp-6BCh]@25 int v273; // [sp+36Ch] [bp-6B8h]@33 int v274; // [sp+370h] [bp-6B4h]@34 char v275; // [sp+377h] [bp-6ADh]@30 char v276; // [sp+378h] [bp-6ACh]@120 char v277; // [sp+37Ch] [bp-6A8h]@67 int v278; // [sp+380h] [bp-6A4h]@370 LPWSTR FilePart; // [sp+384h] [bp-6A0h]@25 int v280; // [sp+388h] [bp-69Ch]@25 int v281; // [sp+38Ch] [bp-698h]@1 int v282; // [sp+390h] [bp-694h]@1 int v283; // [sp+394h] [bp-690h]@1 int v284; // [sp+398h] [bp-68Ch]@1 int v285; // [sp+39Ch] [bp-688h]@1 int v286; // [sp+3A0h] [bp-684h]@25 int v287; // [sp+3A4h] [bp-680h]@1 int v288; // [sp+3A8h] [bp-67Ch]@25 int v289; // [sp+3ACh] [bp-678h]@25 int v290; // [sp+3B0h] [bp-674h]@1 int v291; // [sp+3B4h] [bp-670h]@25 int v292; // [sp+3B8h] [bp-66Ch]@25 char v293; // [sp+3BCh] [bp-668h]@10 char v294; // [sp+3BDh] [bp-667h]@9 char v295; // [sp+3C0h] [bp-664h]@104 char v296; // [sp+68Ch] [bp-398h]@115 int v297; // [sp+6ACh] [bp-378h]@116 int v298; // [sp+6B4h] [bp-370h]@107 int v299; // [sp+6B8h] [bp-36Ch]@107 int v300; // [sp+6BCh] [bp-368h]@107 int v301; // [sp+6C0h] [bp-364h]@107 int v302; // [sp+6C4h] [bp-360h]@109 int v303; // [sp+6C8h] [bp-35Ch]@109 int v304; // [sp+6CCh] [bp-358h]@109 int v305; // [sp+6D0h] [bp-354h]@113 int v306; // [sp+6D4h] [bp-350h]@395 int v307; // [sp+6D8h] [bp-34Ch]@395 int v308; // [sp+6DCh] [bp-348h]@1 char v309; // [sp+6E8h] [bp-33Ch]@224 char v310; // [sp+710h] [bp-314h]@224 char v311; // [sp+716h] [bp-30Eh]@329 char v312; // [sp+734h] [bp-2F0h]@224 __int64 v313; // [sp+73Ch] [bp-2E8h]@107 signed __int16 v314; // [sp+744h] [bp-2E0h]@108 char v315; // [sp+74Ch] [bp-2D8h]@1 int v316; // [sp+760h] [bp-2C4h]@81 char v317; // [sp+770h] [bp-2B4h]@1 int v318; // [sp+784h] [bp-2A0h]@81 char v319; // [sp+794h] [bp-290h]@1 int v320; // [sp+7A8h] [bp-27Ch]@81 char v321; // [sp+7B8h] [bp-26Ch]@1 int v322; // [sp+7CCh] [bp-258h]@81 char v323; // [sp+7DCh] [bp-248h]@1 int v324; // [sp+7F0h] [bp-234h]@81 __int16 v325; // [sp+800h] [bp-224h]@57 CPPEH_RECORD ms_exc; // [sp+A0Ch] [bp-18h]@25 int v327; // [sp+A44h] [bp+20h]@2 v185 = var_0; Size = lpApplicationName; Source = lpCommandLine; v169 = lpProcessAttributes; v164 = lpThreadAttributes; v234 = lpEnvironment; lpFileName = lpCurrentDirectory; v144 = lpStartupInfo; v166 = lpProcessInformation; v148 = var_0_; v290 = 0; v201 = 0; v287 = 0; v233 = 0; v204 = 0; v160 = 0; v146 = dwCreationFlags & 0x8000000; v235 = 0; v162 = 0; v284 = 0; v282 = 0; v249 = 0; v285 = 0; v281 = 0; v283 = 0; v181 = 0; v138 = &v317; v139 = &v321; v140 = &v323; v141 = &v315; v142 = &v319; v229 = 0; v230 = 0; v231 = 0; v232 = 0; v221 = 0; v222 = 0; v223 = 0; v224 = 0; v256 = &v268; v257 = &v258; v252 = &v317; v253 = &v315; v254 = &v321; v255 = &v319; v217 = 0; v218 = 0; v219 = 0; v220 = 0; v197 = 0; v156 = 0; v184 = 0; v183 = 0; *(_DWORD *)v163 = 0; v243 = 0; v244 = 0; v245 = 0; v240 = 0; v241 = 0; v242 = 0; v215 = 0; memset(&v308, 0, 0x60u); *(_DWORD *)lpProcessInformation = 0; *(_DWORD *)(lpProcessInformation + 4) = 0; *(_DWORD *)(lpProcessInformation + 8) = 0; *(_DWORD *)(lpProcessInformation + 12) = 0; if ( var_0_ ) *(_DWORD *)var_0_ = 0; v29 = dwCreationFlags & 0xF7FFFFFF; v327 = v29; if ( (v29 & 0x18) == 24 ) goto LABEL_279; v265 = 0; v263 = 0; if ( v29 & 0x40 ) { v294 = 1; } else { if ( BYTE1(v29) & 0x40 ) { v294 = 5; } else { if ( v29 & 0x20 ) { v294 = 2; } else { if ( SBYTE1(v29) < 0 ) { v294 = 6; } else { if ( (char)v29 < 0 ) { v294 = 3; } else { if ( BYTE1(v29) & 1 ) v294 = (BasepIsRealtimeAllowed(0) != 0) + 3; else v294 = 0; } } } } } v293 = 0; LOWORD(v327) = v327 & 0x3E1F; if ( v327 & 0x800 ) { if ( !(v327 & 0x1000) ) goto LABEL_13; LABEL_279: SetLastError(0x57u); return 0; } if ( !(v327 & 0x1000) && *(_BYTE *)(BaseStaticServerData + 6644) ) v327 |= 0x800u; LABEL_13: if ( !(v327 & 0x800) && NtQueryInformationJobObject(0, 4, &v88, 4, 0) != -1073741790 ) v327 = v327 & 0xFFFFEFFF | 0x800; if ( !v234 || BYTE1(v327) & 4 ) goto LABEL_25; v30 = v234; v155 = v234; while ( *(_BYTE *)v30 || *(_BYTE *)(v30 + 1) ) ++v30; LOWORD(v154) = v30 - (_WORD)v234 + 1; v12 = v30 - (_WORD)v234 + 2; HIWORD(v154) = v12; v86 = 2 * v12; v159 = 0; v13 = NtAllocateVirtualMemory(-1, &v159, 0, &v86, 4096, 4); if ( v13 < 0 ) { v84 = v13; LABEL_290: BaseSetLastNTError(v84); return 0; } v158 = v86; v14 = RtlAnsiStringToUnicodeString(&v157, &v154, 0); if ( v14 < 0 ) { NtFreeVirtualMemory(-1, &v159, &v86, 32768); v84 = v14; goto LABEL_290; } v234 = v159; LABEL_25: v289 = 0; v291 = 0; v292 = 0; v288 = 0; v198 = 0; Str1 = 0; v267 = 0; v280 = 1; v286 = 0; v170 = 0; FilePart = 0; v272 = 0; v248 = 0; Dest = 0; ms_exc.disabled = 0; memcpy(&v174, v144, 0x44u); if ( BYTE1(v176) & 1 && BYTE1(v176) & 6 ) BYTE1(v176) &= 0xFEu; while ( 1 ) { if ( Str1 ) { v123 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v123 + 48) + 24), 0, Str1); Str1 = 0; } if ( v198 ) { v104 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v104 + 48) + 24), 0, v198); v198 = 0; } if ( v289 ) { NtClose(v289); v289 = 0; } dwErrCode = 0; v203 = 1; v251 = 0; v275 = 0; if ( Size ) { if ( !Source || !*Source ) { v275 = 1; Source = (wchar_t *)Size; } goto LABEL_33; } v121 = __readfsdword(24); Str1 = (LPWSTR)RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v121 + 48) + 24), BaseDllTag, 520); if ( !Str1 ) goto LABEL_293; v65 = (size_t)Source; Size = (size_t)Source; v201 = Source; v61 = Source; v180 = Source; if ( *Source != 34 ) goto LABEL_271; v203 = 0; v61 = Source + 1; v180 = Source + 1; Size = (size_t)(Source + 1); while ( *v61 ) { if ( *v61 == 34 ) { v201 = v61; v248 = 1; break; } ++v61; v180 = v61; v201 = v61; } LABEL_259: v179 = *v201; *v201 = 0; v62 = SearchPathW(0, (LPCWSTR)Size, L".exe", 0x104u, Str1, 0); v63 = 2 * v62; v172 = 2 * v62; if ( 2 * v62 && (unsigned int)v63 < 0x208 ) { v64 = GetFileAttributesW(Str1); v134 = v64; if ( v64 != -1 && v64 & 0x10 ) { v63 = 0; } else { v172 = v63 + 1; v63 += 2; } v172 = v63; } if ( !v63 || (unsigned int)v63 >= 0x208 ) { v119 = RtlDetermineDosPathNameType_U(Size); if ( v119 == 5 ) goto LABEL_249; v67 = CreateFileW((LPCWSTR)Size, 0x80000000u, 3u, 0, 3u, 0x80u, 0); v102 = v67; if ( v67 != (HANDLE)-1 ) { CloseHandle(v67); LABEL_249: BaseSetLastNTError(-1073741772); } if ( dwErrCode ) SetLastError(dwErrCode); else dwErrCode = GetLastError(); *v201 = v179; Size = (size_t)Str1; if ( !*v61 || !v203 ) goto LABEL_173; ++v61; v180 = v61; v201 = v61; v251 = 1; v248 = 1; v65 = (size_t)Source; LABEL_271: Size = v65; while ( 1 ) { v66 = *v61; if ( !*v61 ) goto LABEL_259; if ( v66 == 32 || v66 == 9 ) { v201 = v61; goto LABEL_259; } ++v61; v180 = v61; v201 = v61; } } *v201 = v179; Size = (size_t)Str1; if ( BasepIsSetupInvokedByWinLogon(Str1) && !(BYTE3(v327) & 0x80) ) BYTE3(v327) |= 0x80u; LABEL_33: v15 = Size; v165 = RtlDosPathNameToNtPathName_U(Size, &v273, 0, &v237); if ( !v165 ) { v83 = 3; goto LABEL_278; } v198 = v274; RtlInitUnicodeString(&v268, v15); v16 = RtlDetermineDosPathNameType_U(v15); v117 = v16; if ( v16 != 2 && v16 != 1 ) { if ( !v197 ) { v94 = __readfsdword(24); v197 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v94 + 48) + 24), 0, 522); if ( !v197 ) { v83 = 8; goto LABEL_278; } } RtlGetFullPathName_U(v15, 522, v197, 0); RtlInitUnicodeString(&v268, v197); } v258 = v273; v259 = v274; if ( (_WORD)v237 ) { v273 = v237; v274 = v238; } else { v239 = 0; } v205 = 24; v206 = v239; v208 = 64; v207 = &v273; v209 = 0; v210 = 0; v236 = NtOpenFile(&v289, 1048737, &v205, &v250, 5, 96); if ( v236 < 0 ) { v236 = NtOpenFile(&v289, 1048608, &v205, &v250, 5, 96); if ( v236 < 0 ) break; } if ( !v175 ) { v115 = __readfsdword(24); v175 = *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v115 + 48) + 16) + 124); } v17 = NtCreateSection(&v291, 983071, 0, 0, 16, 16777216, v289); v236 = v17; if ( v17 < 0 ) goto LABEL_425; v18 = BasepIsProcessAllowed(Size); v17 = v18; v236 = v18; if ( v18 < 0 ) { BaseSetLastNTError(v18); NtClose(v291); goto LABEL_173; } if ( BYTE1(v327) & 0x20 && *(_BYTE *)(BaseStaticServerData + 6645) ) { BYTE1(v327) &= 0xCFu; v19 = 2048; v327 |= 0x800u; v17 = -1073741519; v236 = -1073741519; v160 = 1; NtClose(v291); v291 = 0; } else { LABEL_425: v19 = 2048; } if ( !v249 ) { if ( v17 >= 0 || v17 == -1073741521 && !BaseIsDosApplication(&v273, -1073741521) ) { if ( v284 ) { v100 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v100 + 48) + 24), 0, v284); v284 = 0; } if ( v285 ) { v113 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v113 + 48) + 24), 0, v285); v285 = 0; } v20 = BasepCheckBadapp(v289, v274, v234, &v284, &v282, &v285, &v281); v90 = v20; if ( v20 < 0 ) { if ( v20 == -1073741790 ) SetLastError(0x4C7u); else BaseSetLastNTError(v20); if ( v291 ) { NtClose(v291); v291 = 0; } goto LABEL_173; } } if ( !v249 && !(BYTE3(v327) & 2) ) { v21 = BasepCheckWinSaferRestrictions(v185, Size, v289, &v156, &v183, &v184); v111 = v21; if ( v21 == -1 ) { SetLastError(0x4ECu); } else { if ( v21 >= 0 ) goto LABEL_52; BaseSetLastNTError(v21); } v214 = 0; goto LABEL_126; } } LABEL_52: if ( v17 >= 0 ) goto LABEL_53; if ( v17 == -1073741541 ) goto LABEL_198; if ( v17 <= -1073741522 ) goto LABEL_277; if ( v17 <= -1073741520 ) { LABEL_198: v54 = 1; v152 = 1; if ( v17 != -1073741520 ) { if ( v17 != -1073741541 ) { v54 = BaseIsDosApplication(&v273, v17); v152 = v54; if ( !v54 ) { v55 = (unsigned int)(unsigned __int16)v273 >> 1; v56 = (const wchar_t *)(v274 + 2 * v55 - 8); v98 = v274 + 2 * v55 - 8; if ( (unsigned __int16)v273 < 8u || __wcsnicmp((const wchar_t *)(v274 + 2 * v55 - 8), L".bat", 4u) && __wcsnicmp(v56, L".cmd", 4u) ) goto LABEL_277; v57 = v275 || v248; if ( v275 || (v161 = 0, v248) ) v161 = 1; v147 = _wcslen(Source); v58 = _wcslen(Str); v109 = v161 + v147 + v58 + v57 + 1; v130 = __readfsdword(24); v59 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v130 + 48) + 24), BaseDllTag, 2 * v109); v60 = (wchar_t *)v59; v128 = v59; if ( !v59 ) goto LABEL_293; _wcscpy((wchar_t *)v59, Str); if ( v275 || v248 ) _wcscat(v60, L"\""); _wcscat(v60, Source); if ( v275 || v248 ) _wcscat(v60, L"\""); RtlInitUnicodeString(&v270, v60); LABEL_215: Source = v272; Size = 0; goto LABEL_216; } } } v204 = 16; if ( !BaseCreateVDMEnvironment(v234, &v264, &v262) || !BaseCheckVDM(v54 | 0x10, Size, Source, lpFileName, &v264, &v296, &v287, v327, &v174) ) goto LABEL_173; v68 = &v298; v69 = (v311 & 7) - 1; if ( (v311 & 7) == 1 ) { v233 = 1; if ( v327 & 8 ) { v83 = 5; goto LABEL_278; } if ( !BaseGetVdmConfigInfo(Source, v287, 16, &v266, &v286) ) goto LABEL_344; Source = v267; Size = 0; LABEL_346: v35 = v290; goto LABEL_347; } goto LABEL_330; } if ( v17 == -1073741519 ) { if ( BYTE1(v327) & 0x20 ) goto LABEL_198; v235 = 1; if ( !BaseCreateVDMEnvironment(v234, &v264, &v262) ) goto LABEL_173; while ( 1 ) { v204 = (v19 & v327) != 0 ? 64 : 32; if ( BaseCheckVDM((v19 & v327) != 0 ? 64 : 32, Size, Source, lpFileName, &v264, &v296, &v287, v327, &v174) ) break; if ( v204 != 32 || GetLastError() != 5 ) goto LABEL_173; v327 |= v19; } v68 = &v298; v69 = (v311 & 7) - 1; if ( (v311 & 7) == 1 ) { v233 = 1; if ( v160 ) v286 = 1; if ( !BaseGetVdmConfigInfo(Source, v287, v204, &v266, &v286) ) { LABEL_344: v82 = v17; goto LABEL_180; } Source = v267; Size = 0; BYTE3(v327) |= 8u; v327 &= 0xFFFFFFE7u; v176 |= 0x40u; goto LABEL_346; } LABEL_330: v70 = v69 - 1; if ( !v70 ) { v83 = 21; goto LABEL_278; } if ( v70 != 2 ) goto LABEL_346; v233 = 4; v35 = v68[3]; v290 = v35; LABEL_347: --v286; if ( v35 ) goto LABEL_122; bInheritHandles = 0; if ( v234 && !(BYTE1(v327) & 4) ) RtlDestroyEnvironment(v234); v234 = v263; LABEL_216: v249 = 1; } else { if ( v17 != -1073741209 ) goto LABEL_277; SetLastError(0x10FEu); LABEL_53: if ( !v235 && v19 & v327 ) BYTE1(v327) &= 0xF7u; v22 = 1; v23 = NtQuerySection(v291, 1, &v187, 48, 0); v236 = v23; if ( v23 < 0 ) goto LABEL_242; if ( v193 & 0x20 ) goto LABEL_277; v325 = 0; if ( !(v327 & 3) || (v126 = __readfsdword(24), *(_BYTE *)(*(_DWORD *)(v126 + 48) + 1)) ) LdrQueryImageFileExecutionOptions(&v273, L"Debugger", 1, &v325, 520, 0); if ( v194 < v7FFE002C || v194 > v7FFE002E ) { v168 = 6; v143 = &v273; NtRaiseHardError(1073741859, 1, 1, &v143, 1, &v168); v96 = __readfsdword(24); if ( *(_DWORD *)(*(_DWORD *)(v96 + 48) + 184) <= 3u ) LABEL_277: v83 = 193; else v83 = 216; goto LABEL_278; } if ( v190 != 2 && v190 != 3 ) { NtClose(v291); v291 = 0; if ( v190 != 7 ) { v83 = 129; goto LABEL_278; } if ( !BuildSubSysCommandLine(L"POSIX /P ", Size, Source, &v270) ) goto LABEL_173; goto LABEL_215; } if ( !BasepIsImageVersionOk(v192, v191) ) goto LABEL_277; if ( !v325 ) { v24 = LoadLibraryA("advapi32.dll"); v25 = v24; v118 = v24; if ( v24 ) { if ( GetProcAddress(v24, "CreateProcessAsUserSecure") ) { v236 = NtQuerySystemInformation(71, &v277, 4, 0); if ( !v236 ) v215 = 1; } FreeLibrary(v25); } v186 = BaseFormatObjectAttributes(&v205, v169, 0); if ( v215 && v185 && v169 ) { v243 = *(_DWORD *)v169; v244 = *(_DWORD *)(v169 + 4); v245 = *(_DWORD *)(v169 + 8); v244 = 0; v186 = BaseFormatObjectAttributes(&v205, &v243, 0); v22 = 1; } v195 = 0; if ( BYTE3(v327) & 1 ) v195 = v22; if ( v327 & 3 ) { v23 = DbgUiConnectToDbg(); v236 = v23; if ( v23 < 0 ) goto LABEL_242; v162 = DbgUiGetThreadDebugObject(); if ( v327 & 2 ) v195 |= 2u; } if ( bInheritHandles ) v195 |= 4u; v149 = v194 == 332 ? v284 : 0; v26 = -1; v27 = NtCreateProcessEx(&v292, 2035711, v186, -1, v195, v291, v162, 0, v156); v236 = v27; if ( v27 < 0 ) goto LABEL_426; if ( v294 ) { v167 = 0; if ( v294 == 4 ) v167 = BasepIsRealtimeAllowed(v22); v236 = NtSetInformationProcess(v292, 18, &v293, 2); if ( v167 ) BasepReleasePrivilege(v167); if ( v236 < 0 ) { v85 = v236; LABEL_363: BaseSetLastNTError(v85); LABEL_364: v81 = v26; goto LABEL_174; } } if ( BYTE3(v327) & 4 ) { v145 = v22; NtSetInformationProcess(v292, 12, &v145, 4); } if ( v204 ) { v290 = v292; if ( !BaseUpdateVDMEntry(v22, &v290, v287, v204) ) { v290 = 0; goto LABEL_364; } v233 |= 2u; } if ( v286 ) { v278 = v286; v27 = NtAllocateVirtualMemory(v292, &v280, 0, &v278, 8192, 64); v236 = v27; if ( v27 < 0 ) goto LABEL_426; } v318 = (unsigned __int16)v268 + 20; v322 = (unsigned __int16)v268 + 16; v324 = (unsigned __int16)v268 + 2; v316 = (unsigned __int16)v258 + 20; v320 = (unsigned __int16)v258 + 16; v28 = 0; v153 = 0; for ( i = 0; i != 5; ++i ) { v28 += *((_DWORD *)(&v138)[4 * i] + 5); v153 = v28; } v116 = __readfsdword(24); v181 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v116 + 48) + 24), 0, v28); if ( !v181 ) { v85 = -1073741801; goto LABEL_363; } i = 0; while ( i != 5 ) { v45 = (int *)&(&v138)[4 * i]; v46 = *v45; v47 = *(_DWORD *)(*v45 + 20); v173 = *(_DWORD *)(*v45 + 20); if ( i ) v48 = *(_DWORD *)(*(v45 - 1) + 8) + *(_DWORD *)(*(v45 - 1) + 20); else v48 = v181; v150 = v48; v49 = v47 & 0xFFFFFFFE; v173 = v49; if ( (unsigned int)v49 > 0xFFFE ) { v49 = 65534; v173 = 65534; } if ( (unsigned int)v49 < 2 ) { v48 = v46 + 32; v150 = v46 + 32; v49 = 2; v173 = 2; } v50 = *v45; *(_DWORD *)(*v45 + 8) = v48; *(_DWORD *)(v50 + 16) = v49; *(_DWORD *)(v50 + 12) = v48; *(_DWORD *)(v50 + 20) = v49; *(_DWORD *)(v50 + 4) = v48; if ( v48 ) **(_WORD **)(v46 + 4) = 0; v51 = *v45; *(_WORD *)v51 = 0; *(_WORD *)(v51 + 2) = v49; ++i; v26 = -1; } v230 = v292; v229 = v289; v231 = v291; if ( v285 ) { v225 = v268; v226 = v269; v227 = v285; v228 = v281; } v151 = &v308; v27 = BasepSxsCreateProcessCsrMessage( v285 != 0 ? (int)&v225 : 0, 0, &v252, &v221, &v256, &v229, &v254, &v217, &v323, &v308); v236 = v27; if ( v27 < 0 || (v27 = NtQueryInformationProcess(v292, 0, &v199, 24, 0), v236 = v27, v27 < 0) ) { LABEL_426: v85 = v27; goto LABEL_363; } v182 = v200; if ( lpFileName ) { v114 = __readfsdword(24); v31 = RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v114 + 48) + 24), BaseDllTag, 522); v170 = v31; if ( !v31 ) { LABEL_293: v82 = -1073741801; goto LABEL_180; } v112 = GetFullPathNameW(lpFileName, 0x104u, (LPWSTR)v31, &FilePart); if ( v112 > 0x104 || (v32 = GetFileAttributesW((LPCWSTR)v31), v110 = v32, v32 == -1) || !(v32 & 0x10) ) { v83 = 267; goto LABEL_278; } } if ( v251 || v275 ) { v73 = __readfsdword(24); v74 = v73; v108 = v73; v75 = _wcslen(Source); v76 = (wchar_t *)RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v74 + 48) + 24), 0, 2 * v75 + 6); Dest = v76; if ( v76 ) { _wcscpy(v76, L"\""); v77 = v201; if ( v251 ) { v179 = *v201; *v201 = 0; } _wcscat(Dest, Source); _wcscat(Dest, L"\""); if ( v251 ) { *v77 = v179; _wcscat(Dest, v77); } } else { if ( v251 ) v251 = 0; if ( v275 ) v275 = 0; } } if ( *(_BYTE *)v151 & 1 ) *(_DWORD *)v163 |= 1u; if ( v251 || (v33 = (int)Source, v275) ) v33 = (int)Dest; if ( !BasePushProcessParameters( v163[0], v292, v182, (LPCWSTR)Size, v170, v33, v234, (int)&v174, v327 | v146, bInheritHandles, v235 != 0 ? 2 : 0, v149, v282) ) goto LABEL_173; RtlFreeUnicodeString(&v266); v267 = 0; if ( !v204 ) { if ( !bInheritHandles ) { if ( !(BYTE1(v176) & 1) ) { if ( !(v327 & 0x8000018) ) { if ( v190 == 3 ) { v236 = NtReadVirtualMemory(v292, v182 + 16, &v216, 4, 0); if ( v236 >= 0 ) { v103 = __readfsdword(24); if ( (*(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v103 + 48) + 16) + 24) & 0x10000003) != 3 ) { v101 = __readfsdword(24); StuffStdHandle(v292, *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v101 + 48) + 16) + 24), v216 + 24); } v99 = __readfsdword(24); if ( (*(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v99 + 48) + 16) + 28) & 0x10000003) != 3 ) { v97 = __readfsdword(24); StuffStdHandle(v292, *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v97 + 48) + 16) + 28), v216 + 28); } v95 = __readfsdword(24); if ( (*(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v95 + 48) + 16) + 32) & 0x10000003) != 3 ) { v93 = __readfsdword(24); StuffStdHandle(v292, *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v93 + 48) + 16) + 32), v216 + 32); } } } } } } } v34 = 262144; if ( v188 >= 0x40000 ) v34 = v188; v23 = BaseCreateStack(v292, v189, v34, &v211); v91 = v23; if ( v23 < 0 ) goto LABEL_242; BaseInitializeContext(&v295, v182, v187, v212, 0); v186 = BaseFormatObjectAttributes(&v205, v164, 0); if ( v215 && v185 && v164 ) { v240 = *(_DWORD *)v164; v241 = *(_DWORD *)(v164 + 4); v242 = *(_DWORD *)(v164 + 8); v241 = 0; v186 = BaseFormatObjectAttributes(&v205, &v240, 0); } v23 = NtCreateThread(&v288, 2032639, v186, v292, &v260, &v295, &v211, 1); v236 = v23; if ( v23 < 0 ) goto LABEL_242; v313 = v182; v298 = v292; v299 = v288; v300 = v260; v301 = v261; switch ( v194 ) { case 0x14Cu: v314 = 0; break; case 0x200u: v314 = 6; break; case 0x8664u: v314 = 9; break; default: DbgPrint("kernel32: No mapping for ImageInformation.Machine == %04x\n", v194); v314 = -1; break; } v304 = v327 & 0xFFFFFFFC; v302 = 0; v303 = 0; if ( v190 == 2 || v235 ) { v298 |= 2u; v52 = GetModuleHandleA(0); v53 = RtlImageNtHeader(v52); v89 = v53; if ( v53 ) { if ( *(_WORD *)(v53 + 92) == 2 ) v298 |= 1u; } } if ( v176 & 0x40 ) v298 |= 1u; if ( v176 & 0x80 ) v298 &= 0xFFFFFFFEu; v305 = v204; if ( v204 ) { if ( v287 ) { v78 = 0; } else { v87 = __readfsdword(24); v78 = *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v87 + 48) + 16) + 16); } v307 = v78; v306 = v287; } memcpy(&v298, &v298, 0x98u); if ( v308 ) { v135 = &v309; v136 = &v310; v137 = &v312; v23 = CsrCaptureMessageMultiUnicodeStringsInPlace(&v283, 3, &v135); v236 = v23; if ( v23 < 0 ) { LABEL_242: v82 = v23; LABEL_180: BaseSetLastNTError(v82); goto LABEL_173; } } CsrClientCallServer(&v296, v283, 65536, 152); if ( v283 ) { CsrFreeCaptureBuffer(v283); v283 = 0; } if ( v297 < 0 ) { BaseSetLastNTError(v297); NtTerminateProcess(v292, v297); goto LABEL_173; } if ( v183 ) { if ( !v185 ) { v79 = BasepReplaceProcessThreadTokens(v183, v292, v288); v80 = v79; v236 = v79; if ( v79 < 0 ) { NtTerminateProcess(v292, v79); v82 = v80; goto LABEL_180; } } } if ( v184 ) { v236 = NtAssignProcessToJobObject(v184, v292); if ( v236 < 0 ) { NtTerminateProcess(v292, -1073741790); LABEL_179: v82 = v236; goto LABEL_180; } } if ( !(v327 & 4) ) NtResumeThread(v288, &v276); v35 = v290; LABEL_122: v214 = 1; if ( v233 ) v233 |= 8u; ms_exc.disabled = 1; v36 = v166; if ( v35 ) { if ( v204 == 32 ) { *(_DWORD *)v166 = v35 | 2; if ( v233 & 4 ) { v260 = 0; v261 = 0; } } else { *(_DWORD *)v166 = v35 | 1; } if ( v292 ) NtClose(v292); } else { *(_DWORD *)v166 = v292; } *(_DWORD *)(v36 + 4) = v288; *(_DWORD *)(v36 + 8) = v260; *(_DWORD *)(v36 + 12) = v261; v292 = 0; v288 = 0; ms_exc.disabled = 0; LABEL_126: ms_exc.disabled = -1; if ( v197 ) { v269 = 0; v268 = 0; v131 = __readfsdword(24); v37 = RtlFreeHeap; RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v131 + 48) + 24), 0, v197); v197 = 0; } else { v37 = RtlFreeHeap; } if ( !v204 ) { BasepSxsCloseHandles(&v221); BasepSxsCloseHandles(&v217); if ( v181 ) { i = 0; do { v38 = (int *)&(&v138)[4 * i]; v39 = *v38; if ( *v38 ) { v40 = v39 + 8; if ( v39 != -8 && *(_DWORD *)v40 ) { if ( *(_DWORD *)(v39 + 8) != *(_DWORD *)(v39 + 12) ) { v133 = *(_DWORD *)v40; RtlFreeUnicodeString(&v132); } v41 = *v38; *(_DWORD *)(*v38 + 8) = *(_DWORD *)(*v38 + 12); *(_DWORD *)(v41 + 16) = *(_DWORD *)(v41 + 20); } v42 = *(_DWORD *)(*v38 + 12); *(_DWORD *)(*v38 + 4) = v42; if ( v42 ) **(_WORD **)(v39 + 4) = 0; v43 = *v38; *(_WORD *)v43 = 0; *(_WORD *)(v43 + 2) = *(_WORD *)(v43 + 20); } ++i; } while ( i != 5 ); v92 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v92 + 48) + 24), 0, v181); v37 = RtlFreeHeap; } } if ( v234 && !(BYTE1(v327) & 4) ) { RtlDestroyEnvironment(v234); v234 = 0; } v129 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v129 + 48) + 24), 0, Dest); v107 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v107 + 48) + 24), 0, Str1); v127 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v127 + 48) + 24), 0, v170); v106 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v106 + 48) + 24), 0, v198); if ( v289 ) NtClose(v289); if ( v291 ) NtClose(v291); if ( v288 ) { NtTerminateProcess(v292, 0); NtClose(v288); } if ( v292 ) NtClose(v292); if ( v184 ) NtClose(v184); if ( v183 ) { if ( v185 ) *(_DWORD *)v148 = v183; else NtClose(v183); } if ( v284 ) { v125 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v125 + 48) + 24), 0, v284); } if ( v285 ) { v105 = __readfsdword(24); v37(*(_DWORD *)(*(_DWORD *)(v105 + 48) + 24), 0, v285); } RtlFreeUnicodeString(&v266); result = RtlFreeUnicodeString(&v270); if ( v265 || v263 ) result = BaseDestroyVDMEnvironment(&v264, &v262); if ( v233 ) { if ( !(v233 & 8) ) { result = BaseUpdateVDMEntry(0, &v287, v233, v204); if ( v290 ) result = NtClose(v290); } } return result; } v178 = _wcslen(Source); if ( !v178 ) { Source = (wchar_t *)Size; v178 = _wcslen((const wchar_t *)Size); } v71 = _wcslen((const wchar_t *)&v325); v72 = 2 * (v178 + v71 + 3); v178 = v72; v124 = __readfsdword(24); v272 = (wchar_t *)RtlAllocateHeap(*(_DWORD *)(*(_DWORD *)(v124 + 48) + 24), BaseDllTag, v72); v270 = 0; v271 = v72; RtlAppendUnicodeToString(&v270, &v325); RtlAppendUnicodeToString(&v270, L" "); RtlAppendUnicodeToString(&v270, Source); Source = v272; Size = 0; NtClose(v291); v291 = 0; v122 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v122 + 48) + 24), 0, Str1); Str1 = 0; v120 = __readfsdword(24); RtlFreeHeap(*(_DWORD *)(*(_DWORD *)(v120 + 48) + 24), 0, v198); v198 = 0; } } if ( !RtlIsDosDeviceName_U(v15) ) goto LABEL_179; v83 = 1200; LABEL_278: SetLastError(v83); LABEL_173: v81 = -1; LABEL_174: _local_unwind2(&ms_exc.prev_er, v81); return 0; }

 

1. 函数的一开始是一大段临时变量声明,然后将从CreateProcessInternalA传过来的参数保存到局部变量中。

     ...
  v185 = var_0;
  Size = lpApplicationName;
  Source = lpCommandLine;
  v169 = lpProcessAttributes;
  v164 = lpThreadAttributes;
  v234 = lpEnvironment;
  lpFileName = lpCurrentDirectory;
  v144 = lpStartupInfo;
  v166 = lpProcessInformation;
  v148 = var_0_;
  v290 = 0;
  v201 = 0;
  v287 = 0;
  v233 = 0;
  v204 = 0;
  v160 = 0;
  v146 = dwCreationFlags & 0x8000000;
      ...
Ps: 插个题外话: v146 = dwCreationFlags & 0x8000000;这句话的意思是判断dwCreationFlags的最高位是不是1,这里使用了"与运算符"来达到目的

 

 

2. 对lpProcessInformation字段进行初始化赋值,lpProcessInformation字段是我们传入的一个数据结构的引用,操作系统在创建完进程之后,会在这个数据结构中填入关于"这次"创建的进程的一些相关信息

*(_DWORD *) lpProcessInformation = 0;
*(_DWORD *)(lpProcessInformation + 4) = 0;
*(_DWORD *)(lpProcessInformation + 8) = 0;
*(_DWORD *)(lpProcessInformation + 12) = 0;
A pointer to a PROCESS_INFORMATION structure that receives identification information about the new process.

关于这个数据结构的定义如下:

typedef struct _PROCESS_INFORMATION 
{
  HANDLE hProcess;
  HANDLE hThread;
  DWORD  dwProcessId;
  DWORD  dwThreadId;
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;

 

3. dwCreationFlags字段的检测和判断

dwCreationFlags 的值至少由一个标志组合成(一些创建标志和优先级类型)。

3.1 首先屏蔽 CREATE_NO_WINDOW 标志,代码如下:

v29 = dwCreationFlags & 0xF7FFFFFF;
v327 = v29;

因为: CREATE_NO_WINDOW = 0x08000000,所以,为了将这个宏代表的0x08000000给置0,所以采用了0xF7FFFFFF,这也是一种"位操作"的思想。

 

3.2 判断dwCreationFlags中的非法位组合

经过屏蔽标志位后,判断dwCreationFlags中是否包含CREATE_NEW_CONSOLE | DETACHED_PROCESS的组合, 如果包含它们的组合(参考MSDN上的说明), 存在这种组合是不合法的, 因此跳转到错误处理中:

if ( (v29 & 0x18) == 24 ) //24 = DETACHED_PROCESS | CREATE_NEW_CONSOLE
    goto LABEL_279;

我们继续跟踪错误代码 LABEL_279:

LABEL_279:
    SetLastError(0x57u);
    return 0;

继续跟进SetLastError()函数.

void __stdcall SetLastError(DWORD dwErrCode)
{
  unsigned __int32 v1; // edi@1

  v1 = __readfsdword(24);
  if ( g_dwLastErrorToBreakOn && dwErrCode == g_dwLastErrorToBreakOn )
    DbgBreakPoint();
  if ( *(_DWORD *)(v1 + 52) != dwErrCode )
    *(_DWORD *)(v1 + 52) = dwErrCode;
}

将57h与Teb->LastErrorValue想比较,如果不相等,就更新LastErrorValue的值为57h, 实际上GetLastError() 函数的返回的错误码就是从Teb->LastErrorValue获取的。

 

3.3 判断dwCreationFlags中的优先级类型的组合

在一开始提到判断dwCreationFlags中包含了"创建标志"和"优先级",接下来代码先判断优先级,我们接着分析:

.text:7C8199A6     mov     [ebp+var_6D4], ebx
.text:7C8199AC     mov     [ebp+var_6DC], ebx
.text:7C8199B2     test    al, 40h
.text:7C8199B4     jnz     IsIdlePriority  //优先级为IDLE_PRIORITY_CLASS
.text:7C8199B4
.text:7C8199BA     test    ah, 40h
.text:7C8199BD     jnz     loc_7C84278E
.text:7C8199BD
.text:7C8199C3     test    al, 20h
.text:7C8199C5     jnz     IsNormalPriority  //优先级为NORMAL_PRIORITY_CLASS
.text:7C8199C5
.text:7C8199CB     test    ah, ah
.text:7C8199CD     js      loc_7C84279A
.text:7C8199CD
.text:7C8199D3     test    al, al
.text:7C8199D5     js      IsHighPriotity   //优先级为HIGH_PRIORITY_CLASS
.text:7C8199D5
.text:7C8199DB     test    ah, 1
.text:7C8199DE     jnz     IsRealTimePriority  //优先级为REALTIME_PRIORITY_CLASS

判断的顺序依次是

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。

这里回想一点我们之前说的关于进程创建时如果指定了过高的优先级时,操作系统是怎么处理的:

3. 如果为新进程指定了Real-Time优先级类别,但是该进程的调用者没有"Increase Scheduling Priority(增加调度优先级)"特权,则使用High优先级类别。换句话说,CreateProcess
不会仅仅因为调用者没有足够的特权来创建Real-Time优先级类别的进程而失败,而是自动"降低"一点,新进程只是没有Real-Time那么高的优先级而已

 

3.4 判断dwCreationFlags中的创建标志的组合

继续判断dwCreationFlag的情况,首先在dwCreationFlag过滤掉表示优先级的标志位,然后在判断是什么创建标志。用与运算符把所有不属于创建标志的位都置0

LOWORD(dwCreationFlagsa) = dwCreationFlagsa & 0x3E1F;

判断CREATE_SEPARATE_WOW_VDM / CREATE_SHARED_WOW_VDM(这两个位是用来表示16bit的windows程序)

.text:7C8199F6      mov     edi, 800h
.text:7C8199FB      mov     esi, 1000h
.text:7C819A00      test    [ebp+dwCreationFlags], edi
.text:7C819A03      jnz     loc_7C8427CA   //dwCreationFlag = CREATE_SEPARATE_WOW_VDM 
.text:7C819A03
.text:7C819A09      test    [ebp+dwCreationFlags], esi
.text:7C819A0C      jnz     short loc_7C819A1F   //dwCreationFlag = CREATE_SHARED_WOW_VDM
.text:7C819A0C
.text:7C819A0E      mov     eax, _BaseStaticServerData
.text:7C819A13      cmp     [eax+19F4h], bl
.text:7C819A19      jnz     loc_7C8427D4

 

 

4.  判断lpEnvironment,和可执行程序的路径相关的处理

判断lpEnvironment是否为空, 如果不为空,将lpEnvironment对应的ANSI字符串转换为UNICODE_STRING, 为空的话跳过这一步,接着调用RtlDosPathNameToNtPathName_U函数将DOS路径转换为NT路径,由于用户给定的路径一般都是DOS路径,而内核需要的是NT路径,因此需要转换一下。

if ( !lpEnvironment_ || BYTE1(dwCreationFlagsa) & 4 )  //判断lpEnvironment是否为空
{
    ...
    v13 = NtAllocateVirtualMemory(-1, &v161, 0, &v88, 4096, 4);  //申请存储UNICODE的空间
        ...
    v14 = RtlAnsiStringToUnicodeString(&v159, &v156, 0); //将ANSI转换为UNICODE
    if ( v14 < 0 )
    {
        NtFreeVirtualMemory(-1, &v161, &v88, 32768);  //释放临时的存储空间
        ..
    }
    ..
    goto LABEL_33;
    ...
LABEL_33:
    v15 = Size;
    v167 = RtlDosPathNameToNtPathName_U(Size, &v275, 0, &v239);  //将DOS路径转换为NT路径

这个关键函数的声明如下:

NTSTATUS  NtAllocateVirtualMemory(
    __in HANDLE  ProcessHandle,
    __inout PVOID  *BaseAddress,
    __in ULONG_PTR  ZeroBits,
    __inout PSIZE_T  RegionSize,
    __in ULONG  AllocationType,
    __in ULONG  Protect
    ); 
NTSTATUS RtlAnsiStringToUnicodeString(
    IN OUT PUNICODE_STRING  DestinationString,
    IN PANSI_STRING  SourceString,
    IN BOOLEAN  AllocateDestinationString
    );
RtlDosPathNameToNtPathName_U是一个windows未公开的函数,如果我们自己需要使用,需要自己在头文件中定义
typedef  NTSTATUS (*DOSPATH_TO_NTPATH)(
           IN PCWSTR DosPathName,
           OUT PUNICODE_STRING NtPathName,
           OUT PWSTR* FilePathInNtPathName OPTIONAL,
           OUT PRELATIVE_NAME* RelativeName OPTIONAL
           );
DOSPATH_TO_NTPATH RtlDosPathNameToNtPathName_U;

 

 

5. 打开文件映像
5.1 接着调用NtOpenFile()得到文件句柄,我们先来看一下这个函数的声明:

NTSTATUS NtOpenFile(
    OUT PHANDLE  FileHandle,
    IN ACCESS_MASK  DesiredAccess, //期望的访问属性
    IN POBJECT_ATTRIBUTES  ObjectAttributes, //对象属性
    OUT PIO_STATUS_BLOCK  IoStatusBlock, //I/0状态块
    IN ULONG  ShareAccess,  //共享模式
    IN ULONG  OpenOptions  //打开选项
    );
v238 = NtOpenFile(&v291, 1048737, &v207, &v252, 5, 96);
if ( v238 < 0 )
{
      v238 = NtOpenFile(&v291, 1048608, &v207, &v252, 5, 96);
      if ( v238 < 0 )
        break;
}

 

5.2 创建程序的内存区
接着通过NtOpenFile得到的handle接着调用了NtCreateSectiond函数得到内存区对象句柄,即我们常说的进程用户空间的虚拟地址空间,在这一步完成创建(事实上,这里用创建这个词不是非常恰当,因为进程的内存空间一直4GB,这里做的实际是获得这个内存区对象的句柄,以方便我们之后使用)。

NTSTATUS NtCreateSection(
    OUT PHANDLE  SectionHandle,  //指向内存区对象的指针(传出参数)
    IN ACCESS_MASK  DesiredAccess,  //访问掩码
    IN POBJECT_ATTRIBUTES  ObjectAttributes  OPTIONAL, //对象属性
    IN PLARGE_INTEGER  MaximumSize  OPTIONAL,  //SETION大小
    IN ULONG  SectionPageProtection,  //内存区页面保护属性
    IN ULONG  AllocationAttributes,
    IN HANDLE  FileHandle  OPTIONAL  //文件句柄
    ); 
v17 = NtCreateSection(&v293, 983071, 0, 0, 16, 16777216, v291);
v238 = v17;
if ( v17 < 0 )
      goto LABEL_425;

 

 

6. 检查windows程序运行授权策略

接着调用BasepIsProcessAllowed函数该函数用来判断应用程序名是否在授权文件列表中,函数实现调用了NtOpenKey函数打开了注册表中的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options键

v18 = BasepIsProcessAllowed(Size);
v17 = v18;
v238 = v18;
if ( v18 < 0 )
{
      BaseSetLastNTError(v18);
      NtClose(v293);
      goto LABEL_173;
}

windows进程/线程创建过程 --- windows操作系统学习_第8张图片

输入gpedit.msc->计算机配置->windows设置->安全设置->软件限制策略->其他规则中可以设置我们的"软件运行策略"

windows进程/线程创建过程 --- windows操作系统学习_第9张图片

 

 

7. 获取进程内存区对象的基本信息

在得到进程对应的内存区对象句柄后调用了NtQuerySection函数,返回后得到节的基本信息(节基地址,大小,属性),中间的那一大段代码咱们可以忽略,我通读之后,发现只是一些无关紧要的内存区的申请,释放操作,还有和前面说的win16的VDM相关的代码(在win32下不会执行),所以可以掠过。

我们直接来到NtQuerySection函数这个代码块,先来看看这个函数的声明:

//将内存区对象作为"镜像执行"文件来查询信息
typedef DWORD ( WINAPI* NTQUERYSECTION)(
            HANDLE, //内存区句柄
            ULONG,  //edi = 1 = SectionImageInformation
            PVOID,  //接受内存区信息Buffer
            ULONG,    //内存区信息的长度
            PULONG  //接受返回的大小
        );  
NTQUERYSECTION NtQuerySection;                                              
v23 = NtQuerySection(v293, 1, &v189, 48, 0);
v238 = v23;
if ( v23 < 0 )
        goto LABEL_242;

 

 

8. 判断映像文件(刚才加载到进程内存对象区域中的映像文件)信息的有效性

8.1 然后判断创建标志中是否包含DEBUG_PROCESS或者DEBUG_ONLY_THIS_PROCESS

如果包含该标志,判断PEB->ReadImageFileExecOptions域是否为0,如果不为0, 调用LdrQueryImageFileExecutionOptions函数查询该信息,如果不包含该标志,也用调用LdrQueryImageFileExecutionOptions函数。

if ( !(dwCreationFlagsa & 3) || (v128 = __readfsdword(24), *(_BYTE *)(*(_DWORD *)(v128 + 48) + 1)) )
//DEBUG_PROCESS OR DEBUG_ONLY_THIS_PROCESS = 3
        LdrQueryImageFileExecutionOptions(&v275, L"Debugger", 1, &v327, 520, 0);

 

8.2 下面检查镜像文件的部分信息的有效性

if ( MachineType < v7FFE002C || MachineType > v7FFE002E )
{
    //MachineType为机器类型,
    ..
}
if ( subsystem_machineType != 2 && subsystem_machineType != 3 )
{
    //子系统版本号 2: 控制台  3: GUI
    ..  
    if ( subsystem_machineType != 7 )
    {
    ..
    }
        if ( !BuildSubSysCommandLine(L"POSIX /P ", Size, Source, &v272) )
          goto LABEL_173;
        goto LABEL_215;
}

接着调用函数BasepIsImageVersionOk判断镜像文件版本是否合法

if ( !BasepIsImageVersionOk(subsystem_major_type, subsystem_minor_type) )
//subsystem_major_type: 子系统的主版本号
//subsystem_minor_type: 子系统的次版本号
        goto LABEL_277;

 

 

9. 加载advapi32.dll
如果创建标志中是否包含DEBUG_PROCESS或者DEBUG_ONLY_THIS_PROCESS(即当前处在调试模式中),就载advapi32.dll并获得CreateProcessAsUserSecure函数的地址:

if ( !v327 )
{
        v24 = LoadLibraryA("advapi32.dll");
    ..
        if ( v24 )
        {
          if ( GetProcAddress(v24, "CreateProcessAsUserSecure") )
          {
            v238 = NtQuerySystemInformation(71, &v279, 4, 0);
        ..
          }
          FreeLibrary(v25);
        }

 

(Ps: 进程分析的过程非常之繁杂的琐碎,很容易让你陷入代码的细节而不能自拔,小瀚建议朋友们每分析一段时间后就翻到前面去仔细看看"进程创建流程"的概览,不断地提醒自己同时从宏观
和微观的角度去看待这个过程,这样,不至于迷失在windows的代码的海洋中,自己也可以准备一张进程创建的纵览图,补充着看)

 

10. NT对象属性的创建

然后调用BaseFormatObjectAttributes将安全属性结构(关于进程安全、权限方面的信息,例如普通应用程序如果想要删除systrem32\calc.exe就必须利用这个字段来提升权限到TrustedInstaller(win7下))格式为NT对象属性结构(得到了对象属性)。

typedef struct _SECURITY_ATTRIBUTES {
  DWORD  nLength;
  LPVOID lpSecurityDescriptor;
  BOOL   bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
..
v188 = BaseFormatObjectAttributes(&v207, v171, 0);
..

 

接着调用了_DbgUiConnectToDbg在实现通过调用NtCreateDebugObject函数来创建调试对象,调用DbgUiGetThreadDebugObject来获得调试对象(作为参数传递到0环)。

 

11. 创建调试对象

接着调用了_DbgUiConnectToDbg在实现通过调用NtCreateDebugObject函数来创建调试对象,然后调用DbgUiGetThreadDebugObject来获得调试对象(作为参数传递到0环)。

if ( dwCreationFlagsa & 3 )
{
          v23 = DbgUiConnectToDbg();
          v238 = v23;
          if ( v23 < 0 )
            goto LABEL_242;
          v164 = DbgUiGetThreadDebugObject();
    ...
}

 

 

12. 最后一步,准备进行模式穿越

最后调用NtCreateProcessEx函数完成3环的创建过程,我们先来看看NtCreateProcessEx的声明:

NTSYSAPI NTSTATUS NTAPI NtCreateProcessEx(
                OUT PHANDLE ProcessHandle,  //保留进程句柄值(传出参数)
                IN ACCESS_MASK DesiredAccess,  //(访问掩码)
                IN POBJECT_ATTRIBUTES ObjectAttributes,  //对象属性
                IN HANDLE InheritFromProcessHandle,  //父进程句柄(FFFFFFFF)
                IN BOOLEAN InheritHandles,   //创建标志
                IN HANDLE SectionHandle OPTIONAL,  //内存区对象句柄
                IN HANDLE DebugPort OPTIONAL,  //调试对象句柄
                IN HANDLE ExceptionPort OPTIONAL,  //异常端口对象句柄
                IN HANDLE Unknown  //作业级别
    );
v27 = NtCreateProcessEx(&v294, 2035711, v188, -1, v197, v293, v164, 0, v158);

这里要注意一点,这个函数NtCreateProcessEx是一个ntdll.dll导出的存根函数,它是我们从用户模式穿越进内核层的一条"通道"

到了这一步,我们和ring3的"缘分""暂时"尽了,注意,是暂时尽了,也就是说,我们的代码在进入内核层之后,还有回到用户层一次

继续跟进NtCreateProcessEx这个函数,我们就要进入内核层的代码了。所以这里是一个分界点。

下面附上函数的流程图:

windows进程/线程创建过程 --- windows操作系统学习_第10张图片

 

 

 

 

阶段四: 创建进程的"执行体进程对象",(Ring0从这里开始)

在开始分析ring0层的进程创建过程之前,我们做一个承上启下的总结

1. 之前在ring3层,CreateProcess已经打开了一个有效的windows可执行文件,并且创建了一个内存区对象,以便稍后将它映射到新的进程地址空间中
2. 接下来,ring3的代码会调用NtCreateProcessEx,来创建一个"windows执行体进程对象",以运行该"进程映像"

创建执行体进程对象(EPROCESS)(过程中自然也包含了创建内核层进程对象KPROCESS)的大致过程如下:

1. 建立起EPROCESS块
2. 创建初始的进程地址空间
3. 初始化内核进程块(KPROCESS)
4. 结束进程地址空间的创建过程(包括初始化工作集列表和虚拟地址空间描述符,以及将映像映射到地址空间中)
5. 建立PEB
6. 完成执行体进程对象的创建过程

建立起EPROCESS

我们知道,在ring3的最后一次调用中,代码调用了NtCreateProcessEx()这个函数,从这个函数以后,代码进入了内核的范畴。所以我们从这个函数开始逐行分析

下面贴出NtCreateProcessEx()函数源代码,它位于WRK的 base\ntos\ps\create.c 文件中

NTSTATUS NtCreateProcessEx
    __out PHANDLE ProcessHandle, //输出参数
    __in ACCESS_MASK DesiredAccess, //对新进程的访问权限
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes, //进程对象的属性
    __in HANDLE ParentProcess, //指向父进程的句柄,
    __in ULONG Flags,  //创建标志
    __in_opt HANDLE SectionHandle, //该进程的内存区对象
    __in_opt HANDLE DebugPort, //新进程的调试端口
    __in_opt HANDLE ExceptionPort, //新进程的异常端口
    __in ULONG JobMemberLevel  //新进程在一个job集中的级别
    )  
{
    NTSTATUS Status;
    PAGED_CODE();
    if (KeGetPreviousMode() != KernelMode) 
    { 
        try 
      {
            ProbeForWriteHandle (ProcessHandle);
        } 
      except (EXCEPTION_EXECUTE_HANDLER) 
      {
            return GetExceptionCode ();
      }
    }
    if (ARGUMENT_PRESENT (ParentProcess)) 
    {
        Status = PspCreateProcess (ProcessHandle,
                                   DesiredAccess,
                                   ObjectAttributes,
                                   ParentProcess,
                                   Flags,
                                   SectionHandle,
                                   DebugPort,
                                   ExceptionPort,
                                   JobMemberLevel);
    } 
    else 
    {
        Status = STATUS_INVALID_PARAMETER;
    }
    return Status;
}

首先对NtCreateProcessEx的参数做一个说明:

1. ProcessHandle: 一个输出参数,如果该进程创建成功,则它包含了所创建的进程的句柄,windows中这种带引用的传出参数的编程非常常见
2. DesiredAccess: 包含了对新进程的访问权限
3. ObjectAttributes: 这是一个可选的"指针参数(即可以为NULL)",它指定了进程对象的属性
4. ParentProcess: 指向父进程的句柄,这是一个必需的参数,不能为NULL,并且调用者必须对该进程具有"PROCESS_CREATE_PROCESS"(这是FLAGS中的一个属性)
5. Flags: 这是创建标志,其中有一个标志"PROCESS_CREATE_FLAGS_INHERIT_HANDLES"非常关键,表明新进程的"对象句柄表"是否要复制父进程的句柄表,或者初始设置为空。
6. SectionHandle: 这是一个可选的句柄,指向一个内存区对象,代表了该进程的映像文件,调用者对于此内存区对象必须具有"SECTION_MAP_EXECUTE"访问权限。
7. DebugPort: 这是一个可选的句柄,指向一个端口对象,如果此句柄参数不为NULL,则此端口被赋值为新进程的调试端口,否则,新进程没有调试端口
8. ExceptionPort: 这是一个可选的端口,指向一个端口对象,如果此句柄参数不为NULL,则此端口被赋值为新进程的异常端口,否则,新进程没有异常端口。调用者对于异常端口对象必须具有
PORT_WRITE和PORT_READ访问权限
9. JobMemberLevel: 指定了要创建的进程在一个job集中的级别

NtCreateProcessEx的代码先判断了线程的前一个模式是不是"内核态(KernelMode)",即判断这个创建请求是从内核态来的还是从用户态来的,我们现在分析的是从ring3创建进程的流程,所以这里"线程的前一个模式"是"用户态"。

接着代码会检查ProcessHandle参数代表的句柄是否可写,然后才把真正的创建工作交给PspCreateProcess函数。

if (KeGetPreviousMode() != KernelMode) 
{ 
        //判断了线程的前一个模式是不是"内核态(KernelMode)"
        try 
      {
            ProbeForWriteHandle (ProcessHandle);
        } 
        except (EXCEPTION_EXECUTE_HANDLER) 
        {
            return GetExceptionCode ();
        }
}
if (ARGUMENT_PRESENT (ParentProcess)) 
{
        //这是真正的创建进程的函数
        Status = PspCreateProcess 
        ...

 

接下来顺藤摸瓜,我们继续跟进代码,代码来到了PspCreateProcess ()这个函数。它位于 base\ntos\ps\create.c 中。

在开始分析PspCreateProcess 之前,要一个知识点要注意:

PspCreateProcess在windows中会被三个函数调用,它们是NtCreateProcessEx、PsCreateSystemProcess、PsInitPhase
1. PsInitPhase()是在系统初始化的早期被调用的,它创建的进程(即System进程)的句柄保存在全局变量PspInitialSystemProcessHandle中,进程对象存放于全局变量
  PsInitialSystemProcess中
2. PsCreateSystemProcess()可用于创建系统进程对象,它创建的进程都是PsInitialSystemProcess的子进程 3. NtCreateProcessEx()即我们接下来要分析的,同时这个NtCreateProcessEx也有可能会从ring3或ring0发出,文章的最后总结会再次提及这点 所以,PspCreateProcess()函数负责创建系统中的"所有"进程(注意,是所有),包括System进程

接下来结合WRK的源代码来逐行分析此函数的流程,为了保持说明的完整性,我依然弱智地贴出完整代码,这次,因为代码实在太长了,我选择直接在代码中添加注释进行说明。当中涉及的数据结构的成员域在之前的EPROCESS/KPROCESS的学习笔记中都有介绍,请朋友结合起来看:

NTSTATUS PspCreateProcess(
    OUT PHANDLE ProcessHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ParentProcess OPTIONAL,
    IN ULONG Flags,
    IN HANDLE SectionHandle OPTIONAL,
    IN HANDLE DebugPort OPTIONAL,
    IN HANDLE ExceptionPort OPTIONAL,
    IN ULONG JobMemberLevel
    ) 
{

    NTSTATUS Status;
    PEPROCESS Process;
    PEPROCESS CurrentProcess;
    PEPROCESS Parent;
    PETHREAD CurrentThread;
    KAFFINITY Affinity;
    KPRIORITY BasePriority;
    PVOID SectionObject;
    PVOID ExceptionPortObject;
    PVOID DebugPortObject;
    ULONG WorkingSetMinimum, WorkingSetMaximum;
    HANDLE LocalProcessHandle;
    KPROCESSOR_MODE PreviousMode;
    INITIAL_PEB InitialPeb;
    BOOLEAN CreatePeb;
    ULONG_PTR DirectoryTableBase[2];
    BOOLEAN AccessCheck;
    BOOLEAN MemoryAllocated;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    SECURITY_SUBJECT_CONTEXT SubjectContext;
    NTSTATUS accesst;
    NTSTATUS SavedStatus;
    ULONG ImageFileNameSize;
    HANDLE_TABLE_ENTRY CidEntry;
    PEJOB Job;
    PPEB Peb;
    AUX_ACCESS_DATA AuxData;
    PACCESS_STATE AccessState;
    ACCESS_STATE LocalAccessState;
    BOOLEAN UseLargePages;
    SCHAR QuantumReset; 

    PAGED_CODE();

    CurrentThread = PsGetCurrentThread ();
    PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);
    CurrentProcess = PsGetCurrentProcessByThread (CurrentThread);

    CreatePeb = FALSE;
    UseLargePages = FALSE;
    DirectoryTableBase[0] = 0;
    DirectoryTableBase[1] = 0;
    Peb = NULL; 

    if (Flags&~PROCESS_CREATE_FLAGS_LEGAL_MASK) 
    {
        return STATUS_INVALID_PARAMETER;
    } 

    /*
    如果父进程句柄不为NULL,则通过ObReferenceObjectByHandle获得父进程对象的EPROCESS指针,放在Parent局部变量中 
    */
    if (ARGUMENT_PRESENT (ParentProcess)) 
    {
        Status = ObReferenceObjectByHandle (ParentProcess,
                                            PROCESS_CREATE_PROCESS,
                                            PsProcessType,
                                            PreviousMode,
                                            &Parent,
                                            NULL);
        if (!NT_SUCCESS (Status)) 
        {
            return Status;
        }

        if (JobMemberLevel != 0 && Parent->Job == NULL) 
        {
            ObDereferenceObject (Parent);
            return STATUS_INVALID_PARAMETER;
        }
        /*
        获得父进程的Affinity(CPU亲和性)设置,新进程工作集最大/最小值被初始化为全局变量PsMaximumWorkingSet和PsMinimumWorkingSet
        */
        Affinity = Parent->Pcb.Affinity;
        WorkingSetMinimum = PsMinimumWorkingSet;
        WorkingSetMaximum = PsMaximumWorkingSet;  
    } 
    else 
    { 
        /*
        如果父进程句柄为NULL,则Affinity设置为全局变量KeActiveProcessors,即系统中当前的可用处理器。此时因为新进程对象尚未被创建,所以这些设置都保存在局部变量中
        */
        Parent = NULL;
        Affinity = KeActiveProcessors;
        WorkingSetMinimum = PsMinimumWorkingSet;
        WorkingSetMaximum = PsMaximumWorkingSet;
    } 
    /*
    调用ObCreateObject函数,创建一个类型为PsProcessType的"内核对象",并置于局部变量Process中,这里所谓的PsProcessType类型的内核对象指的就是"EPROCESS",
  即这一步开始创建了一个执行体进程对象
*/ Status = ObCreateObject (PreviousMode, PsProcessType, ObjectAttributes, PreviousMode, NULL, sizeof (EPROCESS), 0, 0, &Process); if (!NT_SUCCESS (Status)) { goto exit_and_deref_parent; } /* 把Process(EPROCESS)对象中的所有域置为0,然后初始化(或者直接继承父进程的对应成员域)其中部分成员(RundownProtect、ProcessLock、ThreadListHead、QuotaUsage、
  QuotaPeak、QuotaBlock、DeviceMap)
*/ RtlZeroMemory (Process, sizeof(EPROCESS)); ExInitializeRundownProtection (&Process->RundownProtect); PspInitializeProcessLock (Process); InitializeListHead (&Process->ThreadListHead); PspInheritQuota (Process, Parent); ObInheritDeviceMap (Process, Parent); /* 如果父进程句柄不为NULL,则将新进程的Process(EPROCESS)继承父进程的DefaultHardErrorProcessing(默认的硬件错误处理)、
  InheritedFromUniqueProcessId(父进程的PID)
*/ if (Parent != NULL) { Process->DefaultHardErrorProcessing = Parent->DefaultHardErrorProcessing; Process->InheritedFromUniqueProcessId = Parent->UniqueProcessId; } else { Process->DefaultHardErrorProcessing = PROCESS_HARDERROR_DEFAULT; Process->InheritedFromUniqueProcessId = NULL; } /* 检查内存区句柄参数SectionHandle,对于系统进程,此参数为NULL,此时,除非父进程为PsInitialSystemProcess(System进程),否则内存区对象继承自父进程,并且不得为NULL。 如果此参数不为NULL,则利用此句柄参数调用ObReferenceObjectByHandle获得内存区对象的指针。所以,新进程对象的内存区对象已经完成初始化。 Ps: 小瀚建议朋友看到这里翻回去看看"阶段四: 执行体进程创建"的总体概览步骤,从宏观和微观的角度不断加深映像,不要一下子就陷入了代码中。通过总体路线,我们知道下一步基本
  是要创建"进程内核对象KPROCESS"了,基本的代码模式我们也能猜测出来了
*/ if (ARGUMENT_PRESENT (SectionHandle)) { Status = ObReferenceObjectByHandle (SectionHandle, SECTION_MAP_EXECUTE, MmSectionObjectType, PreviousMode, &SectionObject, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } else { SectionObject = NULL; if (Parent != PsInitialSystemProcess) { if (ExAcquireRundownProtection (&Parent->RundownProtect)) { SectionObject = Parent->SectionObject; if (SectionObject != NULL) { ObReferenceObject (SectionObject); } ExReleaseRundownProtection (&Parent->RundownProtect); } if (SectionObject == NULL) { Status = STATUS_PROCESS_IS_TERMINATING; goto exit_and_deref; } } } /* 完成新进程对象的内存区对象初始化为新进程的EPROCESS成员域赋值 */ Process->SectionObject = SectionObject; /* 根据DebugPort参数来初始化新进程对象的DebugPort(调试端口)成员域 */ if (ARGUMENT_PRESENT (DebugPort)) { Status = ObReferenceObjectByHandle (DebugPort, DEBUG_PROCESS_ASSIGN, DbgkDebugObjectType, PreviousMode, &DebugPortObject, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } Process->DebugPort = DebugPortObject; if (Flags&PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT) { PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_NO_DEBUG_INHERIT); } } else { if (Parent != NULL) { DbgkCopyProcessDebugPort (Process, Parent); } } /* 根据ExceptionPort参数来初始化新进程对象的ExceptionPort(异常端口)成员域 */ if (ARGUMENT_PRESENT (ExceptionPort)) { Status = ObReferenceObjectByHandle (ExceptionPort, 0, LpcPortObjectType, PreviousMode, &ExceptionPortObject, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } Process->ExceptionPort = ExceptionPortObject; } /* 设置新进程的退出状态,这里设置为STATUS_PENDING表示尚未完成,正在处理ing */ Process->ExitStatus = STATUS_PENDING; /* 如果指定的父进程不为NULL,则创建一个全新的地址空间(最小工作集大小) */ if (Parent != NULL) { if (!MmCreateProcessAddressSpace (WorkingSetMinimum, Process, &DirectoryTableBase[0])) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit_and_deref; } } /* 如果指定的父进程为NULL(即系统进程,系统进程是没有父进程的说法的),让新进程的句柄表指向当前进程(CurrentProcess)的句柄表,并且利用空闲线程的地址空间来初始化新进程
  的地址空间
*/ else { Process->ObjectTable = CurrentProcess->ObjectTable; Status = MmInitializeHandBuiltProcess (Process, &DirectoryTableBase[0]); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_HAS_ADDRESS_SPACE); Process->Vm.MaximumWorkingSetSize = WorkingSetMaximum; /* 调用KeInitializeProcess函数来初始化新进程内核对象(KPROCESS)的"基本优先级(BasePriority)"、Affinity(CPU亲和性)、进程页面目录(DirectoryTableBase)和超空间的
  页帧号 Ps: 再次回顾阶段四的"总路线图",我们现在到了创建KPROCESS的那一步了
*/ KeInitializeProcess (&Process->Pcb, NORMAL_BASE_PRIORITY, Affinity, &DirectoryTableBase[0], (BOOLEAN)(Process->DefaultHardErrorProcessing & PROCESS_HARDERROR_ALIGNMENT_BIT)); /* 通过PspInitializeProcessSecurity函数初始化新进程的安全属性,主要是从父进程复制一个令牌 */ Status = PspInitializeProcessSecurity (Parent, Process); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } /* 设置新进程的优先级类别: PROCESS_PRIORITY_CLASS_NORMAL(普通级别) */ Process->PriorityClass = PROCESS_PRIORITY_CLASS_NORMAL; /* 如果父进程句柄不为NULL,则复制父进程的优先级,这里注意一个细节,就是要判断一下指定的优先级是否超过了IDLE,因为Real-Time的优先级普通进程是不被允许创建的 */ if (Parent != NULL) { if (Parent->PriorityClass == PROCESS_PRIORITY_CLASS_IDLE || Parent->PriorityClass == PROCESS_PRIORITY_CLASS_BELOW_NORMAL) { Process->PriorityClass = Parent->PriorityClass; } /* 初始化新进程的句柄表,若Flags参数中包含了句柄继承标志,则把父进程句柄表中凡是有继承属性的对象拷贝到新进程的句柄表中(回想句柄表的知识: 即使是复制,同一个对象的
     句柄在不同的进程空间中是不同的)
*/ Status = ObInitProcess ((Flags&PROCESS_CREATE_FLAGS_INHERIT_HANDLES) ? Parent : NULL, Process); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } else { Status = MmInitializeHandBuiltProcess2 (Process); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } Status = STATUS_SUCCESS; SavedStatus = STATUS_SUCCESS; /* 接下来开始初始化新进程的进程地址空间 Ps: 再次回到总路线图,我们现在已经到了"结束进程地址空间的创建过程(包括初始化工作集列表和虚拟地址空间描述符,以及将映像映射到地址空间中)"这一步,做到心中有数。 */ /* 接下来初始化新进程的进程地址空间,有三种可能性 */ if (SectionHandle != NULL) { /* 1. 新进程有新的可执行映像内存区对象SectionObject,调用MmInitializeProcessAddressSpace函数,根据指定的内存区对象来初始化进程地址空间 */ Status = MmInitializeProcessAddressSpace (Process, NULL, SectionObject, &Flags, &(Process->SeAuditProcessCreationInfo.ImageFileName)); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } SavedStatus = Status; CreatePeb = TRUE; UseLargePages = ((Flags & PROCESS_CREATE_FLAGS_LARGE_PAGES) != 0 ? TRUE : FALSE); } else if (Parent != NULL) { /* 2. 没有指定映像内存区对象SectionObject,但父进程也并非PsInitialSystemProcess(非NULL)。也调用MmInitializeProcessAddressSpace函数,但根据父进程来初始化
    进程地址空间。
*/ if (Parent != PsInitialSystemProcess) { Process->SectionBaseAddress = Parent->SectionBaseAddress; Status = MmInitializeProcessAddressSpace (Process, Parent, NULL, &Flags, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } CreatePeb = TRUE; UseLargePages = ((Flags & PROCESS_CREATE_FLAGS_LARGE_PAGES) != 0 ? TRUE : FALSE); /* 并且把父进程的映像名称ImageFileName字符串拷贝到新进程对象的数据结构中 */ if (Parent->SeAuditProcessCreationInfo.ImageFileName != NULL) { ImageFileNameSize = sizeof(OBJECT_NAME_INFORMATION) + Parent->SeAuditProcessCreationInfo.ImageFileName->Name.MaximumLength; Process->SeAuditProcessCreationInfo.ImageFileName = ExAllocatePoolWithTag (PagedPool, ImageFileNameSize, 'aPeS'); if (Process->SeAuditProcessCreationInfo.ImageFileName != NULL) { RtlCopyMemory (Process->SeAuditProcessCreationInfo.ImageFileName, Parent->SeAuditProcessCreationInfo.ImageFileName, ImageFileNameSize); Process->SeAuditProcessCreationInfo.ImageFileName->Name.Buffer = (PUSHORT)(((PUCHAR) Process->SeAuditProcessCreationInfo.ImageFileName) + sizeof(UNICODE_STRING)); } else { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit_and_deref; } } } else { /* 3. 没有指定映像内存区对象SectionObject,但指定了PsInitialSystemProcess(System进程)作为父进程。这对应于系统进程的情形。
      调用MmInitializeProcessAddressSpace,不指定内存区和父进程,直接初始化。
*/ Flags &= ~PROCESS_CREATE_FLAGS_ALL_LARGE_PAGE_FLAGS; Status = MmInitializeProcessAddressSpace (Process, NULL, NULL, &Flags, NULL); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } Process->SeAuditProcessCreationInfo.ImageFileName = ExAllocatePoolWithTag (PagedPool, sizeof(OBJECT_NAME_INFORMATION), 'aPeS'); /* 同样地,把父进程的映像名称字符串ImageFileName拷贝到新进程对象的数据结构中 */ if (Process->SeAuditProcessCreationInfo.ImageFileName != NULL) { RtlZeroMemory (Process->SeAuditProcessCreationInfo.ImageFileName, sizeof(OBJECT_NAME_INFORMATION)); } else { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit_and_deref; } } /* 实际上还有第四种可能性,即没有指定映像内存区对象SectionObject,也没有指定父进程。这对应于系统的引导进程(后蜕化成空闲进程),它的地址空间是在MmInitSystem执行过程
    中初始化的,由MiInitMachineDependent函数调用MmInitializeProcessAddressSpace来完成 (这是潘老师在书上说的,可以我在翻阅了WRK和ReactOS的源代码后也没有找到与这段描述对应的源代码,我的是windows确实实现了,但是WRK中没有提供)
*/ } /* 创建进程ID。利用ExCreateHandle函数在CID句柄表中创建一个进程ID项 */ CidEntry.Object = Process; CidEntry.GrantedAccess = 0; Process->UniqueProcessId = ExCreateHandle (PspCidTable, &CidEntry); if (Process->UniqueProcessId == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto exit_and_deref; } ExSetHandleTableOwner (Process->ObjectTable, Process->UniqueProcessId); /* 对这次进程创建行为进行审计 */ if (SeDetailedAuditingWithToken (NULL)) { SeAuditProcessCreation (Process); } if (Parent) { /* 如果父进程属于一个作业对象,则也加入到父进程所在的作业中,调用PspAddProcessToJob完成操作,注意这里对作业的最大容纳进程数有要求 */ Job = Parent->Job; if (Job != NULL && !(Job->LimitFlags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)) { if (Flags&PROCESS_CREATE_FLAGS_BREAKAWAY) { if (!(Job->LimitFlags & JOB_OBJECT_LIMIT_BREAKAWAY_OK)) { Status = STATUS_ACCESS_DENIED; } else { Status = STATUS_SUCCESS; } } else { Status = PspGetJobFromSet (Job, JobMemberLevel, &Process->Job); if (NT_SUCCESS (Status)) { PACCESS_TOKEN Token, NewToken; Job = Process->Job; Status = PspAddProcessToJob (Job, Process); Token = Job->Token; if (Token != NULL) { Status = SeSubProcessToken (Token, &NewToken, FALSE, Job->SessionId); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } SeAssignPrimaryToken (Process, NewToken); ObDereferenceObject (NewToken); } } } if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } } /* 开始创建Peb,创建Peb分为两种情况,创建进程时建立Peb、复制进程时建立Peb Ps: 再次翻阅总路线图,我们现在来到了"建立PEB"这一步 */ if (Parent && CreatePeb) { RtlZeroMemory (&InitialPeb, FIELD_OFFSET(INITIAL_PEB, Mutant)); InitialPeb.Mutant = (HANDLE)(-1); InitialPeb.ImageUsesLargePages = (BOOLEAN) UseLargePages; if (SectionHandle != NULL) { /* 1. 对于通过映像内存区对象SectionObject来创建进程的情形,调用MmCreatePeb来创建一个Peb,创建数据结构的基本模式都是类似的:
        申请空间->初始化刚才申请的空间->为数据结构中的指定成员域赋值
*/ Status = MmCreatePeb (Process, &InitialPeb, &Process->Peb); if (!NT_SUCCESS (Status)) { Process->Peb = NULL; goto exit_and_deref; } Peb = Process->Peb; } else { /* 2. 对于进程拷贝(fork)的情形,则使用从父进程继承而来的Peb(从这里也可以明白为什么说子进程继承了大部分的父进程的代码空间,从源代码上可以找到理论依据) */ SIZE_T BytesCopied; InitialPeb.InheritedAddressSpace = TRUE; Process->Peb = Parent->Peb; /* 直接复制父进程的Peb */ MmCopyVirtualMemory (CurrentProcess, &InitialPeb, Process, Process->Peb, sizeof (INITIAL_PEB), KernelMode, &BytesCopied); } } Peb = Process->Peb; /* 可以看到,在内核中对这种双链表的操作进行"加锁"是很常见的编程做法,为了保证一致性 */ PspLockProcessList (CurrentThread); /* 把新进程加入到全局的进程链表PsActiveProcessHead中,从数据结构的角度理解就是把Process->ActiveProcessLinks这个链表项插入一个双链表中(头尾断链再重新结合) Ps: 回想利用PsActiveProcessHead进行内核中进程枚举的思路 */ InsertTailList (&PsActiveProcessHead, &Process->ActiveProcessLinks); PspUnlockProcessList (CurrentThread); AccessState = NULL; if (!PsUseImpersonationToken) { AccessState = &LocalAccessState; Status = SeCreateAccessStateEx (NULL, (Parent == NULL || Parent != PsInitialSystemProcess)? PsGetCurrentProcessByThread (CurrentThread) : PsInitialSystemProcess, AccessState, &AuxData, DesiredAccess, &PsProcessType->TypeInfo.GenericMapping); if (!NT_SUCCESS (Status)) { goto exit_and_deref; } } Status = ObInsertObject (Process, AccessState, DesiredAccess, 1, // bias the refcnt by one for future process manipulations NULL, &LocalProcessHandle); if (AccessState != NULL) { SeDeleteAccessState (AccessState); } if (!NT_SUCCESS (Status)) { goto exit_and_deref_parent; } ASSERT(IsListEmpty(&Process->ThreadListHead) == TRUE); /* 调用PspComputeQuantumAndPriority计算新进程的"基本优先级(BasePriority)"和"时限重置值(QuantumReset)" */ BasePriority = PspComputeQuantumAndPriority(Process, PsProcessPriorityBackground, &QuantumReset); /* 对新进程的KPROCESS赋值基本优先级和时限重置值 */ Process->Pcb.BasePriority = (SCHAR)BasePriority; Process->Pcb.QuantumReset = QuantumReset; /* 设置新进程的"内存优先级(进程的访问权限)GrantedAccess"。因为新进程已经被加入到句柄表中了,所以它现在能够被终止了(即具有了PROCESS_TERMINATE的权限,即它有终止的权利) */ Process->GrantedAccess = PROCESS_TERMINATE; if (Parent && Parent != PsInitialSystemProcess) { /* 对于有父进程但父进程不是PsInitialSystemProcess(System系统进程)的进程,首先调用SeAccessCheck执行访问检查 */ Status = ObGetObjectSecurity (Process, &SecurityDescriptor, &MemoryAllocated); if (!NT_SUCCESS (Status)) { ObCloseHandle (LocalProcessHandle, PreviousMode); goto exit_and_deref; } SubjectContext.ProcessAuditId = Process; SubjectContext.PrimaryToken = PsReferencePrimaryToken(Process); SubjectContext.ClientToken = NULL; AccessCheck = SeAccessCheck (SecurityDescriptor, &SubjectContext, FALSE, MAXIMUM_ALLOWED, 0, NULL, &PsProcessType->TypeInfo.GenericMapping, PreviousMode, &Process->GrantedAccess, &accesst); PsDereferencePrimaryTokenEx (Process, SubjectContext.PrimaryToken); ObReleaseObjectSecurity (SecurityDescriptor, MemoryAllocated); if (!AccessCheck) { Process->GrantedAccess = 0; } /* 为进程授予访问权限 */ Process->GrantedAccess |= (PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE | PROCESS_CREATE_PROCESS | PROCESS_SET_INFORMATION | STANDARD_RIGHTS_ALL | PROCESS_SET_QUOTA); } else { /* 如果是PsInitialSystemProcess的子进程,则授予所有访问权限"PROCESS_ALL_ACCESS" */ Process->GrantedAccess = PROCESS_ALL_ACCESS; } /* 设置进程的创建时间 */ KeQuerySystemTime (&Process->CreateTime); try { if (Peb != NULL && CurrentThread->Tcb.Teb != NULL) { ((PTEB)(CurrentThread->Tcb.Teb))->NtTib.ArbitraryUserPointer = Peb; } /* 把新进程的句柄赋值到输出参数"ProcessHandle"中,从而使创建者(调用者)可以获得新进程的句柄 */ *ProcessHandle = LocalProcessHandle; } except (EXCEPTION_EXECUTE_HANDLER) { NOTHING; } if (SavedStatus != STATUS_SUCCESS) { Status = SavedStatus; } exit_and_deref: ObDereferenceObject (Process); exit_and_deref_parent: if (Parent != NULL) { ObDereferenceObject (Parent); } return Status; }

这是一个漫长的过程,但是代码逻辑是很清晰的,我们在看完源代码后再次做一个"稍微"详细一点的总结:

1. 分配并初始化windows EPROCESS执行体进程块
2. 从父进程处继承得到进程的亲和性掩码
3. 新进程的最小和最大工作集尺寸被分别设置为PsMinimumWorkingSet和PsMaximumWorkingSet
4. 将新进程的配额块设置为其父进程配额块的地址,并且递增父进程配额块的引用计数
5. 继承windows的设备名字空间(包括驱动器字母的定义、COM端口等等)
6. 将父进程的进程ID保存在新进程对象的InheritedFromUniqueProcessId域中
7. 创建新进程的主访问令牌(父进程主令牌的副本)。新进程继承了其父进程的安全轮廓(安全描述符),如果通过CreateProcessAsUser来为新进程指定一个不同的访问令牌,
  则该令牌将在后面被正确地改过来
8. 初始化新进程句柄表,如果父进程已经被设置了继承句柄标志,那么,从父进程的句柄表中将任何可继承的句柄拷贝到新进程中 9. 将新进程的退出状态设置为STATUS_PENDING 10. 创建和初始新进程的地址空间 11. 创建内核进程块(KPROCESS) 12. 结束进程地址空间的创建过程 13. 建立Peb 14. 完成执行体进程对象的创建过程(设置返回值,开启审计等)

windows进程/线程创建过程 --- windows操作系统学习_第11张图片

至此,这就是一个"进程对象(EPROCESS/KPROCESS)"的初始化过程了。然而,经过PspCreateProcess函数之后,新建的进程中并没有任何线程对象,所以,它现在还是一个死的进程空间,也就是说,其中的代码并没被"真正"运行起来。所以,我们接下来讨论线程对象的的创建和初始化。

回到之前留下的一个问题,我们在分析Kernel32.dll中的NtCreateProcessEx()的时候,我们提到,那个时候是"暂时"离开ring3,穿越进ring0去创建进程对象(EPROCESS/KPROCESS)。
到了这里,可以解释这句话的意思了,我们的内核代码在创建完进程相关的结构后,就会退出内核模式,穿越回ring3用户模式。继续执行kernel32.dll中的代码逻辑。

 

 

 

 

 

 

阶段五: 创建线程的内核对象

创建完进程对象后,代码从内核模式穿越会用户模式,我们在kernel32.dll中的NtCreateProcessEx()代码处继续往下翻:

 

1. 处理下创建进程后的残余工作
在创建进程返回后,此时EPROCESS,PEB结构已经创建,进程地址空间也已经创建并初始化了。接下处理下创建进程后的残余工作,调用NtSetInformationProcess函数设置进程的优先级和默认处理模式.

NTSYSAPI NTSTATUS NTAPI NtSetInformationProcess(
    IN HANDLE ProcessHandle,  //进程句柄
    IN PROCESS_INFORMATION_CLASS ProcessInformationClass, //进程信息类型索引(18 = ProcessPriorityClass)
    IN PVOID ProcessInformation,  //进程信息
    IN ULONG ProcessInformationLength //进程信息的的大小
);
..
v238 = NtSetInformationProcess(v294, 18, &v295, 2);
..

 

2. 构建线程的环境

接下来要做的就是创建线程, 不过在在此之前还要构建线程的环境,调用BaseCreateStack函数创建栈:

NTSTATUS WINAPI BaseCreateStack    (    
        HANDLE hProcess,  //进程句柄
        SIZE_T StackReserve,  //栈大小
        SIZE_T StackCommit,  //栈的最大值
        PINITIAL_TEB InitialTeb  //初始TEB
);    
...
v23 = BaseCreateStack(v294, v191, v36, &v213);
...

接着调用BaseInitializeContext初始化线程上下文, 然后调用BaseFormatObjectAttributes函数格式化对象(以便传递给NtCreateThread)

....
BaseInitializeContext(&v297, v184, v189, v214, 0);
v188 = BaseFormatObjectAttributes(&v207, v166, 0);
...

 

3. 最后调用NtCreateThread创建线程

NTSYSAPI NTSTATUS NTAPI NtCreateThread( 
    OUT PHANDLE ThreadHandle,  //线程句柄指针
    IN ACCESS_MASK DesiredAccess,  //访问掩码
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,  //对象属性
    IN HANDLE ProcessHandle,   //进程句柄
    OUT PCLIENT_ID ClientId,   //客户端ID结构指针
    IN PCONTEXT ThreadContext,   //线程上下文结构指针
    IN PINITIAL_TEB InitialTeb,  //初始化TEB
    IN BOOLEAN CreateSuspended   //是否创建后挂起(默认是挂起的,题外话: 这就是APC进程注入的原理所在了)
);
//kernel32.dll
..
v23 = NtCreateThread(&v290, 2032639, v188, v294, &v262, &v297, &v213, 1);
...

执行了一小段之后,又再次调用NtCreateThread穿越进内核模式,这次的目的是创建线程对象。

类似于进程的创建过程,线程的创建过程是从NtCreateThread()开始的, 它的源代码位于 base\ntos\ps\create.c 中,我还是决定直接在代码中插入注释,逐行的分析源代码

NTSTATUS NtCreateThread(
    __out PHANDLE ThreadHandle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ProcessHandle,
    __out PCLIENT_ID ClientId,
    __in PCONTEXT ThreadContext,
    __in PINITIAL_TEB InitialTeb,
    __in BOOLEAN CreateSuspended
    ) 
{
    NTSTATUS Status;
    INITIAL_TEB CapturedInitialTeb;

    PAGED_CODE(); 
    try 
    {
        if (KeGetPreviousMode () != KernelMode) 
        {
            ProbeForWriteHandle (ThreadHandle);
            if (ARGUMENT_PRESENT (ClientId)) 
            {
                ProbeForWriteSmallStructure (ClientId, sizeof (CLIENT_ID), sizeof (ULONG));
            }
            if (ARGUMENT_PRESENT (ThreadContext) ) 
            {
                ProbeForReadSmallStructure (ThreadContext, sizeof (CONTEXT), CONTEXT_ALIGN);
            } 
            else 
            {
                return STATUS_INVALID_PARAMETER;
            }
            ProbeForReadSmallStructure (InitialTeb, sizeof (InitialTeb->OldInitialTeb), sizeof (ULONG));
        }
        CapturedInitialTeb.OldInitialTeb = InitialTeb->OldInitialTeb;
        if (CapturedInitialTeb.OldInitialTeb.OldStackBase == NULL &&
            CapturedInitialTeb.OldInitialTeb.OldStackLimit == NULL) 
        { 
            CapturedInitialTeb = *InitialTeb;
        }
    } 
    except (ExSystemExceptionFilter ())
    {
        return GetExceptionCode ();
    }
    Status = PspCreateThread (ThreadHandle,
                              DesiredAccess,
                              ObjectAttributes,
                              ProcessHandle,
                              NULL,
                              ClientId,
                              ThreadContext,
                              &CapturedInitialTeb,
                              CreateSuspended,
                              NULL,
                              NULL);

    return Status;
}

NtCreateThread()所做的事情很简单:

1. 对非内核模式传递过来的调用,检查几个参数是否可写(输出参数TheadHandle、ClientId、输入参数ThreadContext、InitialTeb)
2. 处理InitialTeb参数,将它放到局部变量CapturedInitialTeb中
3. 调用真正创建线程的函数CreateSuspended()。参数原封不动的传过去,并增加了几个参数

PspCreateThread函数的原型如下:

NTSTATUS PspCreateThread(
    OUT PHANDLE ThreadHandle, //输出参数: 新线程的句柄
    IN ACCESS_MASK DesiredAccess,  //新线程的访问权限
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, //新线程对象的属性
    IN HANDLE ProcessHandle, //线程所属的进程的句柄
    IN PEPROCESS ProcessPointer, //所属进程的EPROCESS对象,此参数仅当创建系统线程时才会指向全局PsInitialSystemProcess
    OUT PCLIENT_ID ClientId OPTIONAL, //指向新线程的CLIENT_ID结构
    IN PCONTEXT ThreadContext OPTIONAL, //提供新线程的执行环境(之前创建好的), 它代表了用户模式线程的初始执行环境
    IN PINITIAL_TEB InitialTeb OPTIONAL, //为新线程Teb提供初始值
    IN BOOLEAN CreateSuspended,  //指明新线程被创建起来后是否"被挂起"。如果为TRUE则意味着新线程创建完以后并不立即执行      
    
//以后通过NtResumeThread函数让它开始运行 IN PKSTART_ROUTINE StartRoutine OPTIONAL, //系统线程启动函数的地址 IN PVOID StartContext //系统线程启动函数的执行环境 );

和谈到PspCreateProcess类似,我们在谈到PspCreateThread的时候也要注意一个知识点:

1. PspCteateThread函数仅仅被NtCreateThread和PsCreateSystemThread这两个函数调用,分别用于创建用户线程和系统线程对象。
2. 在PspCteateThread函数的参数中,ThreadContext和InitialTeb参数针对用户线程的创建操作,而StartRoutine和StartContext参数则针对系统线程的创建操作

接下来将再次贴出PspCreateThread函数的一大段代码,我将尽我的能力在代码插入注释,来逐行解释线程创建的过程。

在开始之前,我们先做一个"路线概览",之后在分析详细的代码的时候我们需要不断的回到这个"线路概览"上来,不断从宏观和微观的角度来看待线程的创建过程。

1. 递增进程对象中的线程计数值
2. 创建并初始化一个执行体线程块(ETHREAD)
3. 为新线程生成一个线程ID
4. 在进程的用户模式地址空间中建立Teb
5. 用户模式线程的起始地址被保存在ETHREAD中。对于windows线程,这是系统提供的线程启动函数,位于Kernel32.dll中(对于进程中的第一个线程是BaseThreadStart,对于其他的线程则
  是BaseThreadStart)。用户指定的windows启动地址被保存在ETHREAD块中的另一个位置上,因而,系统提供的线程启动函数可以调用用户指定的启动函数
6. 调用KeInitThread来建立起KTHREAD块。该线程初始的基本优先级和当前优先级均被设置为所属进程的基本优先级,它的亲和性和时限被设置为进程的亲和性和时限。该函数也会设置初始线程
  的理想处理器。KeInitThread接下来为该线程分配一个内核栈(注意和用户线程栈区分),并且为它初始化与机器相关的硬件环境,包括执行环境、陷阱和异常帧。该线程的执行环境也被建立
  起来,因而在内核模式下此线程可在KiThreadStartup中启动起来。最后,KeInitThread将该线程的状态设置为"已初始化",并返回到PspCreateThread 7. 调用任何已等级的系统全局范围的线程来创建通知例程 8. 该线程的访问令牌被设置为指向进程的访问令牌,然后做一次访问检查,以确定调用者是否有权创建该线程。如果你是在本地进程中创建一个线程,那么这一检查将总是成功,但是,如果你是在
  另一个进程中创建一个"远程线程",并且创建线程的进程没有""调试特权,那么这一访问检查可能会失败 9. 最后,该线程做好执行准备

 接下来贴上源代码,希望朋友们能拿起手边的书本,资料,配合着耐心读完:

NTSTATUS PspCreateThread(
    OUT PHANDLE ThreadHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN HANDLE ProcessHandle,
    IN PEPROCESS ProcessPointer,
    OUT PCLIENT_ID ClientId OPTIONAL,
    IN PCONTEXT ThreadContext OPTIONAL,
    IN PINITIAL_TEB InitialTeb OPTIONAL,
    IN BOOLEAN CreateSuspended,
    IN PKSTART_ROUTINE StartRoutine OPTIONAL,
    IN PVOID StartContext
    )  
{

    HANDLE_TABLE_ENTRY CidEntry;
    NTSTATUS Status;
    PETHREAD Thread;
    PETHREAD CurrentThread;
    PEPROCESS Process;
    PTEB Teb;
    KPROCESSOR_MODE PreviousMode;
    HANDLE LocalThreadHandle;
    BOOLEAN AccessCheck;
    BOOLEAN MemoryAllocated;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    SECURITY_SUBJECT_CONTEXT SubjectContext;
    NTSTATUS accesst;
    LARGE_INTEGER CreateTime;
    ULONG OldActiveThreads;
    PEJOB Job;
    AUX_ACCESS_DATA AuxData;
    PACCESS_STATE AccessState;
    ACCESS_STATE LocalAccessState;

    PAGED_CODE(); 

    /*
    首先获得当前线程对象,以及获取此次创建操作来自于内核模式还是用户模式(PreviousMode)
    */
    CurrentThread = PsGetCurrentThread();

    if (StartRoutine != NULL) 
    {
        PreviousMode = KernelMode;
    } 
    else 
    {
        PreviousMode = KeGetPreviousModeByThread (&CurrentThread->Tcb);
    }

    Teb = NULL;

    Thread = NULL;
    Process = NULL;
    /*
    根据进程句柄参数ProcessHandle获得相应的进程对象,放到局部变量Process中
    Ps: 观察总路线图,我们现在处于"递增进程对象中的线程计数值" 
    */
    if (ProcessHandle != NULL) 
    { 
        Status = ObReferenceObjectByHandle (ProcessHandle,
                                            PROCESS_CREATE_THREAD,
                                            PsProcessType,
                                            PreviousMode,
                                            &Process,
                                            NULL);
    } 
    else 
    {
        if (StartRoutine != NULL) 
        {
            ObReferenceObject (ProcessPointer);
            Process = ProcessPointer;
            Status = STATUS_SUCCESS;
        } 
        else 
        {
            Status = STATUS_INVALID_HANDLE;
        }
    }

    if (!NT_SUCCESS (Status)) 
    {
        return Status;
    } 

    if ((PreviousMode != KernelMode) && (Process == PsInitialSystemProcess)) 
    {
        ObDereferenceObject (Process);
        return STATUS_INVALID_HANDLE;
    }
    /*
    调用ObCreateObject函数创建一个线程对象ETHREAD
    */
    Status = ObCreateObject (PreviousMode,
                             PsThreadType,
                             ObjectAttributes,
                             PreviousMode,
                             NULL,
                             sizeof(ETHREAD),
                             0,
                             0,
                             &Thread);

    if (!NT_SUCCESS (Status)) 
    {
        ObDereferenceObject (Process);
        return Status;
    }
    /*
    把整个ETHREAD结构清零
    */
    RtlZeroMemory (Thread, sizeof (ETHREAD)); 
    /*
    对ETHREAD的一些基本的域进行初始化(RundownProtect、ThreadsProcess(指向由进程句柄参数解析出来的EPROCESS对象)、Cid(包括UniqueProcess和UniqueThread成员))
    这里Cid.UniqueProcess是从Process对象中来的,而Cid.UniqueThread则是通过调用ExCreateHandle()函数在CID句柄表中创建一个句柄表项而获得的
    */
    ExInitializeRundownProtection (&Thread->RundownProtect);  
    Thread->ThreadsProcess = Process;
    Thread->Cid.UniqueProcess = Process->UniqueProcessId;
    CidEntry.Object = Thread;
    CidEntry.GrantedAccess = 0;
    Thread->Cid.UniqueThread = ExCreateHandle (PspCidTable, &CidEntry);

    if (Thread->Cid.UniqueThread == NULL) 
    {
        ObDereferenceObject (Thread);
        return (STATUS_INSUFFICIENT_RESOURCES);
    } 
    /*
    继续初始化新线程对象ETHREAD结构中的一些域(ReadClusterSize、LpcReplySemaphore、LpcReplyChain、IrpList、PostBlockList、ThreadLock(线程锁成员)、
  ActiveTimerListLock、ActiveTimerListHead)
*/ Thread->ReadClusterSize = MmReadClusterSize; KeInitializeSemaphore (&Thread->LpcReplySemaphore, 0L, 1L); InitializeListHead (&Thread->LpcReplyChain); InitializeListHead (&Thread->IrpList); InitializeListHead (&Thread->PostBlockList); PspInitializeThreadLock (Thread); KeInitializeSpinLock (&Thread->ActiveTimerListLock); InitializeListHead (&Thread->ActiveTimerListHead); /* 获得"进程"的RundownProtect锁,以避免在创建过程中该进程被停掉(rundown)。直到该线程被插入到进程的线程链表中(通过KeStartThread函数)之后,PspCreateThread此释放该锁 */ if (!ExAcquireRundownProtection (&Process->RundownProtect)) { ObDereferenceObject (Thread); return STATUS_PROCESS_IS_TERMINATING; } if (ARGUMENT_PRESENT (ThreadContext)) { /* 如果ThreadContext非NULL,则此次创建的是用户模式线程,于是调用MmCreateTeb创建一个Teb,并用InitialTeb进行初始化 */ Status = MmCreateTeb (Process, InitialTeb, &Thread->Cid, &Teb); if (!NT_SUCCESS (Status)) { ExReleaseRundownProtection (&Process->RundownProtect); ObDereferenceObject (Thread); return Status; } try { /* 利用ThreadContext中的程序指针(EIP寄存器)来设置线程的启动地址StartAddress,并且将ThreadContext中的EAX寄存器设置到线程的Win32StartAddress域 */ Thread->StartAddress = (PVOID)CONTEXT_TO_PROGRAM_COUNTER(ThreadContext); } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } /* 调用KeInitThread函数,根据进程对象中的信息来初始化新线程内核对象(KTHREAD)的一些属性(包括跟同步相关的各个域: 同步头(Header)、WaitBlock初始化、
     线程的系统服务表(SSDT ServiceTable域)、与APC、定时器相关的域、线程的内核栈的初始化等等)。最后,KeInitThread函数根据所提供的参数信息调用
     KiInitializeContextThread以便完成与特定处理器相关的执行环境的初始化。因此,当这个函数执行完后,新线程的状态是"已初始化(Initialized)" Ps: 回顾总路线图,我们现在已经到了"创建并初始化一个执行体线程块(ETHREAD)"这一步了
*/ if (NT_SUCCESS (Status)) { Status = KeInitThread (&Thread->Tcb, NULL, PspUserThreadStartup, (PKSTART_ROUTINE)NULL, Thread->StartAddress, ThreadContext, Teb, &Process->Pcb); } } else { /* 否则,则是系统线程。首先在CrossThreadFlags标志中设置系统线程位。然后将线程的启动地址设置为StartRoutine参数。最后同样地调用KeInitThread函数,
     所以,KeInitThread函数既可以被用来初始化用户模式线程,也可以被用来初始化系线程
*/ Teb = NULL; PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_SYSTEM); Thread->StartAddress = (PKSTART_ROUTINE) StartRoutine; Status = KeInitThread (&Thread->Tcb, NULL, PspSystemThreadStartup, StartRoutine, StartContext, NULL, NULL, &Process->Pcb); } if (!NT_SUCCESS (Status)) { if (Teb != NULL) { MmDeleteTeb(Process, Teb); } ExReleaseRundownProtection (&Process->RundownProtect); ObDereferenceObject (Thread); return Status; } /* 锁住进程,并确保此进程并不是在退出或终止过程中 */ PspLockProcessExclusive (Process, CurrentThread); if ((Process->Flags&PS_PROCESS_FLAGS_PROCESS_DELETE) != 0 || (((CurrentThread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_TERMINATED) != 0) && (ThreadContext != NULL) && (THREAD_TO_PROCESS(CurrentThread) == Process))) { PspUnlockProcessExclusive (Process, CurrentThread); KeUninitThread (&Thread->Tcb); if (Teb != NULL) { MmDeleteTeb(Process, Teb); } ExReleaseRundownProtection (&Process->RundownProtect); ObDereferenceObject(Thread); return STATUS_PROCESS_IS_TERMINATING; } /* 然后进程的活动线程数加1,并且将新线程加入到进程的线程链表中。 */ OldActiveThreads = Process->ActiveThreads++; InsertTailList (&Process->ThreadListHead, &Thread->ThreadListEntry); /* 调用KeStartThread,初始化KTHREAD剩余的域(和调度相关的域: 优先级(BasePriority)、实现设置(Quantum)、处理器亲和性(Affinity)等等)。经过这一步的处理,新线程就可以
  开始运行了 Ps: 在PspCreateThread和KeStartThread这两个函数中,我们都可以看到InsertTailList(&Process->ThreadListHead, &Thread->ThreadListEntry);这样的调用,这是分别
  在执行体层和内核层维护线程和进程的关系(线程从属进程)即EPROCESS->ETHREAD、KPROCESS->KTHREAD。
*/ KeStartThread (&Thread->Tcb); PspUnlockProcessExclusive (Process, CurrentThread); ExReleaseRundownProtection (&Process->RundownProtect); /* 若这是进程中的第一个线程,则触发该进程的创建通知 */ if (OldActiveThreads == 0) { PERFINFO_PROCESS_CREATE (Process); if (PspCreateProcessNotifyRoutineCount != 0) { ULONG i; PEX_CALLBACK_ROUTINE_BLOCK CallBack; PCREATE_PROCESS_NOTIFY_ROUTINE Rtn; for (i=0; i) { CallBack = ExReferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i]); if (CallBack != NULL) { Rtn = (PCREATE_PROCESS_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack); Rtn (Process->InheritedFromUniqueProcessId, Process->UniqueProcessId, TRUE); ExDereferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i], CallBack); } } } } /* 如果新线程所属的进程在一个作业中,则需要做特定的处理 */ Job = Process->Job; if (Job != NULL && Job->CompletionPort && !(Process->JobStatus & (PS_JOB_STATUS_NOT_REALLY_ACTIVE|PS_JOB_STATUS_NEW_PROCESS_REPORTED))) { PS_SET_BITS (&Process->JobStatus, PS_JOB_STATUS_NEW_PROCESS_REPORTED); KeEnterCriticalRegionThread (&CurrentThread->Tcb); ExAcquireResourceSharedLite (&Job->JobLock, TRUE); if (Job->CompletionPort != NULL) { IoSetIoCompletion (Job->CompletionPort, Job->CompletionKey, (PVOID)Process->UniqueProcessId, STATUS_SUCCESS, JOB_OBJECT_MSG_NEW_PROCESS, FALSE); } ExReleaseResourceLite (&Job->JobLock); KeLeaveCriticalRegionThread (&CurrentThread->Tcb); } PERFINFO_THREAD_CREATE(Thread, InitialTeb); /* 通知哪些接收线程创建事件的出调例程(callout routine),即之前绑定在这个线程创建事件的回调函数的例程 */ if (PspCreateThreadNotifyRoutineCount != 0) { ULONG i; PEX_CALLBACK_ROUTINE_BLOCK CallBack; PCREATE_THREAD_NOTIFY_ROUTINE Rtn; for (i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) { CallBack = ExReferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i]); if (CallBack != NULL) { Rtn = (PCREATE_THREAD_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack); Rtn (Thread->Cid.UniqueProcess, Thread->Cid.UniqueThread, TRUE); ExDereferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i], CallBack); } } } /* 线程对象的引用计数加2: 一个针对当前的创建操作,另一个针对要返回的线程句柄 */ ObReferenceObjectEx (Thread, 2); /* 如果CreateSuspended参数指示新线程立即被挂起,则调用KeSuspendThread挂起新线程 */ if (CreateSuspended) { try { KeSuspendThread (&Thread->Tcb); } except ((GetExceptionCode () == STATUS_SUSPEND_COUNT_EXCEEDED)? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { } if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_TERMINATED) { KeForceResumeThread (&Thread->Tcb); } } /* 根据指定的期望访问权限,调用SeCreateAccessStateEx()函数创建一个访问状态结构(ACCESS_STATE) */ AccessState = NULL; if (!PsUseImpersonationToken) { AccessState = &LocalAccessState; Status = SeCreateAccessStateEx (NULL, ARGUMENT_PRESENT (ThreadContext)?PsGetCurrentProcessByThread (CurrentThread) : Process, AccessState, &AuxData, DesiredAccess, &PsThreadType->TypeInfo.GenericMapping); if (!NT_SUCCESS (Status)) { PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_DEADTHREAD); if (CreateSuspended) { (VOID) KeResumeThread (&Thread->Tcb); } KeReadyThread (&Thread->Tcb); ObDereferenceObjectEx (Thread, 2); return Status; } } /* 调用ObInsertObject函数把新线程对象插入到当前进程的句柄表中 */ Status = ObInsertObject (Thread, AccessState, DesiredAccess, 0, NULL, &LocalThreadHandle); if (AccessState != NULL) { SeDeleteAccessState (AccessState); } if (!NT_SUCCESS (Status)) { /* ObInsertObject调用如果不成功,则终止新线程 */ PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_DEADTHREAD); if (CreateSuspended) { KeResumeThread (&Thread->Tcb); } } else { /* ObInsertObject调用成功,设置好输入参数ThreadHandle和ClienId,准备返回 */ try { *ThreadHandle = LocalThreadHandle; if (ARGUMENT_PRESENT (ClientId)) { *ClientId = Thread->Cid; } } except(EXCEPTION_EXECUTE_HANDLER) { PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_DEADTHREAD); if (CreateSuspended) { (VOID) KeResumeThread (&Thread->Tcb); } KeReadyThread (&Thread->Tcb); ObDereferenceObject (Thread); ObCloseHandle (LocalThreadHandle, PreviousMode); return GetExceptionCode(); } } /* 设置新线程的创建时间 */ KeQuerySystemTime(&CreateTime); ASSERT ((CreateTime.HighPart & 0xf0000000) == 0); PS_SET_THREAD_CREATE_TIME(Thread, CreateTime); if ((Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD) == 0) { Status = ObGetObjectSecurity (Thread, &SecurityDescriptor, &MemoryAllocated); if (!NT_SUCCESS (Status)) { PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_DEADTHREAD); if (CreateSuspended) { KeResumeThread(&Thread->Tcb); } KeReadyThread (&Thread->Tcb); ObDereferenceObject (Thread); ObCloseHandle (LocalThreadHandle, PreviousMode); return Status; } SubjectContext.ProcessAuditId = Process; SubjectContext.PrimaryToken = PsReferencePrimaryToken(Process); SubjectContext.ClientToken = NULL; /* 设置新线程的访问权限: GrantedAccess域 */ AccessCheck = SeAccessCheck (SecurityDescriptor, &SubjectContext, FALSE, MAXIMUM_ALLOWED, 0, NULL, &PsThreadType->TypeInfo.GenericMapping, PreviousMode, &Thread->GrantedAccess, &accesst); PsDereferencePrimaryTokenEx (Process, SubjectContext.PrimaryToken); ObReleaseObjectSecurity (SecurityDescriptor, MemoryAllocated); if (!AccessCheck) { Thread->GrantedAccess = 0; } Thread->GrantedAccess |= (THREAD_TERMINATE | THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION); } else { Thread->GrantedAccess = THREAD_ALL_ACCESS; } /* 最后,调用KeReadyThread函数,调用KeReadyThread函数将线程加入到进程对象中的线程就绪队列中(kprocess->ReadyListHead)
  他使新线程进入"就绪(ready)状态",准备马上执行(就绪态只是表明它有机会获得CPU的调度,并不是指立刻就能执行)。 或者,若此时进程被内存调度换出了内存,则新线程的状态为"转移(transition)",以等待换入内存后再执行
*/ KeReadyThread (&Thread->Tcb); /* 引用计数减1,当前操作完成,返回 */ ObDereferenceObject (Thread); return Status; }

线程创建完成后调用CsrClientCallServer通知CRSS 线程创建成功,所以这个通知过程,我们详细说明一下:

到这个点上,所有必要的执行体进程对象和执行体线程对象都已经被创建出来了。接下来kernel32.dll给windows"子系统CSRSS"发送一个消息,从而它可以进一步建立起新进程和线程,
该消息包含以下信息:
1. 进程和线程的句柄 2. 创建标志中的各个项目 3. 该进程的创建者ID 4. 指示该进程是否属于一个windows应用程序的标志(Csrss可以确定是否要显示启动光标) 当windows子系统Csrss接收到此消息时,它执行以下12个步骤 1. CreateProcess复制一份该进程和线程的句柄。在这一步,进程和线程的使用计数从1(在创建时设置的)增加到2 2. 如果进程的优先级和类别没有指定的话,则CreateProcess自动进行优先级类别设置 3. 分配Csrss进程块 4. 新进程的异常端口被设置为windows子系统的通用功能端口,这样,当该进程中发生异常时,windows子系统将会收到一个消息 5. 如果该进程正在被调试(即它被附载到一个调试器进程)的话,则该进程的调试端口被设置为windows子系统的通用功能端口。这一设置可以保证,windows将把在新进程中发生的调试事件
  (比如线程创建和删除、异常、等等)作为消息发送给windows子系统in个,从而它可以把这些事件分发给新进程的调试器进程
6. 分配并初始化Csrss线程块 7. CreateProcess把该线程插入到进程的线程列表中 8. 递增该会话中的进程计数值 9. 进程的停机级别被设置为0x280,这是进程默认停机级别 10. 将新进程块插入到windows子系统范围内的进程的列表中 11. windows子系统的内核模式部分所用到的,针对每个进程的数据结构(W32PROCESS结构)被分配并初始化 12. 显示应用程序启动光标。即沙漏箭头。这是windows提醒用户的方式: "我正在启动某些东西"

代码调用NtRequestWaitReplyPort()来等待回应,最后调用NtResumeThread()函数恢复线程的执行。

在以上调用完后,会调用内核中线程的启动函数KiThreadStartup, 该函数的实现中调用了PspUserThreadStartup,该函数初始化用户APC,将LdrInitializeThunk函数作为APC函数挂入
APC队列中,最后调用KiDeliverApc发生APC, 通过中断返回3环(注意,这里又再次回到ring3)。
//这里插个题外话,在一个进程注入的技术中谈到的APC注入指的就是这一步了,我们通过注册"进程创建"回调函数往"远程进程"的APC队列中插入了DLL文件,即插入了远程进程的APC队列中,
那么操作系统在这一步就会逐个执行APC队列中的任务,同样也把我们注入的DLL给执行了

当PspUserThreadStartup返回到KiThreadStartup中后,它从内核模式中返回,切换到3环KiUserApcDispatcher,实现中调用LdrInitializeThunk来加载需要的DLL,并且用DLL_PROCESS_ATTACH功能代码来调用DLL的入口点

KiUserApcDispatcher
lea     edi, [esp+arg_C]
pop     eax
call    eax            //LdrInitializeThunk
push    1
push    edi
call    _ZwContinue@8         ;加载完DLL后,调用该函数继续返回到内核

切换到内核后作部分操作后,再次回到3环(注意,这里又再次回到ring3,这一阶段来回次数较多,放慢速度理解一下),

调用用户空间的线程入口函数BaseProcessStart(Kernel32.dll中的BaseProcessStart函数), 该函数在3环中BaseInitializeContext中有指定。 这个时候,线程就从之前指定的入口点代码处开始执行起来了。当然,它要根据优先级遵循CPU的调度,分配到了时间才能执行。

//上面说了这一大段,整理一下流程:

1. KiThreadStartup降低IRQL到APC_LEVEL
2. 然后调用系统初始的线程函数PspUserThreadStartup
3. PspUserThreadStartup把一个用户模式的APC插入到线程的用户APC队列中,此APC例程是在全局变量PspSystemDll中指定的,指向ntdll.dll的LdrInitializeThunk函数
4. PspUserThreadStartup函数返回之后,KiThreadStartup函数返回到用户模式,此时,PspUserThreadStartup插入的APC被交付,于是LdrInitializeThunk函数被调用,
  这是映像加载器(image loader)的初始化函数。LdrInitializeThunk函数完成加载器、堆管理器等初始化工作,然后加载必要的DLL,并且调用这些DLL的入口函数。最后,
  当LdrInitializeThunk返回到用户模式APC分发器时,该线程开始在用户模式下执行,调用应用程序指定的线程启动函数,此启动函数的地址已经在APC交付时备被压入到用户栈中
5. 至此,进程已经完成建立起来了,开始执行用户空间的代码

附上线程创建的流程图

windows进程/线程创建过程 --- windows操作系统学习_第12张图片

至此,阶段5的线程创建也分析结束了,此后线程就正常的运行起来了,至于之后在代码中是否要加载DLL还是别的功能,那和具体的代码逻辑有关,CPU以线程为调度单位根据CPU调度算法轮询地执行线程中的代码逻辑。

这里还有一个问题,我们在文章的最开始说过: 这个ring3层的进程创建分析,即这个进程创建的发起源地是ring3。那如果要从ring0开始创建进程呢?答案是95%几乎一样,唯一不一样的的最开始的入口,我们在内核层创建进程调用的NtCreateProcess(),这也是一个"转接层"函数

NTSTATUS NtCreateProcess(
    __out PHANDLE ProcessHandle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ParentProcess,
    __in BOOLEAN InheritObjectTable,
    __in_opt HANDLE SectionHandle,
    __in_opt HANDLE DebugPort,
    __in_opt HANDLE ExceptionPort
    )
{
    ULONG Flags = 0;

    if ((ULONG_PTR)SectionHandle & 1) 
    {
        Flags |= PROCESS_CREATE_FLAGS_BREAKAWAY;
    }
    if ((ULONG_PTR) DebugPort & 1) 
    {
        Flags |= PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT;
    }
    if (InheritObjectTable) 
    {
        Flags |= PROCESS_CREATE_FLAGS_INHERIT_HANDLES;
    }

    return NtCreateProcessEx (ProcessHandle,
                              DesiredAccess,
                              ObjectAttributes OPTIONAL,
                              ParentProcess,
                              Flags,
                              SectionHandle,
                              DebugPort,
                              ExceptionPort,
                              0);
}

函数的源代码位于 base\ntos\ps\create.c  中,它知识简单地对参数稍作处理,然后把创建进程得分任务交给NtCreateProcessEx函数,之后的流程就和我们之前分析的一模一样了。

ring3创建进程和ring0创建进程的大致流程是:

1.1. ring3: CreateProcessA->CreateProcessInternalA->CreateProcessInternalW->NtCreateProcessEx->PspCreateProcess->NtCreateThread->PspCreateThread->
KiThreadStartup->PspUserThreadStartup->LdrInitializeThunk->BaseProcessStart
////CreateProcessA()只是起来转接层的作用
1.1 ring3: CreateProcess->CreateProcessInternalW->NtCreateProcessEx->PspCreateProcess->->NtCreateThread->PspCreateThread->KiThreadStartup->
PspUserThreadStartup->LdrInitializeThunk->BaseProcessStart
2. ring0: NtCreateProcess->NtCreateProcessEx->PspCreateProcss->->NtCreateThread->PspCreateThread->KiThreadStartup->PspUserThreadStartup-> LdrInitializeThunk->BaseProcessStart

 

 

至此,windows中创建进程/线程的全部流程我们都分析清楚了,现在我们可以理解了windows系统中一个用户进程的整个创建过程,虽然有一部分工作是由windows子系统来完成的,但是从操作系统内核的角度,我们依然可以清晰地看到,windows为了支持进程和线程的概念,是如何以对象的方式来管理它们,并创建和初始化进程和线程,使它们变成真正可以工作的功能实体。

 

本文也到此结束,如有不对的地方,恳请朋友们指正,共同学习

你可能感兴趣的:(windows进程/线程创建过程 --- windows操作系统学习)