在勒索软件和APT事件充斥的时代,检测这些攻击的重要性变得越来越重要。几年前,用于安全事件检测和响应的最佳工具和技术手段集成为SIEM系统,其中结合了IPS/IDS系统的日志,代理,防火墙,AV的日志等。近年来,在我个人看来,已添加了越来越重要的组件-“端点检测和响应-EDR”系统或功能。这些EDR系统的功能包括实时监视端点,数据分析,威胁检测和阻止以及威胁搜寻功能。在渗透测试和红队交战中,这些系统可能使使用公共进攻性安全工具变得困难,因为它们经常被发现和阻止。但是,这些系统有一个弱点,即攻击者可以绕过保护措施。在这篇博文中,我将总结到目前为止找到的所有EDR旁路方法。列出的工具/技术可能并不详尽,但肯定有助于获得良好的概述,并在必要时更好地了解如何使用它们。
Introduction
所有关注攻击性安全社区的人都会在过去两年中一次又一次地遇到
Userland hooking
,
Syscalls
,
P/Invoke
/
D-Invoke
等术语。我自己也遇到了一些我不完全理解的博客文章和工具。我有时觉得我需要从头开始积累知识。由于我在很多情况下不需要这些“新”技术,我把这些课题的研究推迟了几个月。
随着安全事件的日益增多,越来越多的企业建立了安全运营中心(SOC)或计算机应急响应小组(CERT)。另一个术语是“网络防御中心”。这些单位的主要目的是分析新出现的安全事件,并查明和阻止潜在的攻击者。除了SIEM之外,EDR系统也越来越多地被用于分析。与此同时,EDR绕道话题对我们进攻性的安全人员来说变得越来越重要。长话短说:为了能够复制和使用公共技术,我现在必须自己深入研究这些话题。我认为激励自己的最好方法就是写一篇关于这个话题的博客文章。这些工具和技术,实际上已经出版了,比我在这篇文章中要提到的参考文献要古老得多。它们以前已经被恶意软件在野外积极使用。这篇博文将是我发现的公开工具/技术的总结。我强烈建议你阅读这里链接的所有其他博客文章。它们包含了更多的信息和背景知识。在深入讨论主要主题之前,我们必须先了解一些Windows操作系统体系结构的基础知识,以及有关汇编代码的一小部分内容。请跳过这部分。
Assembler code
如果您正在编写一个独立于编程语言的程序,则很可能使用编译器从相应的源代码构建程序。源代码片段基本上被翻译成机器语言,即
01010011 00110011 01100011 011010101 01110010 00110011
这样的最终二进制代码,可以由CPU直接执行:
一些编译器(例如gcc)在转换为机器代码之前会生成汇编代码。汇编代码指令实际上与机器代码具有一对一的映射关系。因此,这是最接近机器码的代码,例如:
通过IDA Pro或Ghidra反汇编程序,您还可以从已编译的源代码中获得汇编代码。
Windows OS architecture
程序员通常不想重新发明轮子,所以基本函数是从现有库中导入的。例如,
printf()
是用C语言从库
stdio.h
导入的。例如,Windows开发人员正在使用应用程序编程接口(API),API也可以导入到程序中。所谓的Win32 API是有文档记录的,由几个库文件(DLL文件)组成,这些文件位于
C:\windows\system32\
文件夹中,例如
kernel32.DLL
、
User32.DLL
等:
NTDLL.dll
不是Win32 API的一部分,也没有正式的文档。
User-mode / Kernel-mode
Windows操作系统具有两种不同的特权级别,这些特权级别是为了保护操作系统免受例如已安装的应用程序导致的崩溃而实现的。
Windows系统上安装的所有应用程序均以所谓的用户模式运行
。
内核和设备驱动程序以所谓的内核模式运行
。用户模式下的应用程序无法访问或操作内核模式下的内存部分。由于内核补丁保护,AV/EDR系统只能在用户模式下监视应用程序行为。用户模式中的最后一个实例是
NTDLL.dll
中的Windows API函数。如果调用了NTDLL.dll中的任何功能,则CPU接下来将切换到内核模式,AV/EDR就不再能够监视该模式。NTDLL.dll的单个功能称为
Syscalls
。
Why should I care?
作为模拟攻击者,我们在哪里需要或使用Windows API?例如,如果我们要将特定的字节(例如
shellcode
)写入进程,则可以使用以下C#代码片段从文件
kernel32.dll
导入
WriteProcessMemory
:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
在此处可以找到一个如何使用
kernel32.dll
函数将shellcode写入远程进程的示例。
我们大多数人最常使用的是PE-Loaders。在大多数情况下,我们希望尽可能长时间地保留注入的内存,以免在磁盘上留下任何痕迹,也不会出现
AV-Evasion
。因此,必须从内存中加载Mimikatz或任何其他C编写的工具,这是通过PE-Loader完成的。
Powersploits Invoke-ReflectivePEInjection
或
Casey Smith
的
C#PE-Loader
大量使用Windows API函数,例如
kernel32.dll
中的
CreateRemoteThread
,
GetProcAddress
,
CreateThread
。
Last but not least - 取决于您使用的Command&Control(C2)框架-他们中的大多数使用WindowsAPI函数作为他们的模块。
但是Win32 API文件中包含的功能(例如
kernel32.dll
,
User32.dll
等)没有直接转换为机器代码,而是从本地API NTDLL.dll映射到其他功能。例如,来自kernel32.dll的
writeProcessMemory
将从NTDLL.dll解析为
NtProtectVirtualMemory
->
NtWriteVirtualMemory
->
NtProtectVirtualMemory
。
第一个
Syscall
NtProtectVirtualMemory
为该进程设置新权限并使其可写
第二个
NtWriteVirtualMemory
实际上写入字节
第三个调用恢复该进程的旧权限。
因此,本机API NTDLL.dll是操作系统前面的最后一个实例。
Userland Hooking
自从NTDLL.dll函数成为最后一个实例以来,AV/EDR可以监视攻击者或恶意软件的可疑活动,市场目前通用这个玩法。他们将自定义DLL文件注入到每个新进程中。您可以找到DLL文件,这些文件是通过Sysinternals
procexp64.exe
从AV/EDR加载到进程中的。您需要检查“View”菜单中的“Show Lower Pane”按钮,然后检查该按钮以显示已加载的DLL:
选择首选过程后,您将在“下部窗格”视图部分中看到已加载的DLL文件。在这种情况下,我们看到由McAfee AV的CMD.EXE加载的DLL的文件:
Powershell.exe
从McAfee注入了更多的DLL,这很可能是因为它监控了更多的用例。
如您所见,McAfee注入了三个DLL文件,其中一个被称为“
Thin Hook Environment
”-最有可能监视Windows API调用的DLL。
因此,这些加载的DLL文件将监控为特定Windows API调用注入它们的过程。在我的上一篇博客文章中,我以签名更改,运行时加密和解密等形式撰写了有关AV-Evasion的文章。如果我们对shellcode进行加密并在运行时对其进行解密以将其写入到远程进程中,则可以调用
writeProcessMemory
,该函数在幕后有时会调用
NtWriteVirtualMemory
。AV/EDR可能的目标之一是查看攻击者在运行时准确加载到内存中的内容。因此他们可以监视
NtWriteVirtualMemory
调用。但是如何进行“监视”呢?
如果程序从kernel32.dll加载了类似
NtWriteVirtualMemory
的函数,则将kernel32.dll的副本放入内存。AV/EDR通常会处理此文件的内存中副本,并将自己的代码添加到特定功能中,例如
NtWriteVirtualMemory
。当程序调用该函数时,将首先执行AV/EDR附加代码,例如在
NtWriteVirtualMemory
的情况下,将对字节进行分析,然后将shell写入远程进程。通过使用此技术,他们可以看到明文shellcode字节,因为此时它们已被解密。
通过修补API函数将自己的代码嵌入内存中的AV/EDR的技术称为
Userland-Hooking
。
通过加载自定义的Invoke-Mimikatz版本(就像我在第二篇博客文章通过手动修改第二部分绕过AMSI并在系统上启用防御程序一样),内存扫描器在解密和PE加载后从内存中捕获了Mimikatz。如果您再次查看该代码-首先完成解密,然后再运行PE-Loader。现在我们知道,PE-Loader调用了几个潜在的可疑Windows API调用。这些调用会触发内存扫描器。因此,避免调用将根本不进行内存扫描。
到目前为止,我已经知道
Userland-Hooking
技术已经公开,它以某种方式unhooking,将其重新修补到内存中,修补AV/EDR的DLL或避免通过使用直接Syscall加载Windows API函数。
Patching the patch
@SpecialHoang和MDsec在2019年初发布了博客文章,解释了如何通过修补补丁来绕过AV/EDR软件:
https://medium.com/@fsx30/bypass-edrs-memory-protection-introduction-to-hooking-2efb21acffd6
https://www.mdsec.co.uk/2019/03/silencing-cylance-a-case-study-in-modern-edrs/
如果您的植入程序或工具从kernel32.dll或NTDLL.dll加载某些功能,则库文件的副本将加载到内存中。AV / EDR供应商通常会从内存中的副本中修补某些功能,并将JMP汇编程序指令放在代码的开头,以将Windows API功能重定向到AV / EDR软件本身的某些检查代码。因此,在调用真实的Windows API函数代码之前,需要进行分析。如果此分析没有导致可疑/恶意行为,并且返回了干净的结果,则随后将调用原始Windows API函数。如果发现恶意软件,则Windows API调用将被阻止,否则该进程将被终止。我从ired.team盗的图,这可能有助于理解该过程:
这两篇博文都侧重于绕过EDR软件
CylancePROTECT
并为此特定软件构建PoC代码。通过修补来自内存中被操纵的NTDLL.dll的其他JMP指令,Cylance的分析代码将永远不会被执行。因此,无法进行检测/阻止:
此技术的一个缺点是,您可能必须为每个不同的AV/EDR更改补丁。它们不太可能在同一点的相同功能之前都放置一条附加的JMP指令。他们很可能会hook不同的功能,并可能在其补丁程序中使用其他位置。如果您已经知道目标环境中已安装了哪种AV/EDR解决方案,则可以使用此技术,并且可以通过打补丁来绕过保护措施。
我还找到了一个包含AV/EDR供应商及其相应的hook Windows API函数的PDF文件的存储库,如果您感兴趣,请在此处查看:
https://github.com/D3VI5H4/Antivirus-Artifacts
Outflankl’s Dumpert and direct system calls
Outflanknl在2019年6月19日的博客文章中发布了一个名为
Dumpert
的工具,他们在其中解释了如何使用直接系统调用绕过
Userland-Hooking
。我不会覆盖博客文章中的所有详细信息,而仅总结最重要的事实以理解该主题。这里使用的技术的目标是在运行时不从ntdll.dll加载任何函数,而是直接使用相应的汇编代码来调用它们。通过反汇编ntdll.dll文件,可以获取其中包含的每个函数的汇编代码。
这里的一个问题是,在Windows OS版本之间,有时甚至在Service Pack /内部版本号之间,汇编代码有时有所不同。Google项目Zero对这些差异进行了一些研究,以便可以在链接的网站上查找它们。通过为所有OS版本嵌入所有不同的汇编代码版本,可以在运行时检查基础操作系统,并为所需的Windows API函数选择正确的汇编代码。可以使用ASM文件通过Visual Studio将汇编程序代码嵌入C项目中。因此,Dumpert项目正在使用ASM文件,该文件在每个Windows版本的汇编代码中都包含所有必要的Windows API函数:
https://github.com/outflanknl/Dumpert/blob/master/Dumpert-DLL/Outflank-Dumpert-DLL/Syscalls.asm
要使用此技术,您需要了解项目所需的确切NTDLL.dll函数,并通过反汇编为它们提取相应的汇编代码。之后,您需要构建一个ASM文件,其中包含针对不同Windows OS版本的所有不同偏移量。听起来很复杂。
使用此技术也有一些缺点:
-
每当发布较新的Windows版本时,您的二进制文件将不再起作用。那是因为每个功能的汇编代码必须再次更改。因此,每当Microsoft发布更改时,您都需要构建新的implant/tool
-
分解所有Windows API函数需要很多工作,并且需要大量时间/工作
但是使用这种技术将使我们能够绕开Userland-Hooking。此技术独立于不同的供应商。他们都根本看不到任何Windows API函数导入或调用。
No function imports -> no patch/hook by the AV/EDR software -> stealth/bypass.
Syswhispers
随着SysWhispers工具的发布,使用相应的C-Header文件创建自定义ASM文件变得更加容易。卸载ntdll.dll的手动开销被省去了。通过执行单个python脚本,可以轻松构建ASM和Header-File:
大约1个月前发布了SysWhispers2,它减少了ASM文件的大小,并在每一代中使用了随机的函数名称哈希。将来将不推荐使用第一个版本,因此您应该使用受支持的版本2。
Dumpert,Syswhispers和Syswhispers2当前仅支持x64 Syscall。如果您需要x86 Syscall,则在Github上发布了SysWhispers2_x86。
如果您不想用C语言编写工具/植入物,也可以使用NimlineWhispers来动手,后者可为ASM文件和Nim-Code头文件建立文件。@ ajpc500还写了一篇不错的博客文章,介绍如何使用NimlineWhispers通过Nim进行Shellcode注入。在此处查看博客文章。我自己玩过Nim syscall Shellcode Injection PoC,它的工作原理很吸引人!请注意,使用默认的NTDLL.dll函数名称将导致以明文形式包含它们的二进制文件,可通过任何hexeditor看到该二进制文件:
在与@IKalendarov谈论NimlineWhispers时,他发现启用了云保护的Windows Defender成功执行了Shellcode,但是引发了警告,指出随后检测到防御逃避:
我发现,通过重命名ASM文件中的Windows API函数,当然也可以重命名shellcode注入代码中的Windows API函数,可以轻松绕过此检测。例如,
NtAllocateVirtualMemory
变为NtAVM,依此类推。如果您的Shellcode本身或其背后的代码包含任何Windows API函数导入-可以再次检测到。因此,shellcode加载程序和shellcode本身应使用Syscall来保持未被Userland-Hooks检测到。
P/Invoke to D/Invoke
@TheRealWover发布了一个名为D/Invoke的C#库。首先,它被添加到SharpSploit,但是后来在TheWover上发布了一个nuget包,可以在这里的任何VisualStudio项目中导入。2020年6月也有相应的博客文章。如果您主要使用C#编码,那么实际上这是您进行Userland-Hooking绕过的最简单方法。我只是从TheWovers帖子中挑选一小部分,因为此博客文章会通过解释所有内容而爆炸。如果您是这个主题的新手,他的博客文章可能有点“繁重”。我不懂第一次读它的一半。@ Jean_Maes_1994发布了一个博客文章,在此总结了通过D / Invoke使用的所有技术。生成的PoC代码
DInvisibleRegistry
可用于查找不同的D/Invoke实现方法,并且在我看来非常有用和易于理解
P/Invoke基本上是从Windows库文件静态导入API调用的默认方式。从上面显示的kernel32.dll导入
WriteProcessMemory
是P/Invoke方法。通过使用此方法,AV / EDR系统可以修补Windows库文件(如NTDLL.dll)的内存副本
与P / Invoke相比,D / Invoke在运行时手动加载Windows API函数,并使用指向其在内存中位置的指针来调用该函数。在编写时,AV / EDR挂钩未检测到运行时手动加载库文件的情况,因此它们不会修补新导入的功能,并且在没有 hook/patch的情况下仍保持原始状态。
有三种不同的方法可以避免通过D / Invoke进行
Userland-Hooking
:
-
手动映射-此方法将目标库文件的完整副本加载到内存中。之后可以从中导出任何功能。
DInvoke.Data.PE.PE_MANUAL_MAP mappedDLL = new DInvoke.Data.PE.PE_MANUAL_MAP();
mappedDLL = DInvoke.ManualMap.Map.MapModuleToMemory(@"C:\Windows\System32\ntdll.dll");
-
OverloadMapping-除了手动映射外,存储在内存中的有效负载还由磁盘上的合法文件支持。因此,有效负载似乎是从磁盘上经过有效签名的合法DLL执行的。
DInvoke.Data.PE.PE_MANUAL_MAP mappedDLL = DInvoke.ManualMap.Overload.OverloadModule(@"C:\Windows\System32\ntdll.dll");
-
Syscalls-使用这种技术,并不是将整个目标库都映射到内存,而是仅从其中提取指定的函数。因此,此方法比手动映射具有更多的隐身性。
IntPtr pAllocateSysCall = DInvoke.DynamicInvoke.Generic.GetSyscallStub("NtAllocateVirtualMemory");
NtAllocateVirtualMemory fSyscallAllocateMemory = (NtAllocateVirtualMemory)Marshal.GetDelegateForFunctionPointer(pAllocateSysCall, typeof(NtAllocateVirtualMemory));
对于这三种方法中的每一种,您还需要为代码中的每个Windows API函数创建非托管的委托。我不会在这里介绍整个过程,因为您可以阅读@TheRealWover或@ Jean_Maes_1994中的链接博客文章。
最初,我计划展示如何将P / Invoke CreateRemoteThread C#shellcode注入PoC移植到D / Invoke Syscall版本中。我在摆弄所有需要的所有NTDLL.dll函数,例如NtOpenProcess,NtAllocateVirtualMemory,NtWriteVirtualMemory和CreateThreadEx,但不幸的是无法成功使我的Shellcode执行正常。这是因为我以前从未使用过那些NTDLL.dll函数,并且一直在为“哪个值应放在哪个函数参数中”,“哪个kernel32.dll函数解析为哪个ntdll.dll函数”而苦苦挣扎,并在许多晚上深思熟虑试图使它起作用。同时,@_RastaMouse只用了几天,他就发表了一篇完整的博客文章,内容涉及这个主题:
https://offensivedefence.co.uk/posts/dinvoke-syscalls/
我的PoC可以处理他博客文章中的信息。只需自己阅读即可。
NTDLL.dll unhooking in C++ or Nim
我们了解到,AV / EDR系统挂接NTDLL.dll的特定功能,以将其自己的代码放入其中进行分析。ired.team上有一篇很好的简短文章,它解释了如何将NTDLL.dll的新副本从磁盘映射到内存,将.text部分从新副本复制到内存中已挂接文件的.text部分,因此 通过覆盖钩子撤消钩子:
还包括用于unhooking过程的C ++ PoC代码以及分步指南。如果您还没有读完,请继续阅读。
再说一次-如果有人不太熟悉C / C ++编码-我最近玩过
OffensiveNim
,而
OffensiveNim
存储库包含一个名为clr_host_cpp_embed_bin.nim的模板,我们可以在其中嵌入纯C ++代码。我们可以使用此模板,并将ired.team网站中的C ++ PoC嵌入其中,并且在Nim中有一个可以正常工作的NTDLL.dll取消对PoC的绑定:
when not defined(cpp):
{.error: "Must be compiled in cpp mode"}
{.emit: """
int test()
{
HANDLE process = GetCurrentProcess();
MODULEINFO mi = {};
HMODULE ntdllModule = GetModuleHandleA("ntdll.dll");
GetModuleInformation(process, ntdllModule, &mi, sizeof(mi));
LPVOID ntdllBase = (LPVOID)mi.lpBaseOfDll;
HANDLE ntdllFile = CreateFileA("c:\\windows\\system32\\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
HANDLE ntdllMapping = CreateFileMapping(ntdllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
LPVOID ntdllMappingAddress = MapViewOfFile(ntdllMapping, FILE_MAP_READ, 0, 0, 0);
PIMAGE_DOS_HEADER