C++反汇编-继承和多重继承

 

学无止尽,积土成山,积水成渊-《C++反汇编与逆向分析技术揭秘》 读书笔记

一、单类继承

  • 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的内存结构中,父类私有的成员数据依然存在。C++语法规定的访问限制仅限于编译层面,在编译过程中进行语法检查,因此访问控制不会影响对象的内存结构。
  • 子类未提供构造函数或析构函数,而父类却需要构造函数与析构函数时,编译器会为子类提供默认的构造函数与析构函数。但是子类有构造函数,而父类不存在构造函数,且没有虚函数,则编译器不会为父类提供默认的构造函数。
 

1. 内存结构:

①先安排父类的数据
②后安排子类新定义的数据
 
说明:基于上述的内存排列方法,即父类数据成员被安排前面。不管是父类的对象,还是子类的对象,父类的数据成员在内存中相对于对象的首地址的偏移值都是一样的。而且成员数据访问都是基于this指针间接寻址的。所以,对于子类对象而言,使用父类指针或者子类指针都可以正确访问其父类数据。
 

2. 虚表

虚表的排列顺序是按虚函数在类继承层次中首次声明的顺序依次排列的。只要继承了父类,其派生类的虚函数表中的父类部分的排列就与父类一样。子类新定义的虚函数会按照声明顺序紧跟其后。
 
 

3. 构造函数

①先调用父类构造函数
②然后按照声明顺序调用成员数据变量的构造函数和初始化列表中指定的成员
③最后再执行子类构造函数的函数体。
 
说明
①父类构造函数,虚表指针修改为指向父类的虚表,所以在父类构造函数内调用虚函数,调用的是父类的虚函数。
②子类构造函数,虚表指针修改为指向子类的虚表
 

4. 析构函数

①先调用子类析造函数
②然后成员对象的析构函数,按照声明的顺序以倒序方式依次调用成员对象的析构函数。
③再执行父类构造函数
 
说明
  • 析构函数执行会首先设置虚表指针为自身虚表,再调用自身的析构函数。防止父类析构函数内调用子类对象的虚函数。
  • 类有派生与继承关系,需要声明析构函数为虚函数。若析构函数不是虚函数时,当使用父类指针指向堆中的子类对象,并使用delete释放对象空间时,编译器会按照指针类型调用父类的析构函数,从而引发错误。
 
识别类之间的关系:
先定位构造函数,根据构造先后顺序得到与之相关的其他类。
再根据虚表,利用IDA中使用引用参考功能可得到所有的构造和析构函数。
 
 

二、多重继承

1. 内存排列:

  • 数据成员的排列顺序由继承父类的先后顺序所决定,从左向右依次排列。
  • 子类虚表指针的个数取决于所继承的父类的个数,有几个父类便对应几个虚表指针(虚基类除外)。
  • 将一个子类对象赋值给某个父类指针时,该父类指针便指向该父类所对应的虚表指针。
 

三、单类继承与多重继承比较:

  • 单继承类
    • 在类对象占用的内存空间中,只保存一份虚表指针
    • 只有一个虚表指针,对应的也只有一个虚表
    • 虚表中各项保存了类中各虚函数的首地址
    • 构造时先构造父类,再构造自身,并且只调用一次父类构造函数
    • 析构时限析构自身,再析构父类,并且只调用一次父类析构函数
  • 多重继承类
    • 在类中所占用的内存空间中,根据继承父类的个数保存对应的虚表指针
    • 根据所保存的虚表指针的个数,对应产生相应个数的虚表。
    • 转换父类指针时,需要跳转到对象的首地址。
    • 构造时需要按照继承顺序调用多个父类构造函数。
    • 析构时先析构自身,然后以与构造函数相反的顺序调用所有父类的析构函数
    • 当对象作为成员时,整个类对象的内存结构和多重继承相似。当类中无虚函数时,整个类对象内存结构和多重继承完全一样,可酌情还原;当父类或成员对象存在虚函数时,通过过观察虚表指针的位置和构造函数、析构函数中填写虚表指针的数目及目标地址,来还原继承或成员关系。

四、 示例

1. 单类继承:

