Windows中的内存地址分为几种?

Windows11中内存数据分为物理内存与虚拟内存

在Windows 11(以及所有现代Windows版本)中,内存按照以下描述的方式组织:

  1. 物理内存 - 这是实际的硬件RAM,由内存条提供的实际存储空间。

  2. 虚拟内存

    - 这是一个抽象层,被划分为两个主要区域:

    • 低地址空间(用户空间):分配给应用程序使用的虚拟地址。在64位Windows系统中,用户空间通常是从0x0000000000000000到0x00007FFFFFFFFFFF,即低48位地址空间。每个进程都有自己独立的用户空间虚拟地址映射。
    • 高地址空间(内核空间):保留给操作系统内核和驱动程序使用的虚拟地址。在64位Windows系统中,内核空间通常从0xFFFF800000000000开始,即高位地址空间。所有进程共享相同的内核空间映射。

读取物理内存地址数据

1. 使用Windbg读取物理内存地址

  1. windbg断点

  2. 输入!db命令查询地址

    1
    !db 0x100000
  3. 带感叹号的命令读取物理内存地址,不带感叹号读取虚拟内存地址

  4. 在WinDbg中读取物理内存的常用命令:

    • !db 物理地址 - 以字节格式显示物理内存
    • !dw 物理地址 - 以字(word)格式(2字节)显示物理内存
    • !dd 物理地址 - 以双字(dword)格式(4字节)显示物理内存
    • !dq 物理地址 - 以四字(qword)格式(8字节)显示物理内存
  5. 读取物理内存内容与长度:

    1
    !db 0x100000 L100      # 显示物理地址0x100000开始的256字节内容

2. 使用MM_COPY_ADDRESS结构读取物理内存

  1. entry.c

    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
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    #include <ntddk.h>
    #include <wdf.h>
    #ifdef DBG
    #define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__)
    #else
    #define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__)
    #endif

    //卸载驱动函数
    VOID DriverUnload(PDRIVER_OBJECT pDriver)
    {
    pDriver;
    DebugMessage(("驱动卸载成功\r\n"));
    }


    NTSTATUS DriverEntry(
    _In_ PDRIVER_OBJECT DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
    {
    //启用足够的调试输出级别
    DbgSetDebugFilterState(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, TRUE);
    // 初始化设备上下文、绑定 PCI 设备等
    DriverObject->DriverUnload = DriverUnload;
    DebugMessage("entry\r\n");
    NTSTATUS status;
    PVOID buffer = NULL;
    SIZE_T size = 0x1000; // 例如读取4KB
    SIZE_T bytesTransferred = 0;
    MM_COPY_ADDRESS sourceAddress;

    // 分配内核内存
    buffer = ExAllocatePoolWithTag(NonPagedPool, size, 'MeRd');
    if (buffer == NULL) {
    return STATUS_INSUFFICIENT_RESOURCES;
    }

    // 设置物理地址
    sourceAddress.PhysicalAddress.QuadPart = 0x100000; // 目标物理地址

    // 执行内存复制
    status = MmCopyMemory(buffer, sourceAddress, size, MM_COPY_MEMORY_PHYSICAL, &bytesTransferred);

    if (NT_SUCCESS(status))
    {
    DebugMessage("读取物理内存成功,传输了 %d 字节\n", (ULONG)bytesTransferred);
    if (bytesTransferred == 0) {
    DebugMessage("警告:没有传输任何字节!\n");
    }
    // 打印buffer内容(例如前16个字节)
    DebugMessage("内存内容: ");
    for (ULONG i = 0; i < min(16, bytesTransferred); i++) {
    DebugMessage( "%02X ", ((PUCHAR)buffer)[i]);
    }
    DebugMessage("\n");

    // 这里可以进一步处理buffer中的数据...
    }
    else {
    DebugMessage("读取物理内存失败,状态码: 0x%x\n", status);
    }

    // 释放分配的内存
    if (buffer != NULL) {
    ExFreePoolWithTag(buffer, 'MeRd');
    }

    return status;
    }

2. 驱动程序中使用MmMapIoSpace将物理内存映射到虚拟内存中读取

  1. entry.c

    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
    #include "ReadAddrP.h"

    NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriverObject)
    {
    DebugMessage("unload pDriverObject addr is %p\n", pDriverObject);
    return STATUS_SUCCESS;
    }

    NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
    {
    pDriverObject->DriverUnload = UnloadDriver;
    DebugMessage("Into DriverEntry %p\n", pDriverObject);
    DebugMessage("pDriverObject address is %p\n", pDriverObject);
    DebugMessage("pRegistryPath is %wZ\n", pRegistryPath);

    PHYSICAL_ADDRESS physicalAddr;
    UCHAR buffer[16] = { 0 };
    // 设置要读取的物理地址,示例选择0x1000
    physicalAddr.QuadPart = 0x1000;

    // 调用独立函数读取物理内存
    NTSTATUS status = ReadPhysicalMemory(physicalAddr, buffer, sizeof(buffer));

    if (NT_SUCCESS(status)) {
    DebugMessage("Successfully read physical memory\n");
    }
    else {
    DebugMessage("Failed to read physical memory, status: 0x%X\n", status);
    }

    return STATUS_SUCCESS;
    }
  2. ReadAddrP.h

    1
    2
    3
    4
    5
    6
    7
    8
    #pragma once
    #include <ntifs.h>

    #pragma warning (disable : 4100)
    #define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__);

    // 函数声明
    NTSTATUS ReadPhysicalMemory(PHYSICAL_ADDRESS physicalAddr, PVOID buffer, SIZE_T size);
  3. ReadAddrP.c

    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
    #include "Function.h"
    #include <ntifs.h>


    // 读取物理内存数据的独立函数
    NTSTATUS ReadPhysicalMemory(PHYSICAL_ADDRESS physicalAddr, PVOID buffer, SIZE_T size)
    {
    NTSTATUS status = STATUS_SUCCESS;
    PVOID mappedAddress = NULL;

    // 将物理地址映射到系统虚拟地址空间
    mappedAddress = MmMapIoSpace(physicalAddr, size, MmNonCached);

    if (mappedAddress != NULL) {
    // 使用try-except块保护内存访问
    __try {
    // 读取数据到缓冲区
    RtlCopyMemory(buffer, mappedAddress, size);

    DebugMessage("Physical memory at 0x%llX:", physicalAddr.QuadPart);
    PUCHAR byteBuffer = (PUCHAR)buffer;
    for (SIZE_T i = 0; i < size; i++) {
    if (i % 8 == 0) DebugMessage("\n");
    DebugMessage("%02X ", byteBuffer[i]);
    }
    DebugMessage("\n");
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
    status = GetExceptionCode();
    DebugMessage("Exception occurred while accessing memory: 0x%X\n", status);
    }

    // 解除映射
    MmUnmapIoSpace(mappedAddress, size);
    }
    else {
    status = STATUS_UNSUCCESSFUL;
    DebugMessage("Failed to map physical address 0x%llX\n", physicalAddr.QuadPart);
    }

    return status;
    }

