Joe1sn's Cabinet

windows内核驱动 2-页表探索

  • 探索CR4
  • 探索页面
    • CR3寄存器于页遍历
    • 虚拟地址->物理地址

[驱动开发]探索CR4

英特尔® 64 位和 IA-32 架构开发人员手册合订本

第3卷第四章第五小节《4-LEVEL PAGING AND 5-LEVEL PAGING》

判断CPU是四级分页还是五级分页

image-20230216154149842

page: 3074

image-20230216155742771

image-20230216160252784

该寄存器的值决定了映射层级是4/5,

  • 1:5级分页
  • 0:4级分页
1
2
3
4
5
6
7
8
//__readcr4() & 0x1000
//__readcr4() & (1<<12)
CR4 Cr4 = { .Value = __readcr4() };
DbgPrint("CR4=0x%016I64X, CR4.LA57=%I64d\n", Cr4.Value, Cr4.Fields.LA57);
if (Cr4.Fields.LA57)
DbgPrint("The system using 5 Level Paging");
else
DbgPrint("The system using 4 Level Paging");

[驱动开发]探索页面

CR3寄存器于页遍历

CR3 寄存器设计 CR3[512][512][512][512]

image-20230217100658871

个人觉得这块儿和CSAPP上面讲的差不多

页号=页首地址页大小=页首地址4KB==页首地址212==页首地址>>12页号 = \frac{页首地址}{页大小}= \frac{页首地址}{4KB}== \frac{页首地址}{2^{12}}== 页首地址>>12

1
2
3
4
5
6
7
8
9
10
11
12
13
//1.得到CR3寄存器的值,从而得到一级页表地址
CR3 Cr3 = { .Value = __readcr3() };

//2.对每一个一级页读取
PL1PTE L1pt = MmGetVirtualForPhysical(L1ptPa.AsLargeInteger);
for (int n1 = 0; n1 < 512; n1++)
{
if (!L1pt[n1].Fields.P || !L1pt[n1].Fields.U_S) continue;
PA L2ptPa = { .Fields4KB.PPN = L1pt[n1].Fields.PPN };
DbgPrint("...");
//3.得到二级页表地址
PL2PTE L2pt = MmGetVirtualForPhysical(L2ptPa.AsLargeInteger);
//4.类似嵌套循环,遍历
  1. CR3PPN是第12位,共40bit,所以一级页表是这个值。CR3的尾12位没有用全为0,同时24=162^{4} = 16,那么向右移12位等效于16禁止右移124=3\frac{12}{4}=3

  2. 得到Level_1_PageTable的值过后,可以使用函数MmGetVirtualForPhysical获得该内存地址的值,从而遍历一级页表。要判断当前L1pt.P是否有效,同时L1pt.U_S可以判断该页的权限,详细可以看开发者手册 Table 4-20

    image-20230217140249814

  3. 依次循环可以遍历完整个内存页(下图中L4pt放不下了)

image-20230217134809379

虚拟地址->物理地址

程序按照4KB分页个数太多不利于实验,可以强制将其分页为1GB,这样便于翻译

image-20230217140941496

编写一个测试程序,该程序能申请出一页的内存,并打印其值

使用PsGetCurrentProcess获得当前进程PROCESS对象,若相等则不启用新方法

使用KeStackAttachProcess附加到要翻译的内存的PROCESS对象中,使用老方法输出就OK了,最后KeUnstackDetachProcess脱离

  • ?如何使用按照名字查找进程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    #include "win10.h"

    NTSTATUS
    QuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID* SystemInformation)
    {
    NTSTATUS Status;
    PVOID Buffer;
    ULONG BufferSize = 4096;

    do
    {
    Buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, BufferSize, 'bisQ');
    if (!Buffer) return STATUS_NO_MEMORY;

    Status = NtQuerySystemInformation(SystemInformationClass, Buffer, BufferSize, &BufferSize);
    if (NT_SUCCESS(Status)) {
    *SystemInformation = Buffer;
    return Status;
    }

    ExFreePool(Buffer);
    if (STATUS_INFO_LENGTH_MISMATCH != Status) return Status;
    } while (TRUE);
    }

    NTSTATUS
    LookUpProcessByImageName(PCWSTR ImageName, PEPROCESS* Process)
    {
    NTSTATUS Status;
    PSYSTEM_PROCESS_INFORMATION ProcessInformationArray = NULL;

    Status = QuerySystemInformation(SystemProcessInformation, &ProcessInformationArray);
    if (!NT_SUCCESS(Status)) return Status;

    PSYSTEM_PROCESS_INFORMATION CurrentInformation = ProcessInformationArray;
    UNICODE_STRING ImageNameUnicodeString;
    RtlInitUnicodeString(&ImageNameUnicodeString, ImageName);

    while (TRUE)
    {
    if (RtlCompareUnicodeString(&CurrentInformation->ImageName, &ImageNameUnicodeString, FALSE) == 0)
    {
    Status = PsLookupProcessByProcessId(CurrentInformation->UniqueProcessId, Process);
    ExFreePool(ProcessInformationArray);
    return Status;
    }

    if (CurrentInformation->NextEntryOffset == 0)
    {
    ExFreePool(ProcessInformationArray);
    return STATUS_NOT_FOUND;
    }

    CurrentInformation = (PSYSTEM_PROCESS_INFORMATION)((PUCHAR)CurrentInformation + CurrentInformation->NextEntryOffset);
    }
    }

    大致思路和win32下按名称查找PID差不多

  • 具体过程

    image-20230217144908513

    这里以0x00000254000003BC Str1 为例子,加载插件打印,耐心等待。。。

    image-20230217145848060

    这时候的数据大小就有83KB了

    format(0x00000254000003BC, "064b"),内存前16位为空是没有用的

    • 高9位 VPN1=4VPN1=4

      int(format(0x00000254000003BC, "064b")[16:][:9],2)

      image-20230217150603957

    • 再9位 VPN2=336VPN2=336

      int(format(0x00000254000003BC, "064b")[16:][9:18],2)

      image-20230217150709452

      该页表的PS=1,则不需要查找下一个页表了(而且后面也没有了)

    • 得到的Pa就是物理页的首地址 + 剩余的30bit位作为VPO = 物理地址

      hex(0x00000001C0000000+int(format(0x00000254000003BC, "064b")[16:][18:],2))

      得到PA+VPO = 0x1c00003bc

    • 暂停系统,Windbg -> memory window -> 右键 -> proeries -> physical memory

      image-20230217151350113

    • 尝试修改

      image-20230217151439604

      再次刷新应该就是1234

      image-20230217151554657

      成功修改

官方文档

2938/4834

image-20230217151657940