C++源码

 1 #include <iostream>
 2 using namespace std;
 3  
 4 class Base {
 5 public:
 6             Base(){ nBase= 1;printf("CBase"); }
 7             ~Base(){ printf("~CBase"); }
 8             virtual void f() { printf("Base:f()");}
 9             virtual void g() { printf("Base:g()");}
10 private:
11             int nBase;
12  
13 };
14  
15 
16 class Derive : public Base {
17 public:
18             Derive(){ nDerive=2;printf("Derive"); }
19             ~Derive(){ printf("~Derive"); }
20             virtual void g(){ printf("Dervie:g()");}
21             virtual void h(){ printf("Dervie:h()");}
22 private:
23             int nDerive;
24 };
25  
26  
27  
28 int main()
29 {
30             Derive d;
31             Base *b = &d;
32             b->g();
33             return 0;

34 }

汇编代码(VS2010编译)

1. 内存分布

1 类Derive对象
2 0019FD30 0139583C =>.rdata:const Derive::`vftable'
3 0019FD34 00000001 =>Base.nBase
4 0019FD38 00000002 =>Derive.nDerive
5
6 虚函数表
7 0139583C 01391163 Base::f(void)
8 01395840 0139110E Derive::g(void)
9 01395844 013911AE Derive::h(void)

2. 构造函数

1 pop     ecx                    ;=>this指针出栈
2 mov [ebp+this], ecx ;=>保存this指针
3 mov ecx, [ebp+this]
4 call j_??0Base@@QAE@XZ ;=>调用基类构造函数Base::Base(void)
5 mov eax, [ebp+this] ;=>eax=this指针
6 mov dword ptr [eax], offset ??_7Derive@@6B@ ;=>初始化虚表指针为const Derive::`vftable'

3. 析构函数

 1 pop     ecx                    ;=>this指针出栈
2 mov [ebp+this], ecx ;=>保存this指针
3 mov eax, [ebp+this]
4 mov dword ptr [eax], offset ??_7Derive@@6B@ ;=>重置虚表指针为const Derive::`vftable'
5 mov esi, esp
6 push offset aDerive ; "~Derive"
7 call ds:__imp__printf
8 add esp, 4
9 cmp esi, esp
10 call j___RTC_CheckEsp
11 mov ecx, [ebp+this] ;=>ecx传参this指针
12 call j_??1Base@@QAE@XZ ; =>调用基类析构函数 Base::~Base(void)

2. 多重继承:

C++源码
 1 #include <iostream>
2 using namespace std;
3
4 class Base1 {
5 public:
6 virtual void f() { cout << "Base1::f" << endl; }
7 virtual void g() { cout << "Base1::g" << endl; }
8 Base1(){b1 = 1; printf("Base1"); }
9 ~Base1(){ printf("~Base1"); }
10 private:
11 int b1;
12
13 };
14
15 class Base2 {
16 public:
17 virtual void f() { cout << "Base2::f" << endl; }
18 virtual void g() { cout << "Base2::g" << endl; }
19 Base2(){b2 = 2; printf("Base2"); }
20 ~Base2(){ printf("~Base2"); }
21 private:
22 int b2;
23 };
24
25 class Derive : public Base1, public Base2{
26 public:
27 virtual void f() { cout << "Derive::f" << endl; }
28 virtual void g1() { cout << "Derive::g1" << endl; }
29 Derive(){ d1 = 3; printf("Derive"); }
30 ~Derive(){ printf("~Derive"); }
31 private:
32 int d1;
33
34 };
35
36 typedef void(*Fun)(void);
37
38 int main()
39 {
40
41 Derive d;
42 Base1 *b1 = &d;
43 b1->f();
44 b1->g();
45 Base2 *b2 = &d;
46 b2->f();
47 b2->g();
48 return 0;
49 }
汇编代码(VS2010编译)

1.内存分布

;内存布局
0019FA0C 008F584C ;=>.rdata:const Derive::`vftable'{for `Base1'} 第一个虚表
0019FA10 00000001 ;=>Base1.b1
0019FA14 008F583C ;=>.rdata:const Derive::`vftable'{for `Base2'} 第二个虚表
0019FA18 00000002 ;=>Base2.b2
0019FA1C 00000003 ;=>Derive.d1 ;虚函数表Derive::`vftable'{for `Base1'}
00FB584C 00FB1041 ;=>Derive::f(void)
00FB5850 00FB1118 ;=>Base1::g(void)
00FB5854 00FB111D ;=>Derive::g1(void) ;虚函数表Derive::`vftable'{for `Base2'}
00FB583C 00FB1113 ;=>Base2::g(void)
00FB5840 00FB1028 ;=>[thunk]:Derive::f`adjustor{8}' (void)

;追踪地址:00FB1028
00FB1028 jmp ?f@Derive@@W7AEXXZ ;=>[thunk]:Derive::f`adjustor{8}' (void)

