进程可以看作一个容器或边界,一个进程使用的资源无法自动被其他进程可见,进程需要使用某些进程间通信机制在不同进程间传递消息。
一个进程中出现的异常并不会影响其他进程,最糟糕的情况是这个进程崩溃,但是系统的其他部分依然不会受到影响。
由应用程序调用 Windows API 创建的进程,可通过如下方式创建:
不通过 Windows API ,直接调用内核函数创建的进程,通常为 Windows 原生进程、最小进程或 Pico 进程。
CreateProcess 或 CreateProcessAsUser ===> CreateProcessInternal ===> NtCreateUserProcess ===> NtCreateUserProcess
说明:
其中 执行体 中的操作使用内核模式,其他部分操作使用用户模式
CreateProcessWithLogonW 或 CreateProcessWithTokenW ===> SlrCreateProcessWithLogon ===> CreateProcessAsUser ===> NtCreateUserProcess ===> NtCreateUserProcess
说明:
其中 执行体 中的操作使用内核模式,其他部分操作使用用户模式
NtCreateUserProcess
说明:
Windows 特殊进程直接调用 NtCreateUserProcess 函数创建进程
Windows 不同的部件维护了不同的进程数据结构,主要的结构如下:
Windows 进程的主数据结构,除进程环境块(PEB)位于进程(用户)地址空间外,其余主要部分都位于系统地址空间中,主要有以下组成部分:
EPROCESS 结构中进程控制块(PCB)的数据结构,其主要有以下组成部分:
由 Windows 子系统进程(csrss)维护的进程数据结构,仅 Windows 应用程序才有相关联的 CSR_EPROCESS 结构。
由于每个会话都有自己的 Windows 子系统实例,所以 CSR_EPROCESS 结构是由每个会话的 Windows 子系统进程维护的。
其主要有以下组成部分:
由 Windows 子系统的内核模式(win32k.sys)部分维护的进程数据结构,当线程首次调用 Windows 并在内核模式中实现 USER 或 GUI 函数时创建的,包含了内核(win32k)中 Windows 图形和窗口管理代码维护 GUI 进程状态信息所需的全部信息。
其主要有以下组成部分:
当一个进程所使用的令牌包含调试权限时,可向计算机上运行的其他任何进程请求所有访问权限,此时对某些进程来说便会存在风险,因此 Windows 提出了 “受保护的进程” 的概念,其属于一种特殊的进程,其他进程无法访问到受保护的进程的相关信息,受保护的进程需要由使用了 Windows 颁发的数字签名的映像文件创建。
Windows 中常见的受保护进程有:
受保护的进程创建过程是在内核模式下进行的,它会在自己的 eprocess 结构中设置一个特殊位,借此修改进程管理器中与安全有关的例程的行为,进而拒绝通常会授予管理员的某些访问权限。
受保护进程轻型(PPL)可以得到与传统受保护进程相同的保护,但其新增了一个属性值的维度,可用于标记不同的签名方,从而针对不同的签名方授予不同程度的保护。
PPL的签名方主要有:
受保护进程的能力始终胜过受保护进程轻型,高优先级签名方的进程可以访问低优先级签名方的进程,但是反之不行。
为了防止注入攻击,进程加载任何 DLL 都必须和主执行文件一样经过代码完整性组件的检查。
借助 PPL ,可实现反恶意软件程序,AM主要包括的组件如下:
运行 AM 时需要预先启动反恶意软件驱动程序(ELAM),其需要微软提供的特殊反恶意软件证书。
进程结构不完整的一种进程,其缺少部分常规进程的结构,Windows 中的最小进程有:
Pico 进程需要依赖于 Pico 提供程序,Pico 提供程序 必须先于任何第三方驱动程序(包括引导驱动程序)加载。
创建一个 Windows 进程涉及操作系统的以下三个部分:
创建一个 Windows 进程的大致流程如下:
阶段一: 转换并验证参数和标志
验证参数,将 Windows 子系统标志和选项转换为原生形式,解析、验证并转换属性列表为原生形式
阶段二: 打开 EXE 并创建节对象
打开要在进程中执行的映像文件(.exe)
阶段三、四: 创建 Windows 进程和线程对象
创建 Windows 执行体进程对象、创建初始线程(栈、上下文、Windows 执行体线程对象)
阶段五: 执行 Windows 子系统指定的进程初始化任务
执行创建之后需要的、与 Windows 子系统有关的进程初始化操作,在 Windows 子系统中设置新的进程和线程
阶段六: 开始执行初始线程
开始执行初始线程(除非指定了 CREATE_SUSPENDED 标志)
阶段七: 最终的进程初始化、开始执行入口点
在新进程和线程上下文中完成地址空间的初始化操作(如加载所需的 DLL ),并开始执行程序的入口点
CreateProcessInternalW 函数中执行的操作(所有已经文档化的进程创建函数均会调用此函数)
1. 确定进程优先级
通过 CreationFlags 参数确定进程的优先级,进程共有 “空闲”或“低”、“低于正常”、“正常”、“高于正常”、“高”、“实时” 几个优先级。
默认的优先级为 “正常”,创建“实时”进程时需要调用方具备 Increaase Scheduling Priority 特权才,否则将转变为“高”优先级。
2. 确定是否需要被调试
当进程创建时指定为需要被调试时,kernel32 会调用 dbgUiConnectToDbg 发起到 ntdll.dll 中原生调试代码的连接,并从当前线程的环境块(TEB)中获取调试对象句柄。
3. 设置硬错误模式
如果创建时使用的标志有指定,kernel32.dll 将设置默认的硬错误模式。
4. 添加进程的属性
将与用户有关的属性列表从 Windows 子系统格式转换为原生模式,并添加到内部属性中。
5. 设置安全属性
进程和初始线程安全属性会被转换为响应的内部表现
6. 是否为“现代化”形态
是否要将进程创建为“现代化”形态,若为“现代化”进程,则会包含对应的安全能力,还会设置一个标志让内核跳过嵌入清单检测操作
7. 将窗口关联至桌面
将进程的所有窗口关联至桌面( STARTUPINFO 中指定的桌面或调用方的当前桌面)
8. 路径转换
将可执行文件的路径名称转换为 NT 内部名称
9. RTL_USER_PROCESS_PARAMETERS 结构转换
将进程的大部分信息转换为 RTL_USER_PROCESS_PARAMETERS 的大型结构
CreateProcessInternalW 通过调用 NtCreateUserProcess 函数来执行的操作
1. 验证参数并构建结构
验证进程创建参数,并构建一个保存所有创建信息的内部结构。
再次验证参数是需要确保对执行体的调用并非源自某种不当手段(hack),并恰好通过这种手段使用伪造或恶意的参数模拟了 ntdll.dll 转换至内核的方法。
2. 找到要执行的映像
根据应用程序的类型,找到运行调用方指定的可执行文件对应的 Windows 映像,并创建区域对象,随后将其映射至新进程的地址空间。如果需要创建受保护进程,则还会检查签名策略。
DOS.bat 或 .cmd ===> 运行 cmd.exe
DOS.exe 、.com 或 .pif ===> 运行 NtVdm.exe
Win16 ===> 运行 NtVdm.exe
Windows ===> 直接运行 EXE
3. 许可检查
如果是现代化进程,则需要通过许可检查来确保其具备许可并且允许运行。
4. 打开要执行的EXE
如果指定的可执行文件是 Windows EXE 文件,则会打开该文件并创建区域对象
5. 找到需要执行的文件
如果需要执行的映像不是 Windows EXE 文件,则会通过一系列步骤找到可用于运行的 Windows 支持映像
NtCreateUserProcess 通过调用 PspAllocateProcess 函数来执行的操作
1. 设置 EPROCESS 对象
使用创建进程时属性列表中指定的属性,未指定时继承父进程的相关属性
2. 创建初始进程地址空间
3. 初始化内核进程结构(KPROCESS)
4. 结束进程地址空间的设置
5. 设置 PEB
6. 完成执行体进程对象的设置工作
NtCreateUserProcess 通过调用 PspCreateThread、PspAllocateThread、PspInsertThread 函数来执行的操作
PspCreateThread: 处理与线程创建有关的工作
PspAllocateThread: 执行体线程对象本身的创建和初始化工作
PspInsertThread: 线程句柄和安全属性的创建工作,调用 KeStartThread 将执行体对象转换为系统中可调度的线程
此阶段创建的线程在创建完成之后便会处于挂起的状态,只有在执行完第五阶段的工作之后才会重新运行。
CreateProcessInternalW 函数中执行的操作
1. 确定 Windows 是否允许该可执行文件
验证文件头中的映像版本、检查 Windows 应用程序证书是否通过组策略禁止了该进程、应用程序是否导入了不允许的 API (部分 Windows 版本中)
2. 创建令牌
如果软件限制策略有指示,则会为该进程创建一个受限令牌。
随后会查询应用程序兼容的数据库,以确认注册表或系统应用程序数据库中是否有关于该进程的任何记录。
3. 获取 SxS 信息
通过调用内部函数获取 SxS 信息,如清单文件、DLL重定向路径、EXE文件所在介质是否为可移动存储介质、安装程序检测标志等。
4. 向 Windows 子系统发送消息
向 Windows 子系统(csrss)发送消息,csrss 根据收到的消息执行相关操作
恢复执行第四阶段中挂起的线程
KiStartUserThread: 内核模式线程启动例程
PspUserThreadStartup: 系统初始线程例程
进程自己 通过调用 ExitProcess 函数终止进程,此时载入该进程的 DLL 将会有机会使用 DLL_PROCESS_DETACH 调用自己的 DllMain 函数执行一些工作。
进程自己或外部 通过调用 TerminateProcess 函数终止进程,此时 DLL 将不会有机会执行工作,所有线程将会被突然终止。