文章目录

  • 内核模式下进程与线程的创建
    • 进程创建
    • 线程创建
  • 用户模式下进程与线程的创建

内核模式下进程与线程的创建

进程创建

在内核模式中,一个进程的创建是从函数NtCreateProcess开始的。该函数位于文件ntoskrnl.exe中,该文件位于%windir%\system32.它对用户传进的部分参数进行简单处理,然后交给函数NtCreateProcessEx

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集中的级别
);

该函数检查句柄ProcessHandle是否可写,然后将创建工作交给函数,PspCreateProcess系统中所有的进程的创建工作均是由该函数负责完成的,其创建进程的步骤大致如下:

  1. 调用函数ObCreateObject创建windows执行体内核对象EPROCESS,该对象为新进程的进程对象,该对象归系统所有,并由系统统一管理

  2. 调用函数ObReferenceObjectByHandle获取内存区对象的指针SectionHandle。

  3. 根据传入的端口参数内容初始化新进程对象的相应字段

  4. 如果父进程不为空,则创建一个全新的地址空间

  5. 调用函数KeInitializeProcess初始化新进程对象的基本优先级,Affinity,进程页表目录和超空间的页帧号

  6. 调用函数PspInitializeProcessSecurity从父进程复制一个令牌初始化新进程的安全属性,并设置进程优先级

  7. 调用函数MmInitializeProcessAddressSpace初始化新进程的地址空间,并将映像映射到地址空间;同时加载Ntdll.dll和系统范围的国家语言支持(NLS)表

  8. 调用函数ExCreateHandle在CID句柄表中创建一个进程ID项。CID是客户身份号(Client ID),一般由进程号和线程号两部分组成,用于识别某个进程

  9. 审计本次进程创建行为,并构造PEB,将新进程假如全局进程链表PsActiveProcessHead中

  10. 调用函数ObInsertObject将新进程对象插入当前进程的句柄表中

  11. 设置进程基本优先级,访问权限,创建时间。

通过以上步骤,进程对象创建完成。但进程时有“惰性”的,离开了线程,进程的空间只是一个固定在内存中的死的空间而已;直到它的第一个线程被创建和初始化之后,进程中的代码才能被真正运作起来

线程创建

与进程的创建类似,内核中线程的创建是从函数NtCreateThread开始的。该函数对用户传递来的参数进行简单检查,并复制InitialTeb到局部变量CaptureInitialTeb中;处理完参数后,交给函数PspCreateThread。该函数是真正创建线程的函数,其原型如下:

NTSTATUS
PspCreateThread(
OUT         PHANDLE             ThreadHandle,                   //返回线程句柄
IN          ACCESS_MASK         DesiredAccess,                  //新线程的访问权限
IN          POBJECT_ATTRIBUTES  ObjectAttributes    OPTIONAL,   //新线程对象属性
IN          HANDLE              ProcessHandle,                  //线程所属进程句柄
IN          PEPROCESS           ProcessPointer,                 //指向所属进程的EPROCESS对象
OUT         PCLIENT_ID          ClientId    OPTIONAL            //返回新线程的CLIENT_ID结构
IN          PCONTEXT            ThreadContext       OPTIONAL,   //新线程的执行环境
IN          PINITIAL_TEB        InitialTeb          OPTIONAL,   //新线程TEB
IN          PKSTART_POUTINE     StartRoutine        OPTIONAL,   //系统线程启动函数地址
IN          PVOID               StartContext                    //系统线程启动函数的执行环境
);

内核中创建线程的步骤大致如下:

  1. 根据进程句柄获得进程对象并将其放到局部变量中
  2. 调用函数ObCreateObject创建线程对象ETHREAD,并初始化
  3. 获取进程的RundownProtect锁,以免线程创建过程中该进程被当掉
  4. 创建线程环境块(TEB),并用InitTeb函数进行初始化;然后,利用ThreadContext中的程序指针eip设置线程的启动地址StartAddress字段,并且将ThreadContext的eax寄存器设置到线程的Win32StartAddress字段。完成这些操作后,调用KeInitThread函数初始化新线程的一些属性
  5. 锁住进程,并将进程的活动线程数量加1,然后调用函数ObInsertObject把新线程加入到进程的线程链中
  6. 调用函数KeStartThread,新线程即可运行

用户模式下进程与线程的创建

