进程,站在内核的角度来说,它就是个结构体。当操作系统想要创建一个进程时,本质上就是分配一块内存,填充一个结构体,今天就来了解一下这个进程结构体EPROCESS。
EPROCESS
每个Windows进程在0环都有一个对应的结构体:EPROCESS,这个结构体包含了进程所有重要的信息。
在Windbg中,执行指令dt _EPROCESS 我们就可以看到这个完整的结构。
这个结构非常的庞大,本篇先混个眼熟,介绍一些比较关键的字段,其余在后续文章中用到时再详细介绍。
+0x000 Pcb
- 成员名:Pcb
- 数据类型:_KPROCESS
- 说明:在EPROCESS开始的位置,有一个Pcb,它是一个KPROCESS结构,同样包含了描述进程的信息,先来看一下这个结构比较关键的一些字段。
- 结构图:
+0x000 Header
- 成员名:Header
- 数据类型:_DISPATCHER_HEADER
- 说明:结构体内若包含_DISPATCHER_HEADER这个数据类型,说明这是一个可等待对象
- 可等待对象:Mutex,Event都是可等待对象,可被作用于WaitForSingleObject这类函数
+0x018 DirectoryTableBase
- 成员名:页目录表基址
- 数据类型:[2] Uint4B
- 说明:进程结构体中最重要的成员,控制整个进程的物理页,进程切换时会将值填入Cr3
+0x038 KernelTime/+0x03c UserTime
- 成员名:KernelTime/UserTime
- 数据类型:Uint4B
- 说明:统计信息,记录了一个进程在内核模式/用户模式下所花的时间
+0x050 ThreadListHead
- 成员名:ThreadListHead
- 数据类型:_LIST_ENTRY
- 说明:指向当前进程的,线程链表
+0x05c Affinity
成员名:Affinity
数据类型:Uint4B
说明:规定进程里面的所有线程能在哪个CPU上跑
- 如果值为1,那这个进程的所有线程只能在0号CPU上跑(00000001)
- 如果值为3,那这个进程的所有线程能在0、1号CPU上跑(000000011)
- 如果值为4,那这个进程的所有线程能在2号CPU上跑(000000100)
- 如果值为5,那这个进程的所有线程能在0,2号CPU上跑(000000101)
4个字节共32位,所以最多只能32核,Windows64位,就64核;如果只有一个CPU,把这个值设置为4,那么这个进程就死了。
+0x062 BasePriority
- 成员名:BasePriority
- 数据类型:Char
- 说明:表示基础优先级/最低优先级,该进程中的所有线程一创建出来时最初的优先级
到这里KPROCESS内部的主要成员就介绍完了,现在又要回到EPROCESS这个结构中了
+0x063 ThreadQuantum
- 成员名:ThreadQuantum
- 数据类型:Char
- 说明:线程时间片的初始值
+0x070 CreateTime/+0x078 ExitTime
- 成员名:CreateTime/ExitTime
- 数据类型:_LARGE_INTEGER
- 说明:进程的创建/退出时间
+0x084 UniqueProcessId
- 成员名:UniqueProcessId
- 数据类型:Ptr32 Void
- 说明:进程的编号,任务管理器中显示的PID就是这个值
+0x088 ActiveProcessLinks
- 成员名:ActiveProcessLinks
- 数据类型:_List_Entry
- 说明:双向链表,所有的活动进程都连接在一起,构成了一个链表
- PsActiveProcessHead指向全局链表头
- 第一个成员指向后一个进程结构体0x88偏移的位置,第二个成员指向前一个结构体0x88偏移的位置
- 通过断链,可以实现简单的进程隐藏
- 结构图:
- 查询示范:
+0x090 QuotaUsage/+0x09c QuotaPeak
- 成员名:QuotaUsage/QuotaPeak
- 数据类型:[3] Uint4B
- 说明:物理页相关的统计信息(到内存部分会详细分析)
+0x0a8 CommitCharge/+0x0ac PeakVirtualSize/+0x0b0 VirtualSize
- 成员名:CommitCharge/PeakVirtualSize/VirtualSize
- 数据类型:Uint4B
- 说明:虚拟内存相关的统计信息(到内存部分会详细分析)
+0x11c VadRoot
- 成员名:VadRoot
- 数据类型:Ptr32 Void
- 说明:指向一个平衡二叉树,标识了0~2G哪些内存被分配了,哪些没被分配;该成员和内存遍历,模块隐藏有关
+0x0bc DebugPort /+0x0c0 ExceptionPort
- 成员名:DebugPort/ExceptionPort
- 数据类型:Ptr32 Void
- 说明:调试相关,通过清零DebugPort,是一种简单的反调试手段,具体关于调试的内容,到调试相关章节会详细分析
+0x0c4 ObjectTable
- 成员名:ObjectTable
- 数据类型:Ptr32 _HANDLE_TABLE
- 说明:句柄表,存储在0环,记录了当前进程所使用的别的进程的句柄地址,可以通过遍历所有进程的句柄表来查看当前程序是否被调试。在句柄表的章节,会详细讲解这个成员的内容
+0x174 ImageFileName
- 成员名:ImageFileName
- 数据类型:[16]UChar
- 说明:进程镜像文件名,最多16个字节。如上面查询活动进程链表的实验中,可以看到进程名为”System”
0x1a0 ActiveThreads
- 成员名:ActiveThreads
- 数据类型:Uint4B
- 说明:活动线程的数量
0x1b0 Peb
- 成员名:Peb
- 数据类型:Ptr32_PEB
- 说明:PEB(Process Enviroment Block 进程环境快):位于3环的一个描述进程的结构,里面包含了进程的模块列表、是否处于调试状态,等信息
- 结构图:
下面简单介绍其中2个成员:
0x2 BeingDebugged
- 成员名:BeingDebugged
- 数据类型:Uchar
- 说明:当进程属于被调试的时候,这个位置的值会被置1。调试器可以通过不断清零这个值,做到简单的反反调试
0xc Ldr
进程隐藏
在前面介绍了EPROCESS里有一个双向链表ActiveProcessLinks,我们可以通过断链,实现简单的进程隐藏。
- 打开OD,然后打开任务管理器,可以看到,OD这个进程
- 然后找到活动进程链表头
- 从后往前遍历(刚打开的进程,位于链表靠后的位置),找到OD这个进程对应的EPROCESS
- 修改OD前后进程结构体的活动进程链表,将OD断链
- 再次打开任务管理器,发现没有OD这个进程了,但是程序仍能正常执行
- 说明任务管理器是通过遍历活动进程链表来查询所有进程的
参考教程:https://www.bilibili.com/video/BV1NJ411M7aE?p=43
参考文章: