驱动无模块注入dll

文章目录

      • 实现效果
      • 三环无模块注入的方案
      • 反射型dll注入方式的改进
      • 零环无模块注入方案
      • petoshellcode
      • 无模块注入流程
      • 实现代码
      • Xenos注入方案研究
        • IT_MMap注入
        • IT_Thread注入
        • IT_Apc注入
      • 火绒的注入思路

实现效果

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

三环无模块注入的方案

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

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

//隐藏注入dll 进程名 dll路径 适配32位和64位
void HideInject(const std::wstring& processName, const std::wstring& dllPath)
{
	vector 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(g_loadDataPtr->procAddress);
	
}

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

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

实现效果如下:

驱动无模块注入dll_第1张图片

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

驱动无模块注入dll_第2张图片

在枚举模块的位置已经找不到我们注入的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_第3张图片

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

驱动无模块注入dll_第4张图片

里面也有一个调用示例。我们需要修改下这个函数,把参数的部分去掉,就可以使用这个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

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

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

驱动无模块注入dll_第5张图片

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

驱动无模块注入dll_第6张图片

里面有三种内核注入方式

驱动无模块注入dll_第7张图片

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

驱动无模块注入dll_第8张图片

都是调用的同一个函数

驱动无模块注入dll_第9张图片

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

驱动无模块注入dll_第10张图片

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

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

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

IT_MMap注入

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

IT_Thread注入

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

IT_Apc注入

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

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

驱动无模块注入dll_第11张图片

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

火绒的注入思路

再来看看火绒早期的注入

https://gitee.com/DragonQuestHero/nahequdongzhurudll

驱动无模块注入dll_第12张图片

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

驱动无模块注入dll_第13张图片

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

驱动无模块注入dll_第14张图片

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

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

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

你可能感兴趣的:(Windows内核,c++,windows)