写代码挂物理页

admin 2023年10月30日01:30:43评论10 views字数 5483阅读18分16秒阅读模式

前面已经介绍了什么是物理页,同时也学会了如何通过windbg来修改物理页,使我们能够读和写高2G的内存和修改静态常量。

但是在正常情况下,我们是不可能使用windbg去调试某个电脑系统内核,然后修改的,因此,本章篇幅,就是我们用来介绍如何通过代码来修改物理页和挂物理页的。

VirtualAlloc分析

在写代码之前,我们 需要先思考,物理地址我们是不可能访问的,但是当我们使用VirtualAlloc函数来申请内存时,系统是怎么做的呢?

根据我们前面学到的知识,其实不难理解,那就是当我们使用VirtualAlloc函数时,其实就是系统和CPU为我们挂了一个可读可写的PDE、PTE、和物理页。

那么问题来了,这个过程是怎样的呢?

比如:此时物理页是0x123456,但是我们可以用汇编指令如:mov dword ptr ds:[0x123456]这样来直接操作嘛,显然是不可以的。我们只能操作线性地址。

那么一定存在一个线性地址,让操作系统和CPU能够通过他来找到PDT和PTT,这样系统就可以直接修改物理页了。

那么这个线性地址在哪呢?

0xC0300000

我们在调试系统中,打开一个程序,在windbg中找到该程序的CR3,这里,我们用计算器举例:

写代码挂物理页

不加任何偏移,指向的就是PDT这张表:

kd> !dd 4bced000
#4bced000 4bd3f067 4bb38067 4e1c5067 00000000
#4bced010 4bdba067 00000000 00000000 00000000
#4bced020 00000000 00000000 00000000 00000000
#4bced030 00000000 00000000 00000000 00000000
#4bced040 00000000 00000000 00000000 00000000
#4bced050 00000000 00000000 00000000 00000000
#4bced060 00000000 00000000 00000000 00000000
#4bced070 00000000 00000000 00000000 00000000

此时,我们在来拆分0xC0300000这个地址:

0xC0300000:

1100 0000 00:0x3000x300*4=0xC00

11 0000 0000:0x3000x300*4=0xC00

000  

写代码挂物理页

calc.exe CR3=4bced000
PDT表:
kd> !dd 4bced000
#4bced000 4bd3f067 4bb38067 4e1c5067 00000000
#4bced010 4bdba067 00000000 00000000 00000000
#4bced020 00000000 00000000 00000000 00000000
#4bced030 00000000 00000000 00000000 00000000
#4bced040 00000000 00000000 00000000 00000000
#4bced050 00000000 00000000 00000000 00000000
#4bced060 00000000 00000000 00000000 00000000
#4bced070 00000000 00000000 00000000 00000000

0xC0300000物理页:
kd> !dd 4bced000+0
#4bced000 4bd3f067 4bb38067 4e1c5067 00000000
#4bced010 4bdba067 00000000 00000000 00000000
#4bced020 00000000 00000000 00000000 00000000
#4bced030 00000000 00000000 00000000 00000000
#4bced040 00000000 00000000 00000000 00000000
#4bced050 00000000 00000000 00000000 00000000
#4bced060 00000000 00000000 00000000 00000000
#4bced070 00000000 00000000 00000000 00000000

我们发现,某个程序的0xC0300000的物理页,就是该程序的PDT表。

补充:

写代码挂物理页

可以看到0xC0300000处,既是PTT又是PDT,说明在这个地方PDT和PTT是同一张表,那么也说明,其实PDT就是一张特殊的PTT。

但是,光有PDT表还不行,我们还需要找到PTT表,如何查找呢?

0xC0000000

还是以计算器举例:

写代码挂物理页

我们在拆分一下:

0xC000000:

1100 0000 00:0x3000x300*4=0xC00

00 0000 0000:0x0000x000*4=0x000

000  

写代码挂物理页

calc.exe 的PTT:
kd> !dd 4bd3f000
#4bd3f000 00000000 00000000 00000000 00000000
#4bd3f010 00000000 00000000 00000000 00000000
#4bd3f020 00000000 00000000 00000000 00000000
#4bd3f030 00000000 00000000 00000000 00000000
#4bd3f040 4ec40067 00000000 00000000 00000000
#4bd3f050 00000000 00000000 00000000 00000000
#4bd3f060 00000000 00000000 00000000 00000000
#4bd3f070 00000000 00000000 00000000 00000000

calc.exe程序的0xC0000000线性地址处的物理页
kd> !dd 4bd3f000 +0x0
#4bd3f000 00000000 00000000 00000000 00000000
#4bd3f010 00000000 00000000 00000000 00000000
#4bd3f020 00000000 00000000 00000000 00000000
#4bd3f030 00000000 00000000 00000000 00000000
#4bd3f040 4ec40067 00000000 00000000 00000000
#4bd3f050 00000000 00000000 00000000 00000000
#4bd3f060 00000000 00000000 00000000 00000000
#4bd3f070 00000000 00000000 00000000 00000000

所以,这里我们可以看到0xC0000000处的线性地址的物理页就是该程序的PTT。

那么既然找到了某个程序的PDT和PTT,不就可以任意操控和修改物理页了嘛?

那么我们该如何写代码呢?

在写代码执行,我们要知道几个公式:

公式

先了解什么是PDI和PTI

10-10-12分页模式:

  • PDI:第一个10

  • PTI:第二个10

访问页目录表公式

0xC0300000 + PDI*4

