多数ARK反内核工具中都存在驱动级别的内存转存功能,该功能可以将应用层中运行进程的内存镜像转存到特定目录下,内存转存功能在应对加壳程序的分析尤为重要,当进程在内存中解码后,我们可以很容易的将内存镜像导出,从而更好的对样本进行分析,当然某些加密壳可能无效但绝大多数情况下是可以被转存的。

在上一篇文章《内核R3与R0内存映射拷贝》介绍了一种方式SafeCopyMemory_R3_to_R0可以将应用层进程的内存空间映射到内核中,要实现内存转储功能我们还是需要使用这个映射函数,只是需要在此函数上增加一些功能而已。

在实现转存之前,需要得到两个东西,进程内模块基地址以及模块长度这两个参数是必不可少的,至于内核中如何得到指定进程的模块数据,在很早之前的文章《内核中枚举进线程与模块》中有详细的参考方法,这里就在此基础之上实现一个简单的进程模块遍历功能。

如下代码中使用的就是枚举进程PEB结构得到更多参数的具体实现,如果不懂得可以研读《内核通过PEB得到进程参数》这篇文章此处不再赘述。

#include <ntddk.h>
#include <windef.h> // 声明结构体
typedef struct _KAPC_STATE
{
LIST_ENTRY ApcListHead[2];
PKPROCESS Process;
UCHAR KernelApcInProgress;
UCHAR KernelApcPending;
UCHAR UserApcPending;
} KAPC_STATE, *PKAPC_STATE; typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY64 InLoadOrderLinks;
LIST_ENTRY64 InMemoryOrderLinks;
LIST_ENTRY64 InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
PVOID SectionPointer;
ULONG CheckSum;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY64 ForwarderLinks;
LIST_ENTRY64 ServiceTagLinks;
LIST_ENTRY64 StaticLinks;
PVOID ContextInformation;
ULONG64 OriginalBase;
LARGE_INTEGER LoadTime;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; // 偏移地址
ULONG64 LdrInPebOffset = 0x018; //peb.ldr
ULONG64 ModListInPebOffset = 0x010; //peb.ldr.InLoadOrderModuleList // 声明API
NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process);
NTKERNELAPI PPEB PsGetProcessPeb(PEPROCESS Process);
NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process); // 根据进程ID返回进程EPROCESS,失败返回NULL
PEPROCESS LookupProcess(HANDLE Pid)
{
PEPROCESS eprocess = NULL;
if (NT_SUCCESS(PsLookupProcessByProcessId(Pid, &eprocess)))
return eprocess;
else
return NULL;
} // 枚举指定进程的模块
// By: LyShark.com
VOID EnumModule(PEPROCESS Process)
{
SIZE_T Peb = 0;
SIZE_T Ldr = 0;
PLIST_ENTRY ModListHead = 0;
PLIST_ENTRY Module = 0;
ANSI_STRING AnsiString;
KAPC_STATE ks; // EPROCESS地址无效则退出
if (!MmIsAddressValid(Process))
return; // 获取PEB地址
Peb = (SIZE_T)PsGetProcessPeb(Process); // PEB地址无效则退出
if (!Peb)
return; // 依附进程
KeStackAttachProcess(Process, &ks);
__try
{
// 获得LDR地址
Ldr = Peb + (SIZE_T)LdrInPebOffset;
// 测试是否可读,不可读则抛出异常退出
ProbeForRead((CONST PVOID)Ldr, 8, 8);
// 获得链表头
ModListHead = (PLIST_ENTRY)(*(PULONG64)Ldr + ModListInPebOffset);
// 再次测试可读性
ProbeForRead((CONST PVOID)ModListHead, 8, 8);
// 获得第一个模块的信息
Module = ModListHead->Flink; while (ModListHead != Module)
{
//打印信息:基址、大小、DLL路径
DbgPrint("模块基址 = %p | 大小 = %ld | 模块名 = %wZ | 完整路径= %wZ \n",
(PVOID)(((PLDR_DATA_TABLE_ENTRY)Module)->DllBase),
(ULONG)(((PLDR_DATA_TABLE_ENTRY)Module)->SizeOfImage),
&(((PLDR_DATA_TABLE_ENTRY)Module)->BaseDllName),
&(((PLDR_DATA_TABLE_ENTRY)Module)->FullDllName)
);
Module = Module->Flink; // 测试下一个模块信息的可读性
ProbeForRead((CONST PVOID)Module, 80, 8);
}
}
__except (EXCEPTION_EXECUTE_HANDLER){ ; } // 取消依附进程
KeUnstackDetachProcess(&ks);
} VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{ } NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n"); ULONG i = 0;
PEPROCESS eproc = NULL;
for (i = 4; i<100000000; i = i + 4)
{
eproc = LookupProcess((HANDLE)i);
if (eproc != NULL)
{
ObDereferenceObject(eproc);
if (strstr(PsGetProcessImageFileName(eproc), "lyshark.exe") != NULL)
{
EnumModule(eproc);
}
}
} DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

如上我们指定获取应用层lyshark.exe进程的模块信息,并可得到以下输出效果:

上篇文章中的代码就不再啰嗦了,这里只给出内存转存的核心代码ProcessDumps的实现流程:

ProcessDumps 代码的功能是将一个进程的内存空间转储(Dump)到磁盘上的一个文件中,该函数接收三个参数,并返回内存转存的状态;

  • 参数 pEprocess:要转储的进程的PEPROCESS结构体指针。
  • 参数 nBase:要转储的内存空间的基地址。
  • 参数 nSize:要转储的内存空间的大小。
  • 函数返回值:转储操作的状态,如果成功则返回 STATUS_SUCCESS,否则返回一个表示错误原因的 NTSTATUS 值。

该函数的实现也非常简单,通过SafeCopyMemory_R3_to_R0函数将应用层中的进程内存映射到内核层中的pBuffer堆中,当映射完成后再通过ZwWriteFile函数将这段内存写出到磁盘中完成转存,函数ProcessDumps的具体流程如下:

  • 1.检查参数 pEprocess 和 nSize 是否为 NULL 或为 0,如果是,则直接返回 STATUS_UNSUCCESSFUL,表示操作失败。
  • 2.分配一个大小为 nSize 的缓冲区,用于存储要转储的内存空间。
  • 3.如果要转储的进程不是当前进程,则将当前线程切换到要转储的进程的上下文中,以便能够访问要转储的进程的内存空间。
  • 4.调用函数 SafeCopyMemory_R3_to_R0,将要转储的内存空间中的数据复制到缓冲区中。
  • 5.如果线程被切换到了要转储的进程的上下文中,则将线程切换回当前进程的上下文中。
  • 6.调用ZwCreateFile创建一个表示输出文件的句柄。
  • 7.通过ZwWriteFile将缓冲区中的数据写入到输出文件中。
  • 8.最后ZwClose关闭输出文件句柄并释放缓冲区内存。

很简单只是利用了SafeCopyMemory_R3_to_R0将进程内存读取到缓冲区内,并将缓冲区写出到C盘目录下,默认将转存数据保存为lyshark_dumps.exe

NTSTATUS ProcessDumps(PEPROCESS pEprocess, ULONG_PTR nBase, ULONG nSize)
{
BOOLEAN bAttach = FALSE;
KAPC_STATE ks = { 0 };
PVOID pBuffer = NULL;
NTSTATUS status = STATUS_UNSUCCESSFUL; if (nSize == 0 || pEprocess == NULL)
{
return status;
} pBuffer = ExAllocatePoolWithTag(PagedPool, nSize, 'lysh');
if (!pBuffer)
{
return status;
} memset(pBuffer, 0, nSize); if (pEprocess != IoGetCurrentProcess())
{
KeStackAttachProcess(pEprocess, &ks);
bAttach = TRUE;
} status = SafeCopyMemory_R3_to_R0(nBase, (ULONG_PTR)pBuffer, nSize); if (bAttach)
{
KeUnstackDetachProcess(&ks);
bAttach = FALSE;
} OBJECT_ATTRIBUTES object;
IO_STATUS_BLOCK io;
HANDLE hFile;
UNICODE_STRING log; // 导出文件名称
RtlInitUnicodeString(&log, L"\\??\\C:\\lyshark_dumps.exe");
InitializeObjectAttributes(&object, &log, OBJ_CASE_INSENSITIVE, NULL, NULL); status = ZwCreateFile(&hFile,
GENERIC_WRITE,
&object,
&io,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_WRITE,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0); if (!NT_SUCCESS(status))
{
DbgPrint("打开文件错误 \n");
return STATUS_SUCCESS;
} ZwWriteFile(hFile, NULL, NULL, NULL, &io, pBuffer, nSize, NULL, NULL);
DbgPrint("写出字节数: %d \n", io.Information);
DbgPrint("[*] LyShark.exe 已转存");
ZwClose(hFile); if (pBuffer)
{
ExFreePoolWithTag(pBuffer, 'lysh');
pBuffer = NULL;
} return status;
} VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
} NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n"); NTSTATUS ntStatus;
PEPROCESS pCurProcess = NULL; __try
{
ntStatus = PsLookupProcessByProcessId((HANDLE)272, &pCurProcess);
if (NT_SUCCESS(ntStatus))
{
// 设置基地址以及长度
ntStatus = ProcessDumps(pCurProcess, 0x140000000, 1024);
ObDereferenceObject(pCurProcess);
}
}
__except (1)
{
ntStatus = GetExceptionCode();
} Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

转存后效果如下图所示:

至于导出的进程无法运行只是没有修复而已(后期会讲),可以打开看看是没错的。

4.5 Windows驱动开发:内核中实现进程数据转储的更多相关文章

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

    搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool

  2. windows 驱动开发入门——驱动中的数据结构

    最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书--<独钓寒江 windows安全编程> 和 <windows驱动 ...

  3. Windows驱动开发(中间层)

    Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...

  4. [Windows驱动开发](一)序言

    笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...

  5. Windows驱动——读书笔记《Windows驱动开发技术详解》

    =================================版权声明================================= 版权声明:原创文章 谢绝转载  请通过右侧公告中的“联系邮 ...

  6. Windows驱动开发-IRP的完成例程

    <Windows驱动开发技术详解 >331页, 在将IRP发送给底层驱动或其他驱动之前,可以对IRP设置一个完成例程,一旦底层驱动将IRP完成后,IRP完成例程立刻被处罚,通过设置完成例程 ...

  7. C++第三十八篇 -- 研究一下Windows驱动开发(二)--WDM式驱动的加载

    基于Windows驱动开发技术详解这本书 一.简单的INF文件剖析 INF文件是一个文本文件,由若干个节(Section)组成.每个节的名称用一个方括号指示,紧接着方括号后面的就是节内容.每一行就是一 ...

  8. C++第三十三篇 -- 研究一下Windows驱动开发(一)内部构造介绍

    因为工作原因,需要做一些与网卡有关的测试,其中涉及到了驱动这一块的知识,虽然程序可以运行,但是不搞清楚,心里总是不安,觉得没理解清楚.因此想看一下驱动开发.查了很多资料,看到有人推荐Windows驱动 ...

  9. windows驱动开发推荐书籍

    [作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...

  10. Windows 驱动开发 - 5

    上篇<Windows 驱动开发 - 4>我们已经完毕了硬件准备. 可是我们还没有详细的数据操作,比如接收读写操作. 在WDF中进行此类操作前须要进行设备的IO控制,已保持数据的完整性. 我 ...

随机推荐

  1. 震惊!二狗子的火锅店被隔壁老王 DDoS 攻击了

    近两年,游戏出海已经成为了出海热潮中的一员.在"后宅经济时代"的影响下,也得益于海外市场的互联网人口,游戏出海涨势非常迅猛.部分游戏在短时间内走红后,就会遭到了一些"有心 ...

  2. <vue 路由 1、路由的基本使用>

    一.     项目创建 参考如下博客地址创建一个vue的项目 https://www.cnblogs.com/yclh/p/15356171.html   vue学习笔记 二.环境搭建+项目创建 二. ...

  3. 深度学习基础课:使用Adam算法

    大家好~我开设了"深度学习基础班"的线上课程,带领同学从0开始学习全连接和卷积神经网络,进行数学推导,并且实现可以运行的Demo程序 线上课程资料: 本节课录像回放 加QQ群,获得 ...

  4. zzuli 1907: 小火山的宝藏收益

    ***题意:中文的 做法:邻接表+DFS,就相当于搜一棵树,比较一下当前结点得到的宝藏多还是子树下面得到的宝藏多,仔细想想就是水题*** #include<iostream> #inclu ...

  5. Go 语言中 defer 使用时有哪些陷阱?

    大家好,我是 frank ,「 Golang 语言开发栈」公众号作者. 01 介绍 defer 的使用方式是在其后紧跟一个函数调用或方法调用,确保在其所在的函数体返回之前执行其调用的函数或方法. 在 ...

  6. Spring——静态/动态代理模式

    代理模式 代理模式: 静态代理 动态代理 学习aop之前,要先了解代理模式 静态代理 抽象角色:一般使用接口或者抽象类来实现 真实角色:被代理的角色 代理角色:代理真实角色:代理真实角色后,一般会做一 ...

  7. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.11.26)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...

  8. 神通数据库的varchar和nvarchar的验证

    神通数据库的varchar和nvarchar的验证 登录神通数据库 isql 注意 神通数据库的默认密码是 szoscar55 Welcome to isql 2.0.56 interactive t ...

  9. [转帖]shell脚本实现文本内容比较交互程序

    背景介绍 脚本基于Comm命令进行功能封装,考虑到命令执行前需要对文本进行排序,并且在多文件需要比较内容时可能会导致多个文本混乱,因此使用Shell封装成了一个交互式程序,快速对文件内容进行判断和输出 ...

  10. [转帖]Sar的一次使用案例

    https://www.jianshu.com/p/b93342d43e13 问题现象 有一台机器,在某个时间点OS类似无响应,造成使用者感觉在该时间点机器应该发生重启,就此问题进行分析. 日志查看 ...