📄 jiurl玩玩win2k进程线程篇 eprocess.htm
字号:
uint32 LowPart<BR>+264 int32 HighPart<BR>+260 int64 QuadPart<BR>+268
uint32 CommitChargeLimit<BR>+26c uint32 CommitChargePeak<BR>+270 struct
_LIST_ENTRY ThreadListHead<BR>+270 struct _LIST_ENTRY *Flink<BR>+274
struct _LIST_ENTRY *Blink<BR>+278 struct _RTL_BITMAP
*VadPhysicalPagesBitMap<BR>+27c uint32 VadPhysicalPages<BR>+280 uint32
AweLock<BR><BR><B>遍历所有进程</B><BR><BR>系统需要能够方便的遍历所有进程。所以进程的 EPROCESS
结构使用链表链在了一起。<BR><BR>所有进程(除了Idle进程)的 EPROCESS 通过 EPROCESS 结构偏移+a0处的
LIST_ENTRY ActiveProcessLinks 链在一起。<BR>+0a0 struct _LIST_ENTRY
ActiveProcessLinks<BR>+0a0 struct _LIST_ENTRY *Flink<BR>+0a4 struct
_LIST_ENTRY *Blink<BR><BR>通过全局变量 PsActiveProcessHead 可以找到这个链。<BR>PID 为 0 的
Idle 进程并没有链在这个链上。我们可以通过全局变量 PsIdleProcess 找到 Idle 进程的 EPROCESS。<BR><BR>使用
LIST_ENTRY 结构来组织链表在 Win2k 中非常常见。Flink,Blink指向的都是另一个 LIST_ENTRY
结构。<BR><BR>对于 Win2k Build 2195 来说,全局变量 PsActiveProcessHead 的地址是
0x8046a180,开始处就是一个 LIST_ENTRY 结构。顺着这个 LIST_ENTRY 结构的 Flink 或者
Blink,我们可以找到每一个进程(除了Idle)的EPROCESS 结构中的 LIST_ENTRY ActiveProcessLinks。这个
ActiveProcessLinks 地址偏移 -0a0 处就是 EPROCESS 结构的地址。从 PsActiveProcessHead
开始顺着这条链一直走下去,直到重新回到 PsActiveProcessHead
,链已经循环了,就说明已经遍历了整个链。<BR><BR>下面我们看实际的例子<BR><BR>kd> ?
PsActiveProcessHead<BR>? PsActiveProcessHead<BR>Evaluate expression:
-2142854784 = 8046a180<BR>// Win2k Build 2195 的 PsActiveProcessHead 位于地址
8046a180<BR><BR>kd> dd 8046a180 l 2<BR>dd 8046a180 l 2<BR>8046a180
8141e0c0 82fa4b00<BR>// 8046a180 处的 LIST_ENTRY,注意 PsActiveProcessHead
并不是某个进程的 +0a0 ActiveProcessLinks<BR>// 我们看看 Flink 找到的进程<BR><BR>kd> ?
8141e0c0-a0<BR>? 8141e0c0-a0<BR>Evaluate expression: -2126389216 =
8141e020<BR>// ActiveProcessLinks 位于 EPROCESS 的 +0a0 处。<BR>// 我们得到的是
ActiveProcessLinks 的地址,所以 EPROCESS 在得到地址 -0a0 处。<BR><BR>kd> !process
8141e020 0<BR>!process 8141e020 0<BR>PROCESS 8141e020 SessionId: 0 Cid:
0008 Peb: 00000000 ParentCid: 0000<BR>DirBase: 00030000 ObjectTable:
81452a68 TableSize: 46.<BR>Image: System<BR>// 是PID为8的进程
System<BR>... <BR>// 顺着 Flink 一直走下去,我们省去了中间的过程<BR><BR>kd> dd
82fa4b00 l 2<BR>dd 82fa4b00 l 2<BR>82fa4b00 8046a180 807e90c0<BR>// 看到了这个
ActiveProcessLinks 的 Flink 为 8046a180 ,就是 PsActiveProcessHead <BR>//
说明已经遍历了整个 ActiveProcessLinks 链<BR><BR>对于 Win2k Build 2195 来说,全局变量
PsIdleProcess 的地址是 0x8046a1fc,是一个 EPROCESS 指针,直接指向 Idle 进程的 EPROCESS
结构。<BR><BR>kd> ? PsIdleProcess<BR>? PsIdleProcess<BR>Evaluate
expression: -2142854660 = 8046a1fc<BR>// Win2k Build 2195 的 PsIdleProcess
位于地址 8046a1fc<BR><BR>kd> dd 8046a1fc l 1<BR>dd 8046a1fc l 1<BR>8046a1fc
8046bb60<BR>// PsIdleProcess 的值为 8046bb60<BR><BR>kd> !process 8046bb60
0<BR>!process 8046bb60 0<BR>PROCESS 8046bb60 SessionId: 0 Cid: 0000 Peb:
00000000 ParentCid: 0000<BR>DirBase: 00030000 ObjectTable: 81452a68
TableSize: 46.<BR>Image: Idle<BR>// 可以看到 8046bb60 是指向 EPROCESS 的,并且这个
EPROCESS 是 PID 为 0 的 Idle 进程的
EPROCESS。<BR><BR><BR><B>进程名</B><BR><BR>EPROCESS 中保存着一个给人看的进程名,Windows
任务管理器中显示的进程名,就是从这里获得的。<BR>更准确的叫法是映像名称。<BR>+1fc byte
ImageFileName[16]<BR>ImageFileName[16]
是一个16个字节长的字节数组,保存着进程名。当进程名的长度大于等于16个字节时,在 ImageFileName[16]
只保存进程名的前15个字节,ImageFileName[16]
最后一个字节为0,字符串的结束符。<BR>不同进程的进程名可以相同,比如打开多个记事本,那么每个记事本的 ImageFileName[16] 都是
"NOTEPAD.EXE",进程名只是给人看的,每个进程的 进程ID 都是不同的。<BR><BR>kd> !process 200
0<BR>!process 200 0<BR>Searching for Process with Cid == 200<BR>PROCESS
8105c2e0 SessionId: 0 Cid: 0200 Peb: 7ffdf000 ParentCid: 01f0<BR>DirBase:
00564000 ObjectTable: 81090c48 TableSize: 298.<BR>Image:
Explorer.exe<BR><BR>kd> db 8105c2e0+1fc l 10<BR>db 8105c2e0+1fc l
10<BR>8105c4dc 45 78 70 6c 6f 72 65 72-2e 65 78 65 00 00 00 00
Explorer.exe....<BR><BR><BR><B>进程ID (PID)</B><BR><BR>+09c void
*UniqueProcessId<BR><BR><B>父进程ID</B> <BR><BR>+1c8 void
*InheritedFromUniqueProcessId<BR><BR><BR><B>页目录</B><BR><BR>+018 uint32
DirectoryTableBase[2]<BR><BR>每个进程有自己4G地址空间,当进程切换时,就需要切换地址空间。也就是切换页目录页表。所以每个进程都需要保存自己页目录的地址。每个进程的页目录所在物理页的物理地址,就保存在进程
EPROCESS 结构偏移 +018 处 DirectoryTableBase数组的第0项中,即
DirectoryTableBase[0]。对于执行地址转换的 CPU
来说需要知道页目录所在物理页的物理地址就可以进行地址转换。对于维护进程的页目录和页表的系统来说,需要把页目录和页表所在的物理页映射到地址空间中。<BR><BR>下面使用
SoftICE 来说明,进程 EPROCESS 中的
DirectoryTableBase数组,有2个元素,其中DirectoryTableBase[0] 就是该进程的页目录的物理地址,也就是该进程
CR3 的值。<BR><BR>:addr<BR>CR3 LDT Base:Limit KPEB Addr PID Name<BR>00030000
8141E020 0008 System<BR>04E4B000 810F7580 008C smss<BR>06582000 810E8D60
00A8 csrss<BR>07607000 810CC2C0 00BC winlogon<BR>07A49000 810C1500 00D8
services<BR>0799A000 810BFD60 00E4 lsass<BR>00AFD000 810A2D60 0174
svchost<BR>00F21000 81092940 0190 svchost<BR>007C4000 8105D600 0200
Explorer<BR>024B9000 824D0020 0260 internat<BR>042B2000 8423E860 0180
conime<BR>*00030000 8046BB60 0000 Idle<BR>// addr 列出当前进程,注意每个进程的 CR3 ,
SoftICE 显示的 KPEB Addr 就是 EPROCESS 的地址<BR><BR>// 转换到 explorer
的地址空间,EPROCESS 在 8105D600 <BR>// 偏移 +018 处 是 uint32
DirectoryTableBase[2] 数组<BR>:addr explorer<BR>:dd 8105d600+18 l
10<BR>0010:8105D618 007C4000 06165000 00000000 00000000
.@|..P..........<BR>// DirectoryTableBase[0] 物理地址为 007C4000
看看这个物理地址,被映射到了哪些虚拟地址<BR>:phys 7c4000<BR>807C4000<BR>C0300000<BR>//
C0300000 是进程页目录被映射到的地址空间<BR>:phys
6165000<BR>86165000<BR>C0301000<BR><BR>// 情况一样<BR>:addr internat<BR>:dd
824d0020+18 l 10<BR>0010:824D0038 024B9000 024BA000 00000000 00000000
..K...K.........<BR>:phys 24b9000<BR>824B9000<BR>C0300000<BR>:phys
24ba000<BR>824BA000<BR>C0301000<BR><BR>// 情况一样<BR>:addr csrss<BR>:dd
810e8d60+18 l 10<BR>0010:810E8D78 06582000 06563000 00000000 00000000 .
X..0V.........<BR>:phys 6582000<BR>86582000<BR>C0300000<BR>:phys
6563000<BR>86563000<BR>C0301000<BR><BR>// 注意这是 System 进程<BR>// System 的
DirectoryTableBase[0] 仍然映射到了 C0300000<BR>// 但是 System 的
DirectoryTableBase[1] 情况和别的进程不一样<BR>:addr system<BR>:dd 8141e020+18 l
10<BR>0010:8141E038 00030000 00000000 00000000 00000000
................<BR>:phys
30000<BR>80030000<BR>C0300000<BR>C04FB000<BR>EB3F1000<BR>F09CA000<BR>:phys
00000000<BR>80000000<BR>C0200000<BR>EB3C1000<BR>F09D4000<BR><BR>我们可以看到
DirectoryTableBase[0] 中就是页目录所在物理页的物理地址。每个进程 EPROCESS +18 处4个字节的值 和 该进程 CR3
中的值是一样的。 EPROCESS +18 处4个字节的物理地址都映射到了 C0300000,而 C0300000
正是页目录所在物理页映射到地址空间中的虚拟地址。我也检查了一个进程的 DirectoryTableBase[0] (EPROCESS +18
处4个字节的值)所指的物理页中的内容,里面的确是 该进程页目录的内容。<BR><BR>从 SoftICE 的输出中,我们可以看到
DirectoryTableBase[1] 中的物理地址所指的物理页,被映射到了地址空间 C0301000 处,也就是
C0301000-C0301FFF 这个页表,我们计算可以得到这个页表负责的是 C0400000-C07fffff 这 4M
虚拟地址空间的映射。我们知道每个进程的 Working Set 开始于 C0502000 ,就落在这个范围之内,而每个进程都有自己的 Working
Set。<BR><BR>其实一个进程保存自己的页目录的物理地址就足够了。由于每个进程的 Working Set 在
C0400000-C07fffff 这个范围之内,每个进程的 Working Set
是不同的,所以负责这个范围的页表也是不同的,所以有理由多保存一个负责地址空间 映射的页表的物理地址。不过要注意的是,进程 System 的
DirectoryTableBase[1] 值为0,并不是对应地址空间范围 C0400000-C07fffff
的页表所在物理页的物理地址。<BR><BR><BR><B>不切换进程,直接访问其他进程的地址空间</B><BR><BR>我们使用(驱动程序中使用)KeAttachProcess(),KeDetachProcess()
切换地址空间到一个指定进程的地址空间,然后访问这个进程地址空间中的内存。现在对于物理内存不超过 512M
的系统,我们有一种方法,可以不用切换地址空间,直接访问指定进程的地址空间中的内存。<BR><BR>我们知道 Win2k 把物理内存的前 512M
的每个物理页,一一对应的映射到地址空间的 80000000-9FFFFFFF 这一段 LargePage
区域中。物理内存不到512M的,有多少就映射多少。这样我们就可以访问所有不超过 512M 的物理内存(当然 80000000-9FFFFFFF
在系统地址空间中,访问需要 ring0 的权限)。对于一个小于512M的物理地址,我们可以用这个物理地址加上 80000000
,得到该物理页映射的一页地址空间的虚拟地址,我们可以使用这个虚拟地址,来访问这个物理中的内容。从前面分析 DirectoryTableBase
时,SoftICE 的输出也可以看出这一点,比如,物理地址 7c4000 ,对应虚拟地址 807C4000
。<BR><BR>对于一个指定的进程,我们可以通过进程链表找到它,并计算出它的 EPROCESS 的地址。就可以找到 EPROCESS
中的该进程页目录所在物理页的物理地址,即 DirectoryTableBase[0]
的值。该进程页目录所在物理页的物理地址如果小于512M,我们就可以计算出这个物理页被映射到地址空间 80000000-9FFFFFFF
中的虚拟地址,就可以访问该进程的页目录。就可以找到该进程的所有页表的物理地址,同样我们根据页表的物理地址,可以算出页表在
80000000-9FFFFFFF
中的虚拟地址,就可以访问该进程的所有页表。就可以找到该进程所有页对应的物理页的物理地址。根据这个物理地址,我们可以算出该物理页在
80000000-9FFFFFFF 中的虚拟地址,从而访问改进程的某一页中的数据。<BR><BR>我们用 SoftICE 实际演示一下,读取进程
Explorer 地址空间 0x400000 中的内容。<BR><BR>:addr<BR>CR3 LDT Base:Limit KPEB Addr
PID Name<BR>00030000 8141E020 0008 System<BR>04E4B000 810F7580 008C
smss<BR>06582000 810E8D60 00A8 csrss<BR>07607000 810CC2C0 00BC
winlogon<BR>07A49000 810C1500 00D8 services<BR>0799A000 810BFD60 00E4
lsass<BR>00AFD000 810A2D60 0174 svchost<BR>00F21000 81092940 0190
svchost<BR>007C4000 8105D600 0200 Explorer<BR>024B9000 824D0020 0260
internat<BR>06A45000 84090220 02A8 conime<BR>*00030000 8046BB60 0000
Idle<BR>// Explorer 的 EPROCESS 地址为 8105D600 ,我们通过进程链表也可以找到<BR><BR>:dd
8105d600+18 l 10<BR>0010:8105D618 007C4000 06165000 00000000 00000000
.@|..P..........<BR>// Explorer 的页目录所在物理页的地址为 007C4000 <BR><BR>:dd
(80000000+7c4000)+(400000>22)*4 l 10<BR>0010:807C4004 028D4067 0268E067
01E35067 0219B067 g@..g.h.gP..g...<BR>// (80000000+7c4000) 就是 Explorer
的页目录的虚拟地址<BR>// (400000>22) 取虚拟地址 400000 的高10bit,这是页目录索引。<BR>//
一个页目录项大小为4个字节,所以 (400000>22)*4 为 400000 对应页目录项在页目录中偏移地址<BR>// 所以 400000
对应的页表的物理地址为 028D4000 (别忘了页目录项和页表项的低12位是标志)<BR><BR>:dd
(80000000+28d4000)+((400000&3ff000)>12)*4 l 10<BR>0010:828D4000
02963005 00612025 08332C34 02954025 .0..% a.4,3.%@..<BR>//
((400000&3ff000)>12) ,取虚拟地址 400000 作为页表索引的10bit,<BR>//
((400000&3ff000)>12)*4 为 400000 在对应页表项在页表中偏移地址<BR>// 所以 400000
对应的物理页的物理地址为 02963000<BR><BR>:dd (80000000+2963000)+(400000&fff) l
10<BR>0010:82963000 00905A4D 00000003 00000004 0000FFFF
MZ..............<BR>// (400000&fff),取虚拟地址 400000 的低12bit作为页内偏移。<BR>//
最后得到进程 Explorer 虚拟地址 400000 处的4个字节值为 00905A4D ,就是"MZ.."<BR><BR>// 切换到
Explorer 地址空间,验证一下<BR>:addr explorer<BR>:dd 400000 l 10<BR>0010:00400000
00905A4D 00000003 00000004 0000FFFF MZ..............<BR>// 可以看到 Explorer
地址空间虚拟地址 00400000 处的值正是 00905A4D ,"MZ.."<BR><BR><BR><B>进程的
HANDLE_TABLE</B><BR><BR>+128 struct _HANDLE_TABLE
*ObjectTable<BR><BR><B>进程的 PEB</B><BR><BR>+1b0 struct _PEB
*Peb<BR><BR><B>线程链表</B><BR><BR>进程的所有线程通过 LIST_ENTRY
结构链在了一个双向循环链表上。<BR>一个链表是以 EPROCESS 结构的 KPROCESS Pcb 中的 ThreadListHead
为链表的链表头。链上的每一项是一个线程的 KTHREAD ETHREAD 结构的 Tcb 中的 ThreadListEntry
。<BR>另一个链表是以 EPROCESS 结构中的 ThreadListHead 为链表的链表头。链上的每一项是一个线程的 ETHREAD
结构中的 ThreadListEntry 。<BR>通过这两个链表中的任何一个,都可以找到一个进程的所有线程的 ETHREAD 结构,当然找到
ETHREAD 结构,就可以找到 ETHREAD 结构中的 KTHREAD。<BR><BR>KTHREAD 链表<BR><BR>struct
_EPROCESS (sizeof=648)<BR>+000 struct _KPROCESS Pcb<BR>+050 struct
_LIST_ENTRY ThreadListHead<BR>+050 struct _LIST_ENTRY *Flink<BR>+054
struct _LIST_ENTRY *Blink<BR><BR>struct _ETHREAD (sizeof=584)<BR>+000
struct _KTHREAD Tcb<BR>+1a4 struct _LIST_ENTRY ThreadListEntry<BR>+1a4
struct _LIST_ENTRY *Flink<BR>+1a8 struct _LIST_ENTRY *Blink<BR><BR>ETHREAD
链表<BR><BR>struct _EPROCESS (sizeof=648)<BR>+270 struct _LIST_ENTRY
ThreadListHead<BR>+270 struct _LIST_ENTRY *Flink<BR>+274 struct
_LIST_ENTRY *Blink<BR><BR>struct _ETHREAD (sizeof=584)<BR>+240 struct
_LIST_ENTRY ThreadListEntry<BR>+240 struct _LIST_ENTRY *Flink<BR>+244
struct _LIST_ENTRY *Blink<BR><BR><B>VAD </B><BR><BR>+194 void
*VadRoot<BR>+198 void *VadHint<BR><BR>VadRoot 是进程 Vad
二叉树的根节点的指针。<BR>通过反汇编和分析函数 MiLocateAddress() 可以知道 ,VadHint 是上一次
MiLocateAddress() 找到的 Vad 的指针。<BR><BR><B>WorkingSet</B><BR><BR>+0e0 uint32
PeakWorkingSetSize<BR>+0e4 uint32 WorkingSetSize<BR>+0e8 uint32
MinimumWorkingSetSize<BR>+0ec uint32 MaximumWorkingSetSize<BR>+0f0
*VmWorkingSetList<BR><BR>WorkingSet 的相关信息。<BR>VmWorkingSetList 是指向 MMWSL
结构的指针。值总是 C0502000 。<BR><BR><B>Section</B><BR><BR>+1ac void
*SectionHandle<BR>+1b4 void *SectionBaseAddress<BR><BR>SectionHandle
是句柄,可以用这个句柄在句柄表中找到对应的对象。<BR>SectionBaseAddress
是载入地址空间的基地址。<BR><BR><BR><B>进程对象的对象体</B><BR><BR>需要说明的是 EPROCESS
就是进程对象的对象体,象其他类型的对象一样,EPROCESS 之前也有对象头。使用 kd 可以很容易看到这一点<BR>kd> !process
8 0<BR>!process 8 0<BR>Searching for Process with Cid == 8<BR>PROCESS
8141e020 SessionId: 0 Cid: 0008 Peb: 00000000 ParentCid: 0000<BR>DirBase:
00030000 ObjectTable: 81452a68 TableSize: 106.<BR>Image:
System<BR><BR>kd> !object 8141e020<BR>!object 8141e020<BR>Object:
8141e020 Type: (814524e0) Process<BR>ObjectHeader:
8141e008<BR>HandleCount: 2 PointerCount: 36<BR><BR>根据 EPROCESS 的地址,kd
可以正确分析出对象类型是进程,说明了 EPROCESS 的确是对象体。<BR><BR><BR>欢迎交流,欢迎交朋友,<BR>欢迎访问 <A
href="http://jiurl.yeah.net/">http://jiurl.yeah.net/</A> <A
href="http://jiurl.cosoft.org.cn/forum">http://jiurl.cosoft.org.cn/forum</A>
<BR><BR><BR><BR><BR><BR><BR><BR></P></TD></TR></TBODY></TABLE></DIV></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -