学习逆向工程也快一年的时间了,从开始的16位实模式下的内存寻址模型到32位下保护模式内存的模型,实模式下的较为简单,段地址*16+偏移地址就是寻址的内存,但是保护模式下就远远没有这么简单了。很简单的一个例子,windows下支持多进程,并且进程之间是相互隔离的,不允许随意的将一个进程的数据写入到另外一个进程的空间中,并且每个时刻CPU只允许运行一个程序,何以为见呢?

打开OD,分别用OD加载两个程序,而且是两个不同的进程,就看代码段的数据,假设我们查看0x0041F69A处的指令,我们发现两个进程的数据是不一样的,这里也会让我们感到奇怪,为什么两个进程处在相同地址处的数据不同呢?当然我们知道是保护机制中隔离在作怪,但是他是如何做到隔离的呢?为什么不能随意的向另外一个进程的空间中写入值呢?所以就这几天的学习情况对一下几个问题进行讨论:

1.OD或者其他调试器中的地址到底是个什么样的地址?虚拟地址,线性地址和物理地址间的联系又是什么?
2.Windows系统是如何做到每个进程地址空间的隔离的?
3.传说中的GDT,LDT表的作用?
4.虚拟地址是如何映射到物理地址的?
5.如何通过虚拟地址找到进程实际所在的物理地址?
6.如何通过实验来验证windows中某个进程中的一个虚拟地址就是物理内存中的地址呢?
7.如何通过修改映射关系来达到在保护模式下,两个不同的进程可以写入到对方进程的物理空间中呢?

首先还是得简单介绍一下保护模式下的两种保护机制:分段和分页。这个应该都比较好理解,将内存中的数据进行分段,不同类型的数据处于不同的段中,比如代码段就处于代码段的内存中,数据就处于数据段的代码中等等。对于分页呢,我的理解是,将内存按照一定的大小划分区域,比如按照4KB大小,将内存分为N块,每块就称为是一页。不知道这么理解是不是正确的。

然后一个比较不同的方面就是在保护模式下,段寄存器(CS,DS,ES,SS,FS,GS)不再作为寻址用,因为只有16位,而且每个寄存器都是32位的,足以进行寻址的操作了。当然,这里段寄存器我们也称为段选择器。这里,段选择器就不再像实模式下进行寻址了,而是为一个索引,索引一个二进制的结构,这个结构中包含了每一段的细节,比如说代码段具有哪些权限,什么权限的进程可以访问它(Ring3 Or Ring0)。当然这个结构我们称为段描述表,其中的项称为段描述符

当然啦这样的描述符表就是全局描述符表(GDT)和局部描述符表(LDT)了。也就是传说中的GDT和LDT了,当然每个系统中只存在一个GDT,并且是必须存在的,而LDT是可选择的,并且每个任务都有一个不同的LDT,这次我们主要集中在GDT上。下面看一下段选择器的结构及GDT的结构:

上面图还是非常清晰的,我们可以看到段选择器包含了当前进程请求的特权,位0表示当前进程请求权限,即是ring0还是ring3,而且一般也只用到这两个权限,位2表示的是使用GDT还是LDT来存放段描述符的,位3到位15就是存放索引的位置。

GDT存放在哪里呢?Intel中有个48位的寄存器GDTR,存放的就是GDTR的地址,应该是从系统初始化的时候就开始存在了,并且这个结构也只有特定的权限进程进行才能进行读写操作,GDTR中,低16位表示的是GDT的大小(以字节为单位),其余32位存储了GDT的起始线性地址,即第一个字节的线性地址。然后就是段描述符,是如下的结构:

是一个64位的结构,最上面的结构是高32位,下面的结构是低32位的结构,其中主要的几个位置就DPL,表示引用段的权限,其他位的作用上图描述的非常清楚,不再多说。下面我们就来看看GDT的结构,我在xp sp3下作的实验,调试器内核模式的,否则看不到GDTR及其结构,首先在虚拟机中用OD加载一个程序,我们来看看每个段寄存器的值是多少:

换了几个不同的程序,发现在ring3层的程序,CS为0x1B,SS为0x23,ring0下就没有继续看了,我们拿CS做例子吧,将CS做分解:0000000000011  0 11

第1,2位为3(0B11),第2位为0,最高位为3,可以看出表示的是引用的段权限为最低,并且存放在GDT中,那我们就看看GDT长啥样子的:

1 kd> r gdtr
2 gdtr=8003f000
3 kd> r gdtl
4 gdtl=000003ff

这里使用gdtr来表示GDT手字节地址,gdtl表示的是GDT中索引的个数,下面我们就来看看GDT表中的数据,如果单单使用dd查看的话太不直观,windbg中我们可以使用dg命令来查看GDT中的结构和值,第一个参数为索引开始值,第二个参数为要查看索引结束值:

