Windows

修改对方进程内存

步骤

1.获取对方进程的页目录表

2.让自己程序的Cr3=对方进程的页目录表

3.然后执行自己程序,访问对方进程的内存地址,就可以对对方的内存进行操作

4.再还原成自己程序的Cr3

以上步骤有几个值得思考的问题

问题1

1.为什么把自己进程的Cr3修改为对方进程的页目录表还是可以执行自己的代码?自己的虚拟地址按照cr3进行查表,查出来的物理地址不会变吗??

这是有要求的,自己的程序必须是驱动,这样才能运行在系统的高两G。
而高两G是享有一样的页表的,才能保证在进程间共享

1722349586980

问题2

2.CPU在切换线程的时候,CR3会改变,那咋整

有时候我们写的驱动程序不一定只有一个线程,那么假设我们有两个线程,一个线程去执行将自己的cr3修改为一个三环进程的cr3,当修改成功后,好巧不巧,cpu将进程切换了,正巧切换成自己程序的另一个线程,那么cr3可能就会被改回原来的cr3,此时线程继续执行,因为cr3已经不是对方进程的了,因此访问对方进程就会崩溃,系统蓝屏。

那如何解决这个问题?

提升IRQL?
别以为稳了,因为如果碰巧要访问的地址是分页内存,被交换到了硬盘上,那就直接寄了,因为IRQL等级高,不能切换线程去把这个分页地址从物理地址拿出来。就会蓝屏

方法1:将要访问的地址映射为一个内核地址,这样就可以操作了,这样就不会造成缺页异常

方法2:cpu如何切线程?cpu有一个硬件叫时钟的硬件,每一段时间就会给cpu一个信号,这个信号就叫做中断信号,这样cpu就知道过去了多久,如果我们把这个信号屏蔽了,这样cpu就不会切线程了
cli(屏蔽中断) sti(恢复中断)
这样cpu就不知道过了多久,也就不知道切线程了

这样改的是屏蔽当前核心的中断

几个小测试:

查到的资料说高两G内存的共享的,于是我们测试一下:
测试将一个驱动程序的cr3改为一个随便一个进程的,看驱动是否能够正常运行

测试代码:

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
#include "header.h"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
KdPrint(("Create!"));
KdBreakPoint();
// 驱动程序卸载例程&注册例程
DriverObject->DriverUnload = DriverUnloadRoutine;
//创建设备
NTSTATUS status;
PDEVICE_OBJECT DeviceObject = NULL;
UNICODE_STRING DeviceName;
RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDevice");
status = IoCreateDevice(
DriverObject, // 驱动程序对象
0, // 设备扩展大小
&DeviceName, // 设备名称
FILE_DEVICE_UNKNOWN, // 设备类型
0, // 设备特征
FALSE, // 非独占设备
&DeviceObject // 返回的设备对象指针
);

if (!NT_SUCCESS(status))
{
KdPrint(("Failed to create device: %X\n", status));
return status;
}
KdPrint(("Device created successfully\n"));

UNICODE_STRING symbolicLink = RTL_CONSTANT_STRING(L"\\??\\MyDevice_Link");
status = IoCreateSymbolicLink(&symbolicLink, &DeviceName);
if (!NT_SUCCESS(status))
{
KdPrint(("Failed to create device: %X\n", status));
return status;
}
KdPrint(("Device created successfully\n"));
return STATUS_SUCCESS;
}
VOID DriverUnloadRoutine(IN PDRIVER_OBJECT DriverObject)
{
if (DriverObject->DeviceObject != NULL)
{
UNICODE_STRING symbolicLink = RTL_CONSTANT_STRING(L"\\??\\MyDevice_Link");
IoDeleteSymbolicLink(&symbolicLink);
IoDeleteDevice(DriverObject->DeviceObject);
}

DbgPrint("Driver unloaded\n");
}

在驱动下了个断点,查看寄存器之后,发现此时cr3是185000(驱动本身不是一个进程, 它是一个由操作系统管理的内核模式下运行的模块 在某种意义上“附属于”System进程,因为它们由操作系统管理,并且在内核模式下运行,而System进程是管理这些内核模式组件的关键部分。 )

1722401639620

然后我们手动修改它的cr3:

1722401770363

随便挑一个,改为0x3eed2020

发现是可以运行的!

为了验证是不是cr3随便一个值都行,我甚至将cr3改为0

1722401887658

结果果然崩溃了。

因此结论是:将驱动的cr3改为任意进程的cr3,都可以运行,没有问题的

2.测试两个驱动程序的页表是否相同

这个问题是我一开始学页表的时候想到的,现在看来有点蠢

本身驱动程序就是属于System这个进程的模块,而Cr3是每一个进程独有的,而不是每一个模块独有的,因此所有驱动程序的Cr3都是System这个进程的cr3,因此页表都是一样的

