X64下更换进程PTE
这篇文章会介绍一下,如何更换进程的pte以及虚拟地址到物理地址转换的相关知识
实现了把自己进程中的虚拟地址指向的内存,更换成另外一处物理内存的操作
实验环境:win7x64
先来一段代码
#include "stdafx.h"
#include "windows.h"
int main()
{
CHAR Buffer[] = "Text";
printf("Buffer Addr : %p\n", Buffer);
getchar();
return 0;
}
可以看到虚拟地址0x0028FE80
windbg切换到进程Test.exe
0: kd> !process 0 0 Test.exe
PROCESS fffffa80039250e0
SessionId: 1 Cid: 08ec Peb: 7efdf000 ParentCid: 0940
DirBase: 62e9a000 ObjectTable: fffff8a001dddce0 HandleCount: 12.
Image: Test.exe
0: kd> .process /i /p fffffa80039250e0
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
0: kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
fffff800`03e90490 cc int 3
0: kd> r cr3
cr3=0000000062e9a000
可以看到切换到该进程后,cr3代表和该进程DirBase值一致,也就是DirBase就是该进程的cr3地址。
X64线性地址结构
x64线性地址占64位,其中低48位代表:
47-39(9位) | 38-30(9位) | 29-21(9位) | 20-12(9位) | 11-0(12位) |
---|---|---|---|---|
PML4E索引(PXE) | PDPTE索引(PPE) | PDE索引 | PTE索引 | 页内偏移 |
其中高16位是符号扩展,要不全是0,要不全是1。
在windows中全是1代表内核地址,全是0代表用户态地址。
虚拟地址0x0028FE80对应2进制为:
000000000 | 000000000 | 000000001 | 010001111 | 1110 1000 0000 |
---|---|---|---|---|
PXE(0x0) | PPE(0x0) | PDE(0x1) | PTE(0x8F) | 偏移(0xE80) |
我们已经计算出该虚拟地址对应的偏移。
我们看下cr3物理地址对应的内容:
0: kd> !dq 62e9a000
#62e9a000 00700000`6286e867 00000000`00000000
cr3指向的地址存放的是pxe的内容:
也就是pxe=0x00700000`6286e867
其中pxe中的12-35位保存的ppe的12-35位,低12位置0。
也就是说ppe首地址=0x0`6286e000
: kd> !dq 0`6286e000
#6286e000 03a00000`623f2867 00800000`62c6f867
计算得出pde首地址=0x0`623f2000
但是pde的偏移是1,我们需要加上相应偏移查看:
1: kd> !dq 0x0`623f2000 + (1 * 0x8)
#623f2008 01600000`632fa867 08100000`61928867
计算得出pte首地址=0x0`632fa000
pte的偏移是0x8f,所以查看对应偏移:
1: kd> !dq 0x0`632fa000 + (0x8f * 0x8)
#632fa478 8e000000`62120867 00000000`00000000
我们得出物理页首地址为:0x0`62120000
我们直接加上页内偏移0xE80
1: kd> !db 0x0`62120000+e80
#62120e80 54 65 78 74 00 cc cc cc-cc cc cc cc a0 78 2e 02 Text.........x..
1: kd> db 0x0028fe80
00000000`0028fe80 54 65 78 74 00 cc cc cc-cc cc cc cc a0 78 2e 02 Text.........x..
可以看到已经正确定位到我们的字符串。
如果不想这么麻烦的话,也可以使用命令:
1: kd> !pte 0x0028fe80
VA 000000000028fe80
PXE at FFFFF6FB7DBED000 PPE at FFFFF6FB7DA00000 PDE at FFFFF6FB40000008 PTE at FFFFF68000001478
contains 007000006286E867 contains 03A00000623F2867 contains 01600000632FA867 contains 8E00000062120867
pfn 6286e ---DA--UWEV pfn 623f2 ---DA--UWEV pfn 632fa ---DA--UWEV pfn 62120 ---DA--UW-V
尝试修改PTE
来看下pte的结构体:
typedef struct{
unsigned __int64 Present:1;
unsigned __int64 ReadWrite:1;
unsigned __int64 UserSupervisor:1;
unsigned __int64 WriteTrough:1;
unsigned __int64 CacheDisabled:1;
unsigned __int64 Accessed:1;
unsigned __int64 Dirty:1;
unsigned __int64 Reserved0:1;
unsigned __int64 GlobalPage:1;
unsigned __int64 Available:3;
unsigned __int64 PageFrameNumber:24;
unsigned __int64 Reserved:27;
unsigned __int64 NxBit:1;
}PTE, *PPTE;
最大物理地址
在Intel中使用MAXPHYADDR来表示最大的物理地址,可以通过CPUID的指令获取
MAXPHYADDR | 寻址空间 | 下一级table有效位 | 备注 |
---|---|---|---|
36位 | 64G | 12-35位提供下一级table物理基地址的高24位,36-51是保留位 | PC 32位机的PAE模式 |
40位 | 1TB | 12-39位提供下一级table物理基地址的高28位,40-51是保留位 | 服务器产品 |
52位 | 暂未实现 | 12-51位提供下一级table物理基地址的高40位,低12位补零 |
结构体PTE中成员PageFrameNumber的长度就是根据MAXPHYADDR来确定的
开一个新进程
#include "stdafx.h"
#include "windows.h"
int main()
{
CHAR Buffer[] = "ABCD";
printf("Buffer :%s Addr : %p\n", Buffer, Buffer);
getchar();
return 0;
}
获取Test2.exe Buffer虚拟地址pte
0: kd> !pte 0024febc
VA 000000000024febc
PXE at FFFFF6FB7DBED000 PPE at FFFFF6FB7DA00000 PDE at FFFFF6FB40000008 PTE at FFFFF68000001278
contains 007000005F769867 contains 03A000005EB6D867 contains 016000005F076867 contains 920000005ED1C867
pfn 5f769 ---DA--UWEV pfn 5eb6d ---DA--UWEV pfn 5f076 ---DA--UWEV pfn 5ed1c ---DA--UW-V
我们尝试把Test.exe进程中的Buffer地址替换成Test2.exe进程中的地址
- 切换回Test.exe查看需要替换的Buffer
0: kd> db 0x0028FE80
00000000`0028fe80 54 65 78 74 00 cc cc cc-cc cc cc cc a0 78 2e 02 Text.........x..
嗯,没错,是我们的地址
- 尝试替换PTE
只有物理地址是不能直接修改内存的,所以我们需要获得PTE项的虚拟地址
不过PTE项首地址在windows下是固定的:
Windows x64下是:0xFFFFF68000000000
Windows x86下是:0xC0000000
虚拟地址对应的PTE地址=(虚拟地址>>12)+PTE项首地址
所以0x0028FE80 对应PTE地址是:
0: kd> ? 0x0028FE80 >> c
Evaluate expression: 655 = 00000000`0000028f
0: kd> dq 0xFFFFF68000000000 + (0x28f * 8)
fffff680`00001478 8e000000`62120867 00000000`00000000
果然和上面得出来PTE的一样。
直接替换Test2的Buffer PTE地址
eq fffff680`00001478 8e000000`5ED1C867
1: kd> dq fffff680`00001478
fffff680`00001478 8e000000`5ed1c867
(5ED1C是Test2.exe的Buffer pfn)
- 然后我们再次切换回Test.exe,可能是因为cpu缓存原因,直接查看地址0x0028FE80 会没有变化。
-
再次查看
1: kd> db 0x0028FE80
00000000`0028fe80 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
00000000`0028fe90 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
00000000`0028fea0 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................
00000000`0028feb0 cc cc cc cc cc cc cc cc-cc cc cc cc 41 42 43 44 ............ABCD
00000000`0028fec0 00 cc cc cc cc cc cc cc-9e 1c 8d 39 e0 fe 24 00 ...........9..$.
我们只能替换该物理页地址,因为偏移原因我们在后面看到了"ABCD"。ok,代表替换成功。
发表评论
要发表评论,您必须先登录。