1 kd> dg 0 3f8
2                                   P Si Gr Pr Lo
3 Sel    Base     Limit     Type    l ze an es ng Flags
4 ---- -------- -------- ---------- - -- -- -- -- --------
5 0000 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
6 0008 00000000 ffffffff Code RE    0 Bg Pg P  Nl 00000c9a
7 0010 00000000 ffffffff Data RW    0 Bg Pg P  Nl 00000c92
8 0018 00000000 ffffffff Code RE    3 Bg Pg P  Nl 00000cfa
9 0020 00000000 ffffffff Data RW    3 Bg Pg P  Nl 00000cf2
10 0028 80042000 000020ab TSS32 Busy 0 Nb By P  Nl 0000008b
11 0030 ffdff000 00001fff Data RW    0 Bg Pg P  Nl 00000c92
12 0038 00000000 00000fff Data RW Ac 3 Bg By P  Nl 000004f3
13 0040 00000400 0000ffff Data RW    3 Nb By P  Nl 000000f2
14 0048 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
15 0050 8054af00 00000068 TSS32 Avl  0 Nb By P  Nl 00000089
16 0058 8054af68 00000068 TSS32 Avl  0 Nb By P  Nl 00000089
17 0060 00022f40 0000ffff Data RW    0 Nb By P  Nl 00000092
18 0068 000b8000 00003fff Data RW    0 Nb By P  Nl 00000092
19 0070 ffff7000 000003ff Data RW    0 Nb By P  Nl 00000092
20 0078 80400000 0000ffff Code RE    0 Nb By P  Nl 0000009a
21 0080 80400000 0000ffff Data RW    0 Nb By P  Nl 00000092
22 0088 00000000 00000000 Data RW    0 Nb By P  Nl 00000092
23 0090 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
24 0098 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
25 00A0 821b2350 00000068 TSS32 Avl  0 Nb By P  Nl 00000089
26 00A8 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
27 00B0 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
28 00B8 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
29 00C0 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
30 00C8 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
31 00D0 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
32 00D8 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
33 00E0 f871a000 0000ffff Code RE Ac 0 Nb By P  Nl 0000009f
34 00E8 00000000 0000ffff Data RW    0 Nb By P  Nl 00000092
35 00F0 804fb688 000003b7 Code EO    0 Nb By P  Nl 00000098
36 00F8 00000000 0000ffff Data RW    0 Nb By P  Nl 00000092
37 0100 f8397400 0000ffff Data RW Ac 0 Bg By P  Nl 00000493
38 0108 f8397400 0000ffff Data RW Ac 0 Bg By P  Nl 00000493
39 0110 f8397400 0000ffff Data RW Ac 0 Bg By P  Nl 00000493
40 0118 00008003 0000f120 <Reserved> 0 Nb By Np Nl 00000000

我们查看CS和SS所在段的信息:

1 kd> dg 1b
2                                   P Si Gr Pr Lo
3 Sel    Base     Limit     Type    l ze an es ng Flags
4 ---- -------- -------- ---------- - -- -- -- -- --------
5 001B 00000000 ffffffff Code RE    3 Bg Pg P  Nl 00000cfa
6 kd> dg 23
7                                   P Si Gr Pr Lo
8 Sel    Base     Limit     Type    l ze an es ng Flags
9 ---- -------- -------- ---------- - -- -- -- -- --------
10 0023 00000000 ffffffff Data RW    3 Bg Pg P  Nl 00000cf2

我们可以看出这里第五列为段的访问权限,和我们手工的出来的结果相同,当然这里我们可以看出来,这里我们根本没有分段,环0和环3的起始结束地址都是从(00000000-ffffffff),所有的段描述符都指向同一个段,所以windows并没有有使用Intel硬件平台为其提供的所有附属项目。所以我猜测windows下虚拟地址和线性地址的值是相同的。而且我也发现其实GDT在进程间隔离也没起到太大的作用,因为这里GDT也只是起到查看段的权限的作用,当然也有门的一些信息,我们这里不讨论门,以上也都是我个人的理解,如果有误也希望各位进行指出。

下面就是分页保护,这个就和进程地址间的隔离关系就比较大了,我们都知道,32位下每个进程分配的空间为4GB,当然这个4GB只是进程看到的,感觉到的,是虚拟地址当然这个地址也是线性地址,并不是都能使用,比如说我们看到指令的地址都处于00400000开始位置,如果到00200000的话还能访问吗?基本上是不行的,你可以用OD尝试一下,这里的位置是不可访问的,执行到此位置就会发生异常,当然如果都能访问的话那岂不是相当恐怖的一件事情!你一个进程就全部占了4GB空间,你让其他进程怎么办?而且你的物理内存很大情况下也不会有4GB的,像我的虚拟机也就只有512MB呢。猜想00200000这个地址应该是被映射到别的进程的空间里面了,是很有可能的。当然映射绝非那么简单的映射。

启动分页后,线性地址空间被分为几个固定长度的存储块,这些块被称为页(大小可以是4KB,2MB或者4MB),当然我们实际中使用的页大小都为4KB,当然如果不启动分页的话线性地址就是物理地址,但是启动后就不一样了,这个线性地址不再是一个简单的地址了,这32位被分成了不同的部分,每个部分有不同的含义,如下:

一个线性地址被分为3个部分,第一个部分(0-11位)才是偏移,第三个部分(22-31位)指定了页目录的数组结构的项,这些项被称为页目录项(PDE),并且页目录的第一个字节地址,即其开始位置的物理地址(不是线性地址)存放在CR3寄存器中,当然如果都是线性地址的话那还怎么到真实的内存中查找值呢?由于索引一共就10位,所以一个页目录最多存储1024个PDE,每个PDE存储了页表的二级数组结构的起始物理地址(不是线性地址),即PDE存储了页表第一个字节的物理地址。第二个部分(12-21位)指定了页表中特定的项,页表中的项被顺序排列成数组,称为页表项(PTE),当然,一个页表最多能存储1024个PTE。当然,每个PTE存储的就是内存页的第一个物理地址,如果将第一个部分(0-11位)的值和PTE提供的物理基址相加,就可以得到物理内存中第一个字节的地址了。具体关系见下面的图:

要注意的是CR3,PDE和PTE中的值都是物理地址!通过CR3来查找进程页目录的物理地址,所以存在如下关系:CR3相当于我的电脑,PDE相当于根目录,PTE相当于磁盘(C,D,E),就是一个索引的关系,通过索引我们就可以找到线性地址所对应的物理地址了,下面看看PDE和PTE的结构:

如果要分析内存保护的话,重要的几项就是U/S标志和W标志。U/S定义了两个基于页的不同特权级:用户和超级用户。如果该位被清0,则PTE指向的页(或在给定PDE之下的页被分配为超级用户权限)。W标志位用于指明一个页或者一组页是只读还是可写,如果W位标志被置位,表明该页(或该页组)可写可读。

但是一般情况下,我们内存是开启PAE分页的,CR4寄存器中有个PAE标志位,如果开启电话那么线性地址就被分为4个部分而不是3个部分,具体见下面的图,PAE的效果就是可以将处理器访问的物理内存扩充到4GB以上,使可用的地址线达到52条:

PDPT索引就是页目录指针表(PDPT),共两位,一共可以有4个数组,其中的4个数组被称为PDPTE。当然此时分页的寄存器和不开启PAE情况下作用也是相同的,CR3指向PDPT的物理地址,当然此时CR3并不是存储物理地址所有52为,因为很多位都为0,所以没必要所有都存储进去,寻找物理地址的过程如下:

这里我们发现将线性地址转换为物理地址的过程和不开启PAE时候的情况是基本上相同的,并且如果开启PAE分页,那么PDPTE,PDE和PTE都是64位的,并且标志位几乎完全相同,最重要的在于其实物理地址在大小上是可变的,而且根据当前处理器可用的地址线数量进行变化:

当然重要的还是CR3寄存器,CR3寄存器存储页目录表首字节的物理地址。如果每个进程都有自己CR3副本,并且把该副本作为内核维护调度上下文的一部分,则两个进程完全会出现拥有相同的线性地址,就像我们OD调试的那样,但是最终映射到的物理地址一定是不相同的!不知道在哪看到的,进程切换的时候CR3的值也发生相应的改变,这样CPU就可以切换到不同的进程空间中了,因为每个进程都是具有自己的空间,而且相互隔离的。还有一个CR0寄存器,其中的WP(写保护)如果置为的话那么超级代码也无法写入只读的保护区域,需要将保护位置为0才行,比如ssdt表,如果我们直接在内核中修改其地址的话肯定会蓝屏的,需要将CR0的WP位变为0,下面为这些控制寄存器的结构图:

结构还是相当清晰的,还有要注意的是,如果开启PAE分页的话,CR3寄存器的位会发生相应的改变:

这里我们发现,PDPT的地址只使用了27位,那如何表示一个52位的地址呢?其实很简单,我们只需要将后面没使用的位全部填充为0就可以了。在不开启PAE分页的情况下,PTE的最高那20位表示的为页基址,比如0x12345,那么后面的位我们可以看做包含着0,即0x12345[0][0][0],如果不含隐式0,这个地址有时称作页帧号(PNF),页帧表示无力内存中的一个区域,用于存需要占用物理内存的页。

windows下每个进程都分配了一个自己专用的CR3控制寄存器的值,并且CR3控制寄存器有页目录20位的PFN,所以每个进程都有自己的页目录,相关的CR3值存储在进程KPROESS的DirectoryTableBase这个字段中,KPROCESS是EPROCESS的子结构,什么是子结构呢?这里表示KPROCESS和EPROCESS部分有重叠:

1 kd> !process 0 0
2 **** NT ACTIVE PROCESS DUMP ****
3 PROCESS 821b9830  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
4     DirBase: 00b18000  ObjectTable: e1000c98  HandleCount: 279.
5     Image: System
6  
7 PROCESS 81c57760  SessionId: none  Cid: 0224    Peb: 7ffd4000  ParentCid: 0004
8     DirBase: 08c40020  ObjectTable: e13f8fb8  HandleCount:  19.
9     Image: smss.exe
10  
11 PROCESS 81fe6648  SessionId: 0  Cid: 0264    Peb: 7ffd5000  ParentCid: 0224
12     DirBase: 08c40040  ObjectTable: e1524858  HandleCount: 344.
13     Image: csrss.exe
14  
15 PROCESS 8211d020  SessionId: 0  Cid: 027c    Peb: 7ffd6000  ParentCid: 0224
16     DirBase: 08c40060  ObjectTable: e1516968  HandleCount: 509.
17     Image: winlogon.exe
18  
19 PROCESS 820fdae0  SessionId: 0  Cid: 02ac    Peb: 7ffdb000  ParentCid: 027c
20     DirBase: 08c40080  ObjectTable: e17ea610  HandleCount: 266.
21     Image: services.exe
22  
23 PROCESS 820bed78  SessionId: 0  Cid: 02c8    Peb: 7ffdf000  ParentCid: 027c
24     DirBase: 08c400c0  ObjectTable: e17eae98  HandleCount: 331.
25     Image: lsass.exe
26  
27 PROCESS 81f4d770  SessionId: 0  Cid: 0368    Peb: 7ffd8000  ParentCid: 02ac
28     DirBase: 08c400e0  ObjectTable: e1a81188  HandleCount:  25.
29     Image: vmacthlp.exe
30  
31 PROCESS 81cd0ca8  SessionId: 0  Cid: 0374    Peb: 7ffd9000  ParentCid: 02ac
32     DirBase: 08c40100  ObjectTable: e17509d8  HandleCount: 199.
33     Image: svchost.exe
34  
35 PROCESS 81cb5a98  SessionId: 0  Cid: 03c4    Peb: 7ffdd000  ParentCid: 02ac
36     DirBase: 08c40120  ObjectTable: e1aceaa0  HandleCount: 252.
37     Image: svchost.exe
38  
39 PROCESS 81c57da0  SessionId: 0  Cid: 0420    Peb: 7ffd3000  ParentCid: 02ac
40     DirBase: 08c40140  ObjectTable: e1ac9c08  HandleCount: 1137.
41     Image: svchost.exe
42  
43 PROCESS 81c7e4d8  SessionId: 0  Cid: 0460    Peb: 7ffd8000  ParentCid: 02ac
44     DirBase: 08c40160  ObjectTable: e1ae2328  HandleCount:  71.
45     Image: svchost.exe
46  
47 PROCESS 81cf98e0  SessionId: 0  Cid: 047c    Peb: 7ffdc000  ParentCid: 02ac
48     DirBase: 08c40180  ObjectTable: e16ed958  HandleCount: 203.
49     Image: svchost.exe
50  
51 PROCESS 821001c0  SessionId: 0  Cid: 05e8    Peb: 7ffd4000  ParentCid: 05c0
52     DirBase: 08c401e0  ObjectTable: e1481e00  HandleCount: 418.
53     Image: explorer.exe
54  
55 PROCESS 81fcb5e8  SessionId: 0  Cid: 0638    Peb: 7ffd9000  ParentCid: 02ac
56     DirBase: 08c40200  ObjectTable: e1524728  HandleCount: 138.
57     Image: spoolsv.exe
58  
59 PROCESS 820f4da0  SessionId: 0  Cid: 0718    Peb: 7ffd6000  ParentCid: 05e8
60     DirBase: 08c401a0  ObjectTable: e17cd9a0  HandleCount: 135.
61     Image: vmtoolsd.exe
62  
63 PROCESS 81f60c08  SessionId: 0  Cid: 0740    Peb: 7ffd9000  ParentCid: 05e8
64     DirBase: 08c40260  ObjectTable: e2046a38  HandleCount:  77.
65     Image: ctfmon.exe
66  
67 PROCESS 81c4b650  SessionId: 0  Cid: 00b4    Peb: 7ffda000  ParentCid: 02ac
68     DirBase: 08c40220  ObjectTable: e1ff58c0  HandleCount: 275.
69     Image: vmtoolsd.exe
70  
71 PROCESS 81e23a20  SessionId: 0  Cid: 040c    Peb: 7ffd3000  ParentCid: 02ac
72     DirBase: 08c40300  ObjectTable: e14b3080  HandleCount:  99.
73     Image: TPAutoConnSvc.exe
74  
75 PROCESS 8202ba80  SessionId: 0  Cid: 0694    Peb: 7ffdc000  ParentCid: 02ac
76     DirBase: 08c40320  ObjectTable: e1e1a2a8  HandleCount: 107.
77     Image: alg.exe
78  
79 PROCESS 81ac2da0  SessionId: 0  Cid: 04ec    Peb: 7ffdc000  ParentCid: 0420
80     DirBase: 08c40340  ObjectTable: e1d4f8f0  HandleCount:  39.
81     Image: wscntfy.exe
82  
83 PROCESS 8204f410  SessionId: 0  Cid: 06ec    Peb: 7ffde000  ParentCid: 040c
84     DirBase: 08c40360  ObjectTable: e1e93e40  HandleCount:  71.
85     Image: TPAutoConnect.exe
86  
87 PROCESS 81ce5650  SessionId: 0  Cid: 05b4    Peb: 7ffde000  ParentCid: 0420
88     DirBase: 08c402e0  ObjectTable: e14f6820  HandleCount: 142.
89     Image: wuauclt.exe
90  
91 PROCESS 81aed818  SessionId: 0  Cid: 01b0    Peb: 7ffda000  ParentCid: 05e8
92     DirBase: 08c40240  ObjectTable: e1a680e8  HandleCount:  60.
93     Image: ?á°????a[LCG].exe
94  
95 PROCESS 820f1340  SessionId: 0  Cid: 0254    Peb: 7ffd6000  ParentCid: 05e8
96     DirBase: 08c400a0  ObjectTable: e12ac518  HandleCount:  15.
97     Image: monitor.exe

这个命令显示了系统中所有活动进程的列表,!process命令用于显示一个或者多个进程的信息,第一个参数一般为进程ID或者是分配给进程的EPROCESS块的16进制信息。我们看看打印出的结果,DirBase这个变量表示的就是存储在CR3寄存器中的物理地址PROCESS后面的16进制数表示的是进程EPROCESS的线性地址,我们可以查看一下monitor.exe这个进程的EPROCESS和KPROCESS值:

1 kd> dt nt!_EPROCESS 820f1340 
2    +0x000 Pcb              : _KPROCESS
3    +0x06c ProcessLock      : _EX_PUSH_LOCK
4    +0x070 CreateTime       : _LARGE_INTEGER 0x1d0c299`b24158da
5    +0x078 ExitTime         : _LARGE_INTEGER 0x0
6    +0x080 RundownProtect   : _EX_RUNDOWN_REF
7    +0x084 UniqueProcessId  : 0x00000254 Void
8    +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x8055b158 - 0x81aed8a0 ]
9    +0x090 QuotaUsage       : [3] 0x848
10    +0x09c QuotaPeak        : [3] 0x870
11    +0x0a8 CommitCharge     : 0xb5
12    +0x0ac PeakVirtualSize  : 0x2168000
13    +0x0b0 VirtualSize      : 0x1a7e000
14    +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0xf8bb6014 - 0x81aed8cc ]
15    +0x0bc DebugPort        : 0x81aee880 Void
16    +0x0c0 ExceptionPort    : 0xe13e20f8 Void
17    +0x0c4 ObjectTable      : 0xe12ac518 _HANDLE_TABLE
18    +0x0c8 Token            : _EX_FAST_REF
19 kd> dt nt!_KPROCESS 820f1340 
20    +0x000 Header           : _DISPATCHER_HEADER
21    +0x010 ProfileListHead  : _LIST_ENTRY [ 0x820f1350 - 0x820f1350 ]
22    +0x018 DirectoryTableBase : [2] 0x8c400a0
23    +0x020 LdtDescriptor    : _KGDTENTRY
24    +0x028 Int21Descriptor  : _KIDTENTRY
25    +0x030 IopmOffset       : 0x20ac
26    +0x032 Iopl             : 0 ''
27    +0x033 Unused           : 0 ''
28    +0x034 ActiveProcessors : 0
29    +0x038 KernelTime       : 0x16
30    +0x03c UserTime         : 1
31    +0x040 ReadyListHead    : _LIST_ENTRY [ 0x820f1380 - 0x820f1380 ]
32    +0x048 SwapListEntry    : _SINGLE_LIST_ENTRY
33    +0x04c VdmTrapcHandler  : (null)
34    +0x050 ThreadListHead   : _LIST_ENTRY [ 0x82033b78 - 0x82033b78 ]
35    +0x058 ProcessLock      : 0
36    +0x05c Affinity         : 1
37    +0x060 StackCount       : 1
38    +0x062 BasePriority     : 8 ''
39    +0x063 ThreadQuantum    : 6 ''
40    +0x064 AutoAlignment    : 0 ''
41    +0x065 State            : 0 ''
42    +0x066 ThreadSeed       : 0 ''
43    +0x067 DisableBoost     : 0 ''
44    +0x068 PowerState       : 0 ''
45    +0x069 DisableQuantum   : 0 ''
46    +0x06a IdealNode        : 0 ''
47    +0x06b Flags            : _KEXECUTE_OPTIONS
48    +0x06b ExecuteOptions   : 0x32 '2'

dt表示的就是查看某个结构,最后的参数为要查看EPROCESS或者KPROCESS的地址,之所以称为子结构就是因为两个结构是重合的!EPROCESS的第一个字段就是KPROCESS,我们可以看见其中DirectoryTableBase的值就是DirBase的值。

好的,下面我们就来做几个实验:

实验一

分别使用手工和windbg的方式将某个虚拟地址转换为物理地址,使用windbg直接查看物理内存中的内容,随后直接修改物理地址中的内容,看看虚拟地址中的数据是否发生变化。同时使用调试工具直接定位物理内存的方法。

首先我们查看一下CR4寄存器的值,使用.formats命令可以查看到其每位的内容:

1 kd> .formats cr4
2 Evaluate expression:
3   Hex:     000006f9
4   Decimal: 1785
5   Octal:   00000003371
6   Binary:  00000000 00000000 00000110 11111001
7   Chars:   ....
8   Time:    Thu Jan 01 08:29:45 1970
9   Float:   low 2.50132e-042 high 0
10   Double:  8.81907e-321

我们可以看到,这里第5位为1,表示开启PAE分页,我们用OD附加我们经常使用的monitor.exe这个程序,加载sys时候经常用到,这里入口如下:

我们可以看到虚拟地址为0041F69A,这个也就是线性地址了,在windows下,那么我们如何得到其物理地址呢?按照上面的PDPT转换过程,首先我们找到进程页面的物理地址:

1 kd> !process 820f1340 
2 PROCESS 820f1340  SessionId: 0  Cid: 0254    Peb: 7ffd6000  ParentCid: 05e8
3     DirBase: 08c400a0  ObjectTable: e12ac518  HandleCount:  15.
4     Image: monitor.exe
5     VadRoot 81b99200 Vads 53 Clone 0 Private 114. Modified 0. Locked 0.
6     DeviceMap e152fe18
7     Token                             e10c7568
8 ........................................................................................

1.转换线性地址并得到其信息
这里我们可以看到,页目录指针表物理地址为08c400a0,我们来将0041F69A这个虚拟地址进行分解:(00 000000010 000011111 011010011010)
得到了线性地址的四个部分,PDPT页目录指针索引号为0,9位页目录索引为2(000000010),9位页表索引为31(000011111),12位物理页偏移量为1690(011010011010)。

2.定位页目录指针表并获取页目录表物理页地址
我们在dd命令前加上一个!表示后面的参数是物理地址,即查看物理地址中的内容:

1 kd> !dd 08c400a0 
2 # 8c400a0 0d21e001 00000000 1ae9f001 00000000
3 # 8c400b0 12be0001 00000000 00c5d001 00000000
4 # 8c400c0 0a266001 00000000 0a267001 00000000
5 # 8c400d0 0a268001 00000000 0a265001 00000000
6 # 8c400e0 0a8b2001 00000000 0a8b3001 00000000
7 # 8c400f0 0a8b4001 00000000 0a8b1001 00000000
8 # 8c40100 0aa82001 00000000 0aa83001 00000000
9 # 8c40110 0aa44001 00000000 0aa81001 00000000

由于我们的页目录指针索引号为0,所以页目录的地址为0d21e001,但是我们只用到了其中的20位,所以页目录表物理地址的首地址为0d21e000

3.定位页表

由于页目录索引为2,而且开启PAE后的PDE大小为64位,即8个字节:

1 kd> !dd 0d21e000 + 0x2 * 8
2 # d21e010 149ed067 00000000 14333067 00000000
3 # d21e020 1c182067 00000000 00000000 00000000
4 # d21e030 00000000 00000000 00000000 00000000
5 # d21e040 0f719067 00000000 00000000 00000000
6 # d21e050 00000000 00000000 00000000 00000000
7 # d21e060 00000000 00000000 00000000 00000000
8 # d21e070 00000000 00000000 00000000 00000000
9 # d21e080 00000000 00000000 00000000 00000000

由此得到页表项的地址为149ed067,同理只用了后20位,所以页表项的物理地址为149ed000

4.定位物理页面

页表索引为31,所以这里为:

1 kd> !dd 149ed000 + 0x1f * 8
2 #149ed0f8 1fb18025 00000000 1bc68025 00000000
3 #149ed108 1be29025 00000000 1bbaa025 00000000
4 #149ed118 1bcab025 00000000 1bbec025 00000000
5 #149ed128 1bf2d025 00000000 1c1ae025 00000000
6 #149ed138 1b914025 00000000 1b855025 00000000
7 #149ed148 1b996025 00000000 1b857025 00000000
8 #149ed158 1ba18025 00000000 1b9d9025 00000000
9 #149ed168 1b95a025 00000000 1bbdb025 00000000

所以物理页面的起始地址为1fb18000,最终的值再加上物理偏移1690就得到了物理地址:

1 kd> !db 1fb18000 + 69A
2 #1fb1869a 55 8b ec 6a ff 68 60 7e-44 00 68 f8 f7 41 00 64 U..j.h`~D.h..A.d
3 #1fb186aa a1 00 00 00 00 50 64 89-25 00 00 00 00 83 ec 68 .....Pd.%......h
4 #1fb186ba 53 56 57 89 65 e8 33 db-89 5d fc 6a 02 ff 15 88 SVW.e.3..].j....
5 #1fb186ca 46 44 00 59 83 0d c4 01-45 00 ff 83 0d c8 01 45 FD.Y....E......E
6 #1fb186da 00 ff ff 15 8c 46 44 00-8b 0d 58 fc 44 00 89 08 .....FD...X.D...
7 #1fb186ea ff 15 90 46 44 00 8b 0d-54 fc 44 00 89 08 a1 94 ...FD...T.D.....
8 #1fb186fa 46 44 00 8b 00 a3 c0 01-45 00 e8 2e 01 00 00 39 FD......E......9
9 #1fb1870a 1d b8 ef 44 00 75 0c 68-34 f8 41 00 ff 15 98 46 ...D.u.h4.A....F

我们看到,真正的物理地址为1fb1869a,这里存放值和我们通过OD中查看的虚拟地址0041F69A中的代码数据完全相同!我们就通过这一复杂的运算关系将物理地址算出来了!实在不容易啊,终于看清庐山真面目了,每次看到虚拟地址都有一种被欺骗的感觉。。。。。。

好的,下面我们就来尝试直接修改物理内存中的数据,如果修改后,不出意外,我们回到OD会发现其中的数据被更改了,使用e指令对指定地址进行修改,后面的bd表示按照多大进行修改,b为byte,d为dword

1 kd> !ed 1fb1869a 90909090
2 kd> !ed 1fb1869e 90909090
3 kd> !ed 1fb186a2 90909090
4 kd> !ed 1fb186a6 90909090
5 kd> !ed 1fb186aa 90909090
6 kd> !db 1fb18000 + 69A
7 #1fb1869a 90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90 ................
8 #1fb186aa 90 90 90 90 00 50 64 89-25 00 00 00 00 83 ec 68 .....Pd.%......h
9 #1fb186ba 53 56 57 89 65 e8 33 db-89 5d fc 6a 02 ff 15 88 SVW.e.3..].j....
10 #1fb186ca 46 44 00 59 83 0d c4 01-45 00 ff 83 0d c8 01 45 FD.Y....E......E
11 #1fb186da 00 ff ff 15 8c 46 44 00-8b 0d 58 fc 44 00 89 08 .....FD...X.D...
12 #1fb186ea ff 15 90 46 44 00 8b 0d-54 fc 44 00 89 08 a1 94 ...FD...T.D.....
13 #1fb186fa 46 44 00 8b 00 a3 c0 01-45 00 e8 2e 01 00 00 39 FD......E......9
14 #1fb1870a 1d b8 ef 44 00 75 0c 68-34 f8 41 00 ff 15 98 46 ...D.u.h4.A....F

我们恢复系统运行,发现果然od中的内容变了!看来此区的物理内存真的就是由monitor.exe这个进程映射过来的。

如何直接在调试器中算出物理地址呢?这里我们可以使用!pte命令,参数为要转换的虚拟地址或者叫线性地址,当然这个还有一个前提就是我们必须把当前调试器进程切换到要调试的进程上,否则转换会失败:

1 kd> !process
2 PROCESS 805539a0  SessionId: none  Cid: 0000    Peb: 00000000  ParentCid: 0000
3     DirBase: 00b18000  ObjectTable: e1000c98  HandleCount: 285.
4     Image: Idle
5     VadRoot 00000000 Vads 0 Clone 0 Private 0. Modified 0. Locked 0.
6     DeviceMap 00000000
7     Token                             e10017c8
8     ElapsedTime                       00:00:00.000
9     UserTime                          00:00:00.000
10     KernelTime                        05:45:22.234
11     QuotaPoolUsage[PagedPool]         0
12     QuotaPoolUsage[NonPagedPool]      0
13     Working Set Sizes (now,min,max)  (7, 50, 450) (28KB, 200KB, 1800KB)
14     PeakWorkingSetSize                0
15     VirtualSize                       0 Mb
16     PeakVirtualSize                   0 Mb
17     PageFaultCount                    0
18     MemoryPriority                    BACKGROUND
19     BasePriority                      0
20     CommitCharge                      0
21  
22         THREAD 80553740  Cid 0000.0000  Teb: 00000000 Win32Thread: 00000000 RUNNING on processor 0

使用!process命令查看当前调试器附加的进程,这里我们为0号进程,我们要得到虚拟地址必须切换要调试的用户态进程:

