首页
论坛
课程
招聘
[原创]详解Windows内存分页机制
2011-6-11 19:18 36958

[原创]详解Windows内存分页机制

2011-6-11 19:18
36958
偶的博客: http://hi.baidu.com/hu3167343 大家支持下,谢谢了.

昨天新买了两本书, 看到了内存分页部分, 特此记录下, 没什么技术含量, 错误之处还请大牛指点.

大多数现代的操作系统都支持虚存, 这使得系统上的每个程序都拥有自己的地址空间. 每当程序读取内存时, 都必须指定一个地址. 对于每个进程, 该地址必须转换为实际的物理内存地址.
例如, 若我们的MzfHips.exe 程序需要的数据在虚拟内存地址的0x0041FF10处, 则实际的物理地址可能被映射为0x01EE2F10.
如下图:

图1: 虚拟地址转物理地址示意图 (图中的数据仅仅是为了演示)

内存地址的转换主要通过一个称为页表目录的特殊表来完成. Intel x86 CPU将页表目录的指针存储在特殊寄存器CR3中. 该寄存器指向一个包含1024个32位值的数组, 称为页目录. 每个数组元素称为页目录项, 它指定了页表在物理内存中的基地址, 还通过状态位指示该页表当前是否存在于内存中. 从页表中可以获得实际的物理地址.

图2: 在内存中寻找页面

下面我们来看下虚拟地址的组成:

由图可知, 虚拟地址的前10位用来定位页目录项, 中间10位用来定位页表项, 最后12位得到具体物理地址的偏移.

到了这里, 我们来总结下具体的步骤:
1.        CPU查询CR3寄存器以找到页表目录的基地址
2.        操作系统根据所请求的虚拟地址的前10位(如图3), 来定位页目录项, 从而在内存中找到相应的页表.
3.        页表根据中间的10位定位该页相应的物理内存首地址
4.        根据虚拟地址的后12位得到具体的物理地址相对于首地址的偏移量.
5.        最后得到的物理地址即包含我们要请求的数据

知道了前面的知识, 下面我们来看下具体的页目录项和页表项中的内容.
由前面我们知道, CR3寄存器指向页目录的首地址, 而页目录是由最多1024个可能的页目录项组成.
下面我们看下页目录项的地址组成:

当访问页目录项时, 要检查U位(第2位), 若U位为0, 则意味着正在处理的页表只能用于内核.
还要检查W位(第1位), 若W位为0, 则内存是只读的.
页目录项指向整个页表, 即整个页面集合, 因此, 页目录项中的设置应用于整个内存页范围.

下面来看下页表项:

当访问页表项时, 仍然首先要检查U位(第2位), 若U位为0, 则只有内核模式的程序才能够访问该内存页.
还要检查W位(第1位), 以判断读写访问权限.
最后还要判断P位(第0位), 若它设置为0, 则当前内存页被换出在磁盘上. 若它设置为1, 则内存是常驻, 并且当前是可用的.
在将内存页换出后, 内存管理器在成功访问它之前必须将该页换入内存.

还有最后一个问题就是, 系统上的大多数可执行程序的其实地址都是0x00400000. 多个进程如何能使用同一个虚拟地址, 而不会在物理内存中发生冲突?那是因为系统上的每个进程都维护一个独立的页目录, 都拥有自己私有的CR3寄存器的值.
当线程发生切换时, 旧线程的状态会被保存起来. 若当前调度运行的线程不属于刚才的进程, 则当前进程的页目录地址会被加载到CR3寄存器中. 页目录地址可以在进程的KPROCESS结构中找到.

分析下MmIsAddressValid函数, 加深下对分页机制的了解.(参考combojiang大叔)
下面是ida 看到的ntoskrnl.exe中导出的MmIsAddressValid。
.text:0040C661 ; BOOLEAN __stdcall MmIsAddressValid(PVOID VirtualAddress)
.text:0040C661                 public MmIsAddressValid
.text:0040C661 MmIsAddressValid proc near              ; CODE XREF: sub_40D65E+Cp
.text:0040C661                                         ; sub_415459:loc_415470p ...
.text:0040C661
.text:0040C661 VirtualAddress  = dword ptr  8
.text:0040C661
.text:0040C661 ; FUNCTION CHUNK AT .text:0041B84E SIZE 00000007 BYTES
.text:0040C661 ; FUNCTION CHUNK AT .text:0044A4F2 SIZE 00000019 BYTES
.text:0040C661
.text:0040C661                 mov     edi, edi
.text:0040C663                 push    ebp
.text:0040C664                 mov     ebp, esp
.text:0040C666                 mov     ecx, [ebp+VirtualAddress] ;取出虚拟地址
.text:0040C669                 mov     eax, ecx
.text:0040C66B                 shr     eax, 14h        ; 右移20位
.text:0040C66E                 mov     edx, 0FFCh      ; 取高10位
.text:0040C673                 and     eax, edx
.text:0040C675                 sub     eax, 3FD00000h  ; 加上0xc0300000
.text:0040C675                                         ; PDE = ((VA >> 22) << 2 )& 0xffc + 0xc0300000
.text:0040C67A                 mov     eax, [eax]
.text:0040C67C                 test    al, 1           ; 页目录项的第0位, 即P位
.text:0040C67E                 jz      loc_41B84E
.text:0040C684                 test    al, al
.text:0040C686                 js      short loc_40C6AC ; 判断page size位
.text:0040C688                 shr     ecx, 0Ah        ; 右移10位
.text:0040C68B                 and     ecx, 3FFFFCh
.text:0040C691                 sub     ecx, 40000000h
.text:0040C697                 mov     eax, ecx        ; PTE = ((VA >> 12) << 2 ) & 0x3FFFC + 0xc0000000
.text:0040C699                 mov     ecx, [eax]      ; ecx = PTE Context
.text:0040C69B                 test    cl, 1           ; 判断present位
.text:0040C69E                 jz      loc_41B84E
.text:0040C6A4                 test    cl, cl
.text:0040C6A6                 js      loc_44A4F2      ; 判断page size位
.text:0040C6AC
.text:0040C6AC loc_40C6AC:                             ; CODE XREF: MmIsAddressValid+25j
.text:0040C6AC                                         ; MmIsAddressValid+3DE9Fj
.text:0040C6AC                 mov     al, 1
.text:0040C6AE
.text:0040C6AE loc_40C6AE:                             ; CODE XREF: MmIsAddressValid+F1EFj
.text:0040C6AE                 pop     ebp
.text:0040C6AF                 retn    4
.text:0040C6AF MmIsAddressValid endp
.text:0040C6AF

Ps:上面说的理论是在未开启PAE模式下的一般情况, 在开启了PAE的情况下, 可能会有所不同。

[公告] 欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (23)
雪    币: 365
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
zycxy 活跃值 2011-6-11 19:37
2
0
你的PDE在C0600000,说明开启的是PAE分页。
雪    币: 781
活跃值: 活跃值 (493)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-6-11 19:49
3
0
恩, 自己的电脑上是开启了, 在分析MmIsAddressValid函数时可以看出, 但是从MmIsAddressValid函数中看分页机制没有太多的影响。
我说的差别是前面的理论方面可能会有差异。
比如说开启了PAE, CR3寄存器就不指向页目录首地址了。
雪    币: 174
活跃值: 活跃值 (23)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
zouzhiyong 活跃值 3 2011-6-11 20:01
4
1
貌似combojiang大侠在rootkit专题中已经分析过~
这里是他分析的详细过程~
http://bbs.pediy.com/showthread.php?t=61327
雪    币: 781
活跃值: 活跃值 (493)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-6-11 20:08
5
0
我擦, 写了一个下午的东西。 居然有了。
不过大叔的高深点,我的小白点, 哈哈。 还是有点差别的啦,
雪    币: 365
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
zycxy 活跃值 2011-6-11 20:26
6
0
还是有差别的,比方说开启PAE后PDE共4*4kb,每项8bytes。而未开启只有4kb,每项4bytes。所以才右移18位再和3ff8做and运算。未开启PAE则不是这样的。都从代码中反映出来了。
雪    币: 781
活跃值: 活跃值 (493)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-6-11 20:30
7
0
原来如此, 学到了.
雪    币: 49
活跃值: 活跃值 (16)
能力值: ( LV15,RANK:520 )
在线值:
发帖
回帖
粉丝
邓韬 活跃值 9 2011-6-11 21:36
8
0
顶起,谢谢你的学习笔记了
雪    币: 71
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mumaren 活跃值 2011-6-11 22:00
9
0
学习了

谢谢学习心得
雪    币: 781
活跃值: 活跃值 (493)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-6-11 22:43
10
0
惊现韬哥.
雪    币: 564
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lixupeng 活跃值 2011-6-11 22:54
11
0
精彩收下!!!
雪    币: 182
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
justlovemm 活跃值 2011-6-12 15:58
12
0
看了楼主的博客,想请教楼主一个问题。
楼主在博客里面写了些winsock的文章,想问一下楼主,考虑过一个TCP的client端,就是连接发起端,使用winsock,不用connect和WSAConnect能实现连接吗?
我手头有个.net的程序,已经确认是TCP连接,winsock的其它函数都能截获,就是找不到对connect或者WSAConnect的调用,难道.net的程序用了什么其它的连接函数吗?
雪    币: 781
活跃值: 活跃值 (493)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-6-12 16:28
13
0
其实对于winsock我也只是学了个皮毛.

对应于connect函数在内核TDI驱动中对应的IRP应该是主功能号为IRP_MJ_INTERNAL_DEVICE_CONTROL. 而次功能号为TDI_CONNECT的例程.
此IRP在MSDN中说明如下:
When a kernel-mode client makes a TDI_CONNECT request, it asks the underlying TDI transport driver to offer a connection on a particular local-node connection endpoint to a remote-node peer.
大意应该是此IRP的产生仅仅发生在本地节点连接远程节点时才会发生. 也就是说外界连接本地, 这个请求并不会发生.

我想兄弟在实验时是在本地主机上连接client和server程序, 所以connect才不会发生.
你可以试下你的client程序连接下远程的主机看能不能截获这个请求.
或者兄弟可以直接选个使用TCP连接的网络程序验证一下, 看能不能截获connect请求即可.

ps.以上只是我个人的理解, 不一定正确. 我对于网络这方面不是很了解. 如有错误, 还请大牛指正.
雪    币: 473
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
comewisdom 活跃值 2011-6-12 16:32
14
0
学习学习学习
雪    币: 182
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
justlovemm 活跃值 2011-6-12 22:50
15
0
大牛就是大牛啊!
楼主说的太深了,我的工作都是在ring 3下做的,这儿是已经确认了那个程序向一个公网的IP地址进行了连接,绝对不是本地地址,通过netstat已经能很明确的知道这一点。另外不管是用OD调试还是自己写API hook,send,WSARecv都能断到或者截获到,但就是无法断到connect和WSAConnect,这个是让我感觉很奇怪的一个事情。
另外我考虑,就是.net的编译器也不会跳过常规api而去直接调用ring 0的代码啊。
难道在ring 3下除了connect和WSAConnect,还有其它的API能实现TCP连接的功能吗?
雪    币: 781
活跃值: 活跃值 (493)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
莫灰灰 活跃值 9 2011-6-13 17:27
16
0
那我就不清楚了, 学艺不精啊!
各路大牛帮忙解答~
雪    币: 182
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
justlovemm 活跃值 2011-6-15 23:53
17
0
多谢了!
不过楼主这么谦虚,我再请教一个问题。
如果按楼主指导的,用windbg在ring0断到"connect函数在内核TDI驱动中对应的IRP应该是主功能号为IRP_MJ_INTERNAL_DEVICE_CONTROL. 而次功能号为TDI_CONNECT的例程",在这个时候能否查看对应线程在ring3的栈,如果可以的话,这时顺着栈向上找,应该能发现那个调用是怎么实现的。
雪    币: 122
活跃值: 活跃值 (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
butian 活跃值 2011-6-16 00:06
18
0
学习中,多谢!
雪    币: 55
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xynzhaocg 活跃值 2011-6-16 00:08
19
0
学习啦
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mytemp 活跃值 2011-6-17 15:48
20
0
分页机制与分段GDT LDT IDT
雪    币: 24
活跃值: 活跃值 (12)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
bighacker 活跃值 2011-6-18 20:07
21
0
兄弟解决了就发出来, 我也很好奇.
雪    币: 0
活跃值: 活跃值 (24)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
tihty 活跃值 2 2011-6-18 20:29
22
0
向各位学习 :)
雪    币: 200
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lynn冲动 活跃值 2019-12-4 18:08
23
0
感谢分享,学习了。
雪    币: 72
活跃值: 活跃值 (413)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
killpy 活跃值 2 2019-12-4 23:25
24
0
游客
登录 | 注册 方可回帖
返回