avatar

Catalog
进程结构体

进程,站在内核的角度来说,它就是个结构体。当操作系统想要创建一个进程时,本质上就是分配一块内存,填充一个结构体,今天就来了解一下这个进程结构体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就是这个值
  • 成员名: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

  • 成员名:Ldr
  • 数据类型:_PEB_LDR_DATA
  • 该结构内有3个双向链表成员,存储了当前进程所有的模块(只是顺序不同),通过断链可以实现简单的模块隐藏
  • 结构图:

进程隐藏

在前面介绍了EPROCESS里有一个双向链表ActiveProcessLinks,我们可以通过断链,实现简单的进程隐藏。

  1. 打开OD,然后打开任务管理器,可以看到,OD这个进程
  2. 然后找到活动进程链表头
  3. 从后往前遍历(刚打开的进程,位于链表靠后的位置),找到OD这个进程对应的EPROCESS
  4. 修改OD前后进程结构体的活动进程链表,将OD断链
  5. 再次打开任务管理器,发现没有OD这个进程了,但是程序仍能正常执行
  6. 说明任务管理器是通过遍历活动进程链表来查询所有进程的

参考教程:https://www.bilibili.com/video/BV1NJ411M7aE?p=43

参考文章:

  1. https://blog.csdn.net/qq_41988448/article/details/103005060
  2. https://blog.csdn.net/qq_38474570/article/details/103722984
Author: cataLoc
Link: http://cata1oc.github.io/2020/03/28/%E8%BF%9B%E7%A8%8B%E7%BB%93%E6%9E%84%E4%BD%93/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