Windows 进程的创建和终止
创建一个进程
总述
如图,创建一个进程主要分为两部分,用户态部分和内核部分。
既然我们想看看一个进程是怎么被创建的,那我们就用 WinDbg 来看看从用户态到内核态都调用了什么:
第一步:我们先看看 nt 下有哪些方法跟创建进程相关的
0: kd> x nt!*CreateProcess*
fffff802`55d8a218 nt!PspSetCreateProcessNotifyRoutine (void)
fffff802`55cd9714 nt!ExpWnfCreateProcessContext (void)
fffff802`55dd9a2f nt!PspCreateProcess$filt$0 (void)
fffff802`55be24f4 nt!PspDeleteCreateProcessContext (void)
fffff802`55c40ed0 nt!MmCreateProcessAddressSpace (void)
fffff802`55dbd430 nt!PspCreateProcess (void)
fffff802`5594fb10 nt!ViCreateProcessCallback (void)
fffff802`55fdaaa4 nt!ViCreateProcessCallbackInternal (ViCreateProcessCallbackInternal)
fffff802`55f04550 nt!NtCreateProcessEx (NtCreateProcessEx)
fffff802`55fd1ce0 nt!VerifierPsSetCreateProcessNotifyRoutineEx (VerifierPsSetCreateProcessNotifyRoutineEx)
fffff802`559f4bf0 nt!ZwCreateProcessEx (ZwCreateProcessEx)
fffff802`56349360 nt!pXdvPsSetCreateProcessNotifyRoutineEx = <no type information>
fffff802`55cfd12c nt!PspValidateCreateProcessProtection (PspValidateCreateProcessProtection)
fffff802`55d89ea0 nt!PsSetCreateProcessNotifyRoutineEx (PsSetCreateProcessNotifyRoutineEx)
fffff802`5632e9d4 nt!PspCreateProcessNotifyRoutineCount = <no type information>
fffff802`55d89f00 nt!PsSetCreateProcessNotifyRoutineEx2 (PsSetCreateProcessNotifyRoutineEx2)
fffff802`5632e9d8 nt!PspCreateProcessNotifyRoutineExCount = <no type information>
fffff802`55d8a050 nt!PsSetCreateProcessNotifyRoutine (PsSetCreateProcessNotifyRoutine)
fffff802`55ed2e70 nt!MiCreateProcessDefaultAweInfo (MiCreateProcessDefaultAweInfo)
fffff802`55be0d1c nt!PspBuildCreateProcessContext (PspBuildCreateProcessContext)
fffff802`559f5970 nt!ZwCreateProcess (ZwCreateProcess)
fffff802`562ec260 nt!PspCreateProcessNotifyRoutine = <no type information>
fffff802`55fd1cc0 nt!VerifierPsSetCreateProcessNotifyRoutine (VerifierPsSetCreateProcessNotifyRoutine)
fffff802`56349378 nt!pXdvPsSetCreateProcessNotifyRoutine = <no type information>
fffff802`55f044c0 nt!NtCreateProcess (NtCreateProcess)
第二步:我们选择 nt!MmCreateProcessAddressSpace 打上断点(不要问我为啥选这个,实在不会选,就直接 bm nt!CreateProcess)
0: kd> bu nt!MmCreateProcessAddressSpace
Breakpoint 2 hit
nt!MmCreateProcessAddressSpace:
fffff802`55c40ed0 488bc4 mov rax,rsp
0: kd> k
# Child-SP RetAddr Call Site
00 ffff928c`4e5a7b48 fffff802`55d08608 nt!MmCreateProcessAddressSpace
01 ffff928c`4e5a7b50 fffff802`55cff75a nt!PspAllocateProcess+0x13ec
02 ffff928c`4e5a82c0 fffff802`55a096b5 nt!NtCreateUserProcess+0xa1a
03 ffff928c`4e5a8a90 00007ff8`cfc2e634 nt!KiSystemServiceCopyEnd+0x25
04 00000000`02aac598 00007ff8`cd818e73 ntdll!NtCreateUserProcess+0x14
05 00000000`02aac5a0 00007ff8`cd8171a6 KERNELBASE!CreateProcessInternalW+0xfe3
06 00000000`02aadb70 00007ff8`ced4cbb4 KERNELBASE!CreateProcessW+0x66
07 00000000`02aadbe0 00007ff8`cb56152d KERNEL32!CreateProcessWStub+0x54
08 00000000`02aadc40 00007ff8`cb4f6722 windows_storage!CInvokeCreateProcessVerb::CallCreateProcess+0x2cd
09 00000000`02aadef0 00007ff8`cb55a75c windows_storage!CInvokeCreateProcessVerb::_PrepareAndCallCreateProcess+0x2d6
0a 00000000`02aadf70 00007ff8`cb55a583 windows_storage!CInvokeCreateProcessVerb::_TryCreateProcess+0x3c
0b 00000000`02aadfa0 00007ff8`cb55a46d windows_storage!CInvokeCreateProcessVerb::Launch+0xef
0c 00000000`02aae040 00007ff8`cb599dc4 windows_storage!CInvokeCreateProcessVerb::Execute+0x5d
0d 00000000`02aae080 00007ff8`cb481d87 windows_storage!CBindAndInvokeStaticVerb::InitAndCallExecute+0x214
0e 00000000`02aae100 00007ff8`cb4f5787 windows_storage!CBindAndInvokeStaticVerb::TryCreateProcessDdeHandler+0x63
0f 00000000`02aae180 00007ff8`cb54586d windows_storage!CBindAndInvokeStaticVerb::Execute+0x1e7
10 00000000`02aae4a0 00007ff8`cb545785 windows_storage!RegDataDrivenCommand::_TryInvokeAssociation+0xad
11 00000000`02aae500 00007ff8`ce152b22 windows_storage!RegDataDrivenCommand::_Invoke+0x141
12 00000000`02aae570 00007ff8`ce1529da SHELL32!CRegistryVerbsContextMenu::_Execute+0xce
13 00000000`02aae5e0 00007ff8`ce15630c SHELL32!CRegistryVerbsContextMenu::InvokeCommand+0xaa
14 00000000`02aae8e0 00007ff8`ce15618d SHELL32!HDXA_LetHandlerProcessCommandEx+0x10c
15 00000000`02aae9f0 00007ff8`cb93be08 SHELL32!CDefFolderMenu::InvokeCommand+0x13d
16 00000000`02aaed50 00007ff8`cb93c7b6 windows_storage!CShellLink::_InvokeDirect+0x1d0
17 00000000`02aaf070 00007ff8`cb93945a windows_storage!CShellLink::_ResolveAndInvoke+0x202
18 00000000`02aaf230 00007ff8`ce15630c windows_storage!CShellLink::InvokeCommand+0x1aa
19 00000000`02aaf310 00007ff8`ce15618d SHELL32!HDXA_LetHandlerProcessCommandEx+0x10c
1a 00000000`02aaf420 00007ff8`ce3709d5 SHELL32!CDefFolderMenu::InvokeCommand+0x13d
1b 00000000`02aaf780 00007ff8`ce6244f9 SHELL32!SHInvokeCommandOnContextMenu2+0x1f5
1c 00000000`02aaf9c0 00007ff8`ceeec3f9 SHELL32!s_DoInvokeVerb+0xc9
1d 00000000`02aafa30 00007ff8`ced47034 shcore!_WrapperThreadProc+0xe9
1e 00000000`02aafb10 00007ff8`cfbe2651 KERNEL32!BaseThreadInitThunk+0x14
1f 00000000`02aafb40 00000000`00000000 ntdll!RtlUserThreadStart+0x21
如上 Windbg 输出的结果所示,正是描述了从用户态的 CreateProcess → 内核态的 NtCreateUserProcess. 其他链路,比如 CreateProcessAsTokenW 我们也可以验证下,这里就不做赘述。
用户态部分,包含一些我们常用到的方法:CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW, CreateProcessAsTokenW.
而内核部分,则都是通过 NT 下的 NtCreateUserProcess 来进行创建。
创建进程流程
创建一个进程,主要以下7个步骤。
步骤1:转换、验证参数和标志
这一步骤主要是将从用户态参数到内核态参数的一个转换,同时添加必要的标识。
其中主要包含以下部分:
- 优先级的确定
- Native 属性和 Win32 属性的映射
- 对现代应用(modern application)的特定标识(PROC_THREAD_ATTRIBUTE_PACKAGE_FULL_NAME),方便后续特殊处理
- Debug 和 Error 的预设
- 确定特定的桌面环境(指进程需要创建到哪个特定的虚拟桌面)
Windows 的虚拟桌面其实只有一个 Desktop 对象,使用 desktop.exe 可以真正的创建多个虚拟桌面。
- 将参数做转换。(比如 c:\temp\a.exe 可能转换成 \device\harddiskvolume1\temp\a.exe)
做完这些工作,创建进程的用户态的初始化基本就结束了。接下来就会尝试调用内核态的 NtCreateUserProcess 来创建进程。
步骤2:打开要执行的镜像
这个部分已经切换到内核模式执行了,主要目的就是确定要怎么打开镜像。
主要包含以下部分:
- 如上图,根据要打开的镜像文件确认需要真正执行的进程。(比如,当文件是一个 .cmd 文件时,真正需要执行的是 cmd.exe 这个进程,那就需要重新回到步骤一 CreateProcessInternalW )
- 如果进程是现代应用,则需要确定他的证书,确保它是可以被运行的。(比如非商店应用在Windows 设定不运行旁加载的情况下是不能被运行的)
- 如果进程是 Trustlet,还需要添加特定的标识
- 会尝试去打开 Windows exe 文件。首先创建 section object,然后判断其是否可以被打开。
- 然后会在 Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options 下寻找特定的 option。例如要打开的文件是SppExtComObj.exe,就会找到确认 Image File Execution Options 下是否存在 SppExtComObj.exe 子项,如果存在 PspAllocateProcess 就会再去找是否存在 debugger 的key,如果这个key存在,就会将 debugger 的值替换 SppExtComObj.exe,并且重新运行步骤一。
这就是镜像劫持(IFEO)的原理了,通过自行在注册表中创建子项,就可以实现想打开A进程,实际打开B进程
- 对于非 Windows exe 文件来说,有以下这些行为:
至此,Windows 就已经可以打开一个可执行文件并且创建了部分对象,并映射到新的进程的地址空间了。
步骤3:创建 Windows 执行体进程对象
这个部分主要是通过 PspAllocateProcess 来创建 Windows 执行体对象(也就是内核中描述进程的对象)。主要分为以下几个部分:
- 设置 EPROCESS 对象。初始化或者从父进程继承属性,同时会根据 IFEO 的各种 key 来确定对应的值(比如:UseLargePages、PerfOptions、IoPriority、PagePriority、CpuPriorityClass、WorkingSetLimitInKB)
- 创建初始进程地址空间。
- 创建内核进程结构,也就是是初始化 KPROCESS。
- 完成进程地址空间的设置。(这块需要有一些内存管理上的知识,有点迷糊,后面再来补上 @frend guo )
- 配置 PEB。
- 完成执行体进程对象的配置。
到此,Windows 执行进程对象已经创建完成了。接下来就该创建第一个线程了。
步骤4:创建初始线程的线程栈和上下文
这个部分主要是创建进程中的第0个线程,并且将其栈和上下文初始化完成。
由于其是在内核中直接创建的线程,所以跟用户模式下创建线程会有些不一样。这里主要分为 PspAllocateThread 和 PspInsertThread 两个部分来分析。
对于 PspAllocateThread 主要包含以下工作:
- 阻止 WOW64 进程的 UMS,还阻止了用户模式下 System 进程中创建线程的调用
- 创建执行体线程对象并初始化
- LPC、IO管理和执行体用到的各种列表都将初始化
- 线程创建时间、TID都将被创建
- 创建线程的栈和上下文
- 为新线程分配 TEB
- 配置 ETHREAD、KTHREAD(通过KeInitThread)
对于 PspInsertThread,主要包含以下工作:
- 跟据属性做一些线程的初始化工作,然后再插入到进程的线程列表里。比如初始化线程的首选处理器(thread ideal processor)、线程组亲和性(thread group affinity)、初始化安全线程对象(如果是IUM下)、调度设置、动态优先级、线程量子(thread quantum)。
- 将线程对象插入到进程句柄表(process handle table)
- 如果是进程的第一个线程被创建,所有进程注册的回调都会被调用。
- 会调用 KeReadyThread 回应执行体,已经处理准备好的状态。
到这里,已经创建了必要的进程和线程对象了。
步骤5:执行 Windows 子系统特定的初始化
这个步骤主要是做一些用户模式下的检查和初始化。也是 Windows 子系统登记此进程的过程。
- Windows 会做一些检查来确保 Windows 是允许该进程运行的。比如校验镜像版本、确保 Windows 认证是否阻止此进程(策略)以及在一些特定 Windows 版本中,是否导入了系统不允许导入的 DLL 或者 API。
- 如果软件策略有约束,则会为此进程创建一个约束的 token,并将其存储到 PEB 中
- CreateProcessInternalW 会调用一些内部方法来获取系统的 SxS 信息
- 根据收集到的要发送到 Csrss 的信息构造到 Windows 子系统的消息。
- 在收到消息后,Windows 子系统将执行以下步骤:
- CsrCreateProcess 会为进程和线程复制句柄。进程和线程的使用计数(usage count)将会从1增加到2
- 分配 Csrss process structure (CSR_PROCESS)
- Csrss 线程结构(CSR_THREAD)将会被分配并初始化
- 通过 CsrCreateThread 将线程插入到进程的线程列表中
- 会话中的进程计数会递增
- 设置进程的关闭优先级(The process shutdown level)为 0x280。也就是进程默认的等级。
- 新创建的 csrss 进程结构将会被插入到 Windows 子系统范畴的进程列表中。
到此,进程、线程的环境建好,需要使用的资源也已经分配好了,Windows 子系统也知道并登记此进程和线程。于是就可以开始执行初始线程了。
步骤6:开始执行初始线程
这个阶段,除非调用者指定 CREATE_SUSPENDED,否则,初始化线程都将恢复运行,并开始执行后续的进程初始化工作。此时的初始化工作已经切换到新进程中了。
步骤7:在新进程的上下文中执行进程的初始化
新的线程开始运行,将在内核模式运行 KiStartUserThread,它将线程的 IRQL 从 DPC 降低到 APC,然后再调用系统初始化线程例程 PspUserThreadStartup,它将执行以下步骤:
- 安装异常链。
- 将 IRQL 降低至 PASSIVE_LEVEL(也就是0)
- 禁用运行时交换主进程 token 的能力
- 根据内核模式下的数据结构(KTHREAD) 设置 TEB 中的 local ID 和线程的首选处理器
- 调用 DbgkCreateThread 来检查新的进程是否向镜像发送消息。(用于 load dll)
- 然后继续做一些列的检查。
- 接下来就会切换到用户模式下,回到 RtlUserThreadStart 中。
到这里,进程的创建就结束了,将执行 Image 中的入口方法,进到进程的上下文中去。
结束一个进程
结束进程主要分为两种方式:主动结束(ExitProcess)和被动停止(TerminateProcess)。
ExitProcess 和 TerminateProcess 调用的执行体中的 NtTerminalProcess。它主要执行以下逻辑:
- 轮询进程中所有线程,如果不是当前线程,调用 PspTerminateThreadByPointer 结束(需要等待返回)。
- 如果需要终止的是当前进程,则判断如果是当前线程,调用 PspTerminateThreadByPointer 结束,不等待返回。
- 最后再清理句柄表和解引用对象。
总结
创建一个继承需要考虑非常多的点,从用户模式到内核模式的转换,内核对象的构建再回到用户模式与子系统的通信。这个部分牵涉到的内容非常多,也非常值得详细研究。先了解大框架,再来细化其中的每个点。
Windows 进程的创建和终止的更多相关文章
- windows进程/线程创建过程 --- windows操作系统学习
有了之前的对进程和线程对象的学习的铺垫后,我们现在可以开始学习windows下的进程创建过程了,我将尝试着从源代码的层次来分析在windows下创建一个进程都要涉及到哪些步骤,都要涉及到哪些数据结构. ...
- windows进程的创建方法
1.WinExec(LPCSTR lpCmdLine,UINT uCmdShow) >>参数: lpCmdLine:指定程序的相对路径或绝对路径,命令行参数 uCmdShow:指定窗口的显 ...
- python不同平台进程的启动与终止
Liunx进程的启动与终止 在使用subprocess创建进程时需要将所有进程设置为一个进程组 preexec_fn:只在 Unix 平台下有效,用于指定一个可执行对象(callable object ...
- UNIX环境编程学习笔记(26)——多线程编程(一):创建和终止线程
lienhua342014-11-08 在进程控制三部曲中我们学习了进程的创建.终止以及获取终止状态等的进程控制原语.线程的控制与进程的控制有相似之处,在表 1中我们列出了进程和线程相对应的控制原语. ...
- windows编程 进程的创建销毁和分析
Windows程序设计:进程 进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,在Windows编程环境下,主要由两大元素组成: • 一个是操作系统用来管理进程的内核对象.操作系统使用内 ...
- Operating System-Process(1)什么是进程&&进程的创建(Creation)&&进程的终止(Termination)&&进程的状态(State)
本文阐述操作系统的核心概念之一:进程(Process),主要内容: 什么是进程 进程的创建(Creation) 进程的终止(Termination) 进程的状态(State) 一.什么是进程 1.1 ...
- 《windows核心编程系列》四谈谈进程的建立和终止
第二部分:工作机理 第一章:进程 上一章介绍了内核对象,这一节开始就要不断接触各种内核对象了.首先要给大家介绍的是进程内核对象.进程大家都不陌生,它是资源和分配的基本单位,而进程内核对象就是与进程相关 ...
- Windows内核之进程基本含义以及进程的创建
进程 1 进程的含义: 1.1 一个是操作系统用来管理进程的内核对象. 内核对象也是系统用来存放关于进程的统计信息的地方. 1.2 还有一个是地址空间,它包括全部可运行模块或DL L 模块的代 ...
- Windows下如何创建低权限进程
1. 前言 在使用 Sysinternals 出品的 Process Explorer 过程中,对 “Run as Limited User” 功能的实现方式颇感兴趣,一番搜寻之下发现Mark ...
随机推荐
- Gitlab-runner+Docker自动部署SpringBoot项目
本文基于Gitlab CI/CD及Docker快速实现项目的自动部署. 注意:本文较长,浏览需要12分钟左右. 1.环境要求 以下服务器的操作系统均为Centos7 服务器A:Gitlab 服务器B: ...
- SmartDialog迁移至4.0:一份真诚的迁移说明
前言 一个开源库,随着不断的迭代优化,难免会遇到一个很痛苦的问题 最初的设计并不是很合理:想添加的很多新功能都受此掣肘 想使得该库更加的强大和健壮,必须要做一个重构 因为重构涉及到对外暴露的api,所 ...
- redo log 和 binlog 的一些总结
1 redo log 和 binlog 的区别 redo log 是 InnoDB 引擎特有的:binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用. redo log 是物理 ...
- HttpResponse,render,redirect,静态文件配置,request对象方法,pycharm连接MySQL,django连接MySQL,django ORM
HttpResponse 主要用于返回字符串类型的数据 def index(request): return HttpResponse('index页面') 在页面中就会显示 index页面 rend ...
- mybatis plus 使用 SQL 保留字(关键字)
MybatisPlus:使用SQL保留字(关键字)的操作 必须要手动在 entity 的字段上加注解,否则最终会报错,因为 mybatis 或者 mybatis plus 不会自动处理
- data:image字符转byte[]
var data = "data:image/bmp;base64,Qk3aHwAAAAAAADYAAAAoAAAAZAAAABsAAAABABgAAAAAAKQfAAAAAQAAAAEAA ...
- 893. Groups of Special-Equivalent Strings - LeetCode
Question 893. Groups of Special-Equivalent Strings Solution 题目大意: AB两个字符串相等的条件是:A中偶数位出现的字符与B中偶数位出现的字 ...
- 好客租房25-react中的事件处理(事件对象)
3.2事件对象 可以通过事件处理程序的参数 React中的事件对象叫做:合成事件(对象) 合成事件:兼容所有浏览器 //导入react import React from 'react' ...
- 以圆类 Circle 及立体图形类 Solid 为基础设计圆锥类 Cone
学习内容:以圆类 Circle 及立体图形类 Solid 为基础设计圆锥类 Cone 代码示例: import java.util.Scanner; class Point4{ private dou ...
- 【摸鱼神器】一次搞定 vue3的 路由 + 菜单 + tabs
做一个管理后台,首先要设置路由,然后配置菜单(有时候还需要导航),再来一个动态tabs,最后加上权限判断. 这个是不是有点繁琐?尤其是路由的设置和菜单的配置,是不是很雷同?那么能不能简单一点呢?如果可 ...