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

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

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

image.png

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

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include <ntifs.h>
#include <windef.h>

#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 <Windows.h>
#include <iostream>
#include <inttypes.h>
#include <capstone/capstone.h>

#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字节。

image.png

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

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

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然后在运行本程序,你会看到正确的输出结果,可打开反内核工具验证是否改写成功。

image.png

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

image.png

相关文章
|
1月前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
运维 JavaScript jenkins
鸿蒙5.0版开发:分析CppCrash(进程崩溃)
在HarmonyOS 5.0中,CppCrash指C/C++运行时崩溃,常见原因包括空指针、数组越界等。系统提供基于posix信号机制的异常检测能力,生成详细日志辅助定位。本文详解CppCrash分析方法,涵盖异常检测、问题定位思路及案例分析。
57 4
|
1月前
|
运维 监控 JavaScript
鸿蒙next版开发:分析JS Crash(进程崩溃)
在HarmonyOS 5.0中,JS Crash指未处理的JavaScript异常导致应用意外退出。本文详细介绍如何分析JS Crash,包括异常捕获、日志分析和典型案例,帮助开发者定位问题、修复错误,提升应用稳定性。通过DevEco Studio收集日志,结合HiChecker工具,有效解决JS Crash问题。
54 4
|
1月前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
71 4
|
1月前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
1月前
|
算法 调度
探索操作系统的心脏:内核与进程管理
【10月更文挑战第25天】在数字世界的复杂迷宫中,操作系统扮演着关键角色,如同人体中的心脏,维持着整个系统的生命力。本文将深入浅出地剖析操作系统的核心组件——内核,以及它如何通过进程管理来协调资源的分配和使用。我们将从内核的概念出发,探讨它在操作系统中的地位和作用,进而深入了解进程管理的机制,包括进程调度、状态转换和同步。此外,文章还将展示一些简单的代码示例,帮助读者更好地理解这些抽象概念。让我们一起跟随这篇文章,揭开操作系统神秘的面纱,理解它如何支撑起我们日常的数字生活。
|
6月前
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器
140 13
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
5月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
192 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
4月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。