English:476K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6G2N6i4c8X3L8r3q4F1K9#2)9J5k6h3&6D9i4K6u0r3j5X3I4G2k6#2)9J5c8U0t1H3x3e0W2Q4x3V1j5H3y4W2)9J5c8U0p5&6i4K6u0r3M7X3g2V1i4K6u0V1N6r3g2S2L8g2)9J5k6s2c8S2j5%4c8A6j5%4y4Q4x3X3c8U0L8$3#2T1K9h3&6A6L8X3N6Q4x3X3c8V1K9i4u0W2j5%4c8Q4x3X3c8K6P5i4y4@1k6h3#2Q4x3X3c8U0j5h3I4D9M7#2)9J5k6r3q4F1k6q4)9J5k6s2y4J5k6r3W2Q4x3X3c8@1L8#2)9J5k6r3u0&6M7r3q4K6M7#2)9J5k6r3q4$3i4K6u0V1k6h3c8J5i4K6u0r3
在本文我们将介绍如何使用直接系统调用(Direct System Calls)以及配合sRDI注入来绕过R3层的行为监控。
随着安全技术的防御能力逐渐增强,另一方面,攻击技术也在不断发展,作为一个Red Team需要研究更先进的技术来绕过当下比较流行的防御和检测机制。
近期一篇恶意代码的研究报告声称,使用"直接系统调用"技术来绕过安全软件用户层Hook的恶意样本正在与日俱增。
研究报告:7e9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2U0P5h3u0W2M7X3u0A6N6q4)9J5k6h3y4G2L8g2)9J5c8X3u0D9L8$3N6Q4x3V1k6W2L8X3c8H3L8$3W2F1N6q4)9J5k6s2y4W2j5%4g2J5K9i4c8&6i4K6u0r3L8h3q4D9N6$3q4J5k6g2)9J5k6r3#2A6N6r3W2Y4j5i4c8A6L8$3&6Q4x3X3c8%4K9r3g2F1i4K6u0V1k6r3W2J5k6h3y4@1i4K6u0V1M7%4W2K6N6r3g2E0i4K6u0V1j5$3q4D9L8s2y4Q4x3X3c8S2M7X3g2Q4x3X3c8#2M7$3g2V1i4K6u0r3
作为一名ReadTeamer,要与时俱进!! 现在轮到我们也来更新一波shellcode攻击代码了。
我们将接下来将使用这种技术证明,在不触碰磁盘的情况下绕过AV/EDR监控的用户层Hook,使用Cobalt Strike来dump LSASS.exe进程内存。
PoC代码可以在这里下载:908K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6G2N6i4c8X3L8r3q4F1K9$3&6D9i4K6u0r3c8s2g2E0M7r3g2J5N6l9`.`.
为了弄清楚直接系统调用的真正含义,首先我们需要先深入Windows操作系统底层架构。
如果你使用过MS-DOS年代Windows系统,也许你会记得,一个简单的程序崩溃可以引起整个操作系统瘫痪。
这是因为操作系统在实模式(Real Mode)运行,处理器在实模式下运行时,不会有内存隔离的概念(没有严格限制或者声明,哪些内存区域是可以访问,哪些不能访问)。
也就意味者,如果你写的程序出现了bug导致内存破坏(Memory Currption)会导致整个操作系统停止运行。
一直到后来出现了可以支持保护模式的新处理器和操作系统,这一现象才被改变。
为了防止一个进程崩溃导致操作系统也跟着崩溃,在保护模式下引入了许多安全措施,通过虚拟内存(Virtual Memory)和权限级别(Privilege Levels),和一个叫Rings的概念,来隔离运行的不同进程之间,以及进程和操作系统之间的内存访问。
Rings一共有4层,Ring0 ~ Ring3分别对应4个特权级别。
Windows操作系统中实际只使用了两个特权级别:
一个是Ring3层,平时我们所见到的应用程序运行在这一层,所以叫它用户层,也叫User-Mode。所以下次听到别人讲(Ring3、用户层、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的系统调用号。
在线查询系统调用号:f65K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6B7x3o6m8J5N6g2)9J5k6i4k6W2P5r3W2D9L8r3W2#2L8g2)9J5k6h3!0J5k6#2)9J5c8Y4y4&6M7$3y4S2L8r3I4K6i4K6u0r3L8Y4c8Q4x3V1j5$3y4q4)9J5c8W2)9J5y4X3&6T1M7%4m8Q4x3@1t1`. 有了这张表,我们可以直接搜索我们想要使用的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这两位大神公布过了: 796K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2E0k6s2y4W2j5#2)9J5k6h3y4G2i4K6u0W2N6h3E0Q4x3V1j5J5x3o6p5&6i4K6u0r3x3o6y4Q4x3V1k6K6K9h3I4W2L8X3y4A6L8X3N6Q4x3X3c8U0P5h3I4S2L8X3y4W2i4K6u0V1j5g2)9J5k6r3y4S2M7$3g2Q4x3X3c8K6N6s2g2V1P5g2)9J5k6r3W2F1i4K6u0V1L8h3!0V1k6i4u0F1i4K6u0V1k6h3c8J5M7#2)9J5c8R3`.`.
如果你仔细研究这些卸载Hook的思路,你会注意到这些方法中用到了诸如:VirtualProtectEx、WriteProcessMemory之类的API来卸载Native API函数的Hook。
但是如果VirtualProtectEx这些API也被Hook和监视了呢?嘿嘿!这下我们就可以通过直接系统调用来卸载这些Hook,不怕半路杀出个程咬金了。
在我们的PoC代码中,基本上和普通卸载Hook的思路一样,恢复被Hook函数的前5字节原始的汇编指令代码。唯一的区别是我们执行恢复时调用的是Direct System Calls函数(ZwProtectVirtualMemory 和ZwWriteVirtualMemory)。
在攻击过程中,通常我们需要使用Mimikatz来获取目标系统上的凭证、Hashes、Kerberos票据。如今,终端检测软件和情报分析系统在检测和预防Mimikatz方面做的相当不错。
如果你正在进行评估,并且你的攻击场景需要尽可能保持隐蔽,直接在终端上使用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的一些优点:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-8-4 10:09
被Adventure编辑
,原因: 修正错别字