草庐IT

驱动开发:内核中实现Dump进程转储

LyShark 2023-04-15 原文

多数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进程转储的更多相关文章

  1. ruby - 在 Ruby 中实现 `call_user_func_array` - 2

    我怎样才能完成http://php.net/manual/en/function.call-user-func-array.php在ruby中?所以我可以这样做:classAppdeffoo(a,b)putsa+benddefbarargs=[1,2]App.send(:foo,args)#doesn'tworkApp.send(:foo,args[0],args[1])#doeswork,butdoesnotscaleendend 最佳答案 尝试分解数组App.send(:foo,*args)

  2. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  3. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  4. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  5. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  6. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  7. ruby-on-rails - 如何在 Ruby on Rails 中实现无向图? - 2

    我需要在RubyonRails中实现无向图G=(V,E)并考虑构建一个Vertex和一个Edge模型,其中Vertex有_多条边。由于边恰好连接两个顶点,您将如何在Rails中执行此操作?您是否知道任何有助于实现此类图表的gem或库(对重新发明轮子不感兴趣;-))? 最佳答案 不知道有任何现有库在ActiveRecord之上提供图形逻辑。您可能必须实现自己的Vertex、EdgeActiveRecord支持的模型(请参阅Rails安装的rails/activerecord中的vertex.rb和edge.rb/test/fixtur

  8. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  9. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  10. ruby-on-rails - 如何在 Ruby on Rails 中实现由 JSF 2.0 (Primefaces) 驱动的 UI 魔法 - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。问题1)我想知道ruby​​onrails是否有功能类似于primefaces的gem。我问的原因是如果您使用primefaces(http://www.primefaces.org/showcase-labs/ui/home.jsf),开发人员无需担心javascript或jquery的东西。据我所知,JSF是一个规范,基于规范的各种可用实现,prim

随机推荐