驱动无模块注入dll

实现效果

在这里插入图片描述

可以看到dll已经成功执行,但是在内存区域里面并没有我们的模块,并且在模块列表里面,也没有我们的dll模块

三环无模块注入的方案

实际上无模块注入的方案在三环也可以完成,这种技术叫反射型dll注入。实现的原理就是在dll内部实现了一个loader函数代替LoadLibrary,然后把这个函数导出直接调用。

BlackBone里面已经封装好了相关的接口,直接调用就可以了

//隐藏注入dll 进程名 dll路径 适配32位和64位
void HideInject(const std::wstring& processName, const std::wstring& dllPath)
{
	vector<DWORD> vecPid = Process::EnumByName(processName);

	if (vecPid.empty())
	{
		MessageBoxA(0, "不存在目标进程", "提示", 0);
		return;
	}

	//首先要拿到目标进程的信息
	Process proc;
	proc.Attach(vecPid.front());

	//确保LdrInitializeProcess被调用
	proc.EnsureInit();

	//将PE文件映射到目标进程 1.PE文件路径 flags(手动映射导入函数) 回调函数
	auto image = proc.mmap().MapImage(dllPath, ManualImports, &MapCallback2);

	printf("ImageBase:%llx\n", image);

	//获取导出函数地址s
	auto g_loadDataPtr = proc.modules().GetExport(image.result(), "g_LoadData");

	//调用导出函数
	auto g_loadData = proc.memory().Read<DllLoadData>(g_loadDataPtr->procAddress);
	
}

我们把这个函数调用来看一下效果

HideInject(L"WeChat.exe", L"C:\\Users\\Administrator\\Desktop\\Test.dll");

实现效果如下:

在这里插入图片描述

在VAD树里可以看到这块内存在分配时的属性是可读可写可执行,而当前的内存属性是可读,说明BlackBone还是做了一些防护措施的,在dllmain执行完成后,把内存属性修改为了只读。

在这里插入图片描述

在枚举模块的位置已经找不到我们注入的dll了。

反射型dll注入方式的改进

事实上这种注入方式已经很隐蔽了,查探不到模块,但是依然还有两个问题,第一是内存区域指向的位置有PE头,第二是在申请时的内存属性为可执行。

那么我们可以针对这种方式进行改进:

  1. 在dll入口函数执行完成之后,把PE头抹掉
  2. 在申请内存时候,只申请可读可写的内存
  3. 在调用dllmain前,将属性修改为可执行
  4. 在执行完dllmain之后,将属性修改为只读

完成这些操作之后,在VAD树的分配时内存属性就会变成读写,当前的内存属性就会变成只读,再把PE头抹掉,就实现了三环的无模块注入了。

各位有需要可以参考这个思路进行自行魔改。

零环无模块注入方案

接下来再来说零环的无模块注入方案,零环的无模块注入方案首先要用到一个项目,将我们的dll文件转成shellcode

petoshellcode

pe转shellcode的原理就是在完整的dll外层套上一个用shellcode的加载器,然后由这个加载器把我们的dll跑起来。

https://github.com/monoxgas/sRDI

这里需要用到一个开源项目叫sRDI,做红队相关的同学应该比较眼熟了。

在这里插入图片描述

这个项目里面实现了一个加载dll的函数,使用shellcode的方式编写的,与位置无关,编译完成之后,可以直接把二进制扣出来用。

在这里插入图片描述

里面也有一个调用示例。我们需要修改下这个函数,把参数的部分去掉,就可以使用这个loader来加载任意一个dll了。

无模块注入流程

接着就可以开始实现驱动层的无模块注入了,流程如下:

  1. 将要注入的dll提取成硬编码存存放到char数组里
  2. 附加目标进程,申请一块不可执行的内存,用来存放硬编码的dll
  3. 用隐藏可执行内存的方式,申请一块可执行的内存,用来存放dllLoader
  4. 修改dllLoader内的dll入口函数地址
  5. 附加到目标进程,并创建线程执行dllLoder
  6. 等待线程执行完成,释放内存

实现代码

实现代码如下:


