内核驱动修改内存

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核驱动修改内存相关的知识,希望对你有一定的参考价值。

概述

本文会利用内核驱动进行读写取第三方应用内存。

内核实现会使用内联汇编 所以对于内核数据结构每个windwos版本不一样需要判断,本文使用19041所写代码。

命令行:winver 即可查看你当前的版本,如下图19042.631 就是构建版本号

或者调用对应内核API.

BOOLEAN PsGetVersion(
  [out, optional] PULONG          MajorVersion,
  [out, optional] PULONG          MinorVersion,
  [out, optional] PULONG          BuildNumber,
  [out, optional] PUNICODE_STRING CSDVersion
);

PsGetVersion 文档

或者链接windbg的时候查看如下图所示。 19041便是构建号。

获取内核进程信息

在内核中IoGetCurrentProcess可以获取到当前进程对应的结构体

如下图所示

// ntddk.h
PEPROCESS IoGetCurrentProcess();

typedef struct _EPROCESS *PEPROCESS;

IoGetCurrentProcess

_EPROCESS 结构体因构建版本不同而有所差异
可以在windbg 输入如下命令查看

dt nt!_EPROCESS

19041构建版本的核心输出:

nt!_EPROCESS
   +0x000 Pcb                : _KPROCESS
   +...   ....    	         :  ....
   +0x0e4 UniqueProcessId    : Ptr32 Void
   +0x0e8 ActiveProcessLinks : _LIST_ENTRY
   +...   ....    	         :  ....
   +0x1ac ImageFileName      : [15] UChar
   +...   ....    	         :  ....

Pcb: 进程控制块,包含很多核心信息比如分页表分段表信息等
UniqueProcessId : 进程id
ActiveProcessLinks: 所有进程的双向链表循环,可以通过这个遍历所有进程
ImageFileName : 进程名

_LIST_ENTRY 数据结构

nt!_LIST_ENTRY
   +0x000 Flink            : Ptr32 _LIST_ENTRY
   +0x004 Blink            : Ptr32 _LIST_ENTRY

Flink指向上个进程的_EPROCESS对象的ActiveProcessLinks地址
Blink指向下个进程的_EPROCESS对象的ActiveProcessLinks地址

Pcb 是一个非常重要的数据结构这里贴出我们关心的数据结构:


nt!_KPROCESS
   +...   ...                : ....
   
   +0x018 DirectoryTableBase : Uint4B
   +...   ...                : ....


DirectoryTableBase 指向的就是cr3中保存的物理地址.注意是物理地址非虚拟地址。我们知道现代操作系统都在弱化段内存强化分页内存,因此我们需要获取其他进程的分页内存进而直接读取内存数据,这样可以无视任何检测。

我们现在创建一个函数传入一个进程pid然后返回对应的分页物理地址:

