首页
社区
课程
招聘
[原创]详解Windows内存分页机制
发表于: 2011-6-11 19:18 40552

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

2011-6-11 19:18
40552

偶的博客: 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的情况下, 可能会有所不同。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

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

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