NTSTATUS Inject(HANDLE pid, char * shellcode, SIZE_T shellcodeSize)
{
	PEPROCESS Process = NULL;
	NTSTATUS status = PsLookupProcessByProcessId(pid, &Process);
	KAPC_STATE kApcState = {0};

	if (!NT_SUCCESS(status))
	{
		return status;
	}

	if (PsGetProcessExitStatus(Process) != STATUS_PENDING)
	{
		ObDereferenceObject(Process);
		return NULL;
	}

	PUCHAR kfileDll = ExAllocatePool(PagedPool, shellcodeSize);
	memcpy(kfileDll, shellcode, shellcodeSize);

	BOOLEAN isuFileAllocatedll = FALSE;
	BOOLEAN isuShellcode = FALSE;
	BOOLEAN isuimageDll = FALSE;

	PUCHAR ufileDll = NULL;
	PUCHAR uShellcode = NULL;
	SIZE_T uShellcodeSize = 0;
	PUCHAR uImage = NULL;
	SIZE_T uImageSize = 0;

	KeStackAttachProcess(Process, &kApcState);
	do 
	{
		//这里填充的是dll
		ufileDll = AllocateMemoryNotExecute(pid, shellcodeSize);

		if (!ufileDll)
		{
			break;
		}
		
		memcpy(ufileDll, kfileDll, shellcodeSize);

		isuFileAllocatedll = TRUE;


		//这里填充的是dllLoader
		uShellcode = AllocateMemory(pid, sizeof(MemLoadShellcode_x64));

		if (!uShellcode)
		{
			break;
		}

		isuShellcode = TRUE;

		memcpy(uShellcode, MemLoadShellcode_x64, sizeof(MemLoadShellcode_x64));

		PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ufileDll;
		PIMAGE_NT_HEADERS pNts = (PIMAGE_NT_HEADERS)(ufileDll + pDos->e_lfanew);
		uImageSize = pNts->OptionalHeader.SizeOfImage;

		//申请内存 存放展开后的dll
		uImage = AllocateMemory(pid, uImageSize);

		DbgPrint("Base:%llx\n", uImage);

		if (!uImage)
		{
			break;
		}

		//替换shellcode里面的dll地址 mov rax,uImage
		uShellcode[0x50f] = 0x90;
		uShellcode[0x510] = 0x48;
		uShellcode[0x511] = 0xb8;
		*(PULONG64)&uShellcode[0x512] = (ULONG64)uImage;

		
		//附加到目标进程 然后创建线程去跑dllLoader
		PETHREAD thread = NULL;
		if (CreateRemoteThreadByProcess(pid, uShellcode, ufileDll, &thread))
		{
			KeWaitForSingleObject(thread, Executive, KernelMode, FALSE, NULL);
			memset(uImage, 0, PAGE_SIZE);
		}
		else 
		{
			isuimageDll = TRUE;
		}
	} while (0);


	//释放内存
	if (isuFileAllocatedll)
	{
		FreeMemory(pid, ufileDll, shellcodeSize);
	}

	if (isuShellcode)
	{
		FreeMemory(pid, uShellcode, uShellcodeSize);
	}

	if (isuimageDll)
	{
		FreeMemory(pid, uImage, uImageSize);
	}

	KeUnstackDetachProcess(&kApcState);

	ExFreePool(kfileDll);

	return status;
}

到这里所有的步骤就已经完成了,之前没研究过的时候总听人说驱动无模块注入,以为有多厉害,其实就是把三环的dlltoshellcode搬到了零环,再改掉分页属性,既隐藏掉了模块,也隐藏了可执行内存属性。

Xenos注入方案研究

既然都研究到这了,就索性把各路大神的注入姿势都研究一下。

https://github.com/DarthTon/Xenos

这个注入工具是一位大佬推荐给我的,说里面有几个比较牛逼的内核注入姿势,不过现在应该被各大游戏公司给杀了个遍。

具体什么原理当时也没看,现在回过头来瞅瞅。

在这里插入图片描述

我拿到了一份汉化版,居然还带BlackBone的驱动,驱动代码估计也是直接引用的。

在这里插入图片描述

里面有三种内核注入方式

在这里插入图片描述

找到这个枚举,然后分别查看引用,就能知道三种注入方案的原理了

在这里插入图片描述

都是调用的同一个函数

在这里插入图片描述

里面都是调用的BlackBone的接口,看来研究内核这个项目是绕不开了。

在这里插入图片描述

最终都是来到了这个函数,里面确实有三种注入的姿势。不得不说他这个注入写的确实好,考虑到的比较全。关保护,抹PE头,判断进程位数等等。自己写的话估计要踩很多坑,才能到他的代码健壮度。

不过这个Xenos的注入工具确实是太水了,调个接口都能拿1.5Kstar,还被传的牛逼哄哄的。。。。这我就不理解了

总结一下这个函数的三种注入方式:

IT_MMap注入

把目标PE在内存中展开后,创建线程调用导出函数LdrLoadDll进行内存加载;跟反射型注入的MapImage很像,大概翻了一下代码还是有区别的,MapImage反射注入dll是在三环把PE展开,而IT_MMap 的注入方式则是在驱动层把dll手动展开,并且抹掉了PE头,不知道为啥反射注入不抹掉。。。搞不懂作者咋想的。

IT_Thread注入

原理就是附加目标进程后,起了一个线程去执行代码

