概述

东方通更换java版本 东方通修改内存_链表

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

内核实现会使用内联汇编 所以对于内核数据结构每个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便是构建号。

东方通更换java版本 东方通修改内存_分页_02

获取内核进程信息

在内核中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地址

东方通更换java版本 东方通修改内存_分页_03

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 不能捕获异常(无法响应中断的后果,所以上面的代码存在逻辑问题)

东方通更换java版本 东方通修改内存_东方通更换java版本_04

tip:

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

写内存信息

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

cr0寄存器如下:

东方通更换java版本 东方通修改内存_东方通更换java版本_05

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;
}

利用API实现

void DisableWP() {
	ULONG_PTR cr0=__readcr0();
	cr0 &= ~0x10000;
	__writecr0(cr0);
}

void EnableWP() {
	ULONG_PTR cr0 = __readcr0();
	cr0 |= 0x10000;
	__writecr0(cr0);
}
NTSTATUS MyWriteProcessMemory(
	HANDLE  hProcess,
	PVOID lpBaseAddress,
	PVOID  lpBuffer,
	SIZE_T  nSize) {

	PEPROCESS Process = NULL;
	KdBreakPoint();
	NTSTATUS Status = PsLookupProcessByProcessId(hProcess, &Process);

	if (!NT_SUCCESS(Status))
	{
		return Status;
	}

	//切换进程
	KAPC_STATE  ApcSate;
	KeStackAttachProcess(Process, &ApcSate);


	//内存拷贝
	DisableWP();

	__try {
		RtlCopyMemory(lpBaseAddress, lpBuffer, nSize);
	}
	__except (1) {

	}
	
	EnableWP();
	//恢复进程
	KeUnstackDetachProcess(&ApcSate);

	//释放引用
	if (Process)
	{
		ObDereferenceObject(Process);
	}

	return Status;
}


NTSTATUS MyReadProcessMemory(
	HANDLE  hProcess,
	PVOID lpBaseAddress,
	PVOID  lpBuffer,
	SIZE_T  nSize) {


	PEPROCESS Process = NULL;
	NTSTATUS Status = PsLookupProcessByProcessId(hProcess, &Process);

	if (!NT_SUCCESS(Status))
	{
		return Status;
	}

	//切换进程
	KAPC_STATE  ApcSate;
	KeStackAttachProcess(Process, &ApcSate);

	PHYSICAL_ADDRESS pa = { 0 };
	pa = MmGetPhysicalAddress(lpBaseAddress);
	PVOID lpBaseMap = MmMapIoSpace(pa, nSize, MmNonCached);
	if (lpBaseMap != NULL)
	{
		//内存拷贝
		RtlCopyMemory(lpBuffer, lpBaseMap, nSize);
		MmUnmapIoSpace(lpBaseMap, nSize);
	}
	//恢复进程
	KeUnstackDetachProcess(&ApcSate);

	//释放引用
	if (Process)
	{
		ObDereferenceObject(Process);
	}

	return Status;
}

源码

https://github.com/fanmingyi/winkernel_read_mm