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;
}

image


获取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,代表替换成功。

温馨提示:虽然已经修改成功,但是如果我们不将正确PTE改回来的话。。会在进程退出的时候蓝屏哦。