Windows内核(补笔记) 在高两G空间里面,FS段选择子经过变换后指向的是 _KPCR(系统内核数据结构)
在低两G空间里面,FS段选择子经过变换后指向的是_TEB
MmGetPhysicalAddress的作用: 用于将虚拟地址转换为物理地址。在Windows操作系统中,内核代码可以通过这个函数来获取对应于某个虚拟地址的物理地址。 MmMapIoSpace的作用:于将物理地址范围映射到内核虚拟地址空间,从而使内核模式代码可以访问该物理地址范围。
完善交换内存的代码以及优化 完成下面的代码:
以下代码存在问题,就是如果被交换到磁盘,就有可能会映射失败
下面是改进的代码
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!" )); 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 mov eax, pDirBase mov cr3, eax 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 (); 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 ); if (oldIrql > PASSIVE_LEVEL) { KeRaiseIrql (oldIrql, &oldIrql); } } } __asm { mov eax, pOldDirBase mov cr3, eax mov eax, cr0 or eax, 0x10000 mov cr0, eax sti } return STATUS_SUCCESS; }
如何改一个段的权限,各种查表当然是一个方法
但是有一个标志位,改掉的话,所有内存全部可以写 ,只要内存有 这个标志位就是WP 标志位(代码中有体现)
值得注意的是,一旦用了cli和sti屏蔽中断,就不能使用 ProbeForWrite 和 ProbeForRead了,大概率是因为这俩函数就是用中断进行操作的。
如果是高版本系统,也就是大于Windows 8.1的版本,可以直接用API的
当然也有缺点,如果版本太高了,就用不了,Win7好像用不了
使用示例:
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; kernelBuffer = ExAllocatePoolWithTag (NonPagedPool, Length, 'tag' ); if (!kernelBuffer) { DbgPrint ("Failed to allocate kernel buffer.\n" ); return ; } RtlZeroMemory (kernelBuffer, Length); PHYSICAL_ADDRESS sourceAddress; sourceAddress.QuadPart = (ULONGLONG)UserBuffer; 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); } 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; 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); } __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可以很容易找到某些已导出但是未公开的函数