;追踪函数:?f@Derive@@W7AEXXZ
00FB1F30 ?f@Derive@@W7AEXXZ proc near
00FB1F30 sub ecx, 8 ;=>调整this指针 this = this+8,则this=>Derive::`vftable'{for `Base2'}
00FB1F33 jmp j_?f@Derive@@UAEXXZ ;=>Derive::f(void)

2.构造函数

 1 00FB14D9 mov     [ebp-4], ecx            ;=>this指针保存在esp-4处
2 00FB14DC mov ecx, [ebp-4]   ;=>ecx获得this指针
3 00FB14DF call j_??0Base1@@QAE@XZ ;=>调用构造函数 Base1::Base1(void)
4 00FB14E4 mov ecx, [ebp-4]   ;=>ecx获得this指针
5 00FB14E7 add ecx, 8    ;=>ecx获得this+8
6 00FB14EA call j_??0Base2@@QAE@XZ ;=>调用构造函数 Base2::Base2(void)
7 00FB14EF mov eax, [ebp-4] ;=>eax获得this指针
8 00FB14F2 mov dword ptr [eax], offset ??_7Derive@@6BBase1@@@ ;=>初始化第一个虚表指针为const Derive::`vftable'{for `Base1'}
9 00FB14F8 mov eax, [ebp-4] ;=>eax获得this指针
10 00FB14FB mov dword ptr [eax+8], offset ??_7Derive@@6BBase2@@@ ;=>初始化第二个虚表指针const Derive::`vftable'{for `Base2'}
11 00FB1502 mov eax, [ebp-4] ;=>eax获得this指针
12 00FB1505 mov dword ptr [eax+10h], 3
13 00FB150C push offset Format ; "Derive"
14 00FB1511 call ds:__imp__printf
15 00FB1517 add esp, 4

3.析构函数

00FB17C9 mov     [ebp-4], ecx        ;=>this指针保存在esp-4处
00FB17CC mov eax, [ebp-4] ;=>ecx获得this指针
00FB17CF mov dword ptr [eax], offset ??_7Derive@@6BBase1@@@ ;=>重置第一个虚表指针为const Derive::`vftable'{for `Base1'}
00FB17D5 mov eax, [ebp-4]
00FB17D8 mov dword ptr [eax+8], offset ??_7Derive@@6BBase2@@@ ;=>重置第二个虚表指针为const Derive::`vftable'{for `Base2'}
00FB17DF push offset aDerive ; "~Derive"
00FB17E4 call ds:__imp__printf
00FB17EA add esp, 4
00FB17ED mov ecx, [ebp-4] ;=>ecx获得this指针
00FB17F0 add ecx, 8 ;=>ec;得this+8
00FB17F3 call j_??1Base2@@QAE@XZ ;=>调用虚构函数Base2::~Base2(void)
00FB17F8 mov ecx, [ebp-4] ;=>ecx获得this指针
00FB17FB call j_??1Base1@@QAE@XZ ;=>调用虚构函数Base1::~Base1(void)

4.虚函数调用