自己实现!Process 0 0的功能

这时候就可以去逆向内核了

查阅资料我们可以知道,内核程序有
ntoskrnl.exe(单核,不开PAE)
ntkrnlpa.exe(单核,开了PAE)
ntkrnlmp.exe (多核处理器内核,不开PAE)
ntkrpamp.exe (多核处理器内核,开了PAE)

如何查看我们当前是用哪个内核,可以用Windbg

我们可以查看程序的模块:

1
2
3
1: kd> lm
start end module name
83c09000 84032000 nt (pdb symbols) c:\symbolslocal\ntkrpamp.pdb\49C89B090A944A63AFF6A937166259C22\ntkrpamp.pdb

nt是 Windows 操作系统的内核模块。 根据PDB我们可以知道,当前我使用的内核程序是ntkrpamp.exe

但是值得注意的是:现代版本的 Windows 已经合并了不同的内核版本,统一使用 ntoskrnl.exe 作为内核映像文件,并在系统启动时动态决定使用哪些功能和优化。

1722426213477

真是可恶啊,害我找半天ntkrpamp.exe,到处都没有翻到,原来你小子统一叫ntoskrnl.exe了,真淦

Windbg可以支持模糊搜索,x命令用于查找模块中包含特定名称的符号

1
x nt!*swap*

通过查资料,我们可以知道一个API叫做KiSwapProcess这个函数,这个函数可以用来切进程,可以看看是否涉及进程遍历。

有时候遇到函数没有导出的,我们可以用找到nt模块基地址,计算出函数的偏移,然后再加上这个内核exe的ImageBase就可以在IDA找到对应的函数了

1
lm /m nt  #提一下,/m是用来过滤的

用IDA查看伪代码可以发现

17225035589731722503714494

最终往cr3写入的是一个叫做v4的变量

通过不断溯源,不断查看引用,最终我们发现,这个v4变量来源为:

1722503813495

来自一个PRKPROCESS结构的变量(我觉得叫PKPROCESS会好一点,P后面那个R我也不知道干啥的,也许是调用约定?),指向KPROCESS结构体

1722504065237

查看发现,KiSwapProcess拿的就是KPROCESS结构体里面的DirectoryTableBase

那么我们的问题变成,如何遍历所有进程的KPROCESS结构体
所以我们要思考,有哪些API会遍历所有进程对象

我们想到PsGetCurrentProcess

1722504460855

发现返回的是一个PEPROCESS的一个结构体,EPROCESS又是什么结构体?我们拿Windbg看一下

1722504614983

发现是一个包含的关系,也就是EPROCESS包含KPROCESS,而且KPROCESS还是在第一个,说明KPROCESS和EPROCESS是同地址

逆向PsGetCurrentProcess函数:

1722519126927

1722519184445

发现那这个结构体非常简单,就三行汇编代码,去fs寄存器地址加上124h的偏移就拿到了

那这个fs:124h又是何方神圣呢?我们再去逆向KeGetCurrentThread这个API函数
1722519638690

这下我们知道了,fs:124存的是ETHREAD结构体

我们捋一下思路,目前是ETHREAD->EPROCESS->DIRBASE

那如何获取全部的EPROCESS呢?

1722520592764

这里有一个链表结构,这个链表就可以遍历到所有进程

这个Link结构指向下一个EPROCESS的Link结构

fs:0是这个结构体:KPCR (Kernel Processor Control Region)

如何查看fs的虚拟地址?在之前的分段机制提到了,但是这里可以直接用Windbg的功能实现:

1
dg fs

1722521702444

这样可以列出来具体KPCR 这个结构体的基地址

这样,我们就可以写驱动程序去遍历EPROCESS从而拿到DirBase了

下面是输出全部进程的信息,再指定输出某进程的DirBase

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
VOID Traversal_Eprocess()
{
ULONG KTHREAD = NULL;
ULONG KPROCESS = NULL;
ULONG EPROCESS = NULL;
LIST_ENTRY* PActiveProcessLinks = nullptr;

KdBreakPoint();

__asm
{
mov eax,fs:[0x124]
mov KTHREAD ,eax //获取ETHREAD

mov eax,[eax+0x50]
mov KPROCESS,eax //获取KPROCESS
}

EPROCESS = KPROCESS;

PActiveProcessLinks = (LIST_ENTRY*)(EPROCESS + 0xb8);

while ((ULONG)(*(ULONG*)PActiveProcessLinks->Flink - 0xb8) != EPROCESS)
{
ULONG Temp_KPROCESS = (ULONG)(*(ULONG*)PActiveProcessLinks->Flink - 0xb8);
ULONG DirBaseAddr = *(ULONG*)(Temp_KPROCESS + 0x18);
CHAR* ImageFileName = (CHAR*)(Temp_KPROCESS + 0x16C);
if (!strcmp(ImageFileName,"Solve.exe"))
{
Solve_DirBase = DirBaseAddr;
}
KdPrint(("进程%s的DirBase为0x%x\n", ImageFileName, DirBaseAddr));
PActiveProcessLinks = PActiveProcessLinks->Flink;
}

KdPrint(("Solve.exe的DirBase为0x%x\n", Solve_DirBase));
}

PsGetNextProcess

我们也可以直接用这个函数,去获得所有的EPROCESS结构体

1
2
3
4
PEPROCESS Process = NULL;
while ((Process = PsGetNextProcess(Process)) != NULL) {
// 对每个进程对象进行操作
}

虽然简单,但是还是有风险的,如果这个函数被HOOK了,那就无法正常获取了

通过Cr3写代码:

用户代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;
int main()
{
char str[256] = { 'h','e','l' ,'l' ,'o' ,' ' ,'w' ,'o' ,'r' ,'l' ,'d' ,'\x00' };
cout <<hex<<"0x" << &str << endl;
int a;
cin >> a;
int b = a * 8;
cout << str << endl;
system("pause");
return 0;
}

驱动代码:

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

ULONG Solve_DirBase = NULL;

VOID Traversal_Eprocess()
{

ULONG KTHREAD = NULL;
ULONG KPROCESS = NULL;
ULONG EPROCESS = NULL;
LIST_ENTRY* PActiveProcessLinks = nullptr;

KdBreakPoint();

__asm
{
mov eax,fs:[0x124]
mov KTHREAD ,eax //获取ETHREAD

mov eax,[eax+0x50]
mov KPROCESS,eax //获取KPROCESS
}

EPROCESS = KPROCESS;

PActiveProcessLinks = (LIST_ENTRY*)(EPROCESS + 0xb8);

while ((ULONG)(*(ULONG*)PActiveProcessLinks->Flink - 0xb8) != EPROCESS)
{
ULONG Temp_KPROCESS = (ULONG)(*(ULONG*)PActiveProcessLinks->Flink - 0xb8);
ULONG DirBaseAddr = *(ULONG*)(Temp_KPROCESS + 0x18);
CHAR* ImageFileName = (CHAR*)(Temp_KPROCESS + 0x16C);
if (!strcmp(ImageFileName,"Solve.exe"))
{
Solve_DirBase = DirBaseAddr;
}
KdPrint(("进程%s的DirBase为0x%x\n", ImageFileName, DirBaseAddr));
PActiveProcessLinks = PActiveProcessLinks->Flink;
}

KdPrint(("Solve.exe的DirBase为0x%x\n", Solve_DirBase));
}

VOID Open()
{
ULONG cr0 = __readcr0();
cr0 &= 0xFFFEFFFF;
__writecr0(cr0);
_disable();
}




VOID Edit_Memory()
{

ULONG Old_cr3 = NULL;
ULONG Ring3_Virtual = 0x1AFCB8;
Open();
__asm
{
mov eax, cr3
mov Old_cr3, eax
mov eax, Solve_DirBase
mov cr3,eax
}

strcpy((char*)Ring3_Virtual, "123456");

__asm
{
mov eax, Old_cr3
mov cr3, eax
}
}




NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
KdPrint(("Create!"));

// 驱动程序卸载例程&注册例程
DriverObject->DriverUnload = DriverUnloadRoutine;
//创建设备
NTSTATUS status;
PDEVICE_OBJECT DeviceObject = NULL;
UNICODE_STRING DeviceName;
RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDevice");
status = IoCreateDevice(
DriverObject, // 驱动程序对象
0, // 设备扩展大小
&DeviceName, // 设备名称
FILE_DEVICE_UNKNOWN, // 设备类型
0, // 设备特征
FALSE, // 非独占设备
&DeviceObject // 返回的设备对象指针
);

if (!NT_SUCCESS(status))
{
KdPrint(("Failed to create device: %X\n", status));
return status;
}
KdPrint(("Device created successfully\n"));

UNICODE_STRING symbolicLink = RTL_CONSTANT_STRING(L"\\??\\MyDevice_Link");
status = IoCreateSymbolicLink(&symbolicLink, &DeviceName);
if (!NT_SUCCESS(status))
{
KdPrint(("Failed to create device: %X\n", status));
return status;
}
KdPrint(("Device created successfully\n"));

Traversal_Eprocess();
Edit_Memory();
return STATUS_SUCCESS;
}




VOID DriverUnloadRoutine(IN PDRIVER_OBJECT DriverObject)
{
if (DriverObject->DeviceObject != NULL)
{
UNICODE_STRING symbolicLink = RTL_CONSTANT_STRING(L"\\??\\MyDevice_Link");
IoDeleteSymbolicLink(&symbolicLink);
IoDeleteDevice(DriverObject->DeviceObject);
}

DbgPrint("Driver unloaded\n");
}