English:https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/python
在本文咱们将介绍如何使用直接系统调用(Direct System Calls)以及配合sRDI注入来绕过R3层的行为监控。git
随着安全技术的防护能力逐渐加强,另外一方面,攻击技术也在不断发展,做为一个Red Team须要研究更先进的技术来绕过当下比较流行的防护和检测机制。程序员
近期一篇恶意代码的研究报告声称,使用"直接系统调用"技术来绕过安全软件用户层Hook的恶意样本正在与日俱增。github
研究报告:https://www.cyberbit.com/blog/endpoint-security/malware-mitigation-when-direct-system-calls-are-used/shell
做为一名ReadTeamer,要与时俱进!! 如今轮到咱们也来更新一波shellcode攻击代码了。编程
咱们将接下来将使用这种技术证实,在不触碰磁盘的状况下绕过AV/EDR监控的用户层Hook,使用Cobalt Strike来dump LSASS.exe进程内存。sass
PoC代码能够在这里下载:https://github.com/outflanknl/Dumpert安全
为了弄清楚直接系统调用的真正含义,首先咱们须要先深刻Windows操做系统底层架构。架构
若是你使用过MS-DOS年代Windows系统,也许你会记得,一个简单的程序崩溃能够引发整个操做系统瘫痪。dom
这是由于操做系统在实模式(Real Mode)运行,处理器在实模式下运行时,不会有内存隔离的概念(没有严格限制或者声明,哪些内存区域是能够访问,哪些不能访问)。
也就意味者,若是你写的程序出现了bug致使内存破坏(Memory Currption)会致使整个操做系统中止运行。
一直到后来出现了能够支持保护模式的新处理器和操做系统,这一现象才被改变。
为了防止一个进程崩溃致使操做系统也跟着崩溃,在保护模式下引入了许多安全措施,经过虚拟内存(Virtual Memory)和权限级别(Privilege Levels),和一个叫Rings的概念,来隔离运行的不一样进程之间,以及进程和操做系统之间的内存访问。
Rings一共有4层,Ring0 ~ Ring3分别对应4个特权级别。
Windows操做系统中实际只使用了两个特权级别:
一个是Ring3层,平时咱们所见到的应用程序运行在这一层,因此叫它用户层,也叫User-Mode。因此下次听到别人讲(Ring三、用户层、User-Mode)时,实际上是在讲同一个概念。
一个是Ring0层,像操做系统内核(Kernel)这样重要的系统组件,以及设备驱动都是运行在Ring0,内核层,也叫Kernel-Mode。
经过这些保护层来隔离普通的用户程序,不能直接访问内存区域,以及运行在内核模式下的系统资源。
当一个用户层程序须要执行一个特权系统操做,或者访问内核资源时。处理器首先须要切换到Ring0模式下才能执行后面的操做。
切换Ring0的代码,也就是直接系统调用所在的地方。
咱们经过监控Notepad.exe进程保存一个.txt文件,来演示一个应用层程序如何切换到内核模式执行的:
上面截图展现了Notepad.exe进程保存一个文件时的执行流程(call stack),从下往上看执行流程。
咱们能够看到 notepad调用了kernel32模块中的WriteFile 函数,而后该函数内部又调用了ntdll中的NtWriteFile来到了Ring3与Ring0的临界点。
由于程序保存文件到磁盘上,因此操做系统须要访问相关的文件系统和设备驱动。应用层程序本身是不容许直接访问这些须要特权资源的。
应用程序直接访问设备驱动会引发一些意外的后果(固然操做系统不会出事,最多就是应用程序的执行流程出错致使崩溃)。因此,在进入内核层以前,调用的最后一个用户层API就是负责切换到内核模式的。
CPU中经过执行syscall指令,来进入内核模式,至少x64架构是这样的。咱们能够经过下面 WinDBG截图中看到,反汇编的NtWriteFile指令:
把被调用函数相关的参数PUSH到栈上之后,ntdll中的NtWriteFile函数的职责就是,设置EAX为对应的"系统调用号",最后执行syscall指令,CPU就来到了内核模式(Ring0)下执行。
进入内核模式后,内核经过diapatch table(SSDT),来找到和系统调用号对应的Kernel API,而后将用户层栈上的参数,拷贝到内核层的栈中,最后调用内核版本的ZwWriteFile函数。
当内核函数执行完成时,使用几乎相同的方法回到用户层,并返回内核API函数的返回值(指向接收数据的指针或文件句柄)。
像NtWriteFile这样在进入内核层以前的函数,通常也是大部分安全产品,好比:AV、EDR和Sanbox软件常常设置Hook的地方,它们经过Inline Hook来劫持执行流程到本身引擎中,以便完成对一些敏感API的监控,
若是发现任何可疑的参数,则直接返回失败,或弹出窗口警告。
正如上图NtWriteFile函数的反汇编指令,你可能注意到了,它只有短短8行汇编指令,在这些指令中最重要的就是:系统调用号、syscall指令、进入NtWriteFile函数前,PUSH到栈上的正确的参数,以及使用正确的调用约定。
有了上面这些知识的铺垫,咱们为什么不本身来实现这几行汇编代码,模拟直接系统调用(Direct System Calls)就能够不用再调用NTDLL中的任何函数了,同时咱们也Bypass了User-Mode(Ring3)下面任何设置在NTDLL函数中的Hook。
这正是本文的目的,在开始动手以前,咱们先来简单了解一下Windows编程接口。
用户层的应用程序要想和底层系统交互,一般使用应用程序编程接口(Application Programming Interface )也就是所谓的API。若是你是编写C/C++应用的Windows程序开发程序员,一般使用 Win32 API。
Win32API是微软封装的一套API接口,由几个DLL(所谓的Win32子系统DLL)组成。在Win32 API下面使用的是Naitve API(ntdll.dll),这个才是真正用户层和系统底层交互的接口,通常称为用户层和内核层之间的桥梁。
可是ntdll中函数大部分都没有被微软记录到官方的开发文档中,为了兼容性问题,大多数状况在写程序时,应该避免直接使用ntdll中的API。
微软在Native API上面又封装一层的神奇之处正是由于Native API是用户层与内核层之间的桥梁,这样就能够在不影响Win32编程接口的状况下对系统结构进行修改。
如今咱们对系统调用和Windows编程API有了一些了解,让咱们看看如何经过编程来绕过Win32接口层,直接调用系统API并绕过潜在的Ring3层Hook。
有一个小问题尚未提到,系统调用号会受OS版本影响而变化,有时甚至是Service Pack、内置版本号等。不过不用担忧,Google Project Zero项目的 @j00ru 成员统计了全部Windows系统版本中的Native API的系统调用号。
在线查询系统调用号:https://j00ru.vexillium.org/syscalls/nt/64/ 有了这张表,咱们能够直接搜索咱们想要使用的Native API,就能够看到该API在不一样系统中的调用号。
咱们须要编写汇编来调用Driect System Calls。 在Virtual Studio项目中须要启用MASM编译依赖的支持,咱们才能在项目中添加.asm文件。
要想经过这种方法来编写一个高级木马来彻底绕过用户层API调用几乎是不可能的,至少实现起来很是麻烦,由于这些参数结构的问题、等等。
有时候可能你只是想在恶意代码中使用一个API函数,可是,未曾想这个API的调用堆栈某处早已被一些AV、EDR软件设置了Hook,随时等你上钩。
让咱们来看看如何使用直接系统调用来卸载Hook。
一般状况下,基于用户模式的AV、EDR软件经过使用跳转指令(JMP),将API入口处前5个字节修改成指向安全软件的Hook函数。
卸载这种Hook的方法也早被 @SpecialHoang 和@domchell这两位大神公布过了: https://www.mdsec.co.uk/2019/03/silencing-cylance-a-case-study-in-modern-edrs/
若是你仔细研究这些卸载Hook的思路,你会注意到这些方法中用到了诸如:VirtualProtectEx、WriteProcessMemory之类的API来卸载Native API函数的Hook。
可是若是VirtualProtectEx这些API也被Hook和监视了呢?嘿嘿!这下咱们就能够经过直接系统调用来卸载这些Hook,不怕半路杀出个程咬金了。
在咱们的PoC代码中,基本上和普通卸载Hook的思路同样,恢复被Hook函数的前5字节原始的汇编指令代码。惟一的区别是咱们执行恢复时调用的是Direct System Calls函数(ZwProtectVirtualMemory 和ZwWriteVirtualMemory)。
若是你正在进行评估,而且你的攻击场景须要尽量保持隐蔽,直接在终端上使用Mimikatz并非最好的方法(即便是在内存中)。另外使用procdump等工具转储LSASS内存一般会被 AV、EDR的Hooks检测到。
所以咱们须要一个替代方案来访问LSASS内存,@SpecialHoang 博客中公布了一个方法,先卸载相关函数的Hook,而后再建立LSASS的内存转储。
做为概念证实,咱们建立了一个名为”Dumpert“的LSASS内存转储工具。此工具结合了直接系统调用和卸载API Hook,可让你建立一个LSASS的minidump。而且可能会Bypass一些AV、EDR产品的检测。
获得Minidump文件后,咱们就能够在本身机器上使用Mimikatz来提取凭证信息。
若是咱们不想磁盘落地,就得须要使用某种注入技术。咱们能够写一个反射式加载的DLL,可是反射式DLL注入会留下能够被检测到的内存数据。
个人同事@StanHacked告诉我一种称为 "Shellcode Reflective DLL Injection"的DLL注入技术。
sRDI能够把一个普通的DLL文件转换为一段不依赖任何位置的Shellcode,这项技术是被Slient Break Security的Nick Landers(@monoxgas)开发,基本上属于RDI的升级版。
相对于标准RDI,使用SRDI的一些优势:
想了解更多sRDI细节的朋友,能够参考这篇文章:https://silentbreaksecurity.com/srdi-shellcode-reflective-dll-injection
为了让这一步更方便,咱们提供了一个aggressor脚本:https://github.com/outflanknl/Dumpert/tree/master/Dumpert-Aggressor,启用脚本之后,能够在beacon菜单中使用dumpert命令来一键搞定!!
可以绕过安全产品Hook的恶意软件正在逐渐增多,咱们须要在咱们的项目中也嵌入这种技术。
本文中,咱们经过编写汇编代码,基于Native API的函数原型、以及不一样版本的系统调用号,实现了直接系统函数调用。使用这些函数时,就像是在直接使用Native API中的函数同样。
咱们将这种技术与API卸载Hook结合,实现了从LSASS中建立一个minidump,而且使用了sRDI结合Cobalt Strike来注入dumpert shellcode到目标系统内存中。
检测恶意使用系统调用比较困难,因为绕过了用户模式编程接口,所以惟一能查找恶意行为的地方只能内核中。可是因为内核被PatchGuard保护,安全产品更要想在运行的内核中建立钩子或修改内核文件,就更难了!!!
我但愿这篇文章可以帮助理解黑客如今使用的这种高级攻击技术,但愿这篇文章能在Read Team行动中给你们一些有用的启发!!