;Base1 *b1 = &d;
00FB1431 lea eax, [ebp-14h] ;=>eax获得this指针
00FB1434 mov [ebp-18h], eax ;=>局部变量b1赋值为this指针 ;b1->f();
00FB1437 mov eax, [ebp-18h] ;=>eax获得b1值
00FB143A mov edx, [eax] ;=>edx指向第一个虚表
00FB143C mov ecx, [ebp-18h] ;=>ecx传递this指针
00FB143F mov eax, [edx] ;=>eax获得成员函数Derive::f(void)的地址
00FB1441 call eax ;=>调用成员函数Derive::f(void) ;b1->g();
00FB1443 mov eax, [ebp-18h] ;=>eax获得b1值
00FB1446 mov edx, [eax] ;=>edx指向第一个虚表
00FB1448 mov ecx, [ebp-18h] ;=>ecx传递this指针
00FB144B mov eax, [edx+4] ;=>eax获得成员函数Derive::g(void)的地址
00FB144E call eax ;=>调用成员函数Derive::g(void) ;Base2 *b2 = &d;
00FB1457 lea ecx, [ebp-14h] ;=>ecx获得this指针
00FB145A add ecx, 8 ;=>ecx=this+8指向第二个虚表
00FB145D mov [ebp-64h] ;=>保存ecx到临时变量中
00FB1460 jmp short loc_FB1469 ;--|
; |
00FB1469 mov edx, [ebp-64h];<----|
00FB146C mov [ebp-1Ch], edx ;=>局部变量b2赋值为this+8 ;b2->f();
00FB146F mov eax, [ebp-1Ch] ;=>eax获得b2值
00FB1472 mov edx, [eax] ;=>edx获得指向第二个虚表
00FB1474 mov ecx, [ebp-1Ch] ;=>ecx传递this+8
00FB1477 mov eax, [edx+4] ;=>eax为第二个虚表的第二个元素=>[thunk]:Derive::f`adjustor{8}' (void),间接调用Derive::f(void)
00FB147A call eax ;b2->g();
00FB147C mov eax, [ebp+b2] ; =>eax获得b2值
00FB147F mov edx, [eax] ;=>edx获得指向第二个虚表
00FB1481 mov ecx, [ebp+b2] ;=>ecx传递this+8
00FB1484 mov eax, [edx] ;=>eax为第二个虚表的第一个元素=>Base2::g(void)
00FB1486 call eax ;=>调用Base2::g(void)

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),并开始执行程序

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

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 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的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代码会在文章的最后给出):

进入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_);
...

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

阶段三: 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;
}

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

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这个函数,我们就要进入内核层的代码了。所以这里是一个分界点。

下面附上函数的流程图:

阶段四: 创建进程的"执行体进程对象",(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. 完成执行体进程对象的创建过程(设置返回值,开启审计等)

至此,这就是一个"进程对象(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<PSP_MAX_CREATE_PROCESS_NOTIFY; 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. 至此,进程已经完成建立起来了,开始执行用户空间的代码

附上线程创建的流程图

至此,阶段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为了支持进程和线程的概念,是如何以对象的方式来管理它们,并创建和初始化进程和线程,使它们变成真正可以工作的功能实体。

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

只要还有梦,心就能飞翔
 
 
 
分类: 逆向工程

最新文章

  1. cookie设置保存用户名,填入中文名之后出现的错误500问题
  2. 福利到~分享一个基于jquery的智能提示控件intellSeach.js
  3. 搭建java web开发环境、使用eclipse编写第一个java web程序
  4. window.open被浏览器拦截的解决方案
  5. 轻量linux-Crunch bang
  6. iOS-图片拉伸,最常用的图片拉伸操作总结(干货)
  7. Apache配置文件中的deny与allow小结
  8. 部署SharePoint解决方式包时遇到的问题
  9. IIS 启用或关闭目录浏览
  10. Python Errors and Exceptions
  11. Android Intent机制与常见的用法
  12. .net mvc 超过了最大请求长度 限制文件上传大小
  13. web 服务器、PHP、数据库、浏览器是如何实现动态网站的
  14. sql server 性能调优之 死锁排查
  15. openmp查看最大线程数量
  16. JavaScript 字符串转json格式
  17. WRT 下 C++ wstring, string, String^ 互转
  18. MySQL管理实务处理
  19. e614. Setting the Initial Focused Component in a Window
  20. 『cs231n』视频数据处理

热门文章

  1. 阐述linux IPC(两):基于socket进程间通信(下一个)
  2. warning: shared library text segment is not shareable
  3. 关联A850刷机包 高级电源 时间中心 优化 ROOT 动力 美化 简化
  4. C语言学习_查找三分之二
  5. [转载]cookie
  6. Android Fragment与Activity之间的数据交换(Fragment从Activity获取数据)
  7. ASP.NET的CMS
  8. Apache &amp; WebDav 配置(一)
  9. 【百度地图API】如何制作一张魔兽地图!!——CS地图也可以,哈哈哈
  10. oracle 创建用户,授权用户,创建表,查询表