这个好理解

访问页表的公式

0xC0000000 + PDI*4096 + PTI*4

这个不好理解,

为什么要+4096呢?因为一个页的大小是4096个字节。

举例:

0xC0000000 +0x1000 = 0xC0001000

1100 0000 00 * 4 = 0xC00

00 0000 0001 * 4 = 0x003

000

写代码挂物理页

MmIsAddressValid分析

MmIsAddressValid 是一个 Windows 内核函数,用于判断给定的虚拟内存地址是否有效。其实就是利用PDE和PTE来检查的。

BOOLEAN MmIsAddressValid(
PVOID VirtualAddress
);

写代码挂物理页

kd> u MmIsAddressValid L40
nt!MmIsAddressValid:
804e45d1 8bff mov edi,edi
804e45d3 55 push ebp
804e45d4 8bec mov ebp,esp
804e45d6 8b4d08 mov ecx,dword ptr [ebp+8];ebp+8为变量也就是VirtualAddress
804e45d9 8bc1 mov eax,ecx;把VirtualAddress赋值给eax
804e45db c1e814 shr eax,14h;将VirtualAddress右移20位,也就是留最高位的12位,我们应该留10位的为什么要留12位呢?因为我们需要*4,PDI*4就等于是左移2位
804e45de bafc0f0000 mov edx,0FFCh;0FFCh=1111 1111 1100,把该值给edx
804e45e3 23c2 and eax,edx;通过与运算,将12位的后两位变成0,这样就达到了*4的目的
804e45e5 2d0000d03f sub eax,3FD00000h;VirtualAddress+0xC0300000 就等于:VirtualAddress-3FD00000,那么此时eax就指向PDE了
804e45ea 8b00 mov eax,dword ptr [eax];取到PDE,赋值给eax
804e45ec a801 test al,1 ;与运算,查看最低位是否为1,也就是看P位是否为1
804e45ee 0f849aeb0000 je nt!MmIsAddressValid+0x4f (804f318e) ;如果P位为1,则跳转
804e45f4 84c0 test al,al ;与运算,如果最高位也就是第7位PS位是否为1,如果为1 sf标志位为1,判断大页还是小页
804e45f6 7824 js nt!MmIsAddressValid+0x53 (804e461c);根据sf位是否为一进行跳转,
804e45f8 c1e90a shr ecx,0Ah;将VirtualAddress右移10位,原理与上述相同
804e45fb 81e1fcff3f00 and ecx,3FFFFCh;3FFFFCh=11 1111 1111 1111 1111 1100 同上,意思是将PDI和PTI的其他位置清0,结果就是PDI*4096(左移12位) + PTI*4(左移2位)
804e4601 81e900000040 sub ecx,40000000h ;ecx = 0xC0000000 + PDI*4096 + PTI*4
804e4607 8bc1 mov eax,ecx
804e4609 8b08 mov ecx,dword ptr [eax] ;把PTE的值赋值给ecx
804e460b f6c101 test cl,1 ;判断P位是否为1
804e460e 0f847aeb0000 je nt!MmIsAddressValid+0x4f (804f318e)
804e4614 84c9 test cl,cl;判断PS位是否为1
804e4616 0f884edf0300 js nt!MmIsAddressValid+0x3f (8052256a)
804e461c b001 mov al,1;返回1,代表线性地址有效
804e461e 5d pop ebp
804e461f c20400 ret 4

分析的差不多了。

我们可以来写代码了,就根据上述的逻辑。

// PdePteCheck.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>


PDWORD xPDE,xPDETest,xPTE,PDE0,PTE0;

//00401020
void __declspec(naked) func()
{
_asm
{
pushad
pushfd

mov eax,xPDE
mov eax,[eax]
cmp eax,0
jnz editPTE
mov ebx,PDE0
mov [ebx],eax

editPTE:
mov eax,xPTE
mov eax,[eax]
mov ebx,PTE0
mov [ebx],eax

popfd
popad
retf


}
}

int main(int argc, char* argv[])
{
//因为如果需要修改PTE和PDE的属性,那么我们需要提权
//我们直接用调用门提权
char buff[6] = {0x44,0x33,0x22,0x11,0x48,0x00};


//我们要挂的物理业的线性地址,先初始化
int x = 0x12345678;

//得到x的线性地址
DWORD xAddr = (DWORD)&x;
printf("xAddr = %xn",xAddr);

//得到x的PDE,和PTE


//xPDETest = (DWORD*)(xAddr >> 20);
//printf("xPDETest = %xn",xPDETest);

//X的PDE
xPDE = (PDWORD)((( xAddr >> 20) & 0xFFC) +0xC0300000);
printf("xPDE = %xn",xPDE);

//X的PTE
xPTE = (PDWORD)(((xAddr >> 10) & 0x3FFFFC) + 0xC0000000);
printf("xPTE = %xn",xPTE);

//我们要修改的是地址0的物理业,所以我们还需要定义0的PDE和PTE
PDE0 = (PDWORD)0xC0300000;
PTE0 = (PDWORD)0xC0000000;


printf("go!n");
//提权

_asm
{
call fword ptr[buff]
}
*((int*)0) = 0x123;
printf("%x", *((int*)0));
getchar();

return 0;
}

写代码挂物理页

代码修改成功!~


原文始发于微信公众号(loochSec):写代码挂物理页

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年10月30日01:30:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   写代码挂物理页https://cn-sec.com/archives/2157466.html

发表评论

匿名网友 填写信息