1 kd> .process /i 820f1340 
2 You need to continue execution (press 'g' <enter>) for the context
3 to be switched. When the debugger breaks in again, you will be in
4 the new process context.
5 kd> g
6 Break instruction exception - code 80000003 (first chance)
7 nt!RtlpBreakWithStatusInstruction:
8 80528bdc cc              int     3
9 kd> !process
10 PROCESS 820f1340  SessionId: 0  Cid: 0254    Peb: 7ffd6000  ParentCid: 05e8
11     DirBase: 08c400a0  ObjectTable: e12ac518  HandleCount:  15.
12     Image: monitor.exe
13     VadRoot 81b99200 Vads 53 Clone 0 Private 114. Modified 0. Locked 0.
14     DeviceMap e152fe18
15     Token                             e10c7568
16     ElapsedTime                       04:05:48.484
17     UserTime                          00:00:00.015
18     KernelTime                        00:00:00.562
19     QuotaPoolUsage[PagedPool]         53156
20     QuotaPoolUsage[NonPagedPool]      2120
21     Working Set Sizes (now,min,max)  (746, 50, 345) (2984KB, 200KB, 1380KB)
22     PeakWorkingSetSize                746
23     VirtualSize                       26 Mb
24     PeakVirtualSize                   33 Mb
25     PageFaultCount                    759
26     MemoryPriority                    BACKGROUND
27     BasePriority                      8
28     CommitCharge                      181
29     DebugPort                         81aee880
30  
31         THREAD 820339c8  Cid 0254.07e0  Teb: 7ffdf000 Win32Thread: e2076710 WAIT: (Executive) KernelMode Non-Alertable
32             b291b7d4  SynchronizationEvent

先使用.process /i + EPROCESS地址 开始切换进程,这里/i后面的参数就是之前说的EPROCESS地址,然后按g运行调试器,当再次断下来的时候我们发现当前进程环境就切换到目标进程中了。

1 kd> !pte 0041f69a
2                     VA 0041f69a
3 PDE at C0600010            PTE at C00020F8
4 contains 00000000149ED067  contains 000000001FB18025
5 pfn 149ed     ---DA--UWEV   pfn 1fb18     ----A--UREV

这里我们看到直接可以通过!pte命令得到物理内存中的对应页和PFN(1fb18),然后我们将contain的值结尾变成0后再加上之前算出的偏移地址就得到了物理地址。

实验二

用OD加载两个不同的进程,将第一个程序的页目录基址改为第二个程序的页目录基址,再通过OD修改第一个进程中的数据,观察第二个进程中的数据是否会发生变化。

这里我们加载两个程序,一个是DbgView.exe还有一个是Monitor.exe,两个不同的进程,先查看这两个进程:

1 PROCESS 81e16020  SessionId: 0  Cid: 0574    Peb: 7ffd6000  ParentCid: 05e8
2     DirBase: 08c40280  ObjectTable: e10b3448  HandleCount:  21.
3     Image: Dbgview.exe
4  
5 PROCESS 8209bda0  SessionId: 0  Cid: 0578    Peb: 7ffde000  ParentCid: 05e8
6     DirBase: 08c402c0  ObjectTable: e218d218  HandleCount:  15.
7     Image: monitor.exe

如果我希望两个进程空间打通的话,直接将Dbview.exe的DirBase改成monitor.exe就可以了,我们试试看:

1 kd> dt nt!_kPROCESS 81e16020 
2    +0x000 Header           : _DISPATCHER_HEADER
3    +0x010 ProfileListHead  : _LIST_ENTRY [ 0x81e16030 - 0x81e16030 ]
4    +0x018 DirectoryTableBase : [2] 0x8c40280
5    +0x020 LdtDescriptor    : _KGDTENTRY
6    +0x028 Int21Descriptor  : _KIDTENTRY
7    +0x030 IopmOffset       : 0x20ac
8    +0x032 Iopl             : 0 ''
9    +0x033 Unused           : 0 ''
10    +0x034 ActiveProcessors : 0
11    +0x038 KernelTime       : 0x42
12    +0x03c UserTime         : 2
13    +0x040 ReadyListHead    : _LIST_ENTRY [ 0x81e16060 - 0x81e16060 ]
14    +0x048 SwapListEntry    : _SINGLE_LIST_ENTRY
15    +0x04c VdmTrapcHandler  : (null)
16    +0x050 ThreadListHead   : _LIST_ENTRY [ 0x820a2c30 - 0x820a2c30 ]
17    +0x058 ProcessLock      : 0
18    +0x05c Affinity         : 1
19    +0x060 StackCount       : 1
20    +0x062 BasePriority     : 8 ''
21    +0x063 ThreadQuantum    : 6 ''
22    +0x064 AutoAlignment    : 0 ''
23    +0x065 State            : 0 ''
24    +0x066 ThreadSeed       : 0 ''
25    +0x067 DisableBoost     : 0 ''
26    +0x068 PowerState       : 0 ''
27    +0x069 DisableQuantum   : 0 ''
28    +0x06a IdealNode        : 0 ''
29    +0x06b Flags            : _KEXECUTE_OPTIONS
30    +0x06b ExecuteOptions   : 0x32 '2'
31 kd> ed 81e16020 + 0x18 08c402c0 
32 kd> dt nt!_kPROCESS 81e16020 
33    +0x000 Header           : _DISPATCHER_HEADER
34    +0x010 ProfileListHead  : _LIST_ENTRY [ 0x81e16030 - 0x81e16030 ]
35    +0x018 DirectoryTableBase : [2] 0x8c402c0
36    +0x020 LdtDescriptor    : _KGDTENTRY
37    +0x028 Int21Descriptor  : _KIDTENTRY
38    +0x030 IopmOffset       : 0x20ac
39    +0x032 Iopl             : 0 ''
40    +0x033 Unused           : 0 ''
41    +0x034 ActiveProcessors : 0
42    +0x038 KernelTime       : 0x42
43    +0x03c UserTime         : 2
44    +0x040 ReadyListHead    : _LIST_ENTRY [ 0x81e16060 - 0x81e16060 ]
45    +0x048 SwapListEntry    : _SINGLE_LIST_ENTRY
46    +0x04c VdmTrapcHandler  : (null)
47    +0x050 ThreadListHead   : _LIST_ENTRY [ 0x820a2c30 - 0x820a2c30 ]
48    +0x058 ProcessLock      : 0
49    +0x05c Affinity         : 1
50    +0x060 StackCount       : 1
51    +0x062 BasePriority     : 8 ''
52    +0x063 ThreadQuantum    : 6 ''
53    +0x064 AutoAlignment    : 0 ''
54    +0x065 State            : 0 ''
55    +0x066 ThreadSeed       : 0 ''
56    +0x067 DisableBoost     : 0 ''
57    +0x068 PowerState       : 0 ''
58    +0x069 DisableQuantum   : 0 ''
59    +0x06a IdealNode        : 0 ''
60    +0x06b Flags            : _KEXECUTE_OPTIONS
61    +0x06b ExecuteOptions   : 0x32 '2'

