1. 线程基本概念
1.1 线程与进程的关系
-
线程是操作系统调度的最小单位: 线程被包含在进程内部,操作系统的调度、进程切换等关键操作均以线程为基础单位进行。线程拥有自己的一套寄存器数据以及堆栈信息。
-
进程可包含多个线程: 一个进程能并发运行多条线程,进程的存在与运行依赖于主线程的启动以及数据的执行 ------ 正是由于拉起主线程并执行相关数据,一个程序才被称为进程。
1.2 线程的特性
-
具备独立执行环境: 每条线程都拥有专属的寄存器和堆栈信息,形成独立的线程上下文环境,因此各线程的执行过程互不干扰、相互独立。
-
共享进程资源: 属于同一进程的线程,可共同使用该进程的数据段、代码段等资源,例如能够同时读取相同的二进制代码。
-
可提高执行效率: 多线程模式下,不同任务可实现并行执行,相较于单线程能有效避免因某个任务阻塞而影响整体运行的情况,从而显著提升执行效率。
2. 线程与进程的关系
2.1 进程的核心特性
-
独立内存空间: 进程拥有专属的虚拟内存空间,范围从 0 开始,直至基于进程位数所划定的区间,内存空间具有独立性。
-
独立执行环境: 操作系统会对进程所需的各类资源进行维护与修正,为其提供可独立运行的执行环境。
-
进程间通信机制: 不同进程间无法直接交互数据,需通过特殊方式实现通信,例如共享内存(如 mmap、VC lock 等具体方式)。
2.2 线程的核心特性
-
进程的组成部分: 线程并非独立存在,而是运行在进程内部的执行单元。
-
共享内存空间: 同一进程中的线程共享该进程的内存空间,无需额外机制即可访问进程资源。
-
独立执行环境: 尽管共享进程内存,但每条线程拥有自己的寄存器、栈信息等专属执行环境,保证了执行的独立性。
-
线程间通信方式: 线程间可直接读写进程中的数据资源,以此实现同步、互斥等交互需求,通信更为便捷。
2.3 线程与进程的关系
-
调度与分配角色: 进程是操作系统进行资源分配和调度的基本单位,而线程是 CPU 调度的最小单位。
-
包含关系: 线程是进程内部的执行单位,一个进程至少包含一个主线程,可在此基础上扩展多个线程。
3. _EPROCESS
3.1 内核与应用层的关系
应用层在获取核心资源方面存在局限性,许多关键的系统管理内容(如进程管理的核心结构等)都存在于内核层面,这使得内核成为掌控系统核心功能的关键区域。
3.2 进程结构体解析
进程包含两个重要结构体:
-
KPROCESS: 属于内核层面的结构体,存储着与进程内核态运行相关的关键信息。
-
EPROCESS: 为执行体结构体,涵盖了进程在执行过程中的众多详细信息,是进程管理的重要数据结构。
3.3 关键函数与机制
-
WaitForSingleObject: 该函数的作用是等待可等待的内核对象,通过它能够实时知晓进程或线程是否结束,在进程和线程的状态监控中发挥重要作用。
-
CR3 寄存器: 它是虚拟地址转换过程中不可或缺的一环。基于操作系统的分页模式,CR3 寄存器帮助解析虚拟地址所对应的物理地址,从而确保每个进程的虚拟内存空间相互独立,互不干扰。
3.4 线程与进程的关联
线程是进程的组成部分,进程结构体中包含线程链表,通过该链表可以指向进程所包含的线程,进而体现出各个线程的状态,清晰展现出进程与线程之间的从属关系。
3.5 进程优先级机制
由于电脑的资源是有限的,进程优先级决定了系统资源的分配顺序。用户可以在相关系统设置中对进程优先级进行调整,以适应不同的应用场景需求。
3.6 进程 ID 的特性
每个进程都拥有唯一的 ID,这一 ID 是进程的标识。用户可以通过多种方式(如任务管理器、系统命令等)查看进程 ID,并且内核中记录的进程 ID 与任务管理器中显示的信息是相互关联、保持一致的。
3.7 进程的遍历与隐藏
-
遍历进程: 存在多种方法可以对系统中的进程进行遍历,以获取进程的相关信息。
-
复杂情况: 当遇到有保护机制的进程时,调试工作会变得较为复杂。
3.8 调试相关内容
-
调试实现: 可以通过 CreateProcess 和 DebugActiveProcess 等 API 来实现对进程的调试。
-
复杂情况: 当遇到有保护机制的进程时,调试工作会变得较为复杂。
3.9 句柄表的作用
当进程创建或打开内核对象时,会得到一个句柄。句柄表的功能是帮助找到该句柄所对应的真正内核对象,其存在防止了应用层对内核对象进行随意修改,保障了系统的稳定性。
3.10 进程保护方式
通过设置 ProtectProcess 标志位,可以使进程难以被打开或附加,从而实现类似 "拒绝访问" 的保护效果,增强进程的安全性。
3.11 内核与 API 的关系
V3 核心编程的重点在于熟悉 API 的使用,编程时需按照规定格式调用 API,并通过返回值和错误处理机制来修正代码,而这些 API 的核心功能最终由内核来处理实现。
4. ETHREAD
4.1 线程的基本属性与验证
- 线程结构体中的 Key32 可用于验证线程所属进程,明确体现了线程是进程的一部分这一核心关系。
4.2 相关内核对象与 API
- Dispatch Handler 属于可等待的内核对象,可通过 WaitForSingleObject 这一 API 进行等待操作。
4.3 线程栈区特点
-
线程拥有应用层和内核层两个栈区,由于权限差异,二者无法共享。
-
栈区存在栈底和栈顶,初始化的栈底在 thread 切换时会被保留;start limit 代表栈顶,current start 则是当前栈顶,用于记录线程切换情况。
4.4 线程的锁、运行标记与状态
-
threed lock 可理解为线程锁,用于线程同步等操作。
-
running 作为运行标记,0 表示未运行,1 表示运行,线程存在多种不同状态。
-
线程结构体多描述线程的就绪、调度、等待等状态,且通过 three list entry 与相关项形成双向循环链表,关联活跃线程,可从中获取线程 ID、创建与结束时间等信息。
4.5 线程类型与优先级
-
线程分为普通线程和内核线程,PS System32 可理解为内核线程。
-
线程优先级从 0 到 31 共 32 个等级,数字越大权限越高;CPU 每个核有 32 个调度链表,按优先级从大到小进行调度。
4.6 线程的创建与归属
- 线程由进程创建,存在 "亲爹" 和 "干爹" 的情况,即线程创建后可以隶属于其他进程。
4.7 API 调用与内核的关系
-
应用层执行 API 最终会通过 SSDT 表找到内核函数来完成调用,过程中会传递符号以寻找对应的内核函数。
-
熟悉 API 参数有助于深入进行内核开发,而 API 调用的成功与否与内核知识、权限等因素相关。
5. 线程创建执行
5.1 线程控制
-
创建 :
- 线程是通过
CreateThread创建的,该API会为线程分配必要的资源且返回内核对象句柄。 - 创建线程可以通参数指定线程属性,线程运行状态(创建运行线程,创建挂起进程)。
- 线程是通过
-
执行 :
- 使用
CreateThread创建线程的时候,当参数不指定Create_SusPended,线程会进去就绪等待调度。 - 指定Create_SusPended时,线程会进入挂起状态,会使用
ResumeThread恢复线程运行状态。
- 使用
-
挂起 : SuspendThread
-
恢复 : ResumeThread
-
终止: TerminateThread
-
远程: CreateRemoteThread
5.2 线程创建

