首页
社区
课程
招聘
[原创]Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)
2015-8-21 09:44 12072

[原创]Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)

2015-8-21 09:44
12072
Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)

上个月就想写了,一直没时间...网上大概搜了一下,原理与操作倒是一大堆,一直没看到源码实现,总得有人动手,这回轮到我了。东西写得很烂,请大牛勿喷。一直觉得靠源码的方式驱动学习是非常好的一种学习方法,比较直观!声明一下,本教程只有讨论开启PAE与关闭PAE两种,至于PSE是否开启没有管...我的虚拟机默认PSE貌似是开启滴?不知是不是写的小工具有问题....对于x64下的等我有时间再写吧。
东西都上传在压缩包中了,Codes文件夹下是工程源码,Demo文件夹下是测试案例,Tool文件夹放的是小工具的Demo和源码。
我的环境:开发环境(win7 sp1 x64 + vs2013社区版 update5 + wdk8.1)
                     测试环境(vm10 + win7 sp1 x86)
一、先说说未开启PAE的情况,祭出intel手册的经典图例:

这幅图就是虚拟地址转为物理地址的原理图(4k页面),看图说话,用伪代码描述一下:
1.Directory Entry(PDE)            = PDBR[Directory];
2.Page-Table Entry(PTE)                = PDE + Table * 4;
3.Physical Address                 = PTE + Offset;
由上可知,Linear Address(线性地址)中的Directory和Table其实就是个索引,在未开启PAE的情况下,PDE、PTE均是32bit(4字节,所以要Table*4),以上只是原理上的描述,实际上,PDE、PTE的后3位是属性值,所以需要把后3位抹掉。
下边上关键代码,基本都步骤都写了注释了,有需要的可以封装成函数。此外,本段代码只是测试用,写的很不规范,比如,在调用MmMapIoSpace应该调用MmUnMapIoSpace释放内存。
// 得到ring3传入的虚拟地址
			size_t* pOutAddress = (size_t*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
			VIRTUAL_ADDRESS virtualAddress = { 0 };
			virtualAddress.ulVirtualAddress = *pOutAddress;
			ULONG pdbr;

			_asm{
				mov eax,  cr3;
				mov pdbr, eax;
			}
			
			PHYSICAL_ADDRESS phyAddress = { 0 };
			phyAddress.LowPart = pdbr;
			PULONG pPdbr = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
			KdPrint(("pdbr = 0x%08X, 映射后的地址0x%p\n", pdbr, pPdbr));
			
			// pPdbr[ulDirBaseIdx] 页目录项
			ULONG ulDirBaseIdx = virtualAddress.stVirtualAddress.dirBaseIndex;
			ULONG ulDirIdx = virtualAddress.stVirtualAddress.dirIndex;
			KdPrint(("第一级,已找到页目录所在项:pPdbr[%d]:0x%08X", ulDirBaseIdx,pPdbr[ulDirBaseIdx]));
			ULONG ulDir = pPdbr[ulDirBaseIdx] & 0xFFFFF000;			// 抹去后3位得到真正的页目录项


			ULONG ulDirPlus = ulDir + ulDirIdx * 4;					// 页表项
			phyAddress.LowPart = ulDirPlus;
			PULONG pDirPlus = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
			KdPrint(("第二级,已找到页表项:ulDirPlus = 0x%08X, 映射后的地址0x%p\n", ulDirPlus, pDirPlus));
			ULONG ulPageTable = *pDirPlus & 0xFFFFF000;				// 抹去后3位得到真正的页表项

			// 得到物理地址
			ULONG ulPhyAddress = ulPageTable + virtualAddress.stVirtualAddress.offset;

			// 映射为虚拟地址,获取其值进行验证
			phyAddress.LowPart = ulPhyAddress;
			PWCHAR pPhyAddress = (PWCHAR)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
			KdPrint(("虚拟地址:0x%08X, 对应物理地址:0x%08X, Value:%S\n", *pOutAddress, ulPhyAddress, pPhyAddress));

			// 传出对应物理地址
			*pOutAddress = ulPhyAddress;


二、开启PAE的情况

同样是4k页面的,伪代码描述如下:
1.Dir.Pointer Entry(PDPTE)         = PDPTR[Directory Pointer];
2.Director Entry(PDE)                 = PDPTE + Directory * 0x8;
3.Page-Table Entry(PTE)         = PDE + Table * 0x8;
4.Physical Address                 = PTE+Offset;
在开启PAE的情况下,PDE、PTE均是64bit(8字节,所以要*8),同样PDE、PTE的后3位是属性值,所以需要把后3位抹掉。
关键代码如下:
// 得到传入的ring3层虚拟地址
			size_t* pOutAddress = (size_t*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
			VIRTUAL_ADDRESS virtualAddress = { 0 };
			virtualAddress.ulVirtualAddress = *pOutAddress;
			ULONG pdbr;

			// 得到页目录指针物理地址
			_asm{
				mov eax,  cr3;
				mov pdbr, eax;
			}
			
			// 映射为虚拟地址以便取值
			PHYSICAL_ADDRESS phyAddress = { 0 };
			phyAddress.LowPart = pdbr;
			PULONG pPdbr = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
			KdPrint(("pdbr = 0x%08X, 映射后的地址0x%p\n", pdbr, pPdbr));
			
			// 定位页目录指针表并获取页目录表物理页地址
			// ulDirAddress 为页目录表物理页地址
			ULONG ulPointerIdx = virtualAddress.stVirtualAddress.dirPointer;
			ULONG ulDirBaseAddress = pPdbr[ulPointerIdx];
			ulDirBaseAddress &= 0xFFFFF000;			// 中间物理地址

			// 定位页表项
			ULONG ulDirAddress = ulDirBaseAddress + virtualAddress.stVirtualAddress.dirIndex * 0x8;
			phyAddress.LowPart = ulDirAddress;
			PULONG pPageTable = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
			ULONG ulPageTable = *pPageTable;
			ulPageTable &= 0xFFFFF000;				 // 中间物理地址

			// 定位物理页面
			ulPageTable += virtualAddress.stVirtualAddress.tableIndex * 0x8;
			phyAddress.LowPart = ulPageTable;
			PULONG pPageBase = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
			ULONG ulPageBase = *pPageBase;
			ulPageBase &= 0xFFFFF000;

			// 得到物理地址
			ULONG ulPhyAddress = ulPageBase + virtualAddress.stVirtualAddress.offset;
			
			// 映射为虚拟地址,获取其值进行验证
			phyAddress.LowPart = ulPhyAddress;
			PWCHAR pPhyAddress = (PWCHAR)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached);
			KdPrint(("虚拟地址:0x%08X, 对应物理地址:0x%08X, Value:%S\n", *pOutAddress, ulPhyAddress, pPhyAddress));

			// 传出对应物理地址
			*pOutAddress = ulPhyAddress;

以上代码步骤是参考安于此生的文章写的,看不懂的可以先看看安于此生的文章《启用PAE后虚拟地址到物理地址的转换》
另附上小工具源码,该工具用于检测系统是否开启PAE、PSE等。效果见下图演示
#define BUFFERSIZE	0x3000
char g_szMemInfo[BUFFERSIZE] = { 0 };
	
// 以下code在 DriverEntry 中
	
	DWORD dwPE  = 0;				// Protection Enable	cr0[0]
	DWORD dwWP  = 0;				// Write Protect		cr0[16]
	DWORD dwPG  = 0;				// Paging				cr0[31]
	DWORD dwPAE = 0;				// 物理地址扩展			cr4[5]
	DWORD dwPSE = 0;				// Page Size Extension	cr4[4]
	DWORD dwCr0 = 0;
	DWORD dwCr4 = 0;

	// 注册卸载函数
	pDriverObj->DriverUnload = driverUnload;

	_asm{
		pushad;
		mov eax, cr0;
		mov dwCr0, eax;

		// PE标志位
		and eax, 0x01;
		mov dwPE, eax;
		mov eax, cr0;

		// WP标志位
		and eax, 0x10000;
		mov dwWP, eax;
		mov eax, cr0;

		// PG标志位
		and eax, 0x80000000;
		mov dwPG, eax;

		// PAE
		//mov eax, cr4; 机器码如下
		_emit 0x0F;
		_emit 0x20;
		_emit 0xE0;
		mov dwCr4, eax;
		and eax, 0x20;
		mov dwPAE, eax;
		
		// PSE
		_emit 0x0F;
		_emit 0x20;
		_emit 0xE0;
		and eax, 0x10;
		mov dwPSE, eax;

		popad;
	}
	
	KdPrint(("PE  = 0x%08X\r\n",dwPE));
	KdPrint(("WP  = 0x%08X\r\n",dwWP));
	KdPrint(("PG  = 0x%08X\r\n",dwPG));
	KdPrint(("PAE = 0x%08X\r\n",dwPAE));
	KdPrint(("PSE = 0x%08X\r\n",dwPSE));
	KdPrint(("Cr0 = 0x%08X\r\n",dwCr0));
	KdPrint(("Cr4 = 0x%08X\r\n",dwCr4));

	//----------------------------------------------------------------------------
	// PE标志位
	if (0 != dwPE){
		RtlStringCchCatNA(
			g_szMemInfo, 
			BUFFERSIZE, 
			"----------------------保护模式(PE=1)-------------------\r\n",
			BUFFERSIZE - sizeof("----------------------保护模式(PE=1)-------------------\r\n"));
	}
	else{
		RtlStringCchCatNA(
			g_szMemInfo,
			BUFFERSIZE ,
			"----------------------实地址模式(PE=0)-------------------\r\n",
			BUFFERSIZE - sizeof("----------------------实地址模式(PE=0)-------------------\r\n"));
	}

	//----------------------------------------------------------------------------
	// WP标志位
	if (0 != dwWP){
		RtlStringCchCatA(
			g_szMemInfo,
			BUFFERSIZE,
			"内存写保护(WP)开启...\r\n"
			);
	}
	else{
		RtlStringCchCatA(
			g_szMemInfo,
			BUFFERSIZE,
			"内存写保护(WP)禁止...\r\n"
			);
	}

	//----------------------------------------------------------------------------
	// PG标志位
	if (0 != dwPG){
		RtlStringCchCatA(
			g_szMemInfo,
			BUFFERSIZE,
			"页机制(PG)启用\r\n"
			);
	}
	else{
		RtlStringCchCatA(
			g_szMemInfo,
			BUFFERSIZE,
			"页机制(PG)禁止\r\n"
			);
	}

	//----------------------------------------------------------------------------
	// PAE标志位
	if (0 != dwPAE){
		RtlStringCchCatA(
			g_szMemInfo,
			BUFFERSIZE,
			"物理地址扩展(PAE)已开启\r\n"
			);
	}
	else{
		RtlStringCchCatA(
			g_szMemInfo,
			BUFFERSIZE,
			"物理地址扩展(PAE)未启用\r\n"
			);
	}

	//----------------------------------------------------------------------------
	// PSE标志位
	if (0 != dwPSE){
		RtlStringCchCatA(
			g_szMemInfo,
			BUFFERSIZE,
			"页面大小扩展(PSE)已开启\r\n"
			);
	}
	else{
		RtlStringCchCatA(
			g_szMemInfo,
			BUFFERSIZE,
			"页面大小扩展(PSE)未启用\r\n"
			);
	}

	KdPrint(("%s\r\n", g_szMemInfo));


最后,看看效果运行图。Demo是在ring3层定义一个Unicoe字符串:“Lthis”,然后将其虚拟地址传入ring0层,ring0解析后传出对应的物理地址。
开启PAE下的效果:


未开启PAE的效果


附件地址:链接:http://pan.baidu.com/s/1kTENdnL 密码:g5j7
有问题欢迎到博客留言:http://www.cnblogs.com/Lthis/

阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

收藏
点赞1
打赏
分享
最新回复 (14)
雪    币: 171
活跃值: (474)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Lthis 1 2015-8-21 10:47
2
0
搞忘记占位了...
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
winuser 2015-8-21 13:53
3
0
如此文章,竟然没人顶,难道PC真的落寞了咩
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
sarsky 2015-8-21 14:00
4
0
人生不易...
雪    币: 20
活跃值: (242)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
GeekCheng 2 2015-8-21 22:13
5
0
http://bbs.pediy.com/showthread.php?t=203391可以参考这个,写x64的
雪    币: 52
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
轩辕追命 2015-8-21 22:40
6
0
不错的文章 厉害啊
雪    币: 171
活跃值: (474)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Lthis 1 2015-8-22 13:53
7
0
[QUOTE=GeekCheng;1388102]http://bbs.pediy.com/showthread.php?t=203391可以参考这个,写x64的[/QUOTE]

不错,看雪现在要求越来越高了,你的帖子我觉得加精都完全可以,没想到看雪连个优秀都不给。之前安于此生发表的关于这方面的帖子至少都是优秀,你这篇难度高些怎么说也该给个精华的。哎!
雪    币: 36
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
贺星寒 2015-8-23 11:57
8
0
看了一路,这篇文章算是不错的。顶楼主
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
好三易语言 2015-8-23 14:31
9
0
看起来好高深
雪    币: 29
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
寒窗苦读 2015-9-1 10:04
10
0
不错,感谢楼主辛勤劳动!
雪    币: 401
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
BIOHAZARDX 2015-9-2 20:33
11
0
32bit技能get中,多谢
雪    币: 49
活跃值: (118)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
smurf 2015-9-10 19:18
12
0
顶楼主,
雪    币: 208
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cxqwe 2015-9-14 12:34
13
0
写的非常好
雪    币: 1126
活跃值: (2076)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Oday小斯 2018-9-2 18:55
14
0
mark
雪    币: 419
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wxhui 2018-9-7 07:44
15
0
谢谢分享
游客
登录 | 注册 方可回帖
返回