如何解码经过编码的 PowerShell 命令
- 从告警、进程创建事件或 EDR 控制台复制完整的命令行,粘贴到 命令行或 Base64 中。Base64 两侧带引号也没关系。
- 解码器会识别
-EncodedCommand参数的任意缩写形式(-e、-ec、-enc等,前缀可以是-或/)并解码。 - 状态栏会说明使用了哪种字符集:PowerShell 所要求的 UTF-16LE,或者在字节看起来不像 UTF-16 时使用 UTF-8。
- 阅读 解码后的脚本。如果其中有
[Convert]::FromBase64String('…')字面量,它们会连同识别出的类型和大小列在下方。文本载荷会在输出窗格中打开;压缩过的载荷(gzip、zlib 或原始 deflate,即配合GzipStream和DeflateStream使用的格式)会提供一个解压按钮;其他二进制载荷可以保存为文件。
一个无害的例子:
powershell.exe -NoP -NonI -W Hidden -Enc VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIAAnAGgAZQBsAGwAbwAnAA==
解码结果是 Write-Output 'hello'。同样的文本用 UTF-8 编码的 Base64 是 V3JpdGUtT3V0cHV0ICdoZWxsbyc=,PowerShell 会拒绝它或把它读错。
在日志中识别经过编码的命令
由于使用 UTF-16LE,编码脚本的开头有一种可识别的 Base64 形式。检测规则就是匹配这些形式:
| Base64 开头 | 脚本开头 |
|---|---|
JAB | $(变量赋值,例如 JABzAD0A 是 $s=) |
SQBFAFgA | IEX |
aQBlAHgA | iex |
SQBuAHYAbwBrAGUALQBFAHgAcAByAGUAcwBzAGkAbwBuAA | Invoke-Expression |
SQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0AA | Invoke-WebRequest |
TgBlAHcALQBPAGIAagBlAGMAdAA | New-Object |
WwBDAG8AbgB2AGUAcgB0AF0AOgA6AEYAcgBvAG0AQgBhAHMAZQA2ADQAUwB0AHIAaQBuAGcA | [Convert]::FromBase64String |
这些形式只在最开头成立。在脚本的后面,同一个词根据其字节偏移量除以 3 的余数,有三种可能的 Base64 写法,所以在日志中只搜索一种写法会漏掉另外两种。
安全地分析可疑命令
经过编码的命令是一种常见手段,用来让脚本在随手查看进程列表时不被发现。告警中出现这种命令时:
- 只解码。 本页只做文本转换,除 Cloudflare 的浏览量统计外不发出任何网络请求。不要把命令粘贴到 PowerShell 窗口中,也绝不要把解码结果传给
iex。 - 阅读意图和指标。 记下 URL、域名、IP 地址、文件路径、注册表项和计划任务名称,用于事件记录和阻止列表。
- 寻找更多层。 嵌套的
FromBase64String调用、IO.Compression.DeflateStream或GzipStream、对字符码做-join、字符串反转,都是常见的包装方式。用同样的方法逐层解码。 - 把提取出的二进制文件当作样本对待。 从嵌套列表中保存的载荷可能是恶意软件。计算它的哈希,并且只在隔离环境中分析。
- 查看已经记录了明文的日志。 启用脚本块日志记录后,Windows 会把解码后的脚本记录为
Microsoft-Windows-PowerShell/Operational中的 4104 事件。进程创建事件(开启命令行审核时的安全日志 4688,或 Sysmon 事件 1)记录的则是编码后的命令行本身。
正当用途与编码标签页
管理员使用 -EncodedCommand,是为了避免脚本经过 cmd.exe、计划任务、远程 shell 或配置管理工具时出现引号问题。编码脚本 用于生成这个参数:输入脚本,选择 命令 前缀(powershell -NoProfile -EncodedCommand、pwsh -NoProfile -EncodedCommand、powershell -enc 或 仅 Base64),然后复制。如果结果超过 cmd.exe 8,191 个字符的命令行长度限制,会出现警告。
在 Python 和 PowerShell 中解码
import base64
script = base64.b64decode(b64).decode("utf-16-le")
$script = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($b64))
两者都只返回文本。编码方面的更多内容见 PowerShell Base64 页面 和 UTF-16 转 Base64 转换器。
常见问题
- 输出看起来是一堆汉字:这是把 UTF-8 的 Base64 按 UTF-16 解码了。当字节模式不符合 UTF-16 时,工具会回退到 UTF-8,状态栏会说明使用了哪种字符集。
- Base64 无效:命令被日志字段长度限制截断了,或被拆成了多行。把各段拼接起来,并删除 Base64 后面不属于它的内容。
- 命令行完全无法解码:这个 Base64 可能位于
-Command "…FromBase64String('…')…"字符串中,而不是跟在-EncodedCommand后面。只粘贴 Base64 字符串;如果它不是 UTF-16 文本,Base64 解码器 或 Base64 转文件 会显示它到底是什么。
常见问题
powershell -enc 是做什么的?
-enc 是 -EncodedCommand 的缩写。PowerShell 会把这个 Base64 参数按 UTF-16LE 文本解码,然后作为脚本运行。它还接受 -e、-ec 等其他缩写形式。
在这里解码恶意的 PowerShell 命令安全吗?
解码只是文本转换,不会执行任何内容,而且本页的内容安全策略会拦截除 Cloudflare 的浏览量统计以外的所有网络请求。永远不要为了看看效果而运行解码后的脚本;保存下来的二进制载荷只能在隔离的分析环境中处理。
为什么 Base64 中每隔一个字符就是 A?
PowerShell 要求 UTF-16LE,其中每个 ASCII 字符后面都跟着一个零字节。这些零字节产生了重复出现的 A,例如 Write- 对应 VwByAGkAdABlAC0A。
怎样不在 PowerShell 中运行就解码 -EncodedCommand?
使用 [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($b64)),它只返回文本。不要把结果通过管道传给 Invoke-Expression 或 iex。