作者:Elastic Security 的 Gabriel Landau。 EDRSandblast 的修改 - 请参阅下面的原始自述文件。
将 GodFault 集成到 EDR Sandblast 中,无需使用任何易受攻击的驱动程序即可达到相同的结果。
C:UsersuserDesktopOffsets>EDRSandblast.exe --kernelmode cmd
______ _____ _____ _____ _ _ _ _
| ____| __ | __ / ____| | | | | | | |
| |__ | | | | |__) | (___ __ _ _ __ __| | |__ | | __ _ ___| |_
| __| | | | | _ / ___ / _` | '_ / _` | '_ | |/ _` / __| __|
| |____| |__| | | ____) | (_| | | | | (_| | |_) | | (_| __ | |_
|______|_____/|_| _|_____/ __,_|_| |_|__,_|_.__/|_|__,_|___/__|
D3FC0N 30 Edition | Thomas DIOT (@_Qazeer) & Maxime MEIGNAN (@th3m4ks)
[!] If kernel mode bypass is enabled, it is recommended to enable usermode bypass as well (e.g. to unhook the NtLoadDriver API call)
[===== KERNEL MODE =====]
[+] Setting up prerequisites for the kernel read/write primitives...
[+] Loading kernel related offsets from the CSV file
[*] System's ntoskrnl.exe file version is: ntoskrnl_22621-1702.exe
[+] Offsets are available for this version of ntoskrnl.exe (ntoskrnl_22621-1702.exe)!
[+] Checking if any EDR kernel notify rountines are set for image loading, process and thread creations...
[+] [NotifyRountines] Enumerating process creation callbacks
[+] Running command: GodFault.exe -t 2684
[?] Server does not appear to be running. Attempting to install it...
[+] CSRSS PID is 748
[+] Testing initial ability to acquire PROCESS_ALL_ACCESS to System: Failure
[+] Ready. Spawning WinTcb.
[+] SpawnPPL: Waiting for child process to finish.
[+] Thread 2684 (KTHREAD FFFF910961E4C080) has been blessed by GodFault
[+] [NotifyRountines] fffff8034df25500 [cng.sys + 0x5500]
[+] [NotifyRountines] fffff8034e9efdc0 [WdFilter.sys + 0x4fdc0]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff803487bc460 [ksecdd.sys + 0x1c460]
[+] [NotifyRountines] fffff8034eff3fd0 [tcpip.sys + 0x13fd0]
[+] [NotifyRountines] fffff8034f5ed980 [iorate.sys + 0xd980]
[+] [NotifyRountines] fffff8034dea8890 [CI.dll + 0x88890]
[+] [NotifyRountines] fffff803525079f0 [dxgkrnl.sys + 0x179f0]
[+] [NotifyRountines] fffff80352be0a70 [vm3dmp.sys + 0x10a70]
[+] [NotifyRountines] fffff8036ebccd00 [peauth.sys + 0x3cd00]
[+] [NotifyRountines] fffff8036eda1550 [wtd.sys + 0x1550]
[+] [NotifyRountines] Found a total of 1 EDR / security products driver(s)
[+] [NotifyRountines] Enumerating thread creation callbacks
[+] [NotifyRountines] fffff8034e9f15c0 [WdFilter.sys + 0x515c0]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff8034e9f1350 [WdFilter.sys + 0x51350]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff8036eb71010 [mmcss.sys + 0x1010]
[+] [NotifyRountines] Found a total of 2 EDR / security products driver(s)
[+] [NotifyRountines] Enumerating image loading callbacks
[+] [NotifyRountines] fffff8034e9f0820 [WdFilter.sys + 0x50820]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff80352ab5710 [ahcache.sys + 0x25710]
[+] [NotifyRountines] Found a total of 1 EDR / security products driver(s)
[+] Checking if EDR callbacks are registered on processes and threads handle creation/duplication...
[+] [ObjectCallblacks] Enumerating Process object callbacks :
[+] [ObjectCallblacks] Callback at FFFF800C5E2F2940 for handle creations & duplications:
[+] [ObjectCallblacks] Status: Enabled
[+] [ObjectCallblacks] Preoperation at 0xfffff8034e9eda30 [WdFilter.sys + 0x4da30]
[+] [ObjectCallblacks] Callback belongs to an EDR and is enabled!
[+] [ObjectCallblacks] Enumerating Thread object callbacks :
[+] [ObjectCallblacks] Object callbacks are present !
[+] [ETWTI] Checking the ETW Threat Intelligence Provider state...
[+] [ETWTI] ETW Threat Intelligence Provider is ENABLED!
[+] Process is NOT "safe" to launch our payload, removing monitoring and starting another process...
[+] [ETWTI] Disabling the ETW Threat Intel provider by patching ProviderEnableInfo at 0xffff91095ce8c430 with 0x00.
[+] [ETWTI] The ETW Threat Intel provider was successfully disabled!
[+] Removing kernel callbacks registered by EDR for process creation, thread creation and image loading...
[+] [NotifyRountines] Removing process creation callbacks
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c2a8 | callback struct: 0xffff91095dbf3a5f | callback function: 0xfffff8034e9efdc0]
[+] [NotifyRountines] Removing thread creation callbacks
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c4a0 | callback struct: 0xffff91095dbf3b1f | callback function: 0xfffff8034e9f15c0]
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c4a8 | callback struct: 0xffff91095dbf3b4f | callback function: 0xfffff8034e9f1350]
[+] [NotifyRountines] Removing image loading callbacks
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c6a0 | callback struct: 0xffff91095dbf3e4f | callback function: 0xfffff8034e9f0820]
[+] Disabling kernel callbacks registered by EDR for process and thread opening or handle duplication...
[+] [ObjectCallblacks] Disabling WdFilter.sys callback...
[+] All EDR drivers were successfully removed from Kernel callbacks!
==================================================
Starting a new unmonitored process...
==================================================
[!] If kernel mode bypass is enabled, it is recommended to enable usermode bypass as well (e.g. to unhook the NtLoadDriver API call)
[===== KERNEL MODE =====]
[+] Setting up prerequisites for the kernel read/write primitives...
[+] Loading kernel related offsets from the CSV file
[*] System's ntoskrnl.exe file version is: ntoskrnl_22621-1702.exe
[+] Offsets are available for this version of ntoskrnl.exe (ntoskrnl_22621-1702.exe)!
[+] Checking if any EDR kernel notify rountines are set for image loading, process and thread creations...
[+] [NotifyRountines] Enumerating process creation callbacks
[+] Running command: GodFault.exe -t 8344
[+] Thread 8344 (KTHREAD FFFF91096169F080) has been blessed by GodFault
[+] Initial blessing successful
[+] [NotifyRountines] fffff8034df25500 [cng.sys + 0x5500]
[+] [NotifyRountines] fffff803487bc460 [ksecdd.sys + 0x1c460]
[+] [NotifyRountines] fffff8034eff3fd0 [tcpip.sys + 0x13fd0]
[+] [NotifyRountines] fffff8034f5ed980 [iorate.sys + 0xd980]
[+] [NotifyRountines] fffff8034dea8890 [CI.dll + 0x88890]
[+] [NotifyRountines] fffff803525079f0 [dxgkrnl.sys + 0x179f0]
[+] [NotifyRountines] fffff80352be0a70 [vm3dmp.sys + 0x10a70]
[+] [NotifyRountines] fffff8036ebccd00 [peauth.sys + 0x3cd00]
[+] [NotifyRountines] fffff8036eda1550 [wtd.sys + 0x1550]
[+] [NotifyRountines] No EDR driver(s) found!
[+] [NotifyRountines] Enumerating thread creation callbacks
[+] [NotifyRountines] fffff8036eb71010 [mmcss.sys + 0x1010]
[+] [NotifyRountines] No EDR driver(s) found!
[+] [NotifyRountines] Enumerating image loading callbacks
[+] [NotifyRountines] fffff80352ab5710 [ahcache.sys + 0x25710]
[+] [NotifyRountines] No EDR driver(s) found!
[+] Checking if EDR callbacks are registered on processes and threads handle creation/duplication...
[+] [ObjectCallblacks] Enumerating Process object callbacks :
[+] [ObjectCallblacks] Callback at FFFF800C5E2F2940 for handle creations & duplications:
[+] [ObjectCallblacks] Status: Disabled
[+] [ObjectCallblacks] Preoperation at 0xfffff8034e9eda30 [WdFilter.sys + 0x4da30]
[+] [ObjectCallblacks] Callback belongs to an EDR but is disabled.
[+] [ObjectCallblacks] Enumerating Thread object callbacks :
[+] [ObjectCallblacks] Object callbacks are not found !
[+] [ETWTI] Checking the ETW Threat Intelligence Provider state...
[+] [ETWTI] ETW Threat Intelligence Provider is DISABLED!
[+] Process is "safe" to launch our payload
[+] Kernel callbacks have normally been removed, starting cmd.exe
WARNING: EDR kernel callbacks will be restored after exiting the cmd prompt (by typing exit)
WARNING: While unlikely, the longer the callbacks are removed, the higher the chance of being detected / causing a BSoD upon restore is!
Microsoft Windows [Version 10.0.22621.1702]
(c) Microsoft Corporation. All rights reserved.
C:UsersuserDesktopOffsets>
?原始自述文件如下?
EDRSandBlast
是一个用C
编写的工具,可将易受攻击的签名驱动程序武器化以绕过 EDR 检测(通知例程回调、对象回调和ETW TI
提供程序)和LSASS
保护。还实施了多种用户态脱钩技术来逃避用户态监控。
自发布以来,用户态 ( --usermode
) 和内核态 ( --kernelmode
) 技术的组合用于在 EDR 审查下转储LSASS
内存,而不会被阻止,也不会在产品中生成“操作系统凭证转储”相关事件(云) ) 安慰。测试针对 3 种不同的 EDR 产品进行,并且每种情况均取得成功。
EDR 产品使用 Windows 上的内核“通知例程”回调来接收系统活动的内核通知,例如进程和线程的创建以及映像 ( exe
/ DLL
) 的加载。
这些内核回调是从内核域定义的,通常是从实现回调的驱动程序中使用许多记录的 API( nt!PsSetCreateProcessNotifyRoutine
、 nt!PsSetCreateThreadNotifyRoutine
等)定义的。这些 API 将驱动程序提供的回调例程添加到内核空间中未记录的例程数组中:
PspCreateProcessNotifyRoutine
用于进程创建PspCreateThreadNotifyRoutine
用于线程创建PspLoadImageNotifyRoutine
用于图像加载EDRSandBlast
枚举这些数组中定义的例程,并删除链接到预定义的 EDR 驱动程序列表的任何回调例程(支持超过 1000 个安全产品驱动程序,请参阅 EDR 驱动程序检测部分。通过利用利用易受攻击的驱动程序提供的任意内核内存读/写原语(请参阅易受攻击的驱动程序部分)。
上述数组的偏移量可以使用多种技术来恢复,请参阅偏移量部分。
EDR(甚至 EPP)产品通常通过使用nt!ObRegisterCallbacks
内核 API 来注册“对象回调”。这些回调允许安全产品在特定对象类型的每次句柄生成时收到通知(Windows 现在支持进程、线程和桌面相关的对象回调)。句柄生成可能发生在对象打开(调用OpenProcess
、 OpenThread
等)以及句柄复制(调用DuplicateHandle
等)时。
通过内核对这些操作中的每一个进行通知,安全产品可以分析句柄创建的合法性(例如未知进程试图打开 LSASS ),甚至在检测到威胁时阻止它。
每次使用ObRegisterCallbacks
注册回调时,都会将一个新项目添加到_OBJECT_TYPE
对象中存在的CallbackList
双链表中,描述受回调影响的对象类型(进程、线程或桌面)。不幸的是,这些项目是通过 Microsoft 未在符号文件中记录或发布的结构来描述的。然而,从不同的ntoskrnl.exe
版本研究它似乎表明,(至少)Windows 10 版本 10240 和 22000(从 2015 年到 2022 年)之间结构没有改变。
上述代表对象回调注册的结构如下:
typedef struct OB_CALLBACK_ENTRY_t {
LIST_ENTRY CallbackList ; // linked element tied to _OBJECT_TYPE.CallbackList
OB_OPERATION Operations ; // bitfield : 1 for Creations, 2 for Duplications
BOOL Enabled ; // self-explanatory
OB_CALLBACK * Entry ; // points to the structure in which it is included
POBJECT_TYPE ObjectType ; // points to the object type affected by the callback
POB_PRE_OPERATION_CALLBACK PreOperation ; // callback function called before each handle operation
POB_POST_OPERATION_CALLBACK PostOperation ; // callback function called after each handle operation
KSPIN_LOCK Lock ; // lock object used for synchronization
} OB_CALLBACK_ENTRY ;
上面提到的OB_CALLBACK
结构也没有文档记录,其定义如下:
typedef struct OB_CALLBACK_t {
USHORT Version ; // usually 0x100
USHORT OperationRegistrationCount ; // number of registered callbacks
PVOID RegistrationContext ; // arbitrary data passed at registration time
UNICODE_STRING AltitudeString ; // used to determine callbacks order
struct OB_CALLBACK_ENTRY_t EntryItems [ 1 ]; // array of OperationRegistrationCount items
WCHAR AltitudeBuffer [ 1 ]; // is AltitudeString.MaximumLength bytes long, and pointed by AltitudeString.Buffer
} OB_CALLBACK ;
为了禁用 EDR 注册的对象回调, EDRSandblast
中实现了三种技术;但目前仅启用一项。
OB_CALLBACK_ENTRY
的Enabled
字段这是EDRSandblast
中启用的默认技术。为了检测和禁用与 EDR 相关的对象回调,需要浏览位于与Process和Thread类型相关的_OBJECT_TYPE
对象中的CallbackList
列表。两个_OBJECT_TYPE
都由内核中的公共全局符号PsProcessType
和PsThreadType
指向。
假设列表中的每一项都符合上述OB_CALLBACK_ENTRY
结构(在撰写本文时,这一假设似乎至少在所有 Windows 10 版本中都成立)。 PreOperation
和PostOperation
字段中定义的函数用于检查它们是否属于 EDR 驱动程序,如果属于,则只需切换Enabled
标志即可禁用回调。
虽然这是一种非常安全的技术,但它存在依赖未记录结构的不便;为了降低对该结构进行不安全操作的风险,将执行基本检查以验证某些字段是否具有预期值:
Enabled
要么是TRUE
要么是FALSE
(别笑, BOOL
是int
,所以它可以是1
或0
以外的任何值);Operations
是OB_OPERATION_HANDLE_CREATE
、 OB_OPERATION_HANDLE_DUPLICATE
或两者;ObjectType
指向PsProcessType
或PsThreadType
。 CallbackList
的链接另一种不依赖于未记录的结构(因此理论上对 NT 内核更改更稳健)的策略是取消进程和线程的整个CallbackList
链接。 _OBJECT_TYPE
对象如下:
struct _OBJECT_TYPE {
LIST_ENTRY TypeList ;
UNICODE_STRING Name ;
[...]
_OBJECT_TYPE_INITIALIZER TypeInfo ;
[...]
LIST_ENTRY CallbackList ;
}
让CallbackList
LIST_ENTRY
的Flink
和Blink
指针指向LIST_ENTRY
本身,有效地使列表为空。由于_OBJECT_TYPE
结构是在内核符号中发布的,因此该技术不依赖于硬编码的偏移量/结构。然而,它有一些缺点。
第一个是不能仅禁用 EDR 的回调;事实上,该技术会影响所有可能由“合法”软件注册的对象回调。不过,应该注意的是,Windows 10 上的任何预安装组件都不会使用对象回调(在撰写本文时),因此禁用它们不会影响计算机的稳定性(如果禁用只是暂时的,则更是如此)。
第二个缺点是,在操作系统的正常运行中,进程或线程句柄操作非常频繁(几乎连续)。因此,如果使用的内核写入原语无法“原子地”执行QWORD
写入,则内核很有可能在覆盖过程中访问_OBJECT_TYPE.CallbackList.Flink
指针。例如,MSI易受攻击的驱动程序RTCore64.sys
一次只能执行DWORD
写入,因此需要2个不同的IOCTL来覆盖指针,在这两个IOCTL之间内核很有可能使用它(导致崩溃)。另一方面,易受攻击的 DELL 驱动程序DBUtil_2_3.sys
可以在一个 IOCTL 中执行任意大小的写入,因此使用此方法不会有导致崩溃的风险。
我们发现的最后一项技术是完全禁用对线程和进程的对象回调支持。在对应于进程和线程类型的_OBJECT_TYPE
结构内部,存在一个TypeInfo
字段,位于记录的_OBJECT_TYPE_INITIALIZER
结构之后。后者包含一个ObjectTypeFlags
位字段,其SupportsObjectCallbacks
标志确定所描述的对象类型(进程、线程、桌面、令牌、文件等)是否支持对象回调注册。如前所述,在撰写本文时,只有进程、线程和桌面对象类型支持 Windows 安装上的这些回调。
由于在读取CallbackList
之前(当然也是在执行回调之前), ObpCreateHandle
或ObDuplicateObject
会检查SupportsObjectCallbacks
位,因此在内核运行时翻转该位会有效地禁用所有对象回调执行。
该方法的主要缺点是KPP (“ PatchGuard ”)监视某些(所有?) _OBJECT_TYPE
结构的完整性,并触发0x109 Bug Check
,参数 4 等于0x8
,这意味着对象类型结构已被更改。
然而,足够快地执行禁用/重新启用(以及其间的“恶意”操作)应该足以“竞赛” PatchGuard (除非您不走运并且在错误的时刻执行了定期检查)。
ETW Microsoft-Windows-Threat-Intelligence
提供商记录有关某些常用恶意使用的 Windows API 的使用情况的数据。这包括nt!MiReadWriteVirtualMemory
API,由nt!NtReadVirtualMemory
(用于转储LSASS
内存)调用并由nt!EtwTiLogReadWriteVm
函数监视。
EDR 产品可以通过分别作为SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT
或PS_PROTECTED_ANTIMALWARE_LIGHT
运行的服务或进程来使用ETW TI
提供商生成的日志,并与Early Launch Anti Malware (ELAM)
驱动程序关联。
正如slaeryan
在CNO Development Labs
博客文章中发布的那样,可以通过在内核内存中将其ProviderEnableInfo
属性修补为0x0
来完全禁用ETW TI
提供程序。有关该技术的更多信息,请参阅前面提到的精彩博客文章。
与内核回调删除类似,在许多 Windows 内核版本的NtoskrnlOffsets.csv
文件中计算必要的ntoskrnl.exe
偏移量( nt!EtwThreatIntProvRegHandleOffset
、 _ETW_REG_ENTRY
的GuidEntry
和_ETW_GUID_ENTRY
的ProviderEnableInfo
)。
为了轻松监控进程执行的操作,EDR 产品通常部署一种称为userland hooking 的机制。首先,EDR 产品注册一个内核回调(通常是图像加载或进程创建回调,见上文),允许它们在每个进程启动时收到通知。
当 Windows 加载进程时,在其实际启动之前,EDR 能够将一些自定义 DLL 注入进程地址空间,其中包含其监视逻辑。加载时,该 DLL 在 EDR 监视的每个函数的开头注入“钩子”。在运行时,当受监视的进程调用受监视的函数时,这些钩子会将控制流重定向到 EDR DLL 中存在的某些监督代码,这允许它检查这些调用的参数并返回值。
大多数时候,受监控的函数是系统调用(例如NtReadVirtualMemory
、 NtOpenProcess
等),其实现驻留在ntdll.dll
中。拦截对Nt*
函数的调用允许产品尽可能接近用户态/内核态边界(同时保留在用户态中),但也可能监视来自某些更高级别 DLL 的函数。
下面是相同功能的示例,在被 EDR 产品挂钩之前和之后:
NtProtectVirtualMemory proc near
mov r10 , rcx
mov eax , 50h
test byte ptr ds : 7FFE0308h , 1
jnz short loc_18009D1E5
syscall
retn
loc_18009D1E5:
int 2Eh
retn
NtProtectVirtualMemory endp
NtProtectVirtualMemory proc near
jmp sub_7FFC74490298 ; --> "hook", jump to EDR analysis function
int 3 ; overwritten instructions
int 3 ; overwritten instructions
int 3 ; overwritten instructions
test byte_7FFE0308 , 1 ; <-- execution resumes here after analysis
jnz short loc_7FFCB44AD1E5
syscall
retn
loc_7FFCB44AD1E5:
int 2Eh
retn
NtProtectVirtualMemory endp
用户态钩子的“弱点”是位于用户态内存中,这意味着它们可以被正在审查的进程直接观察和修改。为了自动检测进程地址空间中的钩子,主要思想是比较磁盘上的原始 DLL 与驻留在内存中的库(可能已被 EDR 更改)之间的差异。为了执行此比较,EDRSandblast 遵循以下步骤:
PEB
中的InLoadOrderModuleList
枚举了所有已加载 DLL 的列表(以避免调用任何可能被监视和可疑的 API)注意:该过程可以推广到在不可写部分中的任何位置查找差异,而不仅仅是在导出函数的开始处,例如,如果 EDR 产品开始在函数中间应用挂钩:) 因此该工具不会使用此功能已在findDiffsInNonWritableSections
中实现。
为了绕过这些钩子执行的监视,可以使用多种技术,每种技术都有优点和缺点。
绕过基于钩子的监控最直观的方法就是删除钩子。由于钩子存在于进程本身可访问的内存中,因此要删除钩子,进程可以简单地:
这种方法相当简单,可以用来一次性删除所有检测到的钩子。在开始时由攻击性工具执行,这使得代码的其余部分完全不知道挂钩机制并在不受监控的情况下正常执行。
然而,它有两个主要缺点。 EDR 可能正在监视NtProtectVirtualMemory
的使用,因此使用它来更改安装了挂钩的页面的权限(至少在概念上)是一个坏主意。此外,如果 EDR 执行一个线程并定期检查钩子的完整性,这也可能会触发一些检测。
有关实现细节,请检查unhook_method
为UNHOOK_WITH_NTPROTECTVIRTUALMEMORY
时unhook()
函数的代码路径。
重要提示:为了简单起见,该技术在 EDRSandblast 中实现,作为用于展示其他旁路技术的基本技术;它们每个都演示了如何获取NtProtectVirtualMemory
的不受监控版本,但随后执行相同的操作(取消特定挂钩)。
要绕过特定的钩子,可以简单地“跳过”并按原样执行函数的其余部分。首先,必须从 DLL 文件中恢复已被 EDR 覆盖以安装挂钩的受监视函数的原始字节。在我们之前的代码示例中,这将是与以下指令对应的字节:
mov r10 , rcx
mov eax , 50h
识别这些字节是一项简单的任务,因为我们能够对库的内存和磁盘版本执行干净的比较,如前所述。然后,我们组装一条跳转指令,该指令用于将控制流重定向到紧随钩子之后的代码,地址为NtProtectVirtualMemory + sizeof(overwritten_instructions)
jmp NtProtectVirtualMemory + 8
最后,我们连接这些操作码,将它们存储在(新的)可执行内存中并保留指向它们的指针。这个对象被称为“蹦床”,然后可以用作函数指针,严格相当于原始的NtProtectVirtualMemory
函数。
对于下面的每种技术来说,该技术的主要好处是钩子永远不会被擦除,因此 EDR 对钩子执行的任何完整性检查都应该通过。然而,它需要分配可写可执行的内存,这是典型的 shellcode 分配,因此引起了 EDR 的审查。
有关实现细节,请检查unhook_method
为UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE
时unhook()
函数的代码路径。请记住,该技术仅在我们的实现中展示,并且最终用于从内存中删除钩子,正如下面的每项技术一样。
EDR 产品为了使其挂钩正常工作,必须将已删除的操作码保存在内存中的某个位置。最糟糕(或者从攻击者的角度来看“更好” ),为了有效地使用原始指令,EDR 可能会在拦截调用后为自己分配一个蹦床来执行原始函数。
可以搜索此蹦床并将其用作挂钩函数的替代品,而无需分配可执行内存,或调用除VirtualQuery
之外的任何 API,VirtualQuery 很可能不会被监控为无害函数。
为了找到内存中的蹦床,我们使用VirtualQuery
浏览整个地址空间,查找已提交和可执行的内存。对于每个这样的内存区域,我们扫描它以查找跳转指令,该指令的目标是被覆盖指令后面的地址(在我们之前的示例中为NtProtectVirtualMemory+8
)。然后可以使用蹦床来调用挂钩函数而不触发挂钩。
这项技术的效果出人意料地好,因为它可以恢复经过测试的 EDR 上的几乎所有蹦床。有关实现细节,请检查unhook_method
为UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE
时unhook()
函数的代码路径。
访问NtProtectVirtualMemory
函数的不受监控版本的另一个简单方法是将ntdll.dll
库的重复版本加载到进程地址空间中。由于两个相同的DLL可以在同一个进程中加载,只要它们具有不同的名称,我们可以简单地将合法的ntdll.dll
文件复制到另一个位置,使用LoadLibrary
加载它(或重新实现加载过程),并使用GetProcAddress
访问该函数例如。
这种技术非常容易理解和实现,并且成功的机会很大,因为大多数 EDR 产品在进程运行后不会在新加载的 DLL 上重新安装挂钩。然而,主要缺点是,以不同名称复制 Microsoft 签名的二进制文件通常会被 EDR 产品视为可疑。
尽管如此,该技术还是在EDRSandblast
中实现。有关实现细节,请检查unhook_method
为UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY
时unhook()
函数的代码路径。
为了使用与系统调用相关的功能,一个程序可以重新实现系统调用(以汇编形式),以便调用相应的操作系统功能,而无需实际接触ntdll.dll
中的代码,这可能会受到 EDR 的监控。这完全绕过了在ntdll.dll
中的系统调用函数上完成的任何用户空间挂钩。
然而这有一些缺点。首先,这意味着能够知道程序所需的函数的系统调用号列表,该列表因 Windows 的每个版本而异。尽管如此,通过实现已知在所有过去版本的 Windows NT 中都有效的多种启发式方法(对ntdll
的Zw*
导出进行排序、搜索 mov rax、相关ntdll
函数中的mov rax, #syscall_number
指令等)可以缓解这种情况,并且检查它们都返回相同的结果(有关更多详细信息,请参阅Syscalls.c
)。
此外,技术上不是系统调用的函数(例如LoadLibraryX
/ LdrLoadDLL
)也可以被监视,并且不能简单地使用系统调用重新实现。
直接系统调用技术在 EDRSandblast 中实现。如前所述,它仅用于安全地执行NtProtectVirtualMemory
,并删除所有检测到的钩子。
有关实现细节,请检查unhook_method
为UNHOOK_WITH_DIRECT_SYSCALL
时unhook()
函数的代码路径。
如前所述,需要内核内存读取或写入的每个操作都依赖于易受攻击的驱动程序来提供此原语。在 EDRSanblast 中,添加对提供读/写原语的新驱动程序的支持可以“轻松”完成,只需要实现三个功能:
ReadMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)
函数,将Size
字节从内核地址Address
复制到用户态缓冲区Buffer
;WriteMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)
函数,将Size
字节从用户态缓冲区Buffer
复制到内核地址Address
;CloseDriverHandle_DRIVERNAME()
确保驱动程序的所有句柄都已关闭(目前在与驱动程序无关的卸载操作之前需要)。例如,EDRSandblast 目前支持两个驱动程序: RTCore64.sys
(SHA256: 01AA278B07B58DC46C84BD0B1B5C8E9EE4E62EA0BF7A695862444AF32E87F1FD
) 和DBUtils_2_3.sys
(SHA256: 0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5
)。如果需要更改使用的易受攻击的驱动程序,或者实施新的驱动程序,则需要更新KernelMemoryPrimitives.h
中的以下代码。
#define RTCore 0
#define DBUtil 1
// Select the driver to use with the following #define
#define VULN_DRIVER RTCore
#if VULN_DRIVER == RTCore
#define DEFAULT_DRIVER_FILE TEXT("RTCore64.sys")
#define CloseDriverHandle CloseDriverHandle_RTCore
#define ReadMemoryPrimitive ReadMemoryPrimitive_RTCore
#define WriteMemoryPrimitive WriteMemoryPrimitive_RTCore
#elif VULN_DRIVER == DBUtil
#define DEFAULT_DRIVER_FILE TEXT("DBUtil_2_3.sys")
#define CloseDriverHandle CloseDriverHandle_DBUtil
#define ReadMemoryPrimitive ReadMemoryPrimitive_DBUtil
#define WriteMemoryPrimitive WriteMemoryPrimitive_DBUtil
#endif
当前使用多种技术来确定特定驱动程序或进程是否属于 EDR 产品。
首先,驱动程序的名称可以简单地用于此目的。事实上,微软为所有需要在内核中插入回调的驱动程序分配了称为“高度”的特定数字。这允许回调执行中的确定顺序,独立于注册顺序,但仅基于驱动程序使用情况。可以在 MSDN 上找到已保留特定高度的驱动程序(供应商)列表。因此,Microsoft 提供了与安全产品相关的近乎全面的安全驱动程序名称列表,主要在“FSFilter Anti-Virus”和“FSFilter Activity Monitor”列表中。这些驱动程序名称列表以及其他贡献都嵌入在 EDRSandblast 中。
此外,EDR 可执行文件和 DLL 通常使用供应商签名证书进行数字签名。因此,检查与进程关联的可执行文件或 DLL 的签名者可以快速识别 EDR 产品。
此外,驱动程序需要由 Microsoft 直接签名才能允许加载到内核空间中。虽然驱动程序的供应商不是驱动程序本身的直接签名者,但供应商的名称仍然包含在签名的属性中;然而,这种检测技术还有待研究和实施。
最后,当遇到 EDRSandblast 未知的 EDR 时,最好的方法是在“审核”模式下运行该工具,并检查已注册内核回调的驱动程序列表;然后可以将驱动程序的名称添加到列表中,重新编译并重新运行该工具。
Local Security Authority (LSA) Protection
机制首次在 Windows 8.1 和 Windows Server 2012 R2 中引入,利用Protected Process Light (PPL)
技术来限制对LSASS
进程的访问。 PPL
保护调节和限制操作,例如受保护进程的内存注入或内存转储,甚至来自持有SeDebugPrivilege
权限的进程。在进程保护模型下,只有保护级别较高的进程才能对受保护的进程进行操作。
Windows 内核使用_EPROCESS
结构来表示内核内存中的进程,该结构包含一个_PS_PROTECTION
字段,该字段通过其Type
( _PS_PROTECTED_TYPE
) 和Signer
( _PS_PROTECTED_SIGNER
) 属性定义进程的保护级别。
通过写入内核内存,EDRSandblast 进程能够将其自身的保护级别升级到PsProtectedSignerWinTcb-Light
。此级别足以转储LSASS
进程内存,因为它“支配” PsProtectedSignerLsa-Light
(使用RunAsPPL
机制运行的LSASS
进程的保护级别)。
EDRSandBlast
实现自我保护如下:
NtQuerySystemInformation
来泄漏所有系统句柄,以查找当前进程上打开的句柄,以及当前进程的EPROCESS
结构在内核内存中的地址。Micro-Star MSI Afterburner
驱动程序的任意读/写漏洞覆盖内核内存中当前进程的_PS_PROTECTION
字段。 _PS_PROTECTION
字段相对于EPROCESS
结构的偏移量(由使用中的ntoskrnl
版本定义)在NtoskrnlOffsets.csv
文件中计算。 Microsoft Credential Guard
是一种基于虚拟化的隔离技术,在 Microsoft Windows 10 (Enterprise edition)
中引入,可防止直接访问LSASS
进程中存储的凭据。
激活Credentials Guard
后,会在Virtual Secure Mode
下创建LSAIso
( LSA 隔离)进程,该功能利用 CPU 的虚拟化扩展来提高内存中数据的安全性。即使使用NT AUTHORITYSYSTEM
安全上下文进行访问,对LSAIso
进程的访问也会受到限制。处理哈希时, LSA
进程对LSAIso
进程执行RPC
调用,并等待LSAIso
结果继续。因此, LSASS
进程不会包含任何秘密,并且会存储LSA Isolated Data
。
正如N4kedTurtle
进行的原始研究中所述:“可以通过修补内存中的g_fParameter_useLogonCredential
和g_IsCredGuardEnabled
的值,在具有 Credential Guard 的系统上启用Wdigest
”。 Wdigest
的激活将导致明文凭据存储在LSASS
内存中,用于任何新的交互式登录(无需重新启动系统)。有关此技术的更多详细信息,请参阅原始研究博客文章。
EDRSandBlast
只是使原始 PoC 对操作安全更加友好,并为许多wdigest.dll
版本提供支持(通过g_fParameter_useLogonCredential
和g_IsCredGuardEnabled
的计算偏移量)。
为了可靠地执行内核监控绕过操作,EDRSandblast 需要确切地知道在哪里读取和写入内核内存。这是通过使用目标映像(ntoskrnl.exe、wdigest.dll)内的全局变量的偏移量以及 Microsoft 在符号文件中发布其定义的结构中特定字段的偏移量来完成的。这些偏移量特定于目标映像的每个版本,并且必须针对特定平台版本至少收集一次。
选择使用“硬编码”偏移量而不是模式搜索来定位 EDRSandblast 使用的结构和变量是合理的,因为负责内核回调添加/删除的未记录的 API 可能会发生变化,并且任何读取或写入内核的尝试都可能发生变化。位于错误地址的内存可能(并且经常会)导致Bug Check
( Blue Screen of Death
)。在红队和正常渗透测试场景中,机器崩溃都是不可接受的,因为崩溃的机器对于防御者来说是高度可见的,并且将丢失攻击时仍在内存中的所有凭据。
为了检索每个特定版本的 Windows 的偏移量,实现了两种方法。
可以使用提供的ExtractOffsets.py
Python脚本提取所需的ntoskrnl.exe
和wdigest.dll
偏移量,该脚本依赖radare2
和r2pipe
从PDB文件下载和解析符号,并从中提取所需的偏移量。然后,偏移量将存储在 CSV 文件中,供 EDRSandblast 稍后使用。
为了支持开箱即用的各种 Windows 版本,Winbindex 引用许多版本的ntoskrnl.exe
和wdigest.dll
二进制文件,并且可以通过ExtractOffsets.py
.这允许从 Windows 更新包中发布的几乎所有文件中提取偏移量(迄今为止,已有 450 多个ntoskrnl.exe
和 30 多个wdigest.dll
版本可用并已预先计算)。
EDRSandBlast
中还实现了一个附加选项,允许程序从 Microsoft Symbol Server 下载所需的.pdb
文件本身,提取所需的偏移量,甚至更新相应的.csv
文件(如果存在)。
使用--internet
选项使工具执行更加简单,同时引入额外的 OpSec 风险,因为在此过程中会下载.pdb
文件并将其放入磁盘上。这是用于解析符号数据库的dbghelp.dll
函数所必需的;但是,将来可能会实现完整的内存中 PDB 解析,以提高这一要求并减少工具的占用空间。
可以在以下位置检索易受攻击的RTCore64.sys
驱动程序:
http://download-eu2.guru3d.com/afterburner/%5BGuru3D.com%5D-MSIAfterburnerSetup462Beta2.zip