这样我们就成功的将两个进程的DirBase设为相同的值,然后回到od,点击一下DbgView所在调试器的窗口,瞬间看到值发生了变化:

两个进程指向的空间相同了哈,而且左边的又一点粘滞的状态。。这时候我们在左边修改一下指令,下面就是见证奇迹的时刻:

两个进程占用了同一个物理空间!关闭调试器后系统立马蓝屏,wow!

通过以上的几个实验,我们已经比较详细的了解了Windows在保护机制下是如何将进程间的空间进行隔离的,相信你对系统也有了 更加深刻的理解,当然有部分是我自己的理解,如果有不对之处还请各位大牛指正!

深入了解Intel保护模式相关推荐

  1. Intel保护模式下的保护机制,Descriptor Fields Used for Protection

    摘自<Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes1, 2A, 2B, 2C, 2 ...

  2. X86汇编语言从实模式到保护模式18:中断和异常的处理与抢占式多任务

    目录 1. 中断和异常概述 1.1 中断的分类 1.1.1 中断(Interrupt) 1.1.2 异常(Exception) 1.2 异常的分类 1.2.1 按异常的来源分类 1.2.2 按异常的性 ...

  3. IA-32 Intel手册学习笔记(二)保护模式下的内存管理

    内存管理概述(Memory Management Overview) Inter体系结构的内存管理可分为两部分:分段和分页. 分段提供了一种机制,这种机制可以为每个程序或者任务提供单独的代码.数据和栈 ...

  4. ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配...

    第16章讲的是分页机制和动态页面分配的问题,说实话这个一开始接触是会把人绕晕的,但是这个的确太重要了,有了分页机制内存管理就变得很简单,而且能直接实现平坦模式. ★PART1:Intel X86基础分 ...

  5. x86汇编语言从实模式百度云_Intel x86 CPU 32位保护模式杂谈之任务切换 上

    目录: 什么是任务 任务由什么组成 任务门描述符是什么东东?有了TSS描述符为什么要有任务门描述符? 参考文献 什么是任务 任务(task)是处理器可以分配.执行.挂起的工作单位,笔者认为和我们操作系 ...

  6. IA-32系统编程指南 - 第三章 保护模式的内存管理【1】

    第三章 保护模式的内存管理[1] [作者:lion3875 原创文章 参考文献<Intel 64 and IA-32 system programming guide>] IA-32保护模 ...

  7. x86CPU 实模式 保护模式 傻傻分不清楚? 基于Xv6-OS 分析CR0 寄存器

    基于Xv6-OS 分析CR0 寄存器 之前一直认为晕乎乎的...啥?什么时候切换real model,怎么切换,为什么要切换? ------------------------------------ ...

  8. ASM:《X86汇编语言-从实模式到保护模式》第10章:32位x86处理器的编程架构

    ★PART1:32位的x86处理器执行方式和架构 1. 寄存器的拓展(IA-32) 从80386开始,处理器内的寄存器从16位拓展到32位,命名其实就是在前面加上e(Extend)就好了,8个通用寄存 ...

  9. 特权级——保护模式的特权级检查 DPL,RPL,CPL, 一致代码段,非一致代码段

    特权级是保护模式下一个重要的概念,CPL,RPL和DPL是其中的核心概念,查阅资料无数,总结如下. 一.CPL.RPL.DPL简单解释     CPL是当前进程的权限级别(Current Privil ...

最新文章

  1. 适用于SQL Server生产环境DBA的七大技巧
  2. 数据库 ' 库名' 已打开,并且一次只能有一个用户访问。 (Microsoft SQL Server,错误: 924)
  3. php common errors
  4. Thinkpad R400 a16驱动安装笔记
  5. 有趣的c语言程序Code,一个有趣的小程序
  6. 五年级计算机学情分析,2015—2016学年小学五年级信息技术上册教学计划
  7. node.js-------使用路由模块
  8. jquery --- 收缩兄弟元素
  9. jmeterhttp代理服务器_Jmeter使用HTTP代理服务器录制
  10. Jquery基础知识
  11. 配置过程中的一些问题
  12. RTT内核对象——对象理解
  13. java 线程的理解_Java多线程基础理解
  14. Nacos系列:Nacos的三种部署模式 1
  15. Spring的DI(Ioc) - 利用构造器注入
  16. 基于Lumerical fdtd的超透镜设计(介质天线结构和金属谐振结构)
  17. Word论文排版技能积累
  18. 为什么要读“无用”的古文
  19. Centos7 Samba Win10配置解决问题【全攻略】:你没有权限访问,请与管理员联系。。。
  20. xp系统链接不上宽带连接服务器,XP系统网络连接正常却上不了网怎么解决

热门文章

  1. JVM内存状况查看方法和分析工具
  2. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析
  3. 物理机与虚拟机IP互ping通,而互ping主机名不通
  4. 如何自动以管理员身份运行.NET程序?
  5. Session 存储方式
  6. 再谈 iptables 防火墙的 指令配置
  7. Nginx 反向代理工作原理简介与配置详解
  8. Spring Cloud Zuul
  9. unity 优秀开源项目
  10. Apache支持多端口配置处理