3.物理内存映射到虚拟内存中读取,使用MDL的方式读取物理内存

MDL(内存描述符列表)读取物理内存的原理基于Windows内核提供的内存管理机制。我来详细解释一下代码中展示的过程:

  1. 物理地址映射到系统空间: 首先使用MmMapIoSpace函数将物理地址映射到系统虚拟地址空间。这是访问物理内存的第一步,因为内核代码通常在虚拟地址空间中运行。
  2. 创建MDL: 使用IoAllocateMdl创建一个MDL结构,它描述了刚刚映射的内存区域。MDL本质上是一种数据结构,用于描述内存页的物理位置。
  3. 构建MDL: 调用MmBuildMdlForNonPagedPool来初始化MDL,这个函数填充MDL结构体中的物理页面信息。因为目标内存在非分页池中,所以不会有分页问题。
  4. 获取系统地址: 通过MmGetSystemAddressForMdlSafe获取可以安全访问的系统地址。这个函数确保内存可以被当前执行上下文访问,并处理了可能的锁定问题。
  5. 数据复制: 使用RtlCopyMemory将数据从映射的地址复制到用户提供的缓冲区。
  6. 资源清理: 完成操作后,释放MDL资源并解除物理内存映射。

MDL的主要优势在于:

  • 内存锁定: MDL可以锁定物理内存页,防止它们被分页出去
  • 描述不连续内存: 可以描述物理上不连续但逻辑上连续的内存区域
  • 安全访问: 提供了一种安全的方式来访问并操作物理内存
  • DMA操作支持: 适用于需要物理地址的DMA操作

这种方法比直接访问物理内存更安全,因为它利用了系统提供的内存管理机制,避免了可能的页面错误和访问冲突。

代码实现

entry.c

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include <ntddk.h>
#include <stdio.h>
#define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__);
// 定义要读取的物理地址
#define TARGET_PHYSICAL_ADDRESS 0x100000
#define BYTES_TO_READ 256 // 读取256字节


VOID PrintMemoryHex(PVOID Buffer, SIZE_T Size);
NTSTATUS ReadPhysicalMemoryWithMdl(
_In_ PHYSICAL_ADDRESS PhysicalAddress,
_Out_ PVOID Buffer,
_In_ SIZE_T Length,
_Out_ PSIZE_T BytesRead
);


// 驱动程序卸载函数
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint("物理内存读取驱动已卸载\n");
}

// 驱动程序入口点
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
SIZE_T bytesRead = 0;
PVOID buffer = NULL;
PHYSICAL_ADDRESS physicalAddress;

UNREFERENCED_PARAMETER(RegistryPath);

// 设置驱动卸载函数
DriverObject->DriverUnload = DriverUnload;

DbgPrint("物理内存读取驱动已加载\n");

// 分配缓冲区
buffer = ExAllocatePoolWithTag(NonPagedPool, BYTES_TO_READ, 'rMeM');
if (buffer == NULL) {
DbgPrint("无法分配内存缓冲区\n");
return STATUS_INSUFFICIENT_RESOURCES;
}

// 初始化物理地址
physicalAddress.QuadPart = TARGET_PHYSICAL_ADDRESS;

// 读取物理内存
status = ReadPhysicalMemoryWithMdl(physicalAddress, buffer, BYTES_TO_READ, &bytesRead);
if (!NT_SUCCESS(status)) {
DbgPrint("读取物理内存失败: 0x%08X\n", status);
ExFreePoolWithTag(buffer, 'rMeM');
return status;
}

DbgPrint("成功读取 %llu 字节的物理内存\n", (ULONGLONG)bytesRead);

// 打印读取的内存内容
PrintMemoryHex(buffer, bytesRead);

// 释放内存
ExFreePoolWithTag(buffer, 'rMeM');

return STATUS_SUCCESS;
}

// 使用MDL读取物理内存的函数
NTSTATUS ReadPhysicalMemoryWithMdl(
_In_ PHYSICAL_ADDRESS PhysicalAddress,
_Out_ PVOID Buffer,
_In_ SIZE_T Length,
_Out_ PSIZE_T BytesRead
)
{
NTSTATUS status = STATUS_SUCCESS;
PVOID mappedAddress = NULL;
PMDL mdl = NULL;

*BytesRead = 0;

// 1. 使用MmMapIoSpace将物理地址映射到系统空间
mappedAddress = MmMapIoSpace(PhysicalAddress, Length, MmNonCached);
if (mappedAddress == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}

__try {
// 2. 创建MDL
mdl = IoAllocateMdl(mappedAddress, (ULONG)Length, FALSE, FALSE, NULL);
if (mdl == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
__leave;
}

// 3. 为非分页池构建MDL
MmBuildMdlForNonPagedPool(mdl);

// 4. 映射MDL和访问
PVOID mappedSystemAddress = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority | MdlMappingNoExecute);
if (mappedSystemAddress == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
__leave;
}

// 5. 复制数据
RtlCopyMemory(Buffer, mappedSystemAddress, Length);
*BytesRead = Length;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
KdPrint(("异常发生在MDL内存访问过程中: 0x%x\n", status));
}

