文章目录
- 实现效果
- 三环无模块注入的方案
- 反射型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<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头,第二是在申请时的内存属性为可执行。
那么我们可以针对这种方式进行改进:
- 在dll入口函数执行完成之后,把PE头抹掉
- 在申请内存时候,只申请可读可写的内存
- 在调用dllmain前,将属性修改为可执行
- 在执行完dllmain之后,将属性修改为只读
完成这些操作之后,在VAD树的分配时内存属性就会变成读写,当前的内存属性就会变成只读,再把PE头抹掉,就实现了三环的无模块注入了。
各位有需要可以参考这个思路进行自行魔改。
零环无模块注入方案
接下来再来说零环的无模块注入方案,零环的无模块注入方案首先要用到一个项目,将我们的dll文件转成shellcode
petoshellcode
pe转shellcode的原理就是在完整的dll外层套上一个用shellcode的加载器,然后由这个加载器把我们的dll跑起来。
https://github.com/monoxgas/sRDI
这里需要用到一个开源项目叫sRDI,做红队相关的同学应该比较眼熟了。
这个项目里面实现了一个加载dll的函数,使用shellcode的方式编写的,与位置无关,编译完成之后,可以直接把二进制扣出来用。
里面也有一个调用示例。我们需要修改下这个函数,把参数的部分去掉,就可以使用这个loader来加载任意一个dll了。
无模块注入流程
接着就可以开始实现驱动层的无模块注入了,流程如下:
- 将要注入的dll提取成硬编码存存放到char数组里
- 附加目标进程,申请一块不可执行的内存,用来存放硬编码的dll
- 用隐藏可执行内存的方式,申请一块可执行的内存,用来存放dllLoader
- 修改dllLoader内的dll入口函数地址
- 附加到目标进程,并创建线程执行dllLoder
- 等待线程执行完成,释放内存
实现代码
实现代码如下:
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