Windows内核(补笔记)

在高两G空间里面,FS段选择子经过变换后指向的是 _KPCR(系统内核数据结构)

在低两G空间里面,FS段选择子经过变换后指向的是_TEB

MmGetPhysicalAddress的作用: 用于将虚拟地址转换为物理地址。在Windows操作系统中,内核代码可以通过这个函数来获取对应于某个虚拟地址的物理地址。
MmMapIoSpace的作用:于将物理地址范围映射到内核虚拟地址空间,从而使内核模式代码可以访问该物理地址范围。

完善交换内存的代码以及优化

完成下面的代码:

以下代码存在问题,就是如果被交换到磁盘,就有可能会映射失败1723769903917

下面是改进的代码

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
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"));

unsigned int pOldDirBase, pDirBase;
pDirBase = 0x3eb015c0;

__asm
{
cli
mov eax, cr3
mov pOldDirBase, eax//; 保存旧的CR3
mov eax, pDirBase
mov cr3, eax

//; 关闭WP标志位
mov eax, cr0
and eax, not 0x10000
mov cr0, eax
}

PVOID lpBaseAddress = (PVOID)0x6B9370;
PHYSICAL_ADDRESS pa = MmGetPhysicalAddress(lpBaseAddress);
if (pa.QuadPart != NULL)
{
PVOID pMapAddr = MmMapIoSpace(pa, 3, MmNonCached);
if (pMapAddr != NULL)
{
KIRQL oldIrql;
oldIrql = KeGetCurrentIrql();

// 确保 IRQL 降到 PASSIVE_LEVEL
if (oldIrql > PASSIVE_LEVEL) {
KeLowerIrql(PASSIVE_LEVEL);
}

*(unsigned char*)pMapAddr = 0xcc;
*((unsigned char*)pMapAddr + 1) = 0xcc;
*((unsigned char*)pMapAddr + 1) = 0xcc;
RtlCopyMemory(lpBaseAddress, pMapAddr, 3);

// 恢复 IRQL
if (oldIrql > PASSIVE_LEVEL) {
KeRaiseIrql(oldIrql, &oldIrql);
}
}
}
__asm
{
//; 恢复Cr3
mov eax, pOldDirBase
mov cr3, eax
//; 恢复WP标志位
mov eax, cr0
or eax, 0x10000
mov cr0, eax
//; 恢复中断
sti
}
return STATUS_SUCCESS;
}

如何改一个段的权限,各种查表当然是一个方法

但是有一个标志位,改掉的话,所有内存全部可以,只要内存有
这个标志位就是WP标志位(代码中有体现)

值得注意的是,一旦用了cli和sti屏蔽中断,就不能使用 ProbeForWrite 和 ProbeForRead了,大概率是因为这俩函数就是用中断进行操作的。

如果是高版本系统,也就是大于Windows 8.1的版本,可以直接用API的

当然也有缺点,如果版本太高了,就用不了,Win7好像用不了

1723806515446

使用示例:

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
VOID ExampleMmCopyMemory(VOID* UserBuffer, SIZE_T Length)
{
NTSTATUS status;
PVOID kernelBuffer;
SIZE_T copiedBytes;

// Allocate memory in kernel space
kernelBuffer = ExAllocatePoolWithTag(NonPagedPool, Length, 'tag');
if (!kernelBuffer) {
DbgPrint("Failed to allocate kernel buffer.\n");
return;
}

// Zero out the memory
RtlZeroMemory(kernelBuffer, Length);

// Define a source physical address (as an example, we assume the UserBuffer is from user mode)
PHYSICAL_ADDRESS sourceAddress;
sourceAddress.QuadPart = (ULONGLONG)UserBuffer;

// Copy memory from the source address to kernel buffer
status = MmCopyMemory(kernelBuffer,
sourceAddress,
Length,
MM_COPY_MEMORY_VIRTUAL,
&copiedBytes);

if (NT_SUCCESS(status)) {
DbgPrint("Memory copied successfully: %Iu bytes.\n", copiedBytes);
} else {
DbgPrint("MmCopyMemory failed with status: 0x%X\n", status);
}

// Free the allocated kernel buffer
ExFreePoolWithTag(kernelBuffer, 'tag');
}

PsLookupProcessByProcessId 函数用于通过进程 ID 查找进程的内核对象 (PEPROCESS)。

