首页
社区
课程
招聘
[原创]windows64位分页机制分析-隐藏可执行内存方法
发表于: 2021-12-22 19:55 18435

[原创]windows64位分页机制分析-隐藏可执行内存方法

2021-12-22 19:55
18435

之前一直没发过文章,只是一直看文章,这次也想分享一些东西出来。由于本人的水平有限,也是初学者,因此如果有一些错误还望海涵。

在32位系统下,windows有两种分页机制,2-9-9-12分页以及10-10-12分页,而在64位下只有一种分页机制,那就是9-9-9-9-12分页。在64位下,线性地址的结构如下图所示

img

在x64体系中,只有48位是用来确定虚拟地址的,前面的16位成为符号拓展位,这16位只有为全1或者为全0两种情况。除了这两种情况之外的地址都是不合法的。这16为为全1时表示这个地址是一个内核空间的地址,而为全0时表示这个地址是一个用户空间的地址。

在64位下,存在三种不同大小的页面,分别为大页、中页、小页。其大小分别为1GB、2MB、4KB。

探索系统的分页机制最好的办法是对内核中的 MiIsAddressValid 函数的实现进行分析。win7和win10的64位系统下使用的是两种不同的查找 ptepdepdptepml4e 的方式。接下来分别对两个系统的该函数进行相应的分析

win7 64位下的 MiIsAddressValid 如下

image-20211222191909910

首先通过移位,查看前16位是否为全0或者全1,如果不是,返回不合法。然后通过移位分别分出9-9-9-9-12这5部分,然后加上相应的基址,找到对应的 pte,pde,pdpte,pml4e ,并判断P位是否为0。如果为0返回不合法。

该函数较为简单,将减去的数值取反加一可以得到对应的基址

可得在win7 64位下,其 PTE_BASEfffff68000000000

win10 1607 以上版本,微软为分页基址加上了随机页目录基址,这让定位 pte 变得更加困难。 PTE_BASE 不再是之前写死的值,而是每次开机都会随机在一定的范围内挑选一个值。而在 win10 64 位下的 MmIsAddressValidEx 函数中,其更换了另外一套寻找 pte,pde,pdpte,pml4e 的方法,如下

image-20211222192618338

可以看到这里计算 pte,pde,pdpte,pml4e 的时候用的都只有一个 pte_base ,不再像win7那样求每个值的时候都使用一个不同的基址。而可以验证,在win7下,使用这样的方法来获取 pte,pde,pdpte,pml4e 一样可行。

这是一个由来已久的方法,在申请一块不可执行的内存后通过修改 ptepde 手动将页面设置为可执行,达到隐藏可执行内存的目的。在编写这样的驱动的过程中也能巩固对x64分页机制的认识。

首先写一个用来验证的程序,程序本身很简单,跳转到我们输入的地址来验证我们写入程序中的shellcode是否可执行。如下

如果跳转到的地址是不可执行的,那么shellcode不会被执行,程序会异常退出。

接下来开始驱动的编写,主要要实现的是一个分配内存的 AllocateMemory 函数,可以往指定pid的进程中写入shellcode,并隐藏其可执行属性,使这块内存在vad树中看来是不可执行的。

image-20211222193451300

在函数中进行进程的挂靠,然后通过 ZwAllocateMemory 在函数中分配一块保护为 PAGE_READWRITE 属性的内存。

image-20211222193618802

最后写入shellcode,并在写入shellcode后将申请的内存全部设置为可执行。

image-20211222193657746

代码如下

最后进行实验,查看代码是否可行。首先开启我们的验证进程,查看其vad树,如下

image-20211222194244271

接下来加载驱动,可看到其在对应进程中申请的内存地址为 12f0000 。而且从输出中可以看出 shellcode 所在的地址的 pte 的最高位(no_execute)从之前的1被修改为了0。

image-20211222194527707

重新查看其vad树,发现 12f0000 出多出了一块 PAGE_READWRITE 属性的内存,从vad树中看其是不可执行的。但是在程序中输入地址,可以看到 shellcode 成功执行,验证成功。

image-20211222194724486

本项目的代码已上传到github,项目地址为https://github.com/smallzhong/hide_execute_memory ,欢迎大家star

printf("%llx", ~0x98000000000 + 1);
>>> fffff68000000000
printf("%llx", ~0x98000000000 + 1);
>>> fffff68000000000
DWORD addr;
scanf("%x", &addr);
__asm
{
    jmp addr
}
DWORD addr;
scanf("%x", &addr);
__asm
{
    jmp addr
}
#include "memory.h"
#include "shellcode.h"
 