// 6. 清理
if (mdl != NULL) {
IoFreeMdl(mdl);
}

if (mappedAddress != NULL) {
MmUnmapIoSpace(mappedAddress, Length);
}

return status;
}

// 将内存内容打印为十六进制格式
VOID PrintMemoryHex(PVOID Buffer, SIZE_T Size)
{
PUCHAR byteBuffer = (PUCHAR)Buffer;
char lineBuf[100];

// 按16字节一行打印
for (SIZE_T i = 0; i < Size; i += 16) {
// 打印行首地址
int linePos = sprintf_s(lineBuf, sizeof(lineBuf), "%04X: ", (UINT32)i);

// 打印十六进制数据
for (SIZE_T j = 0; j < 16 && (i + j) < Size; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%02X ", byteBuffer[i + j]);

// 在第8个字节后添加一个额外的空格用于视觉分隔
if (j == 7) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}

// 补齐不足16字节的部分
if (i + 16 > Size) {
for (SIZE_T j = Size - i; j < 16; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
if (j == 7) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}
}

// 添加ASCII表示
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " |");
for (SIZE_T j = 0; j < 16 && (i + j) < Size; j++) {
UCHAR ch = byteBuffer[i + j];
// 如果字符是可打印字符,则显示该字符,否则显示点号
if (ch >= 32 && ch <= 126) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%c", ch);
}
else {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, ".");
}
}
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "|");

// 打印当前行
DebugMessage("%s\n", lineBuf);
}
}

读取系统虚拟内存地址数据

1. 使用Windbg读取系统虚拟内存地址数据

  1. Windbg中断点

  2. 输入db命令读取虚拟内存数据

    1
    db 0xFFFFB68807741D30

2. 使用驱动程序读取系统虚拟内存地址数据,可以使用指针或者内存复制函数直接读取地址

  1. entry.c
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <ntifs.h>
#include <stdio.h>

#pragma warning (disable : 4100)
#define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__);

// 函数声明
NTSTATUS ReadSystemVirtualMemory(PVOID virtualAddr, PVOID buffer, SIZE_T size);
void PrintBuffer(PVOID buffer, SIZE_T size);
NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriverObject)
{
DebugMessage("unload pDriverObject addr is %p\n", pDriverObject);
return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
UCHAR buffer[64] = { 0 }; // 增加缓冲区大小以显示多行输出

pDriverObject->DriverUnload = UnloadDriver;
DebugMessage("Into DriverEntry %p\n", pDriverObject);
DebugMessage("pDriverObject address is %p\n", pDriverObject);
DebugMessage("pRegistryPath is %wZ\n", pRegistryPath);

// 要读取的系统虚拟地址
PVOID systemVirtualAddr = (PVOID)0xFFFFB68807741D30;

// 调用独立函数读取系统虚拟内存
NTSTATUS status = ReadSystemVirtualMemory(systemVirtualAddr, buffer, sizeof(buffer));

if (NT_SUCCESS(status)) {
DebugMessage("Successfully read system virtual memory\n");
}
else {
DebugMessage("Failed to read system virtual memory, status: 0x%X\n", status);
}
return STATUS_SUCCESS;
}

// 读取系统虚拟内存数据的独立函数
NTSTATUS ReadSystemVirtualMemory(PVOID virtualAddr, PVOID buffer, SIZE_T size)
{
NTSTATUS status = STATUS_SUCCESS;

// 使用try-except块保护内存访问
__try {
// 直接通过指针读取数据到缓冲区
RtlCopyMemory(buffer, virtualAddr, size);
DebugMessage("System virtual memory at 0x%p:", virtualAddr);
PrintBuffer(buffer, size);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
DebugMessage("Exception occurred while accessing memory: 0x%X\n", status);
}
return status;
}

void PrintBuffer(PVOID buffer, SIZE_T size)
{
PUCHAR byteBuffer = (PUCHAR)buffer;

char lineBuf[100];
int linePos = 0;

for (SIZE_T i = 0; i < size; i++) {
if (i % 16 == 0) {
if (i > 0) {
DebugMessage("%s", lineBuf);
}
linePos = 0;
linePos += sprintf_s(lineBuf, sizeof(lineBuf), "%04X: ", (UINT32)i);
}
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%02X ", byteBuffer[i]);
}
if (linePos > 0) {
DebugMessage("%s", lineBuf);
}
}

使用现成的函数读取应用进程中的虚拟内存地址数据

1. 使用MmCopyVirtualMemory读取虚拟内存

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include <ntifs.h>
#include <stdio.h>

#ifdef DBG
#define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__)
#else
#define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__)
#endif
void ReadVirtualMemory();
void PrintBuffer(PVOID buffer, SIZE_T size);
//卸载驱动函数
NTSTATUS DriverUnload(PDRIVER_OBJECT pDriver)
{
pDriver;
DebugMessage("驱动卸载成功\r\n");
return STATUS_SUCCESS;
}


NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
//启用足够的调试输出级别
DbgSetDebugFilterState(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, TRUE);
// 初始化设备上下文、绑定 PCI 设备等
DriverObject->DriverUnload = DriverUnload;
DebugMessage("entry\r\n");
ReadVirtualMemory();
return STATUS_SUCCESS;
}
__declspec(dllimport) NTSTATUS NTAPI MmCopyVirtualMemory(PEPROCESS SourceProcess, PVOID SourceAddress, PEPROCESS TargetProcess, PVOID TargetAddress, SIZE_T BufferSize, KPROCESSOR_MODE PreviousMode, PSIZE_T ReturnSize);
void ReadVirtualMemory()
{
NTSTATUS status;
PVOID buffer = NULL;
SIZE_T size = 0x1000; // 例如读取4KB
PEPROCESS targetProcess = NULL;
HANDLE targetPid = (HANDLE)0xC4C;
SIZE_T bytesRead = 0;

// 分配非分页池缓冲区
buffer = ExAllocatePoolWithTag(NonPagedPool, size, 'rvmB');
if (buffer == NULL) {
DebugMessage("分配缓冲区失败\r\n");
return;
}

// 获取目标进程的 EPROCESS 结构
status = PsLookupProcessByProcessId(targetPid, &targetProcess);
if (!NT_SUCCESS(status)) {
DebugMessage("获取目标进程失败\r\n");
ExFreePool(buffer);
return;
}

// 直接复制内存,不需要附加/分离过程
status = MmCopyVirtualMemory(
targetProcess, // 源进程
(PVOID)0x7FF7571ADA80, // 源地址
PsGetCurrentProcess(), // 目标进程(当前进程)
buffer, // 目标地址(我们的缓冲区)
size, // 要复制的大小
KernelMode, // 处理器模式
&bytesRead // 返回实际复制的字节数
);

if (NT_SUCCESS(status)) {
DebugMessage("成功读取 %llu 字节\r\n", bytesRead);
}
else {
DebugMessage("读取虚拟内存失败\r\n");
}

// 释放 EPROCESS 引用
ObDereferenceObject(targetProcess);
PrintBuffer(buffer, size);
// 释放缓冲区
ExFreePool(buffer);
}

void PrintBuffer(PVOID buffer, SIZE_T size)
{
PUCHAR byteBuffer = (PUCHAR)buffer;
char lineBuf[100];

// 按16字节一行打印
for (SIZE_T i = 0; i < size; i += 16) {
// 打印64位地址格式的行首地址
int linePos = sprintf_s(lineBuf, sizeof(lineBuf), "%016llX: ", (ULONGLONG)((ULONG_PTR)buffer + i));

// 打印十六进制数据
for (SIZE_T j = 0; j < 16 && (i + j) < size; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%02X ", byteBuffer[i + j]);

// 在第8个字节后添加一个额外的空格用于视觉分隔
if (j == 7) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}

// 补齐不足16字节的部分
if (i + 16 > size) {
for (SIZE_T j = size - i; j < 16; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
if (j == 7) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}
}

// 添加ASCII表示
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " |");
for (SIZE_T j = 0; j < 16 && (i + j) < size; j++) {
UCHAR ch = byteBuffer[i + j];
// 如果字符是可打印字符,则显示该字符,否则显示点号
if (ch >= 32 && ch <= 126) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%c", ch);
}
else {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, ".");
}
}
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "|");

// 打印当前行
DebugMessage("%s\n", lineBuf);
}
}

附加进程后读取应用进程中的虚拟内存地址数据

1. 如何读取进程中的虚拟内存地址?

在驱动程序中读取进程内存主要通过以下步骤:

  • 获取目标进程的EPROCESS结构
  • 使用KeStackAttachProcess将当前线程附加到目标进程的地址空间
  • 读取内存数据
  • 使用KeUnstackDetachProcess分离线程

2.测试在驱动中读取进程虚拟内存地址数据

  1. 打开notepad
  2. 使用ce附加notepad->点击查看内存->例如跳转内存(0x7FF682CC0000)->查看数值
  3. 附加别的进程会发现读取不到内存。

应用程序中需要先打开进程句柄或者附加进程才能读取内存。

在驱动程序中需要附加进程才能读取内存。

要想在驱动中读取内存,需要先使用api函数KeStackAttachProcess附加到notepad的进程中,而后才能读取到内存。

KeStackAttachProcess函数的第一个参数为eprocess,获取eprocess需要使用函数PsLookupProcessByProcessld

EprocessPsLookupProcessByProcessld引用一次后需要引用计数减一后释放

  1. 在驱动入口函数中添加代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PEPROCESS Eprocess = 0;
PsLookupProcessByProcessId((HANDLE)0x8EC, &Eprocess);
if (!Eprocess)
return STATUS_UNSUCCESSFUL;

KAPC_STATE kapc;
KeStackAttachProcess(Eprocess, &kapc);

ULONG64 Data = *(ULONG64*)0x7FF682CC0000;
KdPrint(("qi;进入 DriverEntry入口点Data=%d\n",Data));
long long *read = 0x7FF682CC0000;
KdPrint(("qi;*read=%d,read=%llx\n", *read,read));
long long addr = 0x7FF682CC0000;
KdPrint(("qi;addr=%llx\n", addr));
KeUnstackDetachProcess(&kapc);

ObfDereferenceObject(Eprocess);

3.完整驱动代码实现在驱动中读取进程虚拟内存地址数据

entry.c

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#include <ntifs.h>
#include <stdio.h>

#pragma warning (disable : 4100)
#define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__);

// 函数声明
NTSTATUS FindProcessByPid(HANDLE pid, PEPROCESS* Process);
NTSTATUS ReadProcessMemory(PEPROCESS Process, PVOID userAddr, PVOID buffer, SIZE_T size);
void PrintBuffer(PVOID buffer, SIZE_T size);

NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriverObject)
{
DebugMessage("unload pDriverObject addr is %p\n", pDriverObject);
return STATUS_SUCCESS;
}

