Deobfuscates multi-layered PowerShell malware using AST analysis, dynamic tracing, PSDecode, PowerDecode, and Python scripts to reveal hidden payloads and C2 infrastructure. For incident response and malware analysis.
npx claudepluginhub killvxk/cybersecurity-skills-zhThis skill uses the workspace's default tool permissions.
PowerShell 因其与 Windows 的深度集成和强大的脚本功能而被恶意软件作者大量滥用。混淆技术包括:字符串拼接、Base64 编码、字符替换、Invoke-Expression 分层、SecureString 滥用、环境变量操控和反引号插入。现代恶意软件使用多层混淆,需要迭代式去混淆。PSDecode、PowerDecode 和 PowerPeeler 等工具可以自动化大部分这一过程,而手动 AST(抽象语法树)分析则可以处理自定义混淆。PowerPeeler 通过对表达式相关 AST 节点进行指令级动态分析,实现了 95% 的去混淆正确率。
Deobfuscates multi-layer PowerShell malware using AST analysis, dynamic tracing, PSDecode, and PowerDecode to reveal hidden payloads and C2 infrastructure.
Deobfuscates multi-layer PowerShell malware using AST analysis, dynamic tracing, PSDecode, and PowerDecode to reveal hidden payloads and C2 infrastructure. For authorized malware analysis in controlled environments.
Hunts malicious PowerShell activity in Windows EVTX logs (events 4104/4103) by parsing script blocks, detecting obfuscation, AMSI bypasses, encoded payloads, credential dumps, and download cradles.
Share bugs, ideas, or general feedback.
PowerShell 因其与 Windows 的深度集成和强大的脚本功能而被恶意软件作者大量滥用。混淆技术包括:字符串拼接、Base64 编码、字符替换、Invoke-Expression 分层、SecureString 滥用、环境变量操控和反引号插入。现代恶意软件使用多层混淆,需要迭代式去混淆。PSDecode、PowerDecode 和 PowerPeeler 等工具可以自动化大部分这一过程,而手动 AST(抽象语法树)分析则可以处理自定义混淆。PowerPeeler 通过对表达式相关 AST 节点进行指令级动态分析,实现了 95% 的去混淆正确率。
base64、re、subprocess 模块Install-Module PSDecode)PowerShell 恶意软件采用分层混淆来规避静态检测。字符串拼接将命令拆分到多个变量中($a='In'+'voke')。Base64 编码将整个脚本包装在 -EncodedCommand 参数中。字符代码数组使用 [char] 转换([char[]](73,69,88)|%{$r+=$_})。环境变量滥用从 $env: 路径中读取子字符串。反引号插入在 PowerShell 会忽略的字符之间添加反引号(I`nv`oke-Exp`ression)。SecureString 转换使用 ConvertTo-SecureString 配合嵌入的密钥加密字符串。
PowerShell 的抽象语法树暴露了脚本的解析结构,与表面层面的混淆无关。通过遍历 AST 并评估表达式节点,分析人员可以解析拼接的字符串、解码编码值并重建原始命令。PowerPeeler 在指令级别使用这种方法,监控执行过程以将 AST 节点与其评估结果相关联。
通过将 Invoke-Expression(IEX)替换为 Write-Output,分析人员可以安全地捕获通常会被执行的去混淆脚本内容。这种技术通过迭代替换 IEX 调用跨越多层工作,直到最终载荷被揭示。
#!/usr/bin/env python3
"""识别并分类 PowerShell 混淆技术。"""
import re
import base64
import sys
def analyze_obfuscation(script_content):
"""识别 PowerShell 脚本中使用的混淆技术。"""
techniques = []
# 检查 Base64 编码命令
b64_pattern = re.compile(
r'-[Ee](?:nc(?:odedcommand)?)\s+([A-Za-z0-9+/=]{20,})',
re.IGNORECASE
)
if b64_pattern.search(script_content):
techniques.append("Base64 EncodedCommand")
# 检查 FromBase64String
if re.search(r'\[Convert\]::FromBase64String', script_content, re.IGNORECASE):
techniques.append("Base64 FromBase64String")
# 检查字符串拼接
concat_count = script_content.count("'+'") + script_content.count('"+"')
if concat_count > 3:
techniques.append(f"字符串拼接({concat_count} 次连接)")
# 检查字符数组构造
if re.search(r'\[char\]\s*\d+', script_content, re.IGNORECASE):
techniques.append("字符代码数组")
# 检查 Invoke-Expression 变体
iex_patterns = [
r'Invoke-Expression',
r'\bIEX\b',
r'\.\s*\(\s*\$',
r'&\s*\(\s*\$',
r'\|\s*IEX',
r'\|\s*Invoke-Expression',
]
for pattern in iex_patterns:
if re.search(pattern, script_content, re.IGNORECASE):
techniques.append(f"Invoke-Expression 变体:{pattern}")
# 检查反引号混淆
tick_count = script_content.count('`')
if tick_count > 5:
techniques.append(f"反引号插入({tick_count} 个反引号)")
# 检查环境变量滥用
if re.search(r'\$env:', script_content, re.IGNORECASE):
env_refs = re.findall(r'\$env:\w+', script_content, re.IGNORECASE)
if len(env_refs) > 2:
techniques.append(f"环境变量滥用({len(env_refs)} 处引用)")
# 检查 SecureString
if re.search(r'ConvertTo-SecureString', script_content, re.IGNORECASE):
techniques.append("SecureString 加密")
# 检查压缩
if re.search(r'IO\.Compression|DeflateStream|GZipStream',
script_content, re.IGNORECASE):
techniques.append("压缩(Deflate/GZip)")
# 检查 XOR 编码
if re.search(r'-bxor\s+\d+', script_content, re.IGNORECASE):
techniques.append("XOR 编码")
# 检查 Replace 链
replace_count = len(re.findall(r'\.Replace\(', script_content))
if replace_count > 2:
techniques.append(f"Replace 链({replace_count} 次替换)")
return techniques
def decode_base64_command(script_content):
"""提取并解码 Base64 编码的命令。"""
b64_match = re.search(
r'-[Ee](?:nc(?:odedcommand)?)\s+([A-Za-z0-9+/=]{20,})',
script_content, re.IGNORECASE
)
if b64_match:
encoded = b64_match.group(1)
try:
decoded = base64.b64decode(encoded).decode('utf-16-le')
return decoded
except Exception:
return None
return None
def remove_tick_marks(script_content):
"""移除 PowerShell 反引号混淆。"""
# 移除非转义序列的反引号
escape_chars = {'`n', '`r', '`t', '`a', '`b', '`f', '`v', '`0', '``'}
result = []
i = 0
while i < len(script_content):
if script_content[i] == '`' and i + 1 < len(script_content):
pair = script_content[i:i+2]
if pair in escape_chars:
result.append(pair)
i += 2
else:
# 跳过反引号,保留下一个字符
result.append(script_content[i+1])
i += 2
else:
result.append(script_content[i])
i += 1
return ''.join(result)
def resolve_string_concat(script_content):
"""解析简单的字符串拼接模式。"""
# 模式:'str1' + 'str2'
pattern = re.compile(r"'([^']*)'\s*\+\s*'([^']*)'")
while pattern.search(script_content):
script_content = pattern.sub(lambda m: f"'{m.group(1)}{m.group(2)}'",
script_content)
# 模式:"str1" + "str2"
pattern = re.compile(r'"([^"]*)"\s*\+\s*"([^"]*)"')
while pattern.search(script_content):
script_content = pattern.sub(lambda m: f'"{m.group(1)}{m.group(2)}"',
script_content)
return script_content
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"用法:{sys.argv[0]} <powershell_script>")
sys.exit(1)
with open(sys.argv[1], 'r', errors='replace') as f:
content = f.read()
print("[+] 混淆分析")
print("=" * 60)
techniques = analyze_obfuscation(content)
for t in techniques:
print(f" - {t}")
# 尝试自动去混淆
print("\n[+] 正在尝试去混淆")
print("=" * 60)
# 第 1 层:移除反引号
deobfuscated = remove_tick_marks(content)
# 第 2 层:解析字符串拼接
deobfuscated = resolve_string_concat(deobfuscated)
# 第 3 层:解码 Base64
b64_decoded = decode_base64_command(deobfuscated)
if b64_decoded:
print("[+] Base64 解码内容:")
print(b64_decoded[:2000])
deobfuscated = b64_decoded
print(f"\n[+] 去混淆后脚本长度:{len(deobfuscated)} 个字符")
output_file = sys.argv[1] + ".deobfuscated.ps1"
with open(output_file, 'w') as f:
f.write(deobfuscated)
print(f"[+] 已保存到 {output_file}")
import subprocess
import tempfile
import os
def iex_replacement_deobfuscate(script_content, max_layers=10):
"""迭代地将 IEX 替换为 Write-Output 以解包各层。"""
# IEX 替换模式
replacements = [
(r'\bInvoke-Expression\b', 'Write-Output'),
(r'\bIEX\b', 'Write-Output'),
(r'\|\s*IEX\b', '| Write-Output'),
]
current = script_content
layers = []
for layer_num in range(max_layers):
# 应用 IEX 替换
modified = current
for pattern, replacement in replacements:
modified = re.sub(pattern, replacement, modified, flags=re.IGNORECASE)
if modified == current and layer_num > 0:
print(f" [+] 在第 {layer_num} 层未发现更多 IEX 层")
break
# 写入临时文件并在受限的 PowerShell 中执行
with tempfile.NamedTemporaryFile(mode='w', suffix='.ps1',
delete=False) as tmp:
tmp.write(modified)
tmp_path = tmp.name
try:
result = subprocess.run(
['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass',
'-File', tmp_path],
capture_output=True, text=True, timeout=30
)
output = result.stdout.strip()
if output and output != current:
print(f" [+] 第 {layer_num + 1} 层:解包了 "
f"{len(output)} 个字符")
layers.append({
"layer": layer_num + 1,
"technique": "IEX 替换",
"content_length": len(output),
})
current = output
else:
break
except subprocess.TimeoutExpired:
print(f" [!] 第 {layer_num + 1} 层:执行超时")
break
finally:
os.unlink(tmp_path)
return current, layers
def extract_iocs_from_script(deobfuscated_content):
"""从去混淆后的 PowerShell 中提取攻陷指标。"""
iocs = {
"urls": [],
"ips": [],
"domains": [],
"file_paths": [],
"registry_keys": [],
"commands": [],
"base64_blobs": [],
}
# URL
url_pattern = re.compile(
r'https?://[^\s\'"<>)\]]+', re.IGNORECASE
)
iocs["urls"] = list(set(url_pattern.findall(deobfuscated_content)))
# IP 地址
ip_pattern = re.compile(
r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
)
iocs["ips"] = list(set(ip_pattern.findall(deobfuscated_content)))
# 文件路径
path_pattern = re.compile(
r'[A-Za-z]:\\[^\s\'"<>|]+|'
r'\\\\[^\s\'"<>|]+|'
r'%(?:APPDATA|TEMP|USERPROFILE|PROGRAMFILES)%[^\s\'"<>|]*',
re.IGNORECASE
)
iocs["file_paths"] = list(set(path_pattern.findall(deobfuscated_content)))
# 注册表键
reg_pattern = re.compile(
r'(?:HKLM|HKCU|HKCR|HKU|HKCC)(?:\\[^\s\'"<>|]+)+',
re.IGNORECASE
)
iocs["registry_keys"] = list(set(reg_pattern.findall(deobfuscated_content)))
# 可疑命令
suspicious_cmds = [
'New-Object Net.WebClient',
'DownloadString', 'DownloadFile', 'DownloadData',
'Start-Process', 'Invoke-WebRequest',
'New-Object IO.MemoryStream',
'Reflection.Assembly',
'Add-MpPreference -ExclusionPath',
'Set-MpPreference -DisableRealtimeMonitoring',
'New-ScheduledTask', 'Register-ScheduledTask',
]
for cmd in suspicious_cmds:
if cmd.lower() in deobfuscated_content.lower():
iocs["commands"].append(cmd)
return iocs