驱动开发:内核中实现Dump进程转储
多数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
进程的模块信息,并可得到以下输出效果:
上篇文章中的代码就不再啰嗦了,这里只给出内存转存的核心代码,如下代码:
- RtlInitUnicodeString 用于初始化转存后的名字字符串
- ZwCreateFile 内核中创建文件到应用层
- ZwWriteFile 将文件写出到文件
- ZwClose 最后是关闭文件并释放堆空间
很简单只是利用了SafeCopyMemory_R3_to_R0
将进程内存读取到缓冲区内,并将缓冲区写出到C盘目录下。
// 进程内存拷贝函数
// By: LyShark.com
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"));
}
// lyshark.com
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;
}
转存后效果如下所示:
至于导出的进程无法运行只是没有修复而已(后期会讲),可以打开看看是没错的。
驱动开发:内核中实现Dump进程转储的更多相关文章
- Windows驱动开发-内核常用内存函数
搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool
- 驱动开发:内核遍历进程VAD结构体
在上一篇文章<驱动开发:内核中实现Dump进程转储>中我们实现了ARK工具的转存功能,本篇文章继续以内存为出发点介绍VAD结构,该结构的全程是Virtual Address Descrip ...
- Linux 内核开发—内核简单介绍
内核简单介绍 Linux 构成 Linux 为什么被划分为系统空间和内核空间 隔离核心程序和应用程序,实现对核心程序和数据的保护. 什么内核空间,用户空间 内核空间和用户空间是程序执行的两种不同的状态 ...
- Android系统移植与驱动开发----第一章
第一章 Android系统移植与驱动开发 Android源代码定制完全属于自己的嵌入式系统,但是支持的设备不多,所以要移植,而在移植的过程中使用的不得不提的是驱动开发. Android系统构架主要包括 ...
- Linux 驱动开发
linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...
- Window驱动开发
驱动开发 参考文章: Windbg+Vmware驱动调试 http://blog.csdn.net/xuepiaosong/article/details/8236702 驱动调试攻略(WinDbg) ...
- KSM剖析——Linux 内核中的内存去耦合
简介: 作为一个系统管理程序(hypervisor),Linux® 有几个创新,2.6.32 内核中一个有趣的变化是 KSM(Kernel Samepage Merging) 允许这个系统管理程序通 ...
- (转)FS_S5PC100平台上Linux Camera驱动开发详解(一) .
平台linuxstructlinux内核videocam 说明: 理解摄像头驱动需要四个前提: 1)摄像头基本的工作原理和S5PC100集成的Camera控制器的工作原理 ...
- 第六周分析Linux内核创建一个新进程的过程
潘恒 原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 task_struct结构: ...
随机推荐
- 丽泽普及2022交流赛day17 社论
http://zhengruioi.com/contest/1088 SoyTony 重新 rk1 . stO SoyTony Orz 省流:俩计数 . 目录 目录 A 题面 题解 Key 算法 1( ...
- Java学习第五周
这周学习了异常与多线程,线程使用 Exception异常的分类: 1.编译时异常:继承自Exception的异常或者其子类,编译阶段就会报错 2.运行时异常:继承自RuntimeException的异 ...
- Docker默认桥接网络是如何工作的
1. 启动一个Docker容器 一般来说,我们起一个容器比如一个简单的nginx服务会向这样 docker run -d --rm nginx:XXX OK容器起来了,但是并不能通过宿主机被外面的机器 ...
- 一个注解搞定SpringBoot接口定制属性加解密
前言 上个月公司另一个团队做的新项目上线后大体上运行稳定,但包括研发负责人在内的两个人在项目上线后立马就跳槽了,然后又交接给了我这个「垃圾回收人员」. 本周甲方另一个厂家的监控平台扫描到我们这个项目某 ...
- Luogu1064 金明的预算方案 (有依赖的背包)
枚举多个状态 #include <iostream> #include <cstdio> #include <cstring> #include <algor ...
- 从零开始Blazor Server(15)--总结
我们用了14篇文章,基本上把一个后台管理系统需要的UI部分都说的差不多了.所以这套文章也该到了结束的时候了. 这里面有很多问题,比如我们直接使用UI来拉数据库信息而没有使用service,再比如我们大 ...
- PerfView专题 (第十一篇):使用 Diff 功能洞察 C# 内存泄漏增量
一:背景 去年 GC架构师 Maoni 在 (2021 .NET 开发者大会) [https://ke.segmentfault.com/course/1650000041122988/section ...
- 动态规划——leetcode55、跳跃游戏
1.题目描述: 2.解题方法:动态规划 动态规划解题步骤: 1.确定状态: 最后一步:如果能跳到最后一个下标,我们考虑他的最后一步到n-1(最后一个下标),这一步是从 i 跳过来的,i<n-1 ...
- 【C++】实现D3D9 的 Inline hook
[C++]实现D3D9 的 Inline hook 简单介绍一下HOOK原理: 函数调用的过程大致是 先push 参数 进去,再执行 call 函数地址 ,进入函数.此时将所调用的函数的前五个字节 ...
- Codeforces Round #604 (Div. 2) -D
Problem - D - Codeforces 题意 : 有 a 个0,b个1,c个2,d个3,构成一个序列,使得每两个数字之间的差值为1 题解: 就是以四种数字分别为起点,暴力模拟 #includ ...