// 通过PID查找进程的函数
NTSTATUS FindProcessByPid(HANDLE pid, PEPROCESS* Process)
{
NTSTATUS status;

// 通过PID查找进程
status = PsLookupProcessByProcessId(pid, Process);
if (NT_SUCCESS(status)) {
// 获取进程名以确认
PUCHAR procNamePtr = (PUCHAR)(*Process) + 0x5A8; // Windows 11 中EPROCESS结构的ImageFileName偏移
char imageName[16] = { 0 };
RtlCopyMemory(imageName, procNamePtr, 15);

DebugMessage("Found process PID: 0x%X (%d), name: %s\n",
(ULONG)(ULONG_PTR)pid, (ULONG)(ULONG_PTR)pid, imageName);

return STATUS_SUCCESS;
}
else {
DebugMessage("Failed to find process with PID: 0x%X (%d), status: 0x%X\n",
(ULONG)(ULONG_PTR)pid, (ULONG)(ULONG_PTR)pid, status);
return status;
}
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
UCHAR buffer[64] = { 0 };
NTSTATUS status;
PEPROCESS targetProcess = NULL;

pDriverObject->DriverUnload = UnloadDriver;
DebugMessage("Into DriverEntry %p\n", pDriverObject);
DebugMessage("pDriverObject address is %p\n", pDriverObject);
DebugMessage("pRegistryPath is %wZ\n", pRegistryPath);

// 读取进程内存示例
// 使用指定的PID 0x8EC (2284) 和地址 0x7FF682CC0000
HANDLE pid = (HANDLE)0x8EC;

// 使用函数查找进程
status = FindProcessByPid(pid, &targetProcess);
if (NT_SUCCESS(status)) {
// 读取指定的进程内存地址 0x7FF682CC0000
PVOID userVirtualAddr = (PVOID)0x7FF682CC0000;
RtlZeroMemory(buffer, sizeof(buffer));

status = ReadProcessMemory(targetProcess, userVirtualAddr, buffer, sizeof(buffer));

if (NT_SUCCESS(status)) {
DebugMessage("Successfully read process memory from address 0x%p\n", userVirtualAddr);
PrintBuffer(buffer, sizeof(buffer));
}
else {
DebugMessage("Failed to read process memory, status: 0x%X\n", status);
}

// 解除进程对象引用
ObDereferenceObject(targetProcess);
}

return STATUS_SUCCESS;
}

// 读取进程虚拟内存的函数
NTSTATUS ReadProcessMemory(PEPROCESS Process, PVOID userAddr, PVOID buffer, SIZE_T size)
{
NTSTATUS status = STATUS_SUCCESS;
KAPC_STATE apcState;

// 附加到目标进程
KeStackAttachProcess(Process, &apcState);

// 尝试读取内存
__try {
// 检查地址的有效性
if (MmIsAddressValid(userAddr)) {
ProbeForRead(userAddr, size, 1);
RtlCopyMemory(buffer, userAddr, size);
DebugMessage("Process virtual memory at 0x%p read successfully\n", userAddr);
}
else {
status = STATUS_INVALID_ADDRESS;
DebugMessage("Invalid address: 0x%p\n", userAddr);
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
DebugMessage("Exception occurred while accessing process memory: 0x%X\n", status);
}

// 分离进程
KeUnstackDetachProcess(&apcState);
return status;
}

void PrintBuffer(PVOID buffer, SIZE_T size)
{
PUCHAR byteBuffer = (PUCHAR)buffer;
char lineBuf[100];

// 按16字节一行打印
for (SIZE_T i = 0; i < size; i += 16) {
// 打印64位地址格式的行首地址
int linePos = sprintf_s(lineBuf, sizeof(lineBuf), "%016llX: ", (ULONGLONG)((ULONG_PTR)buffer + i));

// 打印十六进制数据
for (SIZE_T j = 0; j < 16 && (i + j) < size; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%02X ", byteBuffer[i + j]);

// 在第8个字节后添加一个额外的空格用于视觉分隔
if (j == 7) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}

// 补齐不足16字节的部分
if (i + 16 > size) {
for (SIZE_T j = size - i; j < 16; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
if (j == 7) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}
}

// 添加ASCII表示
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " |");
for (SIZE_T j = 0; j < 16 && (i + j) < size; j++) {
UCHAR ch = byteBuffer[i + j];
// 如果字符是可打印字符,则显示该字符,否则显示点号
if (ch >= 32 && ch <= 126) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%c", ch);
}
else {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, ".");
}
}
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "|");

// 打印当前行
DebugMessage("%s\n", lineBuf);
}
}

4. WinDbg用户模式根据Pid切换到目标进程后读取进程内存?

用户模式调试器(cdb>或windbg>)可以直接使用PID

Windbg读取特定进程的内存分为几个步骤,而不是单个命令。以读取Notepad.exe (PID 0x8EC) 中地址0x7FF682CC0000的内存内容为例,正确的步骤是:

  1. 首先,切换到目标进程上下文:
1
.process /p /r 0x8EC

这个命令会切换到PID为0x8EC的进程,并刷新(/r)符号和内存信息。Windbg会显示一条消息,表明已经切换到该进程的上下文。

  1. 然后,读取指定地址的内存内容:
1
db 0x7FF682CC0000

这里使用db命令以字节格式显示内存。也可以使用其他显示格式:

  • db - 以字节(byte)格式显示
  • dw - 以字(word/2字节)格式显示
  • dd - 以双字(dword/4字节)格式显示
  • dq - 以四字(qword/8字节)格式显示
  • u - 反汇编显示(如果是代码)
  1. 如果想显示更多内存内容,可以指定长度:
1
db 0x7FF682CC0000 L100