系统规定的固定格式:
c
返回值 系统调用约定 函数名 (参数)
DWORD WINAPI(_stdcall) WorkThread(LPVOID lp)
5.3 并行输出

5.3 主线程阻塞,子线程继续执行

主线程的彻底结束,才能停止子线程的继续执行。
6. 捕获线程状态
通过给子线程设置条件,使得结束状态:

6.1 设置条件等待

WaitForSingleObject的调用可以使得子线程的彻底结束才能往下继续执行主线程代码。


当然我们可以根据捕获该API的返回值判断事件。
6.2 主线程等待多子线程结束再执行

7. 等待就绪线程

通过挂起和恢复的这个流程我们可以看到,当子线程被挂起的时候并不影响主线程往下执行,但是当子线程恢复后,主线程不能立刻退出进程,否则子线程还没来得及执行就被杀死了。
8. 控制挂起恢复

通过这个例子我们可以清晰观察到线程经过创建-挂起-恢复-结束 的一系列流程,以及数据的输出-中断-输出,能够清晰的看到这个生命周期。
9. 销毁终止释放
9.1 TerminateThread终止指定线程

在这个练习中主要依靠TerminateThread结束指定进程,然后GetExitCodeThread检索终止状态达到一个结束并获取状态的一个方法。
9.2 完成条件关闭线程

在这个练习中,我们在子线程完成条件以后通过
ExitThread来结束线程,再由WaitFortSingleObject接受状态值完成放行,再由GetExitCodeThread接受线程终止状态,最后关闭句柄,完成一系列流程。
10. 远程线程调用

打开进程获取句柄,然后根据句柄以及另一个进程中的函数地址作为参数放入
GreateRemoteThreadAPI中完成在另一个进程中创建线程。
11. 线程上下文环境

程序创建一条循环常驻的子线程,主线程等待三秒后挂起子线程,通过 CONTEXT 读取其全部寄存器上下文,读取完毕再恢复线程运行,演示调试器赖以实现的线程暂停与寄存器获取底层逻辑。
12. 简易进程线程信息

这段代码通过 Windows 系统快照 API,先遍历系统中所有进程并打印进程 ID,每获取一个进程后,再拍摄全系统线程快照,并通过进程 ID 匹配筛选出属于当前进程的所有线程,最终打印出每个进程对应的线程 ID,实现枚举系统全部进程及所属线程的功能。
本章完~