在用户层,创建一个进程通常使用kernel32.dll中的函数CreateProcess来完成,该函数一旦返回成功,新的进程和进程中的第一个线程就建立起来了。从用户层的角度看进程创建大致步骤如下:

  1. 调用函数CreateProcessW打开指定的可执行映像文件
  2. 调用ntdll.dll中的存根函数NtCreateProcessEx,该函数利用处理器的陷阱基址切换到内核模式下
    在内核模式下,系统服务分发函数KiSystemService获得CPU控制权,它利用当前线程指定的系统服务表,调用执行体层的函数NtCreateProcessEx,开始内核过程得进程创建。执行体层的进程对象建立以后,进程的地址空间完成初始化,EPROCESS中的进程环境块(PEB)也已完成初始化;例如,跟踪一段CreateProcess代码,可以看到陷阱机制

    以上代码时函数Ntdll.ZwCreateSection的调用过程,前面是一系列的函数参数入栈操作,最后通过call指令调用函数。进入到函数内部看,函数Ntdll.ZwCreateSection是一段代理代码,通过对eax赋值后,为edx指定偏移地址,即可实施call ntdll.KiFastSystemCall调用,如下图:

    其中eax存放系统服务号,从以上反汇编代码可以看到,系统最终通过指令SYSENTER(Windows XP系统)或者 int 2E(Windows 2000系统)进入内核态执行相应程序
  3. PEB创建完成后,意味着进程的创建暂时告一段落。这时候还必须为进程创建第一个线程,通过调用函数NtCreateThread构造一个可供第一个线程运行的环境,如堆的大小,初始线程栈的大小等。这些值的模式初始值来自于PE文件头部相应字段,ntdll.dll中的该函数依旧将任务交由执行层的NtCreateThread来完成。执行体层的线程对象ETHREAD建立以后,线程的ID,TEB等也就完成了初始化
  4. 进程的第一个线程的启动函数是kernel32.dll中的BaseProcessStart函数(其他线程则是调用BaseThreadStart函数),此时的线程是被挂起的,要等到进程完全初始化完成后才真正开始
    注意:
    至此,用户模式下的进程创建实际上才刚刚开始,因为最终的进程要求给Windows子系统来运行,并由子系统维护进程的状态,进程的消息等信息
  5. kernel32.dll向windows子系统发送一个消息, 该消息包含了刚建立的进程和线程的相关信息;Windows子系统csrss.exe接收到此消息后,开始在系统内部建立进程环境;最后,以显示应用程序启动光标作为进程环境创建结束
  6. 当Windows子系统已经知道并登记了新进程和线程后,先前挂起的初始线程被允许恢复执行
    在内核模式中,新线程的启动例程是KiThreadStartup函数,从WRK中可以获取到该函数的代码:


    KiThreadStartup函数首先将中断请求级别(Interrupt ReQuest Level,IRQL)降低到PC_LEVEL,然后进入内核模式调用系统初始的线程函数PspUserThreadStartup。函数PspUserThreadStartup通知缓存管理器预取可执行映像文件的页面,即该进程上一次启动的前10s内引用到的页面。读取后,将一个用户模式APC插入线程的用户APC队列,此APC例程指向ntdll.dll的LdrInitializeThunk函数,然后函数PspUserThreadStartup返回
  7. 当函数KiThreadStartup返回到用户模式时,由PspUserThreadStartup插入的APC也已被交付,于是函数Ntdll.LdrInitializeThunk被调用,该函数是PE映像加载器的初始化函数。
  8. 完成初始化工作以后,加载PE映像中任何必要的DLL,并调用这些DLL的入口函数。最后,当LdrInitializeThunk返回到用户模式APC分发器时,该线程开始在用户模式下执行;然后,它调用用户栈中的线程启动函数
  9. 至此,进程与线程全部建立完成,开始执行用户空间中的代码

