登录  | 立即注册

游客您好!登录后享受更多精彩

查看: 46|回复: 0

WindowsX86内核09.物理地址扩展及查表

[复制链接]

71

主题

2

回帖

147

积分

管理员

积分
147
发表于 2024-12-10 22:09:02 | 显示全部楼层 |阅读模式
1663572681933-17e4d347-c3ff-49cc-bbe5-5d3268127df3.png
PG(CR0 31) 分页标志 1
PSE(CR4 4) 页大小扩展 1
PAE(CR4 5) 物理地址扩展 1
PS(PDE 7) 大分页 1
1663572938746-7e0a189a-e839-41ce-8333-7f6fae14c761.png
1663572754070-170113de-199e-446e-b8a5-0206918bec9e-171421536246815.png
如何内存足够大一个页4M是合理的
如何我们把一个页设为4M对于做表来说跟之前相比,标的数量减少了,偏移也变了(4K的偏移最大是4096 ,而4M的偏移是 4M)
4M页分页情况
1663574233357-65958270-e9bd-47e9-b1cb-6aafadc70f7e.png
跟4K的相比 偏移变成了22位,没有了页表了,因为不需要了
低22位当偏移 高10位当做用户 页目录表下标,页目录表的每一个下标都可以当做4M的内存,一共 1024项
1024 * 4M = 1024 * 4 * 1024 * 1024 = 4G 刚好4G,因此只需要一个页目录表 (1024项) 就可以内存映射完,不需要页表
但是把所有页都弄成 4M 有点浪费,因为太大了,所以我们希望有一种折中的方案,即需要大的页就给大的页,不需要就给4K
完成这个设计就是 靠 PS 位 (0就是4K,1就是4M),他先查ps位,如果为0,那么就按4k的页去查页表,如果为1,他就不会去查页表,直接把页首地址+22位偏移当做物理地址,直接看最后一位是否大于8就可以,因为 ps位在第7位,最低字节的最高位
4M的页一般是给操作系统,3环地址一般给4K
windbg 和已通过 .formats 把一个16进制数字拆成二进制显示
物理地址扩展
当时CPU是32位,他能访问的最大内存是4G,但是在那个年代,内存的发展速度是高于CPU的,因此很多人的内存可能超过了4G,因此cpu需要想办法去支持超过 4个G 的内存
解决办法:
1发布64位的 CPU
但是当时 64位 成本较高 而且对于大多少人来说可能都用不上,所以把cpu升级为64位在当时没必要
2加4根地址线 奔腾CPU 这样物理地址最大就变成了 2^36 = 64 G 了,但是寄存器还是32位
因此我们需要将32位线性地址转成为 36位的物理地址,那怎么转换是一个问题,在16位 转 20位 的年代是通过段寄存器补上了4位
我们只需要把页目录表的首地址改成24位就可以了(之前是20位),但是之前页表和页目录表容不下这四位,而且要兼容以前的的,所以只能扩展 ,四字节扩展成8字节 ,目前用不到的就保留
因为CPU换了,所以操作系统得要重写
一个分页 要么是 4K ,要么是 2M 要么是混用,微软是喜欢混用, 因此在 在 windows系统上,一个页是4K,这个说法是不准确的,但是有的时候说的是3环的内存空间,这是准确的
4K查表
1663577573679-699993fa-6351-400c-9112-1b579788d93b.png
1663578893082-e8cc48d5-0e05-4f53-90a3-879ba14ab64d.png
页目录表和页表解析
原先 :
PDE(页目录表) 1024项 10位
PTE (页表) 1024项 10位
全部填满刚好 4G
现在:
PDE(页目录表) 512项 8字节 (因为原先4自己,变成8字节,所以每项大小增加了一倍,所以项数减少一倍)
PTE (页表) 512项 8字节 (因为原先4自己,变成8字节,所以每项大小增加了一倍,所以项数减少一倍)
所以此时只能 映射 1G 内存,但是一个进程需要4个G内存.因此需要4个页目录表
因此加了一个表 PDPTE 页目录指针表 2位
原来的下标是20位 ,所以刚好拆成
PDPTE 页目录指针表 2位
PDE 页目录表 9位
PTE 页表 9 位
=> 从CR3 拿出 页目录指针表 再拿出 页目录表 再拿出 页表
例如: 我们查 虚拟地址 0x807d3c20 对应的物理地址 CR3 = 185000 先拆成 20 + 12位 => 807d3 c20 所有 offser = c20 807d3 => 10 000000011 111010011
=> PDPTE = 2 => PDE = 3 => PTE = 1d3
kd> !dq 185000 + 2 * 8 CR3+ PDPE * 8 # 185010 0000000000188001 0000000000189001 # 185020 0000000000000000 0000000000000000 # 185030 0000000000000000 0000000000000000 # 185040 0000000000000000 0000000000000000 # 185050 0000000000000000 0000000000000000 # 185060 0000000000000000 0000000000000000 # 185070 0000000000000000 0000000000000000 # 185080 0000000000000000 0000000000000000 取出24位(第12 - 35位),然后低12位再补3个0,就得到了页目录表的地址 000188000 ==> 页目录表的地址(36位的) 页目录表在第3项 !dq 00188000 + 0x3 * 8 kd> !dq 00188000 + 0x3 * 8 上面结果 取第35-12位(低12位补0) * PDE * 8 # 188018 000000003cfc6863 0000000000192063 # 188028 0000000000193063 000000003cf42863 # 188038 0000000000000000 00000000001c2063 # 188048 00000000001c3063 00000000001c4063 # 188058 00000000001c5063 00000000001c6063 # 188068 00000000001c7063 00000000001c8063 # 188078 00000000001c9063 00000000001ca063 # 188088 00000000001cb063 00000000001cc063 这样就查出了页表地址 取出24位(第12 - 35位),然后低12位再补3个0,就得到了页表的地址 03cfc6000 ==> 页表地址 (36位的) 页表在1d3项 !dq 3cfc6000 + 1d3 * 8 kd> !dq 3cfc6000 + 1d3 * 8 上面结果 取第35-12位(低12位补0) * PTE * 8 #3cfc6e98 8000000023f3d963 8000000023f3c963 #3cfc6ea8 8000000023f3b963 0000000000000000 #3cfc6eb8 8000000023d00963 8000000023f7f963 #3cfc6ec8 8000000023f7e963 0000000000000000 #3cfc6ed8 8000000023cc3963 8000000023b82963 #3cfc6ee8 8000000023b81963 0000000000000000 #3cfc6ef8 8000000023dc6963 8000000023dc5963 #3cfc6f08 8000000023cc4963 00000000`00000000 上面结果 物理地址 = 上面结果 上面结果 取第35-12位(低12位补0) + offset 取出24位(第12 - 35位),然后低12位再补3个0,这就是物理地址了,然后再加上之前的偏移 023f3d000 + C20 = 23f3dC20
虚拟地址(va) = 807d3c20
物理地址 (pa) = 23f3dC20 = 023f3d000+ c20
2M页查表
用ps等于少了一张表
1663582150682-3f9c854c-2a93-41af-92f3-cd68cc36e356.png
一般都是系统进程
1 r CR3 和 r CR4 查看cr3 和 cr4的值 CR3 = 00185000 CR4 = 000406f9
2!dq 00185000 以dq格式 查看该地址的值
1663588626724-0f2deccd-e3ab-4ff1-900d-f7909ccb7f92.png
这里我们选择第三项
3 dq 0188000 以dq格式 查看该地址的值
1663588788265-b2c210f3-27a2-46d5-abb3-9123f5fd4658.png
然后去找2M的页 ,最低的数字大于8就行
1663594776152-ef5de3de-7759-4142-bf3a-af331b21d9f6.png
所以物理地址 00e800000 + 21位偏移
通过虚拟地址或得物理地址:
PHYSICAL_ADDRESS MmGetPhysicalAddress( IN PVOID BaseAddress );
模拟MmGetPhysicalAddress函数
就是把虚拟地址转换为物理地址但是用内核的API做的话有时候会被检测到,因此我们可以自己实,这样就可以绕过一些保护
1663572754070-170113de-199e-446e-b8a5-0206918bec9e.png
Drv.cpp
  1. \#include "Drv.h"

  2.                 PhysicalAddress->QuadPart = (pPDT[PDEIndex].BaseAddress << 12) +

  3.                     ((unsigned)VirtualAddress & 0x1FFFFF);

  4.                 //基址左移12位拼成一个36位的物理地址,然后再加上虚拟地址的低21位偏移

  5.             }

  6.             else {//4K

  7.                 //4K的话还得查分页表

  8.                 pa.QuadPart = cr3 = pPDT[PDEIndex].BaseAddress;//用页目录表的地址

  9.                 lpBuffer = MmMapIoSpace(pa, 0x1000, MmNonCached);

  10.                 //得到页表下标

  11.                 int PTEIndex = (unsigned)VirtualAddress >> 12 & 0x1FF;

  12.                 PageTable* pPT = (PageTable*)lpBuffer;

  13.                 //判断是否有效

  14.                 if (pPT[PTEIndex].p == 0) {

  15.                     return STATUS_UNSUCCESSFUL;

  16.                 }

  17.                

  18.                 //4K的物理地址

  19.                 PhysicalAddress->QuadPart = (pPDT[PDEIndex].BaseAddress << 12) +

  20.                                 ((unsigned)VirtualAddress & 0x1FFFFF);

  21.                 //页表的BaseAddress左移12位得到一个36位的物理地址,再加上虚拟地址的低12位偏移

  22.             }

  23.         }

  24.         else {

  25.             //32位地址,表项4个字节

  26.         }

  27.     }

  28.     else {

  29.         //没有开启分页

  30.         return STATUS_UNSUCCESSFUL;

  31.     }

  32.     return STATUS_SUCCESS;

  33. }

  34. /*驱动卸载函数 clean_up*/

  35. VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)

  36. {

  37.   KdPrint(("[51asm] Unload! DriverObject:%p\n", DriverObject));

  38. }

  39. /*1.驱动入口函数*/

  40. NTSTATUS DriverEntry(

  41.   __in struct _DRIVER_OBJECT* DriverObject,

  42.   __in PUNICODE_STRING  RegistryPath)

  43. {

  44.    UNREFERENCED_PARAMETER(RegistryPath);

  45.    PHYSICAL_ADDRESS pa = {0};

  46.    MyGetPhysicalAddress((PVOID)0x807d3c20, &pa);  //调用获取地址函数

  47.    DbgPrint("pa=%llx", pa.QuadPart);

  48.   //4.注册卸载函数

  49.   DriverObject->DriverUnload = Unload;

  50.   return STATUS_SUCCESS;

  51. }
复制代码
物理地址扩展情况
上节课学习的是分页表4K的情况,也是最早的CPU
○34位里有些位是0,64位里对应的位会是L,这是用来表达是64位CPU的
● 分页选项的标志
● 把一个页设计为4M对做表是有影响的
● 4M页的查表方法
1663601848053-7ac565c1-7d10-4eba-943b-772ae623f476.png
● 一般系统的进程系统就会给4M的页.3环的进程很少会给4M的页,只会给4K
物理地址扩展
● 物理地址扩展的由来
4K页查表解析
● CR3还是保持不变,和之前一样
解析
1663601805140-553876a6-15ca-436d-bc90-f912bddea7aa.png
● 流程
kd> !dq 00188000 + 0x3 * 8 188018 000000003cfc6863 0000000000192063 188028 0000000000193063 000000003cf42863 188038 0000000000000000 00000000001c2063 188048 00000000001c3063 00000000001c4063 188058 00000000001c5063 00000000001c6063 188068 00000000001c7063 00000000001c8063 188078 00000000001c9063 00000000001ca063 188088 00000000001cb063 00000000001cc063 这样就查出了页表地址
再取出24位,然后补3个0,就得到了页表的地址 03cfc6000 ==> 页表地址 (36位的) 页表在1d3项 !dq 3cfc6000 + 1d3 * 8 kd> !dq 3cfc6000 + 1d3 * 8 3cfc6e98 8000000023f3d963 8000000023f3c963 3cfc6ea8 8000000023f3b963 0000000000000000 3cfc6eb8 8000000023d00963 8000000023f7f963 3cfc6ec8 8000000023f7e963 0000000000000000 3cfc6ed8 8000000023cc3963 8000000023b82963 3cfc6ee8 8000000023b81963 0000000000000000 3cfc6ef8 8000000023dc6963 8000000023dc5963 3cfc6f08 8000000023cc4963 00000000`00000000 取出24位,这就是物理地址了,然后再加上之前的偏移
0`23f3d000 + C20 = 23f3dC20 最后校验就是 !db 23f3dC20 和 db 807d3C20 因为是36位地址,所以都要加上!来查数据,否则数据就不对
- 页目录指针表格式
1663601515987-0bea7d8e-3395-4bc5-a57d-437e74c3d683.png
- 页目录表页表格式(都是一样的,只是多了地址扩展)
1663601530622-c005a914-3c60-4bdf-a8d2-1f5872da3e65.png
### 2M页查表解析
- 2M页没有页表了,偏移变成21位了
- 上面查4K表的流程是有问题的,因为没有查看PS位
- 在页目录表里要查看PS位,是0还是1,如果页目标表项是4K的话,就是0,是2M的话就是1
- 2M页一般只给系统进程,所以要切换到系统进程来做测试
1663601542400-2a5b6d0a-8500-4fed-a2d9-0ada6d06df1e.png
!process 0 0 找一个系统进程 .process /i /p 855d59e8 (对象ID) 这就切换过来了
然后查看这个进程的CR3,每个进程都有自己的CR3这样就隔离了 CR3 = 00185000 !dq 00185000 ;这就是页目录指针表,这里的每一项都会有512个表项,所以就可以 dq xxxxxxx L 512 在里面找到一个2M的页来查看.
- 2M表的页目录指针表和页目录表格式
1663601599523-493c67d7-5faa-4c53-8faa-080dd6ff7764.png
- 至此32位系统的表格式就全部讲解完毕了,一共就五种情况
### MmGetPhysicalAddress代码实现
- 输入一个地址,把它转换为物理地址
- 它是可以通过内核API来完成MmGetPhysicalAddress
//给一个虚拟地址,传出物理地址  PHYSICAL_ADDRESS MmGetPhysicalAddress(      IN PVOID  BaseAddress  );  //PHYSICAL_ADDRESS 这就是一个LONG LONG 分为高32位和低32位

