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

打印 上一主题 下一主题

主题 880|帖子 880|积分 2640

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

首先是实现驱动部分,驱动程序的实现是一成不变的,仅仅只是做一个读写功能即可,完整的代码如下所示;
  1. // 署名权
  2. // right to sign one's name on a piece of work
  3. // PowerBy: LyShark
  4. // Email: me@lyshark.com
  5. #include <ntifs.h>
  6. #include <windef.h>
  7. #define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
  8. #define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)
  9. #define DEVICENAME L"\\Device\\ReadWriteDevice"
  10. #define SYMBOLNAME L"\\??\\ReadWriteSymbolName"
  11. typedef struct
  12. {
  13.         DWORD pid;       // 进程PID
  14.         UINT64 address;  // 读写地址
  15.         DWORD size;      // 读写长度
  16.         BYTE* data;      // 读写数据集
  17. }ProcessData;
  18. // MDL读取封装
  19. BOOLEAN ReadProcessMemory(ProcessData* ProcessData)
  20. {
  21.         BOOLEAN bRet = TRUE;
  22.         PEPROCESS process = NULL;
  23.         // 将PID转为EProcess
  24.         PsLookupProcessByProcessId(ProcessData->pid, &process);
  25.         if (process == NULL)
  26.         {
  27.                 return FALSE;
  28.         }
  29.         BYTE* GetProcessData = NULL;
  30.         __try
  31.         {
  32.                 // 分配堆空间 NonPagedPool 非分页内存
  33.                 GetProcessData = ExAllocatePool(NonPagedPool, ProcessData->size);
  34.         }
  35.         __except (1)
  36.         {
  37.                 return FALSE;
  38.         }
  39.         KAPC_STATE stack = { 0 };
  40.         // 附加到进程
  41.         KeStackAttachProcess(process, &stack);
  42.         __try
  43.         {
  44.                 // 检查进程内存是否可读取
  45.                 ProbeForRead(ProcessData->address, ProcessData->size, 1);
  46.                 // 完成拷贝
  47.                 RtlCopyMemory(GetProcessData, ProcessData->address, ProcessData->size);
  48.         }
  49.         __except (1)
  50.         {
  51.                 bRet = FALSE;
  52.         }
  53.         // 关闭引用
  54.         ObDereferenceObject(process);
  55.         // 解除附加
  56.         KeUnstackDetachProcess(&stack);
  57.         // 拷贝数据
  58.         RtlCopyMemory(ProcessData->data, GetProcessData, ProcessData->size);
  59.         // 释放堆
  60.         ExFreePool(GetProcessData);
  61.         return bRet;
  62. }
  63. // MDL写入封装
  64. BOOLEAN WriteProcessMemory(ProcessData* ProcessData)
  65. {
  66.         BOOLEAN bRet = TRUE;
  67.         PEPROCESS process = NULL;
  68.         // 将PID转为EProcess
  69.         PsLookupProcessByProcessId(ProcessData->pid, &process);
  70.         if (process == NULL)
  71.         {
  72.                 return FALSE;
  73.         }
  74.         BYTE* GetProcessData = NULL;
  75.         __try
  76.         {
  77.                 // 分配堆
  78.                 GetProcessData = ExAllocatePool(NonPagedPool, ProcessData->size);
  79.         }
  80.         __except (1)
  81.         {
  82.                 return FALSE;
  83.         }
  84.         // 循环写出
  85.         for (int i = 0; i < ProcessData->size; i++)
  86.         {
  87.                 GetProcessData[i] = ProcessData->data[i];
  88.         }
  89.         KAPC_STATE stack = { 0 };
  90.         // 附加进程
  91.         KeStackAttachProcess(process, &stack);
  92.         // 分配MDL对象
  93.         PMDL mdl = IoAllocateMdl(ProcessData->address, ProcessData->size, 0, 0, NULL);
  94.         if (mdl == NULL)
  95.         {
  96.                 return FALSE;
  97.         }
  98.         MmBuildMdlForNonPagedPool(mdl);
  99.         BYTE* ChangeProcessData = NULL;
  100.         __try
  101.         {
  102.                 // 锁定地址
  103.                 ChangeProcessData = MmMapLockedPages(mdl, KernelMode);
  104.                 // 开始拷贝
  105.                 RtlCopyMemory(ChangeProcessData, GetProcessData, ProcessData->size);
  106.         }
  107.         __except (1)
  108.         {
  109.                 bRet = FALSE;
  110.                 goto END;
  111.         }
  112.         // 结束释放MDL关闭引用取消附加
  113. END:
  114.         IoFreeMdl(mdl);
  115.         ExFreePool(GetProcessData);
  116.         KeUnstackDetachProcess(&stack);
  117.         ObDereferenceObject(process);
  118.         return bRet;
  119. }
  120. NTSTATUS DriverIrpCtl(PDEVICE_OBJECT device, PIRP pirp)
  121. {
  122.         PIO_STACK_LOCATION stack;
  123.         stack = IoGetCurrentIrpStackLocation(pirp);
  124.         ProcessData* ProcessData;
  125.         switch (stack->MajorFunction)
  126.         {
  127.         case IRP_MJ_CREATE:
  128.         {
  129.                 break;
  130.         }
  131.         case IRP_MJ_CLOSE:
  132.         {
  133.                 break;
  134.         }
  135.         case IRP_MJ_DEVICE_CONTROL:
  136.         {
  137.                 // 获取应用层传值
  138.                 ProcessData = pirp->AssociatedIrp.SystemBuffer;
  139.                 DbgPrint("进程ID: %d | 读写地址: %p | 读写长度: %d \n", ProcessData->pid, ProcessData->address, ProcessData->size);
  140.                 switch (stack->Parameters.DeviceIoControl.IoControlCode)
  141.                 {
  142.                 // 读取函数
  143.                 case READ_PROCESS_CODE:
  144.                 {
  145.                         ReadProcessMemory(ProcessData);
  146.                         break;
  147.                 }
  148.                 // 写入函数
  149.                 case WRITE_PROCESS_CODE:
  150.                 {
  151.                         WriteProcessMemory(ProcessData);
  152.                         break;
  153.                 }
  154.                 }
  155.                 pirp->IoStatus.Information = sizeof(ProcessData);
  156.                 break;
  157.         }
  158.         }
  159.         pirp->IoStatus.Status = STATUS_SUCCESS;
  160.         IoCompleteRequest(pirp, IO_NO_INCREMENT);
  161.         return STATUS_SUCCESS;
  162. }
  163. VOID UnDriver(PDRIVER_OBJECT driver)
  164. {
  165.         if (driver->DeviceObject)
  166.         {
  167.                 UNICODE_STRING SymbolName;
  168.                 RtlInitUnicodeString(&SymbolName, SYMBOLNAME);
  169.                 // 删除符号链接
  170.                 IoDeleteSymbolicLink(&SymbolName);
  171.                 IoDeleteDevice(driver->DeviceObject);
  172.         }
  173. }
  174. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  175. {
  176.         NTSTATUS status = STATUS_SUCCESS;
  177.         PDEVICE_OBJECT device = NULL;
  178.         UNICODE_STRING DeviceName;
  179.         DbgPrint("[LyShark] hello lyshark.com \n");
  180.         // 初始化设备名
  181.         RtlInitUnicodeString(&DeviceName, DEVICENAME);
  182.         // 创建设备
  183.         status = IoCreateDevice(Driver, sizeof(Driver->DriverExtension), &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &device);
  184.         if (status == STATUS_SUCCESS)
  185.         {
  186.                 UNICODE_STRING SymbolName;
  187.                 RtlInitUnicodeString(&SymbolName, SYMBOLNAME);
  188.                 // 创建符号链接
  189.                 status = IoCreateSymbolicLink(&SymbolName, &DeviceName);
  190.                 // 失败则删除设备
  191.                 if (status != STATUS_SUCCESS)
  192.                 {
  193.                         IoDeleteDevice(device);
  194.                 }
  195.         }
  196.         // 派遣函数初始化
  197.         Driver->MajorFunction[IRP_MJ_CREATE] = DriverIrpCtl;
  198.         Driver->MajorFunction[IRP_MJ_CLOSE] = DriverIrpCtl;
  199.         Driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverIrpCtl;
  200.         // 卸载驱动
  201.         Driver->DriverUnload = UnDriver;
  202.         return STATUS_SUCCESS;
  203. }
