【网络安全】红队攻防之基础免杀
引言
本文主要介绍“反射型 dll 注入”及“柔性加载”技术。
反射型 dll 注入
为什么需要反射型 dll 注入
常规的 dll 注入代码如下:
主要做了几件事情:
从磁盘读取 dll 到 wchar_t 数组
将该 payload 数组写入目标内存
在目标内存中找到 LoadLibraryW 函数
通过 CreateRemoteThread 调用 LoadLibraryW 函数,参数为 dll 在内存中的地址。
这样的操作模式有几个很高危的点。首先,从磁盘读取 dll 需要考虑 dll 的静态免杀,对此我们可以直接写在装载器中并加密。
其次,在目标内存中找到 LoadLibraryW 函数,需要 GetProcAddress LoadLibraryW,这种调用属于很有特征的调用模式,容易被 AV/EDR 归类。对此我们的解决措施就是接下来要提及的反射型 dll 注入技术。
最后,CreateRemoteThread 进行远程线程注入 行为本身就很高危,同时参数是 LoadLibraryW 的地址,一眼 malware。
对此我们优化调用,不再使用 CreateRemoteThread 进而使用创建新进程的方式结合反射型 dll 注入技术改变 dll 注入技术的调用模式。
实现思路
早期的 dll 注入实现原理:
![](https://static001.geekbang.org/infoq/e4/e45c8c04176e4d112b4cb1a48e5c9c72.png)
上图比较清楚的写了反射型 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 在内存中的地址,直接调用即可。
①网络安全学习路线
②20 份渗透测试电子书
③安全攻防 357 页笔记
④50 份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100 个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年 CTF 夺旗赛题解析
具体实现
加载器部分
![](https://static001.geekbang.org/infoq/ef/ef5ea30940102cc5dfd975cad522de9b.png)
首先 shellcode 使用 AES 解密,这部分添加了一些 c 的代码加密
![](https://static001.geekbang.org/infoq/a2/a21b626cf269074c42d3f2eb400acdcf.png)
后来发现原本项目的 release 目录下有 python 的加密脚本:
![](https://static001.geekbang.org/infoq/6f/6fa7bb2b0df8891919f1d676c03c4622.png)
解密载入内存后,使用 GetReflectiveLoaderOffset 计算出 ReflectLoader 函数的偏移:
![](https://static001.geekbang.org/infoq/31/31f2e0c094272bad247d39f44bf2bb6e.png)
最后创建线程调用 ReflectLoader 函数。
dll 部分
ReflectiveLoader 一共做了 5 件事:
一、 解析加载 DLL 所需 kernel32.dll WINAPI 的地址(例如 VirtualAlloc, LoadLibraryA 等),通过关键函数的 hash 在内存中搜索,函数 hash:
![](https://static001.geekbang.org/infoq/69/696450383c99d11b41be82797a21fd3b.png)
遍历内存进行搜索:
![](https://static001.geekbang.org/infoq/66/667676e7a641655936d8cd6e9b913f1e.png)
二、 将 DLL 及其相应的节写入内存中:
![](https://static001.geekbang.org/infoq/9f/9f15fa1c7982b4329e81c06eabe216d8.png)
三、 建立 DLL 导入表,以便 DLL 可以调用 ntdll.dll 和 kernel32.dll WINAPI
![](https://static001.geekbang.org/infoq/26/26cb1c6842c8bd51e388264e78e1ad90.png)
四、 修复重定位表:
![](https://static001.geekbang.org/infoq/16/16d52825116b08e2cebd7ad8b1d568c7.png)
五、 调用 DLL 的入口点:
![](https://static001.geekbang.org/infoq/51/518a072d8af10e5f053965d3ad84d112.png)
最终我们的恶意代码位于 dllmain 中,项目还是采用加载 shellcode 的方式上线 cs。
柔性加载
限制使用具有 RWX 标记的内存,cs 在 4+可以直接进行相关配置。
![](https://static001.geekbang.org/infoq/9b/9b2328f4de9c883384a8178c66a68a1a.png)
推荐配置:
牛刀小试
360
使用 base64+xor 混淆 shellcode:
![](https://static001.geekbang.org/infoq/05/05d919421d45ebef5220ba519b3fa6e9.png)
成功 bypass:
![](https://static001.geekbang.org/infoq/ae/aeb15c4f48816cab870a555d1ee78262.png)
![](https://static001.geekbang.org/infoq/cc/cc3d4a736b15e00725a91c2e87552ba7.png)
火绒
和上述方法相同:
![](https://static001.geekbang.org/infoq/00/00f1d6c4c0f1a75c97aebc7534bdd587.png)
![](https://static001.geekbang.org/infoq/fa/fadbf05cb4826217d381abc8a36a5701.png)
definder
加强 shellcode 的混淆:
依旧报毒,但是类型发生改变了,说明静态的混淆有效果:
![](https://static001.geekbang.org/infoq/eb/eb2b2fef828c90aa6de7ebb8682d7ca9.png)
异或的操作,比较可疑,经过测试发现是 cs 的 shellcode 出现在数组里就报毒,应该是对内存进行的扫描。
所以我们可以使用《文章二》中提及的技术“规避常见的恶意 API 调用模式”,将 shellcode 分片直接写入连续内存。
在测试的过程中发现莫名其妙的过了查杀:
![](https://static001.geekbang.org/infoq/db/dbcd5d4abf4bb388fb8201c6ce4a35e0.png)
很神奇,这段并没有实现内存的切片写入,因为 shellcode 的大小没有达到 4096,实际上相当于直接分配了个大小为 4096 的数组,写入了 shellcode。
而且把这段代码相同的格式放外面就不行,个人感觉 definder 还是没有去检查内存。
可能是有语义分析的引擎,这次刚好绕过了语义分析。
macfee
同上方法可以成功 bypass:
![](https://static001.geekbang.org/infoq/d3/d32928ff7cca2b4ba999e09ee72e97a6.png)
正常执行命令:
![](https://static001.geekbang.org/infoq/0e/0ee2cef19498ec81021c2ee91d0285ed.png)
kasperky Endpoint 11 for windows
用过 macfee 和 definder 的 demo2 测试失败,注释掉代码加载部分不报毒,改用 apc 和创建进程的的方式加载内存:
依旧不行:
![](https://static001.geekbang.org/infoq/80/8067ddbd257bb50cd668310fe71afbb6.png)
使用 syscall 调用 NtCreateThreadEx。这里被坑了,WaitForSingleObject 要使用,不然会异步,没法上线:
能看到效果,行为检测依旧有问题:
![](https://static001.geekbang.org/infoq/89/8965ed166b05df0e003cdad437e1ad23.png)
但漏洞利用防御已经没有相关报警:
![](https://static001.geekbang.org/infoq/0a/0aeddf91ef921c61534c2273252c8ea9.png)
怀疑是 cs 本身流量特征的问题,为了验证我使用卡巴斯基本身的功能禁用了网络请求:
![](https://static001.geekbang.org/infoq/00/0095f01106c5fdcdabffbecc5926d8af.png)
确实不杀也不报警了,确定是 cs 通信的问题。
ESET Endpoint Security
demo3 报警,并且明显检测到网络连接行为
![](https://static001.geekbang.org/infoq/d2/d2f1fb65fc62498ba1fc10b180662495.png)
静态没有问题
![](https://static001.geekbang.org/infoq/22/227254446ce17ca49f58c8338a8573c1.png)
主要应该还是在对内存的检测,而且感觉已经执行到了发包
![](https://static001.geekbang.org/infoq/3c/3cb8b559ac1a5b23c3877d7e10ff06dd.png)
下面根据《三》中的“beacon 的内存加密”对 demo3 进行优化,使用 RefleXXion 工具的第二种将内存设为 NO_ACCESS 并通过注册异常处理还原的方式进行免杀。
![](https://static001.geekbang.org/infoq/4a/4a96b292604da5027e8cac3c98fb94fc.png)
设置流量的白名单:
![](https://static001.geekbang.org/infoq/71/71a6d72cf6acf3ca6488eba10836d8a1.png)
关闭 web 控制后成功并上线
![](https://static001.geekbang.org/infoq/e1/e1959b5e9871167b4680e5f6a7879fc5.png)
eset 在持续在扫描内存,但一直没有权限,一直触发异常,无法进入正常的后门逻辑
![](https://static001.geekbang.org/infoq/12/1200396a80cb36b9b4fb33956623c42b.png)
能绕过内存的检测,但无法正常使用
![](https://static001.geekbang.org/infoq/39/39992d29dcb93046c4667ac705dadc32.png)
感觉 ESET 一直在我程序里进行内存操作,访问到了不可访问的内存段。
可能 ESET 的机制是一直在扫描程序内存,也可能是想要做一些 hook。
我尝试使用 RefleXXion 的第一种方法,将 shellcode 加密并使属性为 RW 或 RX 的方式加载 shellcode:
![](https://static001.geekbang.org/infoq/1e/1ebe70d6d87af09143f6bd6ded849261.png)
可以成功上线,并且正常使用:
![](https://static001.geekbang.org/infoq/00/00a028a4735abee629eb0d03484d7d0d.png)
总结
该系列文章所有的 bypass edr 方法都只在用户态进行操作,已经能规避大多数 AV/EDR 的检测。但不乏一些 edr 进行了比较多的内核层面的限制,如炭黑、fireeye 等。
评论