KeStackAttachProcess 函数用于将当前线程附加到另一个进程的地址空间中。调用此函数后,当前线程将执行附加进程的上下文,允许它访问该进程的用户模式地址空间。

使用 KeStackAttachProcess 附加到另一个进程的地址空间后,实际上是将当前线程的上下文切换到目标进程中。这意味着你不需要手动更改 CR3 寄存器来切换页表,因为 KeStackAttachProcess 会处理这个问题。然而,需要注意的是,虽然上下文切换到目标进程后,你的代码还是在内核态执行,而不是在用户态直接执行代码。

以下是一个基本示例,展示如何使用 KeStackAttachProcess 将当前线程附加到另一个进程,并在其上下文中执行内存读写操作:

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

VOID ExecuteInTargetProcess(HANDLE ProcessId)
{
PEPROCESS targetProcess;
KAPC_STATE apcState;
NTSTATUS status;

// 通过进程 ID 查找目标进程
status = PsLookupProcessByProcessId(ProcessId, &targetProcess);
if (!NT_SUCCESS(status)) {
DbgPrint("Failed to find process with ID: %d, status: 0x%X\n", ProcessId, status);
return;
}

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

// 现在在目标进程的上下文中,可以访问目标进程的用户空间内存
__try {
PVOID userBuffer = (PVOID)0x7FFDF000; // 假设的用户空间地址
SIZE_T length = 100;
CHAR data[100];

// 从用户空间读取数据
RtlCopyMemory(data, userBuffer, length);
DbgPrint("Data read from target process: %s\n", data);

// 可以在这里执行更多的操作,比如修改内存内容
// RtlCopyMemory(userBuffer, "Hello, World!", 13);

} __except (EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("Exception occurred while accessing target process memory.\n");
}

// 恢复原始上下文
KeUnstackDetachProcess(&apcState);

// 释放目标进程对象
ObDereferenceObject(targetProcess);
}

值得注意的是:

调用完PsLookUpProcessByProcessId,一定要用ObDereferenceObject去减少进程的引用,否则可能会莫名其妙蓝屏

调用完 KeStackAttachProcessKe,需要调用UnstackDetachProcess

使用 RtlCopyMemory 函数从目标进程的用户空间读取数据。在执行这种操作时,应该使用 __try/__except 块来捕获可能的异常,例如非法内存访问。

有时候想一套代码,可以同时生成x64,x86版本的,这时候就可以使用条件宏去定义

1
2
3
4
5
#ifdef _M_X64
__int64 OldCr0;
#else
unsigned long OldCr0;
#endif

内核重载介绍

在内核中有很多HOOK, 例如:KiFastCallEntry, SSDT,IDT,OBJECT HOOK,甚至是内核API的内联HOOK , 有些HOOK很容易找到,并还原, 有些HOOK就很难找到. 在某些时候(例如一个病毒HOOK了int3中断,这样调试器就无法得到断点事件),清除HOOK是一种反反调试的技术. 也是防护与反防护的技术. —总之内核层的对抗非常的激烈, 为了能够一举将所有的HOOK或者其它防护技术统统屏蔽, 内核重载技术应运而生. 简单来说, 内核重载就是将文件中的内核(ntkrnlpa.exe)重新加载到内存中, 这样一来, 所有对内核中的修改都会被还原.

但是, 直接加载并覆盖到原来的内存的话会产生很多错误,例如, 系统开机之后,会对很多的数据进行初始化, 这些数据被初始化之后系统才能正常运行, 如果直接将内核文件的数据覆盖了,就没有这些数据了. 因此, 可以只将原始内核的代码段加载到内存 , 并通过一些设置, 让原始的内核代码变得可以用

但是具体还没学那么深,就先提一嘴得了。

https://www.cnblogs.com/ltyandy/p/11439466.html

WRK的利用:

WRK 是微软于 2006 年针对教育和学术界开放的 Windows 内核的部分源码,WRK(Windows Research Kernel)也就是 Windows研究内核,在 WRK 中不仅仅只提供了 Windows 内核模块的部分代码,其还提供了编译工具。

如果我们能找到系统加载PE的Loader代码扒出来,这种原厂的加载PE不得超级稳定and有效?😂

用VScode可以很容易找到某些已导出但是未公开的函数