●访问物理地址有两种方法
● 实现MmGetPhysicalAddress
实现这个功能就可以不用操作系统的API了,可以通过硬件的一些方式直接操控所有进程的内存了,可以秒杀保护...
杀软都检测不到.调用API操作肯定是会被阻止的.所以自己实现就有这些好处.
从本质来说我们并不是和系统通讯,而是直接和CPU通讯
/代码实现****/
  1. \#include <intrin.h>

  2. //页目录指针表,页目录表,页表.这3个表格式都是一样的,只是有些字段用的不一样,所以直接定义一个表就行了

  3. //但是得做两个,32位和64位

  4. struct PageTable {

  5.   unsigned int p   : 1; //P位,是否有效位

  6.   unsigned int rw  : 1;

  7.   unsigned int us  : 1;

  8.   unsigned int pwt : 1;

  9.   unsigned int pcd : 1;

  10.   unsigned int a   : 1; //是否以访问

  11.   unsigned int d   : 1;

  12.   unsigned int ps  : 1; //ps位

  13.   unsigned int g   : 1;

  14.   unsigned int avl : 3; //低12位到此结束

  15.   unsigned int BaseAddress : 24; //24位的页基址

  16.   unsigned int res : 28; //打开PAE的页的28位高位保留位

  17.   //12 + 24 + 28 = 64位 所以一共八个字节

  18. };

  19. NTSTATUS MyGetPhysicalAddress(IN PVOID  VirtualAddress, PHYSICAL_ADDRESS*  PhysicalAddress)
  20. {

  21.    

  22.     //获取CR0的值,使用内联汇编的话会出现不兼容的问题,因为64位不让内联汇编

  23.     ULONG cr3 = __readcr3();//通过内部函数来解决,每个寄存器都有内部函数来获取值,

  24.         //也有__writecr3

  25.     ULONG cr0 = __readcr0();//cr0和cr4要包含<intrin.h>头文件

  26.     ULONG cr4 = __readcr4();

  27.     //判断cr0的最高位是不是1,就是是否开启分页

  28.     if (cr0 & 0x80000000) {

  29.         //然后再看PAE有没有开启 PAE是cr4第五位

  30.         if (cr4 & 0x20) { //100000 PAE

  31.             //36位物理地址,并且表项8个字节

  32.             //获取页目录指针表地址,要访问物理地址了.cr3是物理地址

  33.             PHYSICAL_ADDRESS pa;

  34.             pa.QuadPart = cr3;

  35.             //0x1000是映射大小;第三个参数是是否放到缓存里,MmNonCached是不放;

  36.             PVOID lpBuffer = MmMapIoSpace(pa,0x1000,MmNonCached);

  37.             /*

  38.                 它为分页表单独做了一个缓存叫做TLB

  39.                 意思就是读取的时候为了兼容把所有的表拆的很散,格式很乱

  40.                 所以CPU都会给这些表做缓存

  41.                 把内存里的表全部拼好放到缓存里,以后就只查询缓存.这样格式再乱也没关系了

  42.                 缓存就有个问题就是啥时候刷新...

  43.                 如果cr3改了,它就会更新了

  44.             */

  45.             //页目录指针表用高两位查,用虚拟地址的高两位查

  46.             //直接右移30位,但是要注意符号位,所以清掉最高位 & 0x3,

  47.             //但是强转位unsigned的话就不需要清了

  48.             int PDPTIndex = (unsigned)VirtualAddress >> 30;

  49.             //页目录指针表(直接把这个地址(物理内存)当作页目录指针表)

  50.             PageTable* pPDPT = (PageTable*)lpBuffer;

  51.             if (pPDPT[PDPTIndex].p == 0) { //判断P位

  52.                 return STATUS_UNSUCCESSFUL;

  53.             }

  54.             //获取页目录表地址

  55.             pa.QuadPart = cr3 = pPDPT[PDPTIndex].BaseAddress;

  56.             lpBuffer = MmMapIoSpace(pa, 0x1000, MmNonCached);

  57.             //拿到地址后右移21位再与上9位,也就是高2位要清掉,得到页目录表的下标

  58.             int PDEIndex = ((unsigned)VirtualAddress >> 21) & 0x1FF;

  59.             //得到页目录表地址

  60.             PageTable* pPDT = (PageTable*)lpBuffer;

  61.             //判断是否有效

  62.             if (pPDT[PDEIndex].p == 0) {

  63.                 return STATUS_UNSUCCESSFUL;

  64.             }

  65.             //判断PS位

  66.             if (pPDT[PDEIndex].ps) {//2M

  67.                 //2M的话物理地址就可以直接给了

  68.                 PhysicalAddress->QuadPart = (pPDT[PDEIndex].BaseAddress << 12) +

  69.                     ((unsigned)VirtualAddress & 0x1FFFFF);

  70.                 //基址左移12位拼成一个36位的物理地址,然后再加上虚拟地址的低21位偏移

  71.             }

  72.             else {//4K

  73.                 //4K的话还得查分页表

  74.                 pa.QuadPart = cr3 = pPDT[PDEIndex].BaseAddress;//用页目录表的地址

  75.                 lpBuffer = MmMapIoSpace(pa, 0x1000, MmNonCached);

  76.                 //得到页表下标

  77.                 int PTEIndex = (unsigned)VirtualAddress >> 12 & 0x1FF;

  78.                 PageTable* pPT = (PageTable*)lpBuffer;

  79.                 //判断是否有效

  80.                 if (pPT[PTEIndex].p == 0) {

  81.                     return STATUS_UNSUCCESSFUL;

  82.                 }

  83.                

  84.                 //4K的物理地址

  85.                 PhysicalAddress->QuadPart = (pPDT[PDEIndex].BaseAddress << 12) +

  86.                                 ((unsigned)VirtualAddress & 0x1FFFFF);

  87.                 //页表的BaseAddress左移12位得到一个36位的物理地址,再加上虚拟地址的低12位偏移

  88.             }

  89.         }

  90.         else {

  91.             //32位地址,表项4个字节

  92.         }

  93.     }

  94.     else {

  95.         //没有开启分页

  96.         return STATUS_UNSUCCESSFUL;

  97.     }

  98.     return STATUS_SUCCESS;

  99. }
复制代码


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|断点社区 |网站地图

GMT+8, 2024-12-24 11:04 , Processed in 0.056550 second(s), 33 queries .

Powered by XiunoBBS

Copyright © 2001-2025, 断点社区.

快速回复 返回顶部 返回列表