复制代码
上方的驱动程序很简单关键部分已经做好了备注,此类驱动换汤不换药没啥难度,接下来才是本节课的重点,让我们开始了解一下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:用于输出数据的一个指针
这两个函数如果能搞明白,那么如下反汇编完整代码也就可以理解了。
  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <Windows.h>
  3. #include <iostream>
  4. #include <inttypes.h>
  5. #include <capstone/capstone.h>
  6. #pragma comment(lib,"capstone64.lib")
  7. #define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
  8. #define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)
  9. typedef struct
  10. {
  11.         DWORD pid;
  12.         UINT64 address;
  13.         DWORD size;
  14.         BYTE* data;
  15. }ProcessData;
  16. int main(int argc, char* argv[])
  17. {
  18.         // 连接到驱动
  19.         HANDLE handle = CreateFileA("\\??\\ReadWriteSymbolName", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  20.         ProcessData data;
  21.         DWORD dwSize = 0;
  22.         // 指定需要读写的进程
  23.         data.pid = 6932;
  24.         data.address = 0x401000;
  25.         data.size = 64;
  26.         // 读取机器码到BYTE字节数组
  27.         data.data = new BYTE[data.size];
  28.         DeviceIoControl(handle, READ_PROCESS_CODE, &data, sizeof(data), &data, sizeof(data), &dwSize, NULL);
  29.         for (int i = 0; i < data.size; i++)
  30.         {
  31.                 printf("0x%02X ", data.data[i]);
  32.         }
  33.         printf("\n");
  34.         // 开始反汇编
  35.         csh dasm_handle;
  36.         cs_insn *insn;
  37.         size_t count;
  38.         // 打开句柄
  39.         if (cs_open(CS_ARCH_X86, CS_MODE_32, &dasm_handle) != CS_ERR_OK)
  40.         {
  41.                 return 0;
  42.         }
  43.         // 反汇编代码
  44.         count = cs_disasm(dasm_handle, (unsigned char *)data.data, data.size, data.address, 0, &insn);
  45.         if (count > 0)
  46.         {
  47.                 size_t index;
  48.                 for (index = 0; index < count; index++)
  49.                 {
  50.                         /*
  51.                         for (int x = 0; x < insn[index].size; x++)
  52.                         {
  53.                                 printf("机器码: %d -> %02X \n", x, insn[index].bytes[x]);
  54.                         }
  55.                         */
  56.                         printf("地址: 0x%"PRIx64" | 长度: %d 反汇编: %s %s \n", insn[index].address, insn[index].size, insn[index].mnemonic, insn[index].op_str);
  57.                 }
  58.                 cs_free(insn, count);
  59.         }
  60.         cs_close(&dasm_handle);
  61.         getchar();
  62.         CloseHandle(handle);
  63.         return 0;
  64. }
复制代码
通过驱动加载工具加载WinDDK.sys然后在运行本程序,你会看到正确的输出结果,反汇编当前位置处向下64字节。

说完了反汇编接着就需要讲解如何对内存进行汇编操作了,汇编引擎这里采用了XEDParse该引擎小巧简洁,著名的x64dbg就是在运用本引擎进行汇编替换的,本引擎的使用非常简单,只需要向XEDParseAssemble()函数传入一个规范的结构体即可完成转换,完整代码如下所示。
  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <Windows.h>
  3. #include <iostream>
  4. extern "C"
  5. {
  6. #include "D:/XEDParse/XEDParse.h"
  7. #pragma comment(lib, "D:/XEDParse/XEDParse_x64.lib")
  8. }
  9. using namespace std;
  10. #define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
  11. #define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)
  12. typedef struct
  13. {
  14.         DWORD pid;
  15.         UINT64 address;
  16.         DWORD size;
  17.         BYTE* data;
  18. }ProcessData;
  19. int main(int argc, char* argv[])
  20. {
  21.         // 连接到驱动
  22.         HANDLE handle = CreateFileA("\\??\\ReadWriteSymbolName", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  23.         ProcessData data;
  24.         DWORD dwSize = 0;
  25.         // 指定需要读写的进程
  26.         data.pid = 6932;
  27.         data.address = 0x401000;
  28.         data.size = 0;
  29.         XEDPARSE xed = { 0 };
  30.         xed.x64 = FALSE;
  31.         // 输入一条汇编指令并转换
  32.         scanf_s("%llx", &xed.cip);
  33.         gets_s(xed.instr, XEDPARSE_MAXBUFSIZE);
  34.         if (XEDPARSE_OK != XEDParseAssemble(&xed))
  35.         {
  36.                 printf("指令错误: %s\n", xed.error);
  37.         }
  38.         // 生成堆
  39.         data.data = new BYTE[xed.dest_size];
  40.         // 设置长度
  41.         data.size = xed.dest_size;
  42.         for (size_t i = 0; i < xed.dest_size; i++)
  43.         {
  44.                 // 替换到堆中
  45.                 printf("%02X ", xed.dest[i]);
  46.                 data.data[i] = xed.dest[i];
  47.         }
  48.         // 调用控制器,写入到远端内存
  49.         DeviceIoControl(handle, WRITE_PROCESS_CODE, &data, sizeof(data), &data, sizeof(data), &dwSize, NULL);
  50.         printf("[LyShark] 指令集已替换. \n");
  51.         getchar();
  52.         CloseHandle(handle);
  53.         return 0;
  54. }
复制代码
通过驱动加载工具加载WinDDK.sys然后在运行本程序,你会看到正确的输出结果,可打开反内核工具验证是否改写成功。

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


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天空闲话

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表