PowerShell スクリプトを手動で難読化し、シグネチャベースの検知を回避するためのテクニック、実例、そして少しの理論をまとめた教育目的のコレクションです。このリポジトリの内容は個人調査の結果であり、オンライン資料の読解や、ラボやペンテストでの試行錯誤を含みます。何を鵜呑みにしないでください。
YouTube 動画プレゼンテーション: youtube.com/watch?v=tGFdmAh_lXE
⚠️ 免責事項: このリポジトリに記載された技術や概念を、テスト許可のないシステムへの不正アクセスに使用することは違法です。行動の責任はあなたにあります。邪悪にならないでください。
エントロピーという科学用語は、一般にシステムのランダム性または無秩序さの尺度と定義され、AV回避において重要です。なぜなら、マルウェアは分析や検知を困難にするために、高度にランダム化・暗号化・エンコード(難読化)されたコードを含むことが多いからです。アンチウイルス製品は、さまざまな方法の1つとして、エントロピー分析を使用して潜在的に悪意のあるファイルやペイロードを識別します。
この概念を理解することは重要です。コードを難読化する際、自分が選んだ変更によって生じるエントロピーの変動に注意する必要があるからです。シグネチャを破るのは簡単ですが、エントロピーレベルに注意を払わなければ、高度なAV/EDRはそれを見抜きます。
心に留めておくべき原則: エントロピーが大きいほど、データが難読化または暗号化されている可能性が高く、ファイル/ペイロードが悪意のあるものである可能性が高くなります。幸い、それを下げる方法があります。
Claude E. Shannon は、1948年の論文 A Mathematical Theory of Communication で、データセット内のエントロピーを測定するために使用できる公式を紹介しました。以下は、開発するペイロードのエントロピーを測定するために使用できる Shannon Entropy のシンプルなPython実装です:
#!/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))
オンラインの Shannon Entropy calculator または Microsoft の Sigcheck.exe を -a オプション付きで使用することもできます。
フラグされないペイロードのバリエーションを生み出すために試行錯誤の難読化テストに飛び込む前に、成熟したエレガントな方法は、スクリプト内でマルウェア検知をトリガーする部分を特定することです。特にC2コマンドのような短いスクリプトでは、些細な変更を加えるだけで即座にレーダーから外れるかもしれません。
そのようなトリガーを特定するための優れたツールは AMSITrigger です。悪意のあるスクリプトを含むファイルでの使用例を示します。赤い領域は難読化を適用すべき部分を示しています:

スクリプトをチャンクごとに実行して手動でトリガーを特定することもできます。
スクリプトを難読化する際、変数/クラス/関数名をランダムなものに置き換えることを優先すべきです。そうすることで、他のテクニックと組み合わせて、簡単に検知を回避できます。ただし、開発するペイロードのエントロピーに注意する必要があります。次の標準的なリバースシェルスクリプトを考えてみてください。これは、すべてではないにしてもほとんどのAVに検知される一般的なものです:

次に、以下の難読化バージョンを考えてみましょう:

このバージョンでは、すべての変数名が32文字のランダムな名前に置き換えられています。また、(pwd).Path を $(gl) に置き換えました。このペイロードのShannon entropyは4.96です。これを書いている時点では、MS Defender や他の多くの製品に検知されていません:

次にこのバージョンを考えてみましょう:

このバリエーションもすべての変数名が置き換えられていますが、今回は 'f' 文字を x 個並べた名前になっており、その結果ペイロードのエントロピーが大幅に低下しています。ここでも (pwd).Path を $(gl) に置き換えました。繰り返しますが、これを書いている時点で MS Defender には検知されていません。このペイロードのShannon entropyは0.76です。

⚡ これらのバリエーションはどちらも一般的なAVを回避できますが、2番目のものはエントロピーが低く、EDRや他の高度なアンチマルウェアエンジンによって処理される際により良い可能性があります。⚠️ この例における2番目のペイロードのパフォーマンスが優れているのは、確かにエントロピーレベルのせいだと言っているわけではありません(実際には長さやその両方、あるいは何か他の理由かもしれません)。しかし、難読化する際に念頭に置くべき重要な側面であり、この例はその概念を強調するためのものです。
以下のスクリプトを使用して、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、空文字列以外の任意のもののブール型キャストは 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](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/main/-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 と評価される比較の結果(当然):(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](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/main/%21%5Bbool%5D%24null)
[System.Collections.CaseInsensitiveComparer] -ne [bool][datetime]'2023-01-01'
[bool]$(Get-LocalGroupMember Administrators)
!!!![bool][bool][bool][bool][bool][bool]