内核和用户模式下进程与线程创建相关推荐

  1. 用户模式下的线程同步

    在以下两种基本情况下,线程之间需要相互通信 1.需要让多个线程同时访问一个共享资源,同时不能破坏资源的完整性 2.一个线程需要通知其他线程某项任务已经完成. 原子访问相关的内容就直接略过了,因为感觉实 ...

  2. linux 返回非法指令,linux – ARM Cortex A7在内核模式下返回PMCCNTR = 0,在用户模式下返回非法指令(即使在PMUSERENR = 1之后)...

    我想在Raspberry Pi 2上读取循环计数寄存器(PMCCNTR),它有一个ARM Cortex A7内核.我为它编译了一个内核模块,如下所示: #include #include int in ...

  3. linux7启动某个服务器,如何在单用户模式下启动RHEL 7 CentOS 7服务器

    对于Linux系统管理员,以单用户模式引导RHEL 7 / CentOS 7服务器是最常见的日常活动.单用户模式被视为维护或紧急模式,我们可以执行我们的故障排除步骤.以下是我们需要在单用户模式下启动R ...

  4. CentOS6.8单用户模式下修改密码

    CentOS6.8单用户模式下修改密码 1. 选择进入菜单menu界面,在开启系统出现如下界面时,按Esc键(只需按一下) 2. 然后进入到如下界面 3. 上图中红色矩形类的内容,按"a&q ...

  5. linux单用户模式修复磁盘,在单用户模式下使用fsck命令修复受损的Mac硬盘

    在Mac上使用磁盘工具来恢复硬盘是官方推荐的方法,不过万一连系统都进不去就操蛋了.所以在很多情况下,在Unix/Linux系统的单用户模式下使用fsck都是最后的救命稻草. fsck 这个命令行工具在 ...

  6. 在单用户模式下启动SQL Server的不同方法

    In this article, we will review different ways to start SQL Server in single user mode. 在本文中,我们将介绍在单 ...

  7. linux进入单用户模式改密码,1.4linux单用户模式下修改root密码和救援模式修改root密码...

    在忘记root密码无法登入系统的情况下,有两种修改root密码的方法. 第一种:进入单用户模式下,直接修改root密码,前提是grub没有设置密码. 第二种:使用安装光盘启动,进入救援模式更改root ...

  8. Windows用户模式下的线程同步

    Interlocked系列函数 原子访问:线程在访问某个资源的时候能保证没有其他线程会在同一时刻访问同一资源 函数名 功能 InterlockedExchangeAdd InterlockedExch ...

  9. 驱动开发笔记1—内核中的事件、进程、线程、自旋锁

    内核模式下的等待 KeWaitForSingleObject() 和 KeWaitForMultipleObjects() NTSTATUS KeWaitForSingleObject(IN PVOI ...

最新文章

  1. git 使用笔记 oschina ,mac
  2. 关于网站图片格式 png,jpg,
  3. python 带pydev的eclipse无法导入win32api包(或无法导入其他包)
  4. 阿里预面:谈谈你对双亲委派机制的理解?这个名字有啥问题?如何打破?为啥双亲委派?...
  5. IE6/7 double padding-bottom Bug
  6. laravel redis_php session 存储到redis里
  7. kotlin之泛型的使用
  8. onready怎么加img_用插件VMarker在vue中给图片加标记
  9. settings.xml‘ has syntax errors
  10. python模块以及导入出现ImportError: No module named 'xxx'问题
  11. emc re 整改 超标_CE认证EMC测试不合格,如何整改 ;
  12. Windows管理用户账号
  13. python的自省到底有什么用
  14. source insight 4.0 使用make命令编译
  15. opengl自学记录_键盘控制图形平移
  16. 三明市机器人协会_永安三中斩获三明市青少年机器人竞赛综合技能项目第一名...
  17. 你好,uv变换(新手入门向聊天教程)
  18. 集装箱 扩展程序_工厂一平米集装箱公厕销售,供求信息
  19. ffmpeg进行音频解码,QAudioOutput播放解码后的音频
  20. 目标阿里P7 30岁程序员三次面试,意外收到offer(Java研发岗)

热门文章

  1. 成功解决ValueError: too many values to unpack (expected 2)
  2. 成功解决raise XGBoostError(_LIB.XGBGetLastError()) xgboost.core.XGBoostError: b'[22:08:00] C:\\Users\\Ad
  3. DL之ResNeXt:ResNeXt算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
  4. TF之p2p:基于TF利用p2p模型部分代码实现提高图像的分辨率
  5. 算法训练_ALGO14_回文数
  6. 日期时间类,按特定格式显示日期时间
  7. JAVA常见的排序算法
  8. C++ 管理数据内存的方法
  9. Sphinx-安装和配置
  10. 加入收藏 lsk的BLOG