IT_Apc注入

这个就更没什么说的了,就是APC注入。

三个方式对比还是第一个最优

在这里插入图片描述

也就是这个玩意,有机会把里面的Map注入方式的代码给抠出来自己拿来用。

火绒的注入思路

再来看看火绒早期的注入

https://gitee.com/DragonQuestHero/nahequdongzhurudll

在这里插入图片描述

他这里是注册了一个模块监控的回调,相当于现在可以监控任意一个进程的主模块,等这个主模块跑起来的时候,再注入dll

在这里插入图片描述

先搜一下原理,再来看代码

在这里插入图片描述

大概就是等到目标进程跑起来的时候,替换一下IAT,这样就能实现在进程启动的时候注入dll了。没写注释看的头疼。。。就这样吧。加上这一个一共就有五种驱动注入的姿势了,后面想怎么注就怎么注了。

驱动无模块注入完整源码:

https://download.csdn.net/download/qq_38474570/87263833?spm=1001.2014.3001.5501

驱动注入DLL源码是一段用于实现在内核层次进行DLL注入的代码。通常情况下,DLL注入用于在目标进程的地址空间中运行指定的DLL文件,从而实现对目标进程的监控、修改或增强功能。 驱动注入DLL源码需要使用Windows驱动开发工具包(WDK)来编写,以下是一种可能的实现方式: ```c #include <ntifs.h> // 目标进程ID #define TARGET_PROCESS_ID 1234 // 待注入DLL路径 #define DLL_PATH L"C:\\path\\to\\mydll.dll" // 在进程创建时触发的回调函数 VOID ProcessNotifyCallback(HANDLE hParentId, HANDLE hProcessId, BOOLEAN bCreate) { UNICODE_STRING usDllPath; PUNICODE_STRING pusDllPath; NTSTATUS status; HANDLE hTargetProcess; PEPROCESS pTargetProcess; PVOID pAllocatedMemory; SIZE_T ulDllPathSize; // 判断是否为目标进程创建 if (hParentId != NULL && hTargetProcess != NULL && (HANDLE)PsGetCurrentProcessId() == hParentId) { // 打开目标进程 status = PsLookupProcessByProcessId(hProcessId, &pTargetProcess); if (NT_SUCCESS(status)) { // 分配内存来保存DLL路径 ulDllPathSize = sizeof(DLL_PATH); pAllocatedMemory = ExAllocatePoolWithTag(NonPagedPool, ulDllPathSize, 'DLLI'); if (pAllocatedMemory != NULL) { RtlInitUnicodeString(&usDllPath, DLL_PATH); pusDllPath = (PUNICODE_STRING)pAllocatedMemory; RtlCopyUnicodeString(pusDllPath, &usDllPath); // 注入DLL status = ZwOpenProcess(&hTargetProcess, PROCESS_ALL_ACCESS, NULL, &hProcessId); if (NT_SUCCESS(status)) { status = LdrLoadDll(pTargetProcess, NULL, pusDllPath, &hModule); ZwClose(hTargetProcess); } // 释放内存 ExFreePoolWithTag(pAllocatedMemory, 'DLLI'); } } } } // 注册进程创建通知回调 NTSTATUS RegisterProcessNotifyCallback(VOID) { PVOID pCallbackRegistration; return PsSetCreateProcessNotifyRoutine(ProcessNotifyCallback, FALSE, &pCallbackRegistration); } // 取消注册进程创建通知回调 VOID UnregisterProcessNotifyCallback(VOID) { PsSetCreateProcessNotifyRoutine(ProcessNotifyCallback, TRUE, NULL); } // 驱动入口函数 NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING puniRegistryPath) { NTSTATUS status; // 注册进程创建通知回调 status = RegisterProcessNotifyCallback(); if (!NT_SUCCESS(status)) { DbgPrint("Failed to register process notify callback\n"); return status; } // 等待驱动被卸载 KeWaitForSingleObject(&(pDriverObject->DeviceObject->DeviceLock), Executive, KernelMode, FALSE, NULL); // 取消注册进程创建通知回调 UnregisterProcessNotifyCallback(); return STATUS_SUCCESS; } ``` 以上代码是一种简单的驱动注入DLL的实现方式。当目标进程创建时,会触发回调函数,该回调函数会打开目标进程并注入指定的DLL文件。 需要注意的是,驱动DLL注入在实际使用中需要谨慎使用,因为其对系统稳定性和安全性有较大的影响,一般情况下最好选择更安全可靠的用户级DLL注入方式。此外,驱动级开发需要高深的系统内核知识和经验,对于普通开发者而言较为复杂。使用驱动DLL注入时务必遵守法律法规和道德规范,避免造成不良后果。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鬼手56

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值