巧妙的EDR绕过技术及其实现方法
最近我在测试一些EDR检测间接系统调用的能力时,想到了一个独特的绕过方法。
直接与间接系统调用的问题
直接和间接系统调用的一个缺点是,从调用堆栈中可以明显看出你绕过了EDR的用户模式钩子。以下是直接、间接和常规调用的示例调用堆栈:
- 直接系统调用的调用堆栈
- 间接系统调用的调用堆栈
- 常规挂钩Nt函数调用的调用堆栈
从最后一张图片可以看出,当通过挂钩函数进行调用时,EDR钩子的返回地址会出现在调用堆栈中(在我的案例中是hmpalert)。
这是一个有趣的困境:我们不想调用挂钩函数,因为那可能触发检测;但如果我们完全绕过钩子,也可能触发检测。
第一个想法:TOCTOU
时间检查到时间使用(TOCTOU)是软件利用中常用的技术。当对对象执行安全检查,但在检查时间和使用时间之间无法阻止修改该对象时,就会出现漏洞。
我的初步想法是利用类似的竞争条件来对抗EDR的钩子:使用良性参数调用挂钩函数,然后在调用过程中快速将其替换为恶意参数。
第二个想法:硬件断点
这个想法更简单:选择一个我想调用的被EDR挂钩的ntdll函数,然后在syscall指令上设置硬件断点。
通过在执行syscall指令上设置执行断点,我们能够在EDR完成检查之后、系统调用发生之前拦截执行。这基本上允许我们挂钩EDR的钩子,并将任何合法调用转换为自定义系统调用。
我们可以使用不会触发检测的良性参数调用挂钩函数,然后在EDR已经检查调用后将参数替换为恶意参数。我们甚至可以根据需要更改系统调用号,以调用与EDR认为我们正在进行的不同的系统调用。
寻找合适的目标
为了测试我的想法,我想出了一个会立即触发检测事件的函数调用。最终,我选择使用旧的进程注入代码。
该代码的工作原理类似于进程空心化:它创建一个处于挂起状态的新进程,将自身注入到挂起的进程中,然后使用SetThreadContext()将主线程的入口点更改为恶意代码的入口点。
我选择的目标是Sophos Intercept X,因为它宣传能够检测进程空心化攻击。
使用硬件断点绕过检查
对于我们的第一个示例,我们只需在NtSetContextThread()的syscall和retn指令上设置断点。
以下是查找这些指令的示例代码:
|
|
在断点处理程序中,我们只需更改RCX和RDX寄存器,它们包含NtSetContextThread()的参数1和参数2。
结果
成功!代码能够将自身注入到notepad中并显示消息框。
第三个想法:故意触发异常
不使用硬件断点,我们将尝试引发CPU异常。常规异常可以以与断点异常完全相同的方式处理,但我们不需要调用NtSetContextThread()来设置它们。
我们知道EDR在调用NtSetContextThread()时会检查上下文结构,所以让我们利用这一点。大多数软件在尝试读取地址之前会检查地址是否为NULL,但如果它既不是NULL也不是有效地址呢?
如果我们将上下文地址设置为0x1337会发生什么?
|
|
当我们运行它时…EDR的钩子尝试读取无效内存并使进程崩溃。
现在我们有了一个无需任何硬件断点即可触发异常的简单方法。
结论
我们有两种绕过EDR钩子而不实际绕过EDR钩子的方法。虽然我不确定将强制异常方法转化为通用EDR绕过的实际可行性或难易程度。
第一种方法更通用,但可能也更容易被检测到。由于异常处理程序允许我们在不使用NtSetContextThread()的情况下更改线程的上下文,我们可能会结合这两种方法。
这只是一个有趣的周末副项目,我觉得应该发布出来。希望有人会发现这些信息有用。