ULONG getOsVersionNumber()
{
        /*
         Windows 1020H2)    19042
         Windows 10200419041
         Windows 101909)    18363
         Windows 101903)    18362
         Windows 101809)    17763
         Windows 101803)    17134
         Windows 101709)    16299
         Windows 101703)    15063
         Windows 101607)    14393
         Windows 101511)    10586
         Windows 10    (1507)    10240
 
         Windows 8.1(更新1)    MajorVersion = 6 MinorVersion = 3 BuildNumber = 9600
         Windows 8.1            MajorVersion = 6 MinorVersion = 3 BuildNumber = 9200
         Windows 8                MajorVersion = 6 MinorVersion = 2 BuildNumber = 9200
    */
    RTL_OSVERSIONINFOEXW version = {0};
 
    NTSTATUS status = RtlGetVersion(&version);
 
    if (!NT_SUCCESS(status))
    {
        return 0;
    }
 
    return version.dwBuildNumber;
}
 
ULONG64 getPte(ULONG64 VirtualAddress)
{
    ULONG64 pteBase = getPteBase();
    return ((VirtualAddress >> 9) & 0x7FFFFFFFF8) + pteBase;
}
 
ULONG64 getPde(ULONG64 VirtualAddress)
{
    ULONG64 pteBase = getPteBase();
    ULONG64 pte = getPte(VirtualAddress);
    return ((pte >> 9) & 0x7FFFFFFFF8) + pteBase;
}
 
ULONG64 getPdpte(ULONG64 VirtualAddress)
{
    ULONG64 pteBase = getPteBase();
    ULONG64 pde = getPde(VirtualAddress);
    return ((pde >> 9) & 0x7FFFFFFFF8) + pteBase;
}
 
ULONG64 getPml4e(ULONG64 VirtualAddress)
{
    ULONG64 pteBase = getPteBase();
    ULONG64 ppe = getPdpte(VirtualAddress);
    return ((ppe >> 9) & 0x7FFFFFFFF8) + pteBase;
}
 
ULONG64 getPteBase()
{
    static ULONG64 pte_base = NULL;
    if (pte_base) return pte_base;
 
    // 获取os版本
    ULONG64 versionNumber = 0;
    versionNumber = getOsVersionNumber();
    KdPrintEx((77, 0, "系统版本%lld\r\n", versionNumber));
 
    // win7或者1607以下
    if (versionNumber == 7601 || versionNumber == 7600 || versionNumber < 14393)
    {
        pte_base = 0xFFFFF68000000000ull;
        return pte_base;
    }
    else // win10 1607以上
    {
        //取PTE(第一种)
        UNICODE_STRING unName = { 0 };
        RtlInitUnicodeString(&unName, L"MmGetVirtualForPhysical");
        PUCHAR func = (PUCHAR)MmGetSystemRoutineAddress(&unName);
        pte_base = *(PULONG64)(func + 0x22);
        return pte_base;
    }
 
    return pte_base;
}
 
PVOID AllocateMemory(HANDLE pid, ULONG64 size)
{
    PEPROCESS Process = NULL;
    NTSTATUS status = PsLookupProcessByProcessId(pid, &Process);
    if (!NT_SUCCESS(status))
    {
        return NULL;
    }
 
    // 如果当前找到的进程已经退出
    if (PsGetProcessExitStatus(Process) != STATUS_PENDING)
    {
        return NULL;
    }
 
    KAPC_STATE kapc_state = { 0 };
    KeStackAttachProcess(Process, &kapc_state);
 
    PVOID BaseAddress = NULL;
 
    status = ZwAllocateVirtualMemory(NtCurrentProcess(), &BaseAddress, 0, &size, MEM_COMMIT, PAGE_READWRITE);
    if (!NT_SUCCESS(status))
    {
        return NULL;
    }
 
    //写入shellcode
    RtlMoveMemory(BaseAddress, shellcode, sizeof(shellcode);
 
    // 修改可执行
    SetExecutePage(BaseAddress, size);
 
    KeUnstackDetachProcess(&kapc_state);
 
    return BaseAddress;
}
 
BOOLEAN SetExecutePage(ULONG64 VirtualAddress, ULONG size)
{
    ULONG64 startAddress = VirtualAddress & (~0xFFF); // 起始地址
    ULONG64 endAddress = (VirtualAddress + size) & (~0xFFF); // 结束地址
 
    for (ULONG64 curAddress = startAddress; curAddress <= endAddress; curAddress += PAGE_SIZE)
    {
        PHardwarePte pde = getPde(curAddress);
 
        KdPrintEx((77, 0, "修改之前pde = %llx ", *pde));
 
        if (MmIsAddressValid(pde) && pde->valid == 1)
        {
            pde->no_execute = 0;
            pde->write = 1;
        }
 
        PHardwarePte pte = getPte(curAddress);
        KdPrintEx((77, 0, "pte = %llx\r\n", *pte));
        if (MmIsAddressValid(pte) && pte->valid == 1)
        {
            pte->no_execute = 0;
            pte->write = 1;
        }
 
        KdPrintEx((77, 0, "pde = %p pte = %p address = %p\r\n", pde, pte, curAddress));
        KdPrintEx((77, 0, "修改之后pde = %llx pte = %llx\r\n", *pde, *pte));
    }
 
    return TRUE;
}
#include "memory.h"
#include "shellcode.h"
 
ULONG getOsVersionNumber()
{
        /*
         Windows 1020H2)    19042
         Windows 10200419041
         Windows 101909)    18363
         Windows 101903)    18362
         Windows 101809)    17763
         Windows 101803)    17134
         Windows 101709)    16299
         Windows 101703)    15063
         Windows 101607)    14393
         Windows 101511)    10586
         Windows 10    (1507)    10240
 
         Windows 8.1(更新1)    MajorVersion = 6 MinorVersion = 3 BuildNumber = 9600
         Windows 8.1            MajorVersion = 6 MinorVersion = 3 BuildNumber = 9200
         Windows 8                MajorVersion = 6 MinorVersion = 2 BuildNumber = 9200
    */
    RTL_OSVERSIONINFOEXW version = {0};
 
    NTSTATUS status = RtlGetVersion(&version);
 
    if (!NT_SUCCESS(status))
    {
        return 0;
    }
 
    return version.dwBuildNumber;
}
 
ULONG64 getPte(ULONG64 VirtualAddress)
{
    ULONG64 pteBase = getPteBase();
    return ((VirtualAddress >> 9) & 0x7FFFFFFFF8) + pteBase;
}
 
ULONG64 getPde(ULONG64 VirtualAddress)
{
    ULONG64 pteBase = getPteBase();
    ULONG64 pte = getPte(VirtualAddress);
    return ((pte >> 9) & 0x7FFFFFFFF8) + pteBase;
}
 
ULONG64 getPdpte(ULONG64 VirtualAddress)
{
    ULONG64 pteBase = getPteBase();
    ULONG64 pde = getPde(VirtualAddress);
    return ((pde >> 9) & 0x7FFFFFFFF8) + pteBase;
}
 
ULONG64 getPml4e(ULONG64 VirtualAddress)
{
    ULONG64 pteBase = getPteBase();
    ULONG64 ppe = getPdpte(VirtualAddress);
    return ((ppe >> 9) & 0x7FFFFFFFF8) + pteBase;
}
 
ULONG64 getPteBase()
{
    static ULONG64 pte_base = NULL;
    if (pte_base) return pte_base;
 
    // 获取os版本
    ULONG64 versionNumber = 0;
    versionNumber = getOsVersionNumber();

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-7-6 14:41 被smallzhong_编辑 ,原因:
收藏
免费 9
支持
分享
最新回复 (5)
雪    币: 1628
活跃值: (3512)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
pg?
2021-12-23 10:22
0
雪    币: 2999
活跃值: (5368)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
3
tmflxw pg?
什么?
修改PTE不会PG啊(
2021-12-23 10:53
0
雪    币: 45
活跃值: (423)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
“而在windows操作系统中,由于种种原因,又只用了这48位线性地址中的 44位 。”,44位是针对Windows 8.1(不包括Windows 8.1)的系统。Windows 8.1之后使用48位线性地址,用户和内核空间各128TB。
2021-12-23 13:31
0
雪    币: 2999
活跃值: (5368)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
5
binaryhead “而在windows操作系统中,由于种种原因,又只用了这48位线性地址中的 44位 。”,44位是针对Windows 8.1(不包括Windows 8.1)的系统。Windows 8.1之后使用48位 ...

这个是写错了,已经改掉了

最后于 2022-12-14 18:32 被smallzhong_编辑 ,原因:
2021-12-23 13:55
0
雪    币: 1642
活跃值: (4733)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
前16位不是F或者0吗
2023-4-12 15:19
0
游客
登录 | 注册 方可回帖
返回
//