这将显示从该地址开始的256个字节(0x100)。

  1. 当完成操作后,如果需要返回到之前的上下文,可以使用:
1
.process 0

或者简单地重新连接到调试会话。

这种分步骤的方法比单命令更灵活,可以在切换到进程上下文后执行多种操作,而不仅仅是读取内存。

5. 怎么在Windbg中查找进程pid?

对于WinDbg而言,进程名本身确实不是直接用于命令执行的关键参数。WinDbg主要依赖进程ID (PID)来定位和操作特定进程,而不是通过进程名。

在WinDbg中:

  1. 进程ID (PID) 是唯一标识进程的方式,如.process /p /r 0x8EC
  2. 进程名可以用来帮助识别进程,但不是直接用于命令语法的

虽然WinDbg不直接使用进程名来执行命令,但它确实提供了一些方法来查找进程名对应的PID:

  • 可以使用!process 0 0命令列出所有进程,然后手动查找Notepad.exe对应的PID
  • 或者使用筛选命令:!process 0 0 notepad.exe来找到特定名称的进程

这与许多其他调试器和系统工具的设计理念一致——进程ID是系统资源的唯一标识符,而进程名只是一个人类可读的标签,可能存在重复(比如多个notepad.exe实例)。

所以在WinDbg工作流程中,通常需要先确定目标进程的PID,然后才能对该进程执行操作。

6.Windbg内核调试会话中使用EPROCESS切换进程后读取内存数据

在内核调试会话中尝试直接切换到用户模式进程上下文是行不通的会出现错误”Process 00000000`000008ec has invalid page directories”表明在内核调试器中无法直接使用PID切换到该进程。

因为在内核调试会话(kd>提示符)中,需要使用进程地址(EPROCESS)而不是PID来切换进程上下文。

Windbg内核调试会话中读取内存数据的步骤:

  1. 使用!process 0 0输出可以看出Notepad.exe进程的EPROCESS地址是ffffb6880beee080`。
  2. 使用EPROCESS地址切换进程:
1
.process /p /r 0xffffb6880beee080
  1. 然后读取内存:
1
db 0x7FF682CC0000
  1. 当完成操作后,如果需要返回到之前的上下文,可以使用:
1
.process 0

或者简单地重新连接到调试会话。

  1. 另一种方法是,可以使用!process命令找到进程后,右键点击输出中的进程地址,然后选择”Switch To Process”菜单项来切换。

物理内存与虚拟内存有映射关系,那么读取到对应的虚拟内存与物理内存数据是否相同?

结论:读取到对应的虚拟内存与物理内存数据完全相同

1.读取系统虚拟内存对应的物理地址,将此物理地址映射到系统虚拟内存中读取

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include <ntifs.h>
#include <stdio.h>

#pragma warning (disable : 4100)
#define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__);

// 函数声明
VOID QueryKernelAddressMapping(PVOID VirtualAddress);
VOID QueryAndPrintAllocatedMemory(SIZE_T Size);
VOID ReadAndPrintMemory(PVOID VirtualAddress, PHYSICAL_ADDRESS PhysicalAddress, SIZE_T Size);


NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriverObject) {
DebugMessage("驱动程序卸载, 地址: %p\n", pDriverObject);
return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {
pDriverObject->DriverUnload = UnloadDriver;

// 功能1: 查询内核自身地址的物理映射
QueryKernelAddressMapping(pDriverObject);

// 功能2: 查询动态分配内存的物理映射
QueryAndPrintAllocatedMemory(64);

return STATUS_SUCCESS;
}



// 查询内核虚拟地址的物理映射并读取数据
VOID QueryKernelAddressMapping(PVOID VirtualAddress) {
PHYSICAL_ADDRESS physicalAddress;

// 检查地址是否有效
if (!MmIsAddressValid(VirtualAddress)) {
DebugMessage("错误: 地址 0x%p 无效\n", VirtualAddress);
return;
}

// 获取物理地址
physicalAddress = MmGetPhysicalAddress(VirtualAddress);

DebugMessage("内核虚拟地址: 0x%p 映射到物理地址: 0x%016llX\n",
VirtualAddress, physicalAddress.QuadPart);

// 读取并打印内存内容
ReadAndPrintMemory(VirtualAddress, physicalAddress, 64);
}

// 查询和打印动态分配的内存映射
VOID QueryAndPrintAllocatedMemory(SIZE_T Size) {
PVOID allocatedMemory;
PHYSICAL_ADDRESS physicalAddress;

// 分配非分页内存
allocatedMemory = ExAllocatePool2(POOL_FLAG_NON_PAGED, Size, 'VMAP');
if (allocatedMemory == NULL) {
DebugMessage("错误: 内存分配失败\n");
return;
}

// 写入测试模式
for (SIZE_T i = 0; i < Size; i++) {
((PUCHAR)allocatedMemory)[i] = (UCHAR)(i & 0xFF);
}

// 获取物理地址
physicalAddress = MmGetPhysicalAddress(allocatedMemory);

DebugMessage("分配的内存: 虚拟地址 0x%p 映射到物理地址 0x%016llX\n",
allocatedMemory, physicalAddress.QuadPart);

// 读取并打印内存内容
ReadAndPrintMemory(allocatedMemory, physicalAddress, Size);

// 释放内存
ExFreePool(allocatedMemory);
}

// 读取并打印虚拟地址和物理地址的内存内容
VOID ReadAndPrintMemory(PVOID VirtualAddress, PHYSICAL_ADDRESS PhysicalAddress, SIZE_T Size) {
PVOID mappedPhysAddress;
PUCHAR virtualBuffer;
PUCHAR physicalBuffer;

// 为虚拟地址内存拷贝分配缓冲区
virtualBuffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, Size, 'VBUF');
if (virtualBuffer == NULL) {
DebugMessage("错误: 无法分配虚拟缓冲区\n");
return;
}

// 拷贝虚拟地址内存内容
RtlCopyMemory(virtualBuffer, VirtualAddress, Size);

// 打印虚拟地址内存内容
DebugMessage("虚拟地址 0x%p 的内存内容:\n", VirtualAddress);
PrintMemoryBuffer(VirtualAddress, virtualBuffer, Size);

// 将物理地址映射到系统空间以便读取
mappedPhysAddress = MmMapIoSpace(PhysicalAddress, Size, MmNonCached);
if (mappedPhysAddress != NULL) {
// 为物理地址内存拷贝分配缓冲区
physicalBuffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, Size, 'PBUF');
if (physicalBuffer != NULL) {
// 拷贝物理地址内存内容
RtlCopyMemory(physicalBuffer, mappedPhysAddress, Size);

// 打印物理地址内存内容
DebugMessage("物理地址 0x%016llX 的内存内容:\n", PhysicalAddress.QuadPart);
PrintMemoryBuffer((PVOID)PhysicalAddress.QuadPart, physicalBuffer, Size);

// 释放物理缓冲区
ExFreePool(physicalBuffer);
}

// 解除物理地址映射
MmUnmapIoSpace(mappedPhysAddress, Size);
}
else {
DebugMessage("警告: 无法映射物理地址 0x%016llX\n", PhysicalAddress.QuadPart);
}

// 释放虚拟缓冲区
ExFreePool(virtualBuffer);
}


// 打印内存数据的函数 - 按64位地址格式打印
VOID PrintMemoryBuffer(PVOID VirtualAddress, PVOID Buffer, SIZE_T Size) {
PUCHAR byteBuffer = (PUCHAR)Buffer;
char lineBuf[100];
ULONG_PTR baseAddr = (ULONG_PTR)VirtualAddress;

// 按16字节一行打印
for (SIZE_T i = 0; i < Size; i += 16) {
// 打印64位地址格式的行首地址
int linePos = sprintf_s(lineBuf, sizeof(lineBuf), "%016llX: ", (ULONGLONG)(baseAddr + i));

// 打印十六进制数据
for (SIZE_T j = 0; j < 16 && (i + j) < Size; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%02X ", byteBuffer[i + j]);

// 在第8个字节后添加一个额外的空格
if (j == 7) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}

// 对齐不足16字节的行
if (Size - i < 16) {
SIZE_T spaces = 16 - (Size - i);
for (SIZE_T j = 0; j < spaces; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
if (j == 7 - (Size - i) || (Size - i <= 8 && j == spaces - 1)) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}
}

// 添加ASCII表示
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " |");
for (SIZE_T j = 0; j < 16 && (i + j) < Size; j++) {
UCHAR ch = byteBuffer[i + j];
// 可打印字符显示原样,不可打印字符显示点号
if (ch >= 32 && ch <= 126) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%c", ch);
}
else {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, ".");
}
}
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "|");

// 打印当前行
DebugMessage("%s\n", lineBuf);
}
}

2.读取进程虚拟内存对应的物理地址,将此物理地址映射到系统虚拟内存中读取

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#include <ntifs.h>
#include <stdio.h>

#pragma warning (disable : 4100)
#define DebugMessage(x, ...) DbgPrintEx(0, 0, x, __VA_ARGS__);

// 函数声明
NTSTATUS QueryProcessVirtualAddressMapping(HANDLE ProcessId, PVOID VirtualAddress);
NTSTATUS ReadProcessMemoryByPhysicalAddress(PHYSICAL_ADDRESS PhysicalAddress, PVOID Buffer, SIZE_T Size);
VOID PrintMemoryContents(PVOID Buffer, SIZE_T Size);

// 驱动卸载函数
NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriverObject)
{
DebugMessage("驱动程序已卸载\n");
return STATUS_SUCCESS;
}

// 驱动入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
NTSTATUS status;

// 设置卸载函数
pDriverObject->DriverUnload = UnloadDriver;

// 测试进程虚拟地址映射功能
// 使用指定的PID和虚拟地址(实际使用时需要修改)
HANDLE targetPid = (HANDLE)0x8EC; // 示例PID
PVOID targetAddress = (PVOID)0x7FF682CC0000; // 示例虚拟地址

status = QueryProcessVirtualAddressMapping(targetPid, targetAddress);

return STATUS_SUCCESS;
}