PVOID GetDirectoryTableBase(HANDLE  hProcess) 
	
	PEPROCESS Process = NULL;



	__try 
		//DbgBreakPoint();
		Process = IoGetCurrentProcess();
		//IoGetCurrentProcess内核实现其实非常简单如下所示,可以使用windbg : u IoGetCurrentProcess 查看反编译
			//__asm 
	//	//fs 指向 _kpcr 的结构
	//	//fs  0x120指向_KPRCB
	//	//_KPRCB偏移0x4指向 _KTHREAD 
	//	//也就是fs : [00000124h]指向一个_KTHREAD结构、、 _ETHTREAD(PsGetCurrentThread函数就是eax, dword ptr fs : [00000124h] 实现的)
	//	//
	//	mov     eax, dword ptr fs : [00000124h]
	//	mov     eax, dword ptr[eax + 80h]
	//	mov		Process,eax
	//
		PEPROCESS Head = Process;
		//遍历双向链表
		while (Process)
		
			//校验内存是否在物理页映射了( 是否分页结构体p标志位有效)
			if (MmIsAddressValid(Process)) 
			    //相关偏移
				int pidOffset = 0x0e4;
				int imageFileNameOffset = 0x1ac;
				int DirectoryTableBaseOffset = 0x018;
				int activeProcessLinksOffset = 0x0e8;
				if (MmIsAddressValid((char*)Process + pidOffset)) 
					// _EPROCESS --->>+0x0e4 UniqueProcessId  : Ptr32 Void
					//获取进程pid
					HANDLE ProcessID = *(HANDLE*)((char*)Process + pidOffset);
					if (MmIsAddressValid((char*)Process + imageFileNameOffset)) 
						//_EPROCESS --->>  +0x1ac ImageFileName    : [15] UChar
						//获取进程名称
						UCHAR* ImageFileName = (UCHAR *)((char*)Process + imageFileNameOffset);
						if (MmIsAddressValid((char*)Process + DirectoryTableBaseOffset)) 
							//_EPROCESS --->> +0x000 Pcb              : _KPROCESS
							//_KPROCESS --->>    +0x018 DirectoryTableBase : Uint4B
							//分页地址
							PVOID DirectoryTableBase = *(PVOID*)((char*)Process + DirectoryTableBaseOffset);
							//如果当前进程pid和寻找pid相同直接返回分页地址
							if (ProcessID == hProcess)
							
								DbgPrint("pid %d ImageFileName :%s DirectoryTableBase:%p\\n", ProcessID, ImageFileName, DirectoryTableBase);

								return DirectoryTableBase;
							
							else 
								DbgPrint("[My learning] find next \\r\\n", __FUNCTION__);
							
			
							if (MmIsAddressValid((char*)Process + activeProcessLinksOffset)) 
								//   _EPROCESS --->>  +0x0e8 ActiveProcessLinks : _LIST_ENTRY
								//遍历双向链表,寻找下一个
								PLIST_ENTRY Entry = (PLIST_ENTRY)((char*)Process + activeProcessLinksOffset);
								Process = (PEPROCESS)((char *)Entry->Flink - activeProcessLinksOffset);
								//兜底双向链表全部搜寻不到
								if (Process == Head)
								
									DbgPrint("[My learning] Process == Head \\r\\n ", __FUNCTION__);
									break;
								
							
						
					
				
			
		
	
	__except (1) 
		Process = NULL;
		DbgPrint("[My learning] %s __exception\\r\\n", __FUNCTION__);
	

	return NULL;

读取内存信息


PVOID pOldDirectoryTableBase;
//hProcess目标进程
//lpBaseAddress读取的进程内存地址
//lpBuffer读取到哪 必须内核地址
//nSize 读取字节
NTSTATUS MyReadProcessMemory(
	HANDLE  hProcess,
	PVOID lpBaseAddress,
	PVOID  lpBuffer,
	SIZE_T  nSize) 



	__try 
		//获取目标进程的页表
		PVOID pDirectoryTableBase = GetDirectoryTableBase((HANDLE)hProcess);
		if (pDirectoryTableBase == NULL)
		
			return STATUS_UNSUCCESSFUL;
		
		__asm 
			//防止当前进程被切换出去
			cli
			//保存环境
			pushad
			pushf

			//保存旧的CR3
			mov eax, cr3
			mov pOldDirectoryTableBase, eax

			//修改CR3
			mov eax, pDirectoryTableBase
			mov cr3, eax
		
		if (MmIsAddressValid(lpBaseAddress))
		
			ProbeForRead(lpBaseAddress, nSize, 4);
			RtlCopyMemory(lpBuffer, lpBaseAddress, nSize);
			DbgPrint("[My learning] MmIsAddressValid is Ok\\r\\n", __FUNCTION__);
		


		__asm 
			//恢复环境
			mov eax, pOldDirectoryTableBase
			mov pOldDirectoryTableBase, eax
			popf
			popad
			//恢复
			sti
		

		DbgPrint("[My learning] STATUS_SUCCESS\\r\\n", __FUNCTION__);

		return STATUS_SUCCESS;
	
	__except (1) 
		DbgPrint("[My learning] %s __exception\\r\\n", __FUNCTION__);
		__asm 
			//恢复环境
			mov eax, pOldDirectoryTableBase
			mov pOldDirectoryTableBase, eax
			popf
			popad
			//恢复
			sti
		
	

	DbgPrint("[My learning] STATUS_UNSUCCESSFUL\\r\\n", __FUNCTION__);

	return STATUS_UNSUCCESSFUL;

核心思想:把目标进程的页目录置换到自己的内核进程中,那么你读取内存信息操作系统通过MMU等来自己算出结果。

__asm 
			//略....

			//保存旧的CR3
			mov eax, cr3
			mov pOldDirectoryTableBase, eax

			//修改CR3
			mov eax, pDirectoryTableBase
			mov cr3, eax

当我们读出结果的时候需要把cr3换回来

__asm 
			//恢复环境
			mov eax, pOldDirectoryTableBase
			mov pOldDirectoryTableBase, eax

cli指令的作用是用于不响应中断,那么会导致操作变为原子执行,直到调用sti才能恢复。这里是为了防止内核进程,当前运行此代码的线程被切走运行当前进程的其他代码,但是此时cr3已经变成了目标进程的页目录,切换线程不会更新cr3会导致可能读写到目标进程数据的风险。

另外注意cli会导致__try__except 不能捕获异常(无法响应中断的后果,所以上面的代码存在逻辑问题)

tip:

  1. CR3存放当前进程的页目录物理地址
  2. CR3切换线程不会改变
  3. windbg 的!process 0 0 命令可以查看所有进程信息

写内存信息

我们知道页表中有读写属性,如果我们写入的是具有只读的内存那么回抛出错误,我们可以通过简单修改CR0.WP为0来关闭这个保护。

cr0寄存器如下:


NTSTATUS MyWriteProcessMemory(
	HANDLE  hProcess,
	PVOID lpBaseAddress,
	PVOID  lpBuffer,
	SIZE_T  nSize) 




	NTSTATUS Status = STATUS_SUCCESS;

	PVOID pDirectoryTableBase = GetDirectoryTableBase((HANDLE)hProcess);
	
	if (pDirectoryTableBase == NULL)
	
		DbgPrint("[My learning] pDirectoryTableBase ==null %s\\r\\n", __FUNCTION__);
		return STATUS_UNSUCCESSFUL;
	
	KdBreakPoint();
	__asm 
		//防止当前进程被切换出去
		cli

		//保存环境
		//pushad
		//pushf
		//保存旧的CR3
		mov eax, cr3
		mov pOldDirectoryTableBase, eax

		//修改CR3
		mov eax, pDirectoryTableBase
		mov cr3, eax

		//关闭写保护
		mov eax,cr0
		and eax,not 10000h
		mov cr0,eax

		

		
	

	if (MmIsAddressValid(lpBaseAddress))
	
		RtlCopyMemory(lpBaseAddress, lpBuffer, nSize);
		DbgPrint("[My learning] MmIsAddressValid is %s \\r\\n", __FUNCTION__);
	
	else 
		DbgPrint("[My learning] MmIsAddressinValid is %s \\r\\n", __FUNCTION__);
	

	__asm 
		//恢复环境
		mov eax, pOldDirectoryTableBase
		mov cr3, eax
		//popfd
		//popad
	

		//关闭写保护
		mov eax, cr0
		or eax,  10000h
		mov cr0, eax

		//恢复
		sti
	


	return Status;

源码

https://github.com/fanmingyi/winkernel_read_mm

以上是关于内核驱动修改内存的主要内容,如果未能解决你的问题,请参考以下文章

内核驱动修改内存

往android的内核添加驱动及 ueventd.rc 修改

Linux内核USB驱动

linux内核驱动module_init解析

Windows驱动开发-内核常用内存函数

内核模式和用户模式驱动程序