• 驱动开发:内核实现进程汇编与反汇编


    在笔者上一篇文章《驱动开发:内核MDL读写进程内存》简单介绍了如何通过MDL映射的方式实现进程读写操作,本章将通过如上案例实现远程进程反汇编功能,此类功能也是ARK工具中最常见的功能之一,通常此类功能的实现分为两部分,内核部分只负责读写字节集,应用层部分则配合反汇编引擎对字节集进行解码,此处我们将运用capstone引擎实现这个功能。

    首先是实现驱动部分,驱动程序的实现是一成不变的,仅仅只是做一个读写功能即可,完整的代码如下所示;

    // 署名权
    // right to sign one's name on a piece of work
    // PowerBy: LyShark
    // Email: me@lyshark.com
    #include 
    #include 
    
    #define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
    #define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)
    
    #define DEVICENAME L"\\Device\\ReadWriteDevice"
    #define SYMBOLNAME L"\\??\\ReadWriteSymbolName"
    
    typedef struct
    {
    	DWORD pid;       // 进程PID
    	UINT64 address;  // 读写地址
    	DWORD size;      // 读写长度
    	BYTE* data;      // 读写数据集
    }ProcessData;
    
    // MDL读取封装
    BOOLEAN ReadProcessMemory(ProcessData* ProcessData)
    {
    	BOOLEAN bRet = TRUE;
    	PEPROCESS process = NULL;
    
    	// 将PID转为EProcess
    	PsLookupProcessByProcessId(ProcessData->pid, &process);
    	if (process == NULL)
    	{
    		return FALSE;
    	}
    
    	BYTE* GetProcessData = NULL;
    	__try
    	{
    		// 分配堆空间 NonPagedPool 非分页内存
    		GetProcessData = ExAllocatePool(NonPagedPool, ProcessData->size);
    	}
    	__except (1)
    	{
    		return FALSE;
    	}
    
    	KAPC_STATE stack = { 0 };
    	// 附加到进程
    	KeStackAttachProcess(process, &stack);
    
    	__try
    	{
    		// 检查进程内存是否可读取
    		ProbeForRead(ProcessData->address, ProcessData->size, 1);
    
    		// 完成拷贝
    		RtlCopyMemory(GetProcessData, ProcessData->address, ProcessData->size);
    	}
    	__except (1)
    	{
    		bRet = FALSE;
    	}
    
    	// 关闭引用
    	ObDereferenceObject(process);
    
    	// 解除附加
    	KeUnstackDetachProcess(&stack);
    
    	// 拷贝数据
    	RtlCopyMemory(ProcessData->data, GetProcessData, ProcessData->size);
    
    	// 释放堆
    	ExFreePool(GetProcessData);
    	return bRet;
    }
    
    // MDL写入封装
    BOOLEAN WriteProcessMemory(ProcessData* ProcessData)
    {
    	BOOLEAN bRet = TRUE;
    	PEPROCESS process = NULL;
    
    	// 将PID转为EProcess
    	PsLookupProcessByProcessId(ProcessData->pid, &process);
    	if (process == NULL)
    	{
    		return FALSE;
    	}
    
    	BYTE* GetProcessData = NULL;
    	__try
    	{
    		// 分配堆
    		GetProcessData = ExAllocatePool(NonPagedPool, ProcessData->size);
    	}
    	__except (1)
    	{
    		return FALSE;
    	}
    
    	// 循环写出
    	for (int i = 0; i < ProcessData->size; i++)
    	{
    		GetProcessData[i] = ProcessData->data[i];
    	}
    
    	KAPC_STATE stack = { 0 };
    
    	// 附加进程
    	KeStackAttachProcess(process, &stack);
    
    	// 分配MDL对象
    	PMDL mdl = IoAllocateMdl(ProcessData->address, ProcessData->size, 0, 0, NULL);
    	if (mdl == NULL)
    	{
    		return FALSE;
    	}
    
    	MmBuildMdlForNonPagedPool(mdl);
    
    	BYTE* ChangeProcessData = NULL;
    
    	__try
    	{
    		// 锁定地址
    		ChangeProcessData = MmMapLockedPages(mdl, KernelMode);
    
    		// 开始拷贝
    		RtlCopyMemory(ChangeProcessData, GetProcessData, ProcessData->size);
    	}
    	__except (1)
    	{
    		bRet = FALSE;
    		goto END;
    	}
    
    	// 结束释放MDL关闭引用取消附加
    END:
    	IoFreeMdl(mdl);
    	ExFreePool(GetProcessData);
    	KeUnstackDetachProcess(&stack);
    	ObDereferenceObject(process);
    
    	return bRet;
    }
    
    NTSTATUS DriverIrpCtl(PDEVICE_OBJECT device, PIRP pirp)
    {
    	PIO_STACK_LOCATION stack;
    	stack = IoGetCurrentIrpStackLocation(pirp);
    	ProcessData* ProcessData;
    
    	switch (stack->MajorFunction)
    	{
    
    	case IRP_MJ_CREATE:
    	{
    		break;
    	}
    
    	case IRP_MJ_CLOSE:
    	{
    		break;
    	}
    
    	case IRP_MJ_DEVICE_CONTROL:
    	{
    		// 获取应用层传值
    		ProcessData = pirp->AssociatedIrp.SystemBuffer;
    
    		DbgPrint("进程ID: %d | 读写地址: %p | 读写长度: %d \n", ProcessData->pid, ProcessData->address, ProcessData->size);
    
    		switch (stack->Parameters.DeviceIoControl.IoControlCode)
    		{
    		// 读取函数
    		case READ_PROCESS_CODE:
    		{
    			ReadProcessMemory(ProcessData);
    			break;
    		}
    		// 写入函数
    		case WRITE_PROCESS_CODE:
    		{
    			WriteProcessMemory(ProcessData);
    			break;
    		}
    
    		}
    
    		pirp->IoStatus.Information = sizeof(ProcessData);
    		break;
    	}
    
    	}
    
    	pirp->IoStatus.Status = STATUS_SUCCESS;
    	IoCompleteRequest(pirp, IO_NO_INCREMENT);
    	return STATUS_SUCCESS;
    }
    
    VOID UnDriver(PDRIVER_OBJECT driver)
    {
    	if (driver->DeviceObject)
    	{
    		UNICODE_STRING SymbolName;
    		RtlInitUnicodeString(&SymbolName, SYMBOLNAME);
    
    		// 删除符号链接
    		IoDeleteSymbolicLink(&SymbolName);
    		IoDeleteDevice(driver->DeviceObject);
    	}
    }
    
    NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
    {
    	NTSTATUS status = STATUS_SUCCESS;
    	PDEVICE_OBJECT device = NULL;
    	UNICODE_STRING DeviceName;
    
    	DbgPrint("[LyShark] hello lyshark.com \n");
    
    	// 初始化设备名
    	RtlInitUnicodeString(&DeviceName, DEVICENAME);
    
    	// 创建设备
    	status = IoCreateDevice(Driver, sizeof(Driver->DriverExtension), &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &device);
    	if (status == STATUS_SUCCESS)
    	{
    		UNICODE_STRING SymbolName;
    		RtlInitUnicodeString(&SymbolName, SYMBOLNAME);
    
    		// 创建符号链接
    		status = IoCreateSymbolicLink(&SymbolName, &DeviceName);
    
    		// 失败则删除设备
    		if (status != STATUS_SUCCESS)
    		{
    			IoDeleteDevice(device);
    		}
    	}
    
    	// 派遣函数初始化
    	Driver->MajorFunction[IRP_MJ_CREATE] = DriverIrpCtl;
    	Driver->MajorFunction[IRP_MJ_CLOSE] = DriverIrpCtl;
    	Driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverIrpCtl;
    
    	// 卸载驱动
    	Driver->DriverUnload = UnDriver;
    
    	return STATUS_SUCCESS;
    }
    

    上方的驱动程序很简单关键部分已经做好了备注,此类驱动换汤不换药没啥难度,接下来才是本节课的重点,让我们开始了解一下Capstone这款反汇编引擎吧,Capstone是一个轻量级的多平台、多架构的反汇编框架。Capstone旨在成为安全社区中二进制分析和反汇编的终极反汇编引擎,该引擎支持多种平台的反汇编,非常推荐使用。

    这款反汇编引擎如果你想要使用它则第一步就是调用cs_open()官方对其的解释是打开一个句柄,这个打开功能其中的参数如下所示;

    • 参数1:指定模式 CS_ARCH_X86 表示为Windows平台
    • 参数2:执行位数 CS_MODE_32为32位模式,CS_MODE_64为64位
    • 参数3:打开后保存的句柄&dasm_handle

    第二步也是最重要的一步,调用cs_disasm()反汇编函数,该函数的解释如下所示;

    • 参数1:指定dasm_handle反汇编句柄
    • 参数2:指定你要反汇编的数据集或者是一个缓冲区
    • 参数3:指定你要反汇编的长度 64
    • 参数4:输出的内存地址起始位置 0x401000
    • 参数5:默认填充为0
    • 参数6:用于输出数据的一个指针

    这两个函数如果能搞明白,那么如下反汇编完整代码也就可以理解了。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    #include 
    #include 
    
    #pragma comment(lib,"capstone64.lib")
    
    #define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
    #define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)
    
    typedef struct
    {
    	DWORD pid;
    	UINT64 address;
    	DWORD size;
    	BYTE* data;
    }ProcessData;
    
    int main(int argc, char* argv[])
    {
    	// 连接到驱动
    	HANDLE handle = CreateFileA("\\??\\ReadWriteSymbolName", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    
    	ProcessData data;
    	DWORD dwSize = 0;
    
    	// 指定需要读写的进程
    	data.pid = 6932;
    	data.address = 0x401000;
    	data.size = 64;
    
    	// 读取机器码到BYTE字节数组
    	data.data = new BYTE[data.size];
    	DeviceIoControl(handle, READ_PROCESS_CODE, &data, sizeof(data), &data, sizeof(data), &dwSize, NULL);
    	for (int i = 0; i < data.size; i++)
    	{
    		printf("0x%02X ", data.data[i]);
    	}
    
    	printf("\n");
    
    	// 开始反汇编
    	csh dasm_handle;
    	cs_insn *insn;
    	size_t count;
    
    	// 打开句柄
    	if (cs_open(CS_ARCH_X86, CS_MODE_32, &dasm_handle) != CS_ERR_OK)
    	{
    		return 0;
    	}
    
    	// 反汇编代码
    	count = cs_disasm(dasm_handle, (unsigned char *)data.data, data.size, data.address, 0, &insn);
    
    	if (count > 0)
    	{
    		size_t index;
    		for (index = 0; index < count; index++)
    		{
    			/*
    			for (int x = 0; x < insn[index].size; x++)
    			{
    				printf("机器码: %d -> %02X \n", x, insn[index].bytes[x]);
    			}
    			*/
    
    			printf("地址: 0x%"PRIx64" | 长度: %d 反汇编: %s %s \n", insn[index].address, insn[index].size, insn[index].mnemonic, insn[index].op_str);
    		}
    		cs_free(insn, count);
    	}
    	cs_close(&dasm_handle);
    
    	getchar();
    	CloseHandle(handle);
    	return 0;
    }
    

    通过驱动加载工具加载WinDDK.sys然后在运行本程序,你会看到正确的输出结果,反汇编当前位置处向下64字节。

    说完了反汇编接着就需要讲解如何对内存进行汇编操作了,汇编引擎这里采用了XEDParse该引擎小巧简洁,著名的x64dbg就是在运用本引擎进行汇编替换的,本引擎的使用非常简单,只需要向XEDParseAssemble()函数传入一个规范的结构体即可完成转换,完整代码如下所示。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include 
    
    extern "C"
    {
    #include "D:/XEDParse/XEDParse.h"
    #pragma comment(lib, "D:/XEDParse/XEDParse_x64.lib")
    }
    
    using namespace std;
    
    #define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
    #define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)
    
    typedef struct
    {
    	DWORD pid;
    	UINT64 address;
    	DWORD size;
    	BYTE* data;
    }ProcessData;
    
    int main(int argc, char* argv[])
    {
    	// 连接到驱动
    	HANDLE handle = CreateFileA("\\??\\ReadWriteSymbolName", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    
    	ProcessData data;
    	DWORD dwSize = 0;
    
    	// 指定需要读写的进程
    	data.pid = 6932;
    	data.address = 0x401000;
    	data.size = 0;
    
    	XEDPARSE xed = { 0 };
    	xed.x64 = FALSE;
    
    	// 输入一条汇编指令并转换
    	scanf_s("%llx", &xed.cip);
    	gets_s(xed.instr, XEDPARSE_MAXBUFSIZE);
    	if (XEDPARSE_OK != XEDParseAssemble(&xed))
    	{
    		printf("指令错误: %s\n", xed.error);
    	}
    
    	// 生成堆
    	data.data = new BYTE[xed.dest_size];
    
    	// 设置长度
    	data.size = xed.dest_size;
    
    	for (size_t i = 0; i < xed.dest_size; i++)
    	{
    		// 替换到堆中
    		printf("%02X ", xed.dest[i]);
    		data.data[i] = xed.dest[i];
    	}
    
    	// 调用控制器,写入到远端内存
    	DeviceIoControl(handle, WRITE_PROCESS_CODE, &data, sizeof(data), &data, sizeof(data), &dwSize, NULL);
    
    	printf("[LyShark] 指令集已替换. \n");
    	getchar();
    	CloseHandle(handle);
    	return 0;
    }
    

    通过驱动加载工具加载WinDDK.sys然后在运行本程序,你会看到正确的输出结果,可打开反内核工具验证是否改写成功。

    打开反内核工具,并切换到观察是否写入了一条mov eax,1的指令集机器码,如下图已经完美写入。

  • 相关阅读:
    mydumper 介绍及使用
    Android中级——MVVM
    Vue_02 快速入门 基础语法02
    Python语言学习实战-内置函数all()和any()的使用(附源码和实现效果)
    hdlbits系列verilog解答(移位寄存器)-23
    一文读懂 BizDevOps:数字化转型下的技术破局
    阿里P8架构师首推Netty实战干货,实战篇+面试篇,将知识点一网打尽
    【随缘题目集1】找不重复的数字,模拟实现aoti,宏实现offsetof
    Sonatype Nexus 如何把多仓库合并在一起
    【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建&二分答案&拓扑排序)
  • 原文地址:https://www.cnblogs.com/LyShark/p/17145544.html