// 查询进程虚拟地址映射函数
NTSTATUS QueryProcessVirtualAddressMapping(HANDLE ProcessId, PVOID VirtualAddress)
{
NTSTATUS status;
PEPROCESS process = NULL;
KAPC_STATE apcState;
UCHAR buffer[64] = { 0 };
PHYSICAL_ADDRESS physicalAddress = { 0 };

DebugMessage("开始查询进程(PID: 0x%X)虚拟地址 0x%p 的映射\n",
(ULONG)(ULONG_PTR)ProcessId, VirtualAddress);

// 1. 获取进程对象
status = PsLookupProcessByProcessId(ProcessId, &process);
if (!NT_SUCCESS(status)) {
DebugMessage("无法找到进程(PID: 0x%X), 错误码: 0x%X\n",
(ULONG)(ULONG_PTR)ProcessId, status);
return status;
}

// 2. 获取进程名称
PUCHAR procNamePtr = (PUCHAR)process + 0x5A8; // Windows 11 中EPROCESS结构的ImageFileName偏移
char imageName[16] = { 0 };
RtlCopyMemory(imageName, procNamePtr, 15);
DebugMessage("已找到进程: %s (PID: 0x%X)\n", imageName, (ULONG)(ULONG_PTR)ProcessId);

// 3. 附加到目标进程
KeStackAttachProcess(process, &apcState);

// 4. 查询虚拟地址映射
__try {
// 检查地址是否有效
if (MmIsAddressValid(VirtualAddress)) {
DebugMessage("虚拟地址 0x%p 有效\n", VirtualAddress);

// 获取物理地址
physicalAddress = MmGetPhysicalAddress(VirtualAddress);
DebugMessage("对应的物理地址: 0x%016llX\n", physicalAddress.QuadPart);

// 通过虚拟地址读取内存
RtlZeroMemory(buffer, sizeof(buffer));
__try {
ProbeForRead(VirtualAddress, sizeof(buffer), 1);
RtlCopyMemory(buffer, VirtualAddress, sizeof(buffer));

DebugMessage("通过虚拟地址读取的内存内容:\n");
PrintMemoryContents(buffer, sizeof(buffer));
}
__except (EXCEPTION_EXECUTE_HANDLER) {
DebugMessage("通过虚拟地址读取内存时发生异常: 0x%X\n", GetExceptionCode());
}
}
else {
DebugMessage("虚拟地址 0x%p 无效\n", VirtualAddress);
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
DebugMessage("查询地址映射时发生异常: 0x%X\n", GetExceptionCode());
}

// 5. 分离进程
KeUnstackDetachProcess(&apcState);

// 6. 如果物理地址有效,尝试通过物理地址读取内存
if (physicalAddress.QuadPart != 0) {
RtlZeroMemory(buffer, sizeof(buffer));
status = ReadProcessMemoryByPhysicalAddress(physicalAddress, buffer, sizeof(buffer));

if (NT_SUCCESS(status)) {
DebugMessage("通过物理地址 0x%016llX 读取的内存内容:\n", physicalAddress.QuadPart);
PrintMemoryContents(buffer, sizeof(buffer));
}
}

// 7. 解除进程对象引用
ObDereferenceObject(process);

return STATUS_SUCCESS;
}

// 通过物理地址读取内存
NTSTATUS ReadProcessMemoryByPhysicalAddress(PHYSICAL_ADDRESS PhysicalAddress, PVOID Buffer, SIZE_T Size)
{
NTSTATUS status = STATUS_SUCCESS;
PVOID mappedAddress = NULL;

// 将物理地址映射到系统空间
mappedAddress = MmMapIoSpace(PhysicalAddress, Size, MmNonCached);
if (mappedAddress == NULL) {
DebugMessage("无法映射物理地址 0x%016llX\n", PhysicalAddress.QuadPart);
return STATUS_UNSUCCESSFUL;
}

// 读取内存内容
__try {
RtlCopyMemory(Buffer, mappedAddress, Size);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
DebugMessage("读取映射内存时发生异常: 0x%X\n", status);
}

// 解除映射
MmUnmapIoSpace(mappedAddress, Size);

return status;
}

// 打印内存内容
VOID PrintMemoryContents(PVOID Buffer, SIZE_T Size)
{
PUCHAR byteBuffer = (PUCHAR)Buffer;
char lineBuf[100];

// 按16字节一行打印
for (SIZE_T i = 0; i < Size; i += 16) {
// 打印行首地址
int linePos = sprintf_s(lineBuf, sizeof(lineBuf), "%04X: ", (UINT32)i);

// 打印十六进制数据
for (SIZE_T j = 0; j < 16 && (i + j) < Size; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%02X ", byteBuffer[i + j]);

// 在第8个字节后添加一个额外的空格用于视觉分隔
if (j == 7) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}

// 补齐不足16字节的部分
if (i + 16 > Size) {
for (SIZE_T j = Size - i; j < 16; j++) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
if (j == 7) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " ");
}
}
}

// 添加ASCII表示
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, " |");
for (SIZE_T j = 0; j < 16 && (i + j) < Size; j++) {
UCHAR ch = byteBuffer[i + j];
// 如果字符是可打印字符,则显示该字符,否则显示点号
if (ch >= 32 && ch <= 126) {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "%c", ch);
}
else {
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, ".");
}
}
linePos += sprintf_s(lineBuf + linePos, sizeof(lineBuf) - linePos, "|");

// 打印当前行
DebugMessage("%s\n", lineBuf);
}
}

3.WinDbg中查看物理地址与虚拟地址之间的映射关系

WinDbg还提供!pte命令,可以查看物理地址与虚拟地址之间的映射关系

1. 物理地址查询虚拟地址

要查看物理地址0x100000映射到哪些虚拟地址,可以使用以下命令:

1
!pa2va 0x100000

这个命令会显示该物理地址当前映射到的系统虚拟地址(如果有映射的话)。

2. 系统虚拟地址查询物理地址

要查看系统虚拟地址0xFFFFB68807741D30对应的物理地址:

1
!vtop 0 0xFFFFB68807741D30

其中第一个参数0是CR3寄存器值,使用0表示使用当前进程的CR3。这个命令会显示虚拟地址的物理映射信息,包括PDE、PTE等页表结构。

3. 使用DT命令查看页表结构

如果需要详细了解页表结构:

1
!pte 0xFFFFB68807741D30    # 显示页表项信息

4. 进程虚拟地址查询物理地址

对于特定进程的虚拟地址(0x7FF682CC0000)映射:

  1. 使用!process 0 0输出可以看出Notepad.exe进程的EPROCESS地址是ffffb6880beee080`。
  2. 使用EPROCESS地址切换进程:
1
.process /p /r 0xffffb6880beee080
  1. 然后读取内存映射:
1
!vtop 0x7FF682CC0000 # 查看进程虚拟地址对应的物理地址
  1. 当完成操作后,如果需要返回到之前的上下文,可以使用:
1
.process 0

这些命令可以帮助理解Windows系统中虚拟地址和物理地址之间的映射关系。在调试驱动程序或分析内存问题时非常有用。需要记住,物理地址到虚拟地址的映射是动态的,可能会随着系统运行而变化。