AV Evasion ShellCode
Última actualización
Última actualización
El formato de archivo ejecutable de Windows, también conocido como PE (Portable Executable), es una estructura de datos que contiene la información necesaria para los archivos. Es una forma de organizar el código de un archivo ejecutable en un disco. Los componentes del sistema operativo Windows, como los cargadores de Windows y DOS, pueden cargarlo en la memoria y ejecutarlo en función de la información del archivo analizado que se encuentra en el PE.
En general, la estructura de archivos predeterminada de los binarios de Windows, como los archivos EXE, DLL y de código objeto, tiene la misma estructura PE y funciona en el sistema operativo Windows para ambas arquitecturas de CPU (x86 y x64).
Una estructura PE contiene varias secciones que contienen información sobre el binario, como metadatos y enlaces a una dirección de memoria de bibliotecas externas. Una de estas secciones es el encabezado PE , que contiene información de metadatos, punteros y enlaces para abordar secciones en la memoria. Otra sección es la sección Datos , que incluye contenedores que incluyen la información necesaria para que el cargador de Windows ejecute un programa, como el código ejecutable, recursos, enlaces a bibliotecas, variables de datos, etc.
Hay diferentes tipos de contenedores de datos en la estructura PE, cada uno de los cuales contiene datos diferentes.
.text almacena el código real del programa
.data contiene las variables inicializadas y definidas
.bss contiene los datos no inicializados (variables declaradas sin valores asignados)
.rdata contiene los datos de solo lectura
.edata : contiene objetos exportables e información de tabla relacionada
.idata objetos importados e información de tabla relacionada
.reloc información de reubicación de imagen
.rsrc vincula recursos externos utilizados por el programa, como imágenes, íconos, archivos binarios integrados y archivos de manifiesto, que contienen toda la información sobre las versiones del programa, los autores, la empresa y los derechos de autor.
La estructura del PE es un tema amplio y complicado, y no vamos a entrar en demasiados detalles sobre los encabezados y las secciones de datos. Esta tarea proporciona una descripción general de alto nivel de la estructura de PE. Si estás interesado en obtener más información sobre el tema, te sugerimos consultar las siguientes salas de THM donde se explica con mayor detalle el tema:
Disección de encabezados de PE
Al observar el contenido de PE, veremos que contiene un montón de bytes que no son legibles por humanos. Sin embargo, incluye todos los detalles que el cargador necesita para ejecutar el archivo. Los siguientes son pasos de ejemplo en los que el cargador de Windows lee un binario ejecutable y lo ejecuta como un proceso.
Secciones de encabezado: DOS, Windows y encabezados opcionales se analizan para proporcionar información sobre el archivo EXE. Por ejemplo,
El número mágico comienza con "MZ", que le indica al cargador que se trata de un archivo EXE.
Firmas de archivos
Si el archivo está compilado para la arquitectura de CPU x86 o x64.
Marca de tiempo de creación.
Analizar los detalles de la tabla de secciones, como
Número de secciones que contiene el archivo.
Mapear el contenido del archivo en la memoria según
La dirección de EntryPoint y el desplazamiento de ImageBase.
RVA: Dirección Virtual Relativa, Direcciones relacionadas con Imagebase.
Las importaciones, DLL y otros objetos se cargan en la memoria.
Se localiza la dirección EntryPoint y se ejecuta la función de ejecución principal.
Hay un par de razones por las que necesitamos aprender sobre esto. Primero, dado que estamos tratando con temas de empaque y desempaque, la técnica requiere detalles sobre la estructura del PE.
La otra razón es que los analistas de software antivirus y malware analizan los archivos EXE en función de la información del encabezado PE y otras secciones de PE. Por lo tanto, para crear o modificar malware con capacidad de evasión antivirus dirigido a una máquina con Windows, debemos comprender la estructura de los archivos ejecutables portátiles de Windows y dónde se puede almacenar el código shell malicioso.
Podemos controlar en qué sección de Datos almacenar nuestro código de shell según cómo definimos e inicializamos la variable de código de shell. Los siguientes son algunos ejemplos que muestran cómo podemos almacenar el código shell en PE:
Definir el código de shell como una variable local dentro de la función principal lo almacenará en la sección .TEXT PE.
Definir el código shell como una variable global lo almacenará en la sección .Data .
Otra técnica implica almacenar el código shell como un binario sin formato en una imagen de icono y vincularlo dentro del código, por lo que, en este caso, aparece en la sección Datos .rsrc .
Podemos agregar una sección de datos personalizada para almacenar el código shell.
La VM adjunta es una máquina de desarrollo de Windows que tiene las herramientas necesarias para analizar archivos EXE y leer los detalles que comentamos. Para su comodidad, proporcionamos una copia del software PE-Bear en el escritorio, que ayuda a verificar la estructura de PE: encabezados, secciones, etc. PE-Bear proporciona una interfaz gráfica de usuario para mostrar todos los detalles EXE relevantes. Para cargar un archivo EXE para su análisis, seleccione Archivo -> Cargar PE (Ctrl + O).
Una vez que se carga un archivo, podemos ver todos los detalles de PE. La siguiente captura de pantalla muestra los detalles de PE del archivo cargado, incluidos los encabezados y las secciones que analizamos anteriormente en esta tarea.
Shellcode es un conjunto de instrucciones de código de máquina diseñadas que le indican al programa vulnerable que ejecute funciones adicionales y, en la mayoría de los casos, proporciona acceso a un shell del sistema o crea un shell de comando inverso.
Una vez que el shellcode se inyecta en un proceso y el software o programa vulnerable lo ejecuta, modifica el flujo de ejecución del código para actualizar los registros y funciones del programa para ejecutar el código del atacante.
Generalmente está escrito en lenguaje ensamblador y traducido a códigos de operación hexadecimales (códigos operativos). Escribir un código shell único y personalizado ayuda a evadir significativamente el software antivirus. Pero escribir un código shell personalizado requiere excelentes conocimientos y habilidades para manejar el lenguaje ensamblador, ¡lo cual no es una tarea fácil!
Para crear su propio código shell, se requiere un conjunto de habilidades:
Una comprensión decente de las arquitecturas de CPU x86 y x64.
Lenguaje ensamblador.
Fuerte conocimiento de lenguajes de programación como C.
Familiaridad con los sistemas operativos Linux y Windows.
Para generar nuestro propio código shell, necesitamos escribir y extraer bytes del código máquina ensamblador. Para esta tarea, usaremos AttackBox para crear un código shell simple para Linux que escriba la cadena "THM, Rocks!". El siguiente código ensamblador utiliza dos funciones principales:
Función de escritura del sistema (sys_write) para imprimir una cadena que elijamos.
Función de salida del sistema (sys_exit) para finalizar la ejecución del programa.
Para llamar a esas funciones, usaremos syscalls . Una llamada al sistema es la forma en que un programa solicita al núcleo que haga algo. En este caso, solicitaremos al kernel que escriba una cadena en nuestra pantalla y salga del programa. Cada sistema operativo tiene una convención de llamada diferente con respecto a las llamadas al sistema, lo que significa que para usar la escritura en Linux, probablemente usará una llamada al sistema diferente a la que usaría en Windows. Para Linux de 64 bits, puede llamar las funciones necesarias desde el kernel configurando los siguientes valores:
rax
System Call
rdi
rsi
rdx
0x1
sys_write
unsigned int fd
const char buf
size_t count
0x3c
sys_exit
int error_code
Para sys_write
, el primer parámetro enviado rdi
es el descriptor de archivo en el que escribir. El segundo parámetro rsi
es un puntero a la cadena que queremos imprimir y el tercero rdx
es el tamaño de la cadena a imprimir.
Para sys_exit
, rdi debe configurarse con el código de salida del programa. Usaremos el código 0, lo que significa que el programa salió exitosamente.
Copie el siguiente código a su AttackBox en un archivo llamado thm.asm
:
Expliquemos un poco más el código ASM. Primero, nuestra cadena de mensaje se almacena al final de la sección .text. Como necesitamos un puntero a ese mensaje para imprimirlo, saltaremos a la instrucción de llamada antes del mensaje en sí. Cuando call GOBACK
se ejecuta, la dirección de la siguiente instrucción después de la llamada se insertará en la pila, que corresponde a donde está nuestro mensaje. Tenga en cuenta que 0dh, 0ah al final del mensaje es el equivalente binario a una nueva línea (\r\n).
A continuación, el programa inicia la rutina GOBACK y prepara los registros necesarios para nuestra primera función sys_write().
Especificamos la función sys_write almacenando 1 en el registro rax.
Configuramos rdi en 1 para imprimir la cadena en la consola del usuario (STDOUT).
Colocamos un puntero a nuestra cadena, que se envió cuando llamamos a GOBACK y lo almacenamos en rsi.
Con la instrucción syscall ejecutamos la función sys_write con los valores que preparamos.
Para la siguiente parte, hacemos lo mismo para llamar a la función sys_exit, por lo que configuramos 0x3c en el registro rax y llamamos a la función syscall para salir del programa.
A continuación, compilamos y vinculamos el código ASM para crear un archivo ejecutable de Linux x64 y finalmente ejecutamos el programa.
Ensamblador y enlace de nuestro código.
Usamos el nasm
comando para compilar el archivo asm, especificando la -f elf64
opción para indicar que estamos compilando para Linux de 64 bits. Observe que como resultado obtenemos un archivo .o, que contiene código objeto, que debe vincularse para que sea un archivo ejecutable que funcione. El ld
comando se utiliza para vincular el objeto y obtener el ejecutable final. La -o
opción se utiliza para especificar el nombre del archivo ejecutable de salida.
Ahora que tenemos el programa ASM compilado, extraigamos el código shell con el objdump
comando volcando la sección .text del binario compilado.
Volcar la sección .text
Ahora necesitamos extraer el valor hexadecimal del resultado anterior. Para hacer eso, podemos usar objcopy
para volcar la .text
sección en un nuevo archivo llamado thm.text
en formato binario de la siguiente manera:
Extrae la sección .text
El thm.text contiene nuestro código shell en formato binario, por lo que para poder usarlo, primero necesitaremos convertirlo a hexadecimal. El xxd
comando tiene la -i
opción de generar el archivo binario en una cadena C directamente:
Genera el equivalente hexadecimal de nuestro código shell.
Finalmente lo tenemos, un código shell formateado de nuestro ensamblado ASM. ¡Eso fue divertido! Como vemos, se requiere dedicación y habilidades para generar shellcode para tu trabajo.
Para confirmar que el código shell extraído funciona como esperábamos, podemos ejecutar nuestro código shell e inyectarlo en un programa C.
Luego, lo compilamos y ejecutamos de la siguiente manera,
Compilador de nuestro programa C.
¡Lindo! funciona. Tenga en cuenta que compilamos el programa C deshabilitando la protección NX, lo que puede impedirnos ejecutar el código correctamente en el segmento o pila de datos.
Comprender los códigos shell y cómo se crean es esencial para las siguientes tareas, especialmente cuando se trata de cifrar y codificar el código shell.
Responda las siguientes preguntas
Modifique su programa C para ejecutar el siguiente código shell. ¿Qué es la bandera?
Shellcode se puede generar para un formato específico con un lenguaje de programación particular. Esto depende de ti. Por ejemplo, si su cuentagotas, que es el archivo exe principal, contiene el código shell que se enviará a la víctima y está escrito en C, entonces necesitamos generar un formato de código shell que funcione en C.
La ventaja de generar shellcode a través de herramientas públicas es que no necesitamos crear un shellcode personalizado desde cero y ni siquiera necesitamos ser expertos en lenguaje ensamblador. La mayoría de los marcos C2 públicos proporcionan su propio generador de código shell compatible con la plataforma C2. Por supuesto, esto es muy conveniente para nosotros, pero el inconveniente es que la mayoría, o podemos decir todos, los shellcodes generados son bien conocidos por los proveedores de AV y pueden detectarse fácilmente.
Usaremos Msfvenom en AttackBox para generar un código shell que ejecute archivos de Windows. Crearemos un código shell que ejecute la calc.exe
aplicación.
Generar Shellcode para ejecutar calc.exe
Como resultado, el marco Metasploit genera un código shell que ejecuta la calculadora de Windows (calc.exe). La calculadora de Windows se utiliza ampliamente como ejemplo en el proceso de desarrollo de malware para mostrar una prueba de concepto. Si la técnica funciona, aparecerá una nueva instancia de la calculadora de Windows. Esto confirma que cualquier código shell ejecutable funciona con el método utilizado.
Los piratas informáticos inyectan shellcode en un hilo nuevo o en ejecución y lo procesan utilizando diversas técnicas. Las técnicas de inyección de Shellcode modifican el flujo de ejecución del programa para actualizar los registros y funciones del programa para ejecutar el propio código del atacante.
Ahora continuemos usando el código shell generado y ejecutémoslo en el sistema operativo. El siguiente es un código C que contiene nuestro código shell generado que se inyectará en la memoria y ejecutará "calc.exe".
En AttackBox, guardemos lo siguiente en un archivo llamado : calc.c
Ahora compilémoslo como un archivo exe:
Compile nuestro programa C para Windows
Una vez que tengamos nuestro archivo exe, transfirámoslo a la máquina con Windows y ejecutémoslo. Para transferir el archivo, puede usar smbclient desde su AttackBox para acceder al recurso compartido SMB en \MACHINE_IP\Tools con los siguientes comandos (recuerde que la contraseña delthm
usuario es Password321
):
Copie calc-MSC.exe a la máquina Windows
Esto debería copiar su archivo en C:\Tools\
la máquina con Windows.
Si bien el AV de su máquina debería estar desactivado, no dude en intentar cargar su carga útil en THM Antivirus Check enhttp://MACHINE_IP/
.
El marco Metasploit tiene muchos otros formatos y tipos de shellcode para todas sus necesidades. Le sugerimos encarecidamente que experimente más con él y amplíe sus conocimientos generando diferentes códigos de shell.
El ejemplo anterior muestra cómo generar shellcode y ejecutarlo dentro de una máquina de destino. Por supuesto, puede replicar los mismos pasos para crear diferentes tipos de shellcode, por ejemplo, el shellcode de Meterpreter.
Shellcode también se puede almacenar en .bin
archivos, que es un formato de datos sin procesar. En este caso, podemos obtener su código shell usando el xxd -i
comando.
C2 Frameworks proporciona shellcode como un archivo binario sin formato .bin
. Si este es el caso, podemos usar el comando del sistema Linuxxxd
para obtener la representación hexadecimal del archivo binario. Para ello ejecutamos el siguiente comando:xxd -i
.
Creemos un archivo binario sin formato usando msfvenom para obtener el código shell:
Genere un código shell sin formato para ejecutar calc.exe
Y ejecute el xxd
comando en el archivo creado:
Obtenga el código shell usando el comando xxd
Si comparamos la salida con el código shell anterior creado con Metasploit , coincide.
En nuestro objetivo de evitar el AV , encontraremos dos enfoques principales para entregar el código shell final a una víctima. Dependiendo del método, encontrará que las cargas útiles generalmente se clasifican como cargas útiles en etapas o sin etapas . En esta tarea, veremos las diferencias entre ambos enfoques y las ventajas de cada método.
Una carga útil sin etapas incorpora el código shell final directamente en sí misma. Piense en ello como una aplicación empaquetada que ejecuta el código shell en un proceso de un solo paso. En tareas anteriores, incorporamos un ejecutable que incorporaba un calc
código de shell simple, creando una carga útil sin etapas.
En el ejemplo anterior, cuando el usuario ejecuta la carga maliciosa, se ejecutará el código de shell incrustado, proporcionando un shell inverso al atacante.
Las cargas útiles por etapas funcionan mediante el uso de shellcodes intermediarios que actúan como pasos que conducen a la ejecución de un shellcode final. Cada uno de estos shellcodes intermediarios se conoce como stager y su objetivo principal es proporcionar un medio para recuperar el shellcode final y ejecutarlo eventualmente.
Si bien puede haber cargas útiles con varias etapas, el caso habitual implica tener una carga útil de dos etapas donde la primera etapa, a la que llamaremos stage0 , es un código shell stub que se conectará nuevamente a la máquina del atacante para descargar el código shell final. ejecutado.
Una vez recuperado, el código auxiliar de stage0 inyectará el código de shell final en algún lugar de la memoria del proceso de carga útil y lo ejecutará (como se muestra a continuación).
Al decidir qué tipo de carga útil usar, debemos ser conscientes del entorno que atacaremos. Cada tipo de carga útil tiene ventajas y desventajas según el escenario de ataque específico.
En el caso de cargas útiles sin etapas, encontrará las siguientes ventajas:
El ejecutable resultante incluye todo lo necesario para que nuestro código shell funcione.
La carga útil se ejecutará sin requerir conexiones de red adicionales. Cuantas menos interacciones en la red, menores serán tus posibilidades de ser detectado por un IPS .
Si está atacando un host con una conectividad de red muy restringida, es posible que desee que toda su carga útil esté en un solo paquete.
Para cargas útiles por etapas, tendrá:
Tamaño reducido en disco. Dado que stage0 solo se encarga de descargar el código shell final, lo más probable es que sea de tamaño pequeño.
El código shell final no está incrustado en el ejecutable. Si se captura su carga útil, el Equipo Azul solo tendrá acceso al código auxiliar de stage0 y nada más.
El shellcode final se carga en la memoria y nunca toca el disco. Esto lo hace menos propenso a ser detectado por soluciones antivirus .
Puedes reutilizar el mismo dropper stage0 para muchos shellcodes, ya que puedes simplemente reemplazar el shellcode final que se entrega a la máquina víctima.
En conclusión, no podemos decir que ningún tipo es mejor que el otro a menos que le agreguemos algo de contexto. En general, las cargas útiles sin etapas son más adecuadas para redes con mucha seguridad perimetral, ya que no dependen de tener que descargar el código shell final de Internet. Si, por ejemplo, está realizando un USB Drop Attack dirigido a computadoras en un entorno de red cerrado donde sabe que no obtendrá una conexión nuevamente con su máquina, el camino a seguir es sin etapas.
Las cargas útiles por etapas, por otro lado, son excelentes cuando desea reducir al mínimo su huella en la máquina local. Dado que ejecutan la carga útil final en la memoria, algunas soluciones antivirus pueden tener más dificultades para detectarlos. También son excelentes para evitar exponer sus códigos shell (que generalmente requieren un tiempo considerable para prepararse), ya que el código shell no se coloca en el disco de la víctima en ningún momento (como un artefacto).
Al crear cargas útiles con msfvenom o usarlas directamente en Metasploit , puede optar por utilizar cargas útiles por etapas o sin etapas. Como ejemplo, si desea generar un shell TCP inverso, encontrará que existen dos cargas útiles para ese propósito con nombres ligeramente diferentes (observe el_
versus /
después shell
):
Carga útil
Tipo
windows/x64/shell_reverse_tcp
Payoads sin etapas
windows/x64/shell/reverse_tcp
Payload por etapas
Generalmente encontrará que los mismos patrones de nombres se aplican a otros tipos de caparazones. Para usar un Meterpreter sin etapas, por ejemplo, usaríamos windows/x64/meterpreter_reverse_tcp
, en lugar de windows/x64/meterpreter/reverse_tcp
, que funciona como su contraparte en etapas.
El código puede parecer intimidante al principio, pero es relativamente sencillo. Analicemos qué hace paso a paso.
La primera parte del código importará algunas funciones API de Windows a través de P/Invoke. Las funciones que necesitamos son las tres siguienteskernel32.dll
:
Función WinAPI
Descripción
Nos permite reservar algo de memoria para que la utilice nuestro código shell.
Crea un hilo como parte del proceso actual.
Se utiliza para la sincronización de subprocesos. Nos permite esperar a que finalice un hilo antes de continuar.
La parte del código encargada de importar estas funciones es la siguiente:
La parte más importante de nuestro código estará en la Stager()
función, donde se implementará la lógica del escenario. La función Stager recibirá una URL desde donde se descargará el código shell a ejecutar.
La primera parte de la Stager()
función creará un nuevo WebClient()
objeto que nos permitirá descargar el código shell mediante solicitudes web. Antes de realizar la solicitud real, sobrescribiremos el ServerCertificateValidationCallback
método encargado de validar los certificados SSL cuando usemos solicitudes HTTPS para que el WebClient no se queje de certificados autofirmados o no válidos, que usaremos en el servidor web que aloja las cargas útiles. Después de eso, llamaremos al DownloadData()
método para descargar el código shell desde la URL dada y lo almacenaremos en la shellcode
variable:
Una vez que nuestro código shell esté descargado y disponible en la shellcode
variable, necesitaremos copiarlo en la memoria ejecutable antes de ejecutarlo. Solemos VirtualAlloc()
solicitar un bloque de memoria al sistema operativo. Observe que solicitamos suficiente memoria para asignar shellcode.Length
bytes y configuramos el PAGE_EXECUTE_READWRITE
indicador, haciendo que la memoria asignada sea ejecutable, legible y escribible. Una vez que nuestro bloque de memoria ejecutable está reservado y asignado a la codeAddr
variable, usamos Marshal.Copy()
para copiar el contenido de la shellcode
variable en la codeAddr
variable.
Ahora que tenemos una copia del código shell asignada en un bloque de memoria ejecutable, usamos la CreateThread()
función para generar un nuevo hilo en el proceso actual que ejecutará nuestro código shell. El tercer parámetro pasado a CreateThread apunta a codeAddr
, donde se almacena nuestro código shell, de modo que cuando se inicia el hilo, ejecuta el contenido de nuestro código shell como si fuera una función normal. El quinto parámetro se establece en 0, lo que significa que el hilo comenzará inmediatamente.
Una vez que se haya creado el hilo, llamaremos a la WaitForSingleObject()
función para indicarle a nuestro programa actual que debe esperar a que finalice la ejecución del hilo antes de continuar. Esto evita que nuestro programa se cierre antes de que el subproceso Shellcode tenga la oportunidad de ejecutarse:
Para compilar el código, sugerimos copiarlo en una máquina con Windows como un archivo llamado staged-payload.cs y compilarlo con el siguiente comando:
Una vez compilada nuestra carga útil, necesitaremos configurar un servidor web para alojar el código shell final. Recuerde que nuestro stager se conectará a este servidor para recuperar el código shell y ejecutarlo en la memoria de la máquina víctima. Comencemos generando un código shell (el nombre del archivo debe coincidir con la URL en nuestro escenario):
Tenga en cuenta que estamos usando el formato sin formato para nuestro código shell, ya que el stager cargará directamente todo lo que descargue en la memoria.
Ahora que tenemos un código shell, configuremos un servidor HTTPS simple. Primero, necesitaremos crear un certificado autofirmado con el siguiente comando:
Se le pedirá cierta información, pero no dude en presionar Intro para obtener cualquier información solicitada, ya que no necesitamos que el certificado SSL sea válido. Una vez que tengamos un certificado SSL, podemos generar un servidor HTTPS simple usando python3 con el siguiente comando:
Con todo esto listo, ahora podemos ejecutar nuestra carga útil de stager. El stager debe conectarse al servidor HTTPS y recuperar el archivo shellcode.bin para cargarlo en la memoria y ejecutarlo en la máquina víctima. Recuerde configurar un detector nc para recibir el shell inverso en el mismo puerto especificado cuando ejecuta msfvenom:
La codificación es el proceso de cambiar los datos de su estado original a un formato específico según el algoritmo o tipo de codificación. Se puede aplicar a muchos tipos de datos, como vídeos, HTML, URL y archivos binarios (EXE, imágenes, etc.).
La codificación es un concepto importante que se utiliza comúnmente para diversos fines, que incluyen, entre otros:
Compilación y ejecución del programa.
Almacenamiento y transmisión de datos.
Procesamiento de datos como conversión de archivos.
De manera similar, cuando se trata de técnicas de evasión AV , la codificación también se utiliza para ocultar cadenas de código shell dentro de un binario. Sin embargo, la codificación no es suficiente para fines de evasión. Hoy en día, el software antivirus es más inteligente y puede analizar un binario y, una vez que se encuentra una cadena codificada, se decodifica para verificar la forma original del texto.
También puede utilizar dos o más algoritmos de codificación en conjunto para dificultar que el AV descubra el contenido oculto. La siguiente figura muestra que convertimos la cadena "THM" a una representación hexadecimal y luego la codificamos usando Base64. En este caso, debe asegurarse de que su cuentagotas ahora maneje dicha codificación para restaurar la cadena a su estado original.
El cifrado es uno de los elementos esenciales de la seguridad de la información y los datos que se centra en prevenir el acceso no autorizado y la manipulación de los datos. El proceso de cifrado implica convertir texto sin formato (contenido no cifrado) en una versión cifrada llamada texto cifrado. El texto cifrado no se puede leer ni descifrar sin conocer el algoritmo utilizado en el cifrado y la clave.
Al igual que la codificación, las técnicas de cifrado se utilizan para diversos fines, como almacenar y transmitir datos de forma segura, así como el cifrado de un extremo a otro. El cifrado se puede utilizar de dos formas: teniendo una clave compartida entre dos partes o utilizando claves públicas y privadas.
¿Por qué necesitamos saber sobre codificación y cifrado?
Los proveedores de AV implementan su software AV para incluir en la lista de bloqueo la mayoría de las herramientas públicas (como Metasploit y otras) utilizando técnicas de detección estáticas o dinámicas. Por lo tanto, sin modificar el código shell generado por estas herramientas públicas, la tasa de detección de su cuentagotas es alta.
La codificación y el cifrado se pueden utilizar en técnicas de evasión AV en las que codificamos y/o ciframos el código shell utilizado en un cuentagotas para ocultarlo del software AV durante el tiempo de ejecución. Además, las dos técnicas se pueden utilizar no sólo para ocultar el código shell sino también funciones, variables, etc. En esta sala, nos centramos principalmente en cifrar el código shell para evadir Windows Defender.
Las herramientas públicas como Metasploit proporcionan funciones de codificación y cifrado. Sin embargo, los proveedores de AV son conscientes de la forma en que estas herramientas construyen sus cargas útiles y toman medidas para detectarlas. Si intenta utilizar estas funciones listas para usar, es probable que su carga útil se detecte tan pronto como el archivo toque el disco de la víctima.
Generemos una carga útil simple con este método para demostrar ese punto. En primer lugar, puede enumerar todos los codificadores disponibles para msfvenom con el siguiente comando:
Listado de codificadores dentro del marco Metasploit
Podemos indicar que queremos usar el shikata_ga_nai
codificador con el -e
interruptor (codificador) y luego especificar que queremos codificar la carga útil tres veces con el -i
interruptor (iteraciones):
Codificación utilizando Metasploit Framework (Shikata_ga_nai)
Si intentamos cargar nuestra carga útil recién generada en nuestra máquina de prueba, el AV la marcará instantáneamente antes de que tengamos la oportunidad de ejecutarla:
Si la codificación no funciona, siempre podemos intentar cifrar la carga útil. Intuitivamente, esperaríamos que esto tuviera un índice de éxito más alto, ya que descifrar la carga útil debería resultar una tarea más difícil para el AV . Intentemos eso ahora.
Puede generar fácilmente cargas útiles cifradas utilizando msfvenom. Sin embargo, las opciones para algoritmos de cifrado son un poco escasas. Para enumerar los algoritmos de cifrado disponibles, puede utilizar el siguiente comando:
Listado de módulos de cifrado dentro de Metasploit Framework
Construyamos una carga útil cifrada XOR . Para este tipo de algoritmo, deberá especificar una clave. El comando quedaría de la siguiente manera:
Xoring Shellcode utilizando el marco Metasploit
Una vez más, si cargamos el shell resultante en THM Antivirus Check! página en , el AVhttp://MACHINE_IP/
aún la marcará . La razón sigue siendo que los proveedores de antivirus han invertido mucho tiempo en garantizar que se detecten cargas útiles simples de msfvenom.
La mejor manera de superar esto es utilizar nuestros propios esquemas de codificación personalizados para que el AV no sepa qué hacer para analizar nuestra carga útil. Tenga en cuenta que no tiene que hacer nada demasiado complejo, siempre que sea lo suficientemente confuso para que el AV lo analice. Para esta tarea, tomaremos un shell inverso simple generado por msfvenom y usaremos una combinación de XOR y Base64 para evitar Defender.
Comencemos generando un shell inverso con msfvenom en formato CSharp:
Generar un formato de código shell CSharp
El codificador
Antes de construir nuestra carga útil real, crearemos un programa que tomará el código shell generado por msfvenom y lo codificará de la forma que queramos. En este caso, primero aplicaremos XOR a la carga útil con una clave personalizada y luego la codificaremos usando base64. Aquí está el código completo para el codificador (también puede encontrar este código en su máquina Windows en C:\Tools\CS Files\Encryptor.cs):
El código es bastante sencillo y generará una carga útil codificada que integraremos en la carga útil final. Recuerde reemplazar la buf
variable con el código shell que generó con msfvenom.
Para compilar y ejecutar el codificador, podemos usar los siguientes comandos en la máquina Windows:
Compilando y ejecutando nuestro codificador CSharp personalizado
Carga útil de autodecodificación
Dado que tenemos una carga útil codificada, debemos ajustar nuestro código para que decodifique el código shell antes de ejecutarlo. Para hacer coincidir el codificador, decodificaremos todo en el orden inverso al que lo codificamos, por lo que comenzamos decodificando el contenido base64 y luego continuamos aplicando XOR al resultado con la misma clave que usamos en el codificador. Aquí está el código de carga útil completo (también puede obtenerlo en su máquina Windows en C:\Tools\CS Files\EncStageless.cs
):
Tenga en cuenta que simplemente hemos combinado un par de técnicas realmente simples que se detectaron cuando se usaron por separado. Aun así, esta vez el AV no se quejará de la carga útil, ya que la combinación de ambos métodos no es algo que pueda analizar directamente.
Compilemos nuestra carga útil con el siguiente comando en la máquina Windows:
Antes de ejecutar nuestra carga útil, configuremos un nc
oyente. Después de copiar y ejecutar nuestra carga útil en la máquina víctima, deberíamos recuperar la conexión como se esperaba:
Configurar nc Listener
Como puede ver, a veces basta con realizar ajustes simples. La mayoría de las veces, cualquier método específico que encuentre en línea probablemente no funcionará de inmediato, ya que es posible que ya existan firmas de detección para ellos. Sin embargo, usar un poco de imaginación para personalizar cualquier método podría resultar suficiente para lograr un bypass exitoso.
Otro método para anular la detección AV basada en disco es utilizar un empaquetador. Los empaquetadores son piezas de software que toman un programa como entrada y lo transforman para que su estructura se vea diferente, pero su funcionalidad sigue siendo exactamente la misma. Los empacadores hacen esto con dos objetivos principales en mente:
Comprime el programa para que ocupe menos espacio.
Proteger el programa de la ingeniería inversa en general.
Los desarrolladores de software suelen utilizar los empaquetadores que desean proteger su software contra ingeniería inversa o craqueo. Logran cierto nivel de protección implementando una combinación de transformaciones que incluyen comprimir, cifrar, agregar protecciones de depuración y muchas otras. Como ya habrás adivinado, los empaquetadores también se utilizan comúnmente para ocultar malware sin mucho esfuerzo.
Existe una gran cantidad de empaquetadores, incluidos UPX, MPRESS, Themida y muchos otros.
Si bien cada empacador opera de manera diferente, veamos un ejemplo básico de lo que haría un empacador simple.
Cuando se empaqueta una aplicación, se transformará de alguna manera mediante una función de empaquetado . La función de empaquetado debe poder ofuscar y transformar el código original de la aplicación de una manera que pueda revertirse razonablemente mediante una función de desempaquetado para que se conserve la funcionalidad original de la aplicación. Si bien a veces el empaquetador puede agregar algún código (para dificultar la depuración de la aplicación, por ejemplo), generalmente querrá poder recuperar el código original que escribió al ejecutarla.
La versión empaquetada de la aplicación contendrá el código de la aplicación empaquetada. Dado que este nuevo código empaquetado está ofuscado, la aplicación debe poder descomprimir el código original. Para este fin, el empaquetador incrustará un código auxiliar que contiene un desempaquetador y redirigirá el punto de entrada principal del ejecutable a él.
Cuando se ejecute su aplicación empaquetada, sucederá lo siguiente:
El desempaquetador se ejecuta primero, ya que es el punto de entrada del ejecutable.
El desempaquetador lee el código de la aplicación empaquetada.
El desempaquetador escribirá el código original descomprimido en algún lugar de la memoria y dirigirá el flujo de ejecución de la aplicación hacia él.
A estas alturas, podemos ver cómo los empaquetadores ayudan a evitar las soluciones AV . Digamos que creó un ejecutable de shell inverso, pero el antivirus lo detecta como malicioso porque coincide con una firma conocida. En este caso, el uso de un empaquetador transformará el ejecutable del shell inverso para que no coincida con ninguna firma conocida mientras esté en el disco. Como resultado, debería poder distribuir su carga útil al disco de cualquier máquina sin muchos problemas.
Sin embargo, las soluciones AV aún podrían detectar su aplicación empaquetada por un par de razones:
Si bien su código original puede transformarse en algo irreconocible, recuerde que el ejecutable empaquetado contiene un código auxiliar con el código del desempaquetador. Si el desempaquetador tiene una firma conocida, las soluciones AV aún pueden marcar cualquier ejecutable empaquetado basándose únicamente en el código auxiliar del desempaquetador.
En algún momento, su aplicación descomprimirá el código original en la memoria para poder ejecutarlo. Si la solución AV que está intentando eludir puede realizar análisis en la memoria, es posible que aún lo detecten después de descomprimir su código.
Comencemos con un código shell básico de C#. También puede encontrar este código en su máquina Windows en C:\Tools\CS Files\UnEncStagelessPayload.cs
:
Esta carga útil toma un código shell generado por msfvenom y lo ejecuta en un hilo separado. Para que esto funcione, necesitarás generar un nuevo código shell y ponerlo en la shellcode
variable del código:
Luego puede compilar su carga útil en la máquina Windows usando el siguiente comando:
Una vez que tenga un ejecutable que funcione, puede intentar cargarlo en THM Antivirus Check. página (enlace en el escritorio). El AV debe señalarlo inmediatamente. Usemos un empaquetador en la misma carga útil y veamos qué sucede.
ConfuserEx le pedirá que indique las carpetas en las que funcionará. Asegúrese de seleccionar su escritorio como directorio base, como se muestra en la imagen a continuación. Una vez configurado el directorio base, arrastre y suelte el ejecutable que desea empaquetar en la interfaz y debería terminar con lo siguiente:
Vayamos a la pestaña de configuración y seleccionemos nuestra carga útil. Una vez seleccionado, presione el botón "+" para agregar configuraciones a su carga útil. Esto debería crear una regla denominada "verdadero". Asegúrese de habilitar la compresión también:
Ahora editaremos la regla "verdadera" y la estableceremos en el valor preestablecido Máximo:
Finalmente, nos dirigiremos al menú “¡Proteger!” pestaña y presione "Proteger":
¡La nueva carga útil debería estar lista y, con suerte, no activará ninguna alarma cuando se cargue en THM Antivirus Checker! (atajo disponible en su escritorio). De hecho, si ejecuta su carga útil y configura unnc
oyente, debería poder recuperar un shell:
Hasta ahora, todo bien, pero ¿recuerdas que hablamos de que los AV realizan escaneos en memoria? Si intenta ejecutar un comando en su shell inverso, el antivirus detectará su shell y lo eliminará. Esto se debe a que Windows Defender conectará ciertas llamadas a la API de Windows y realizará un escaneo en memoria cada vez que se utilicen dichas llamadas a la API. En el caso de cualquier shell generado con msfvenom, se invocará y detectará CreateProcess().
Si bien derrotar el escaneo en memoria está fuera del alcance de esta sala, hay un par de cosas simples que puede hacer para evitar la detección:
Sólo espera un poco . Intente generar el shell inverso nuevamente y espere unos 5 minutos antes de enviar cualquier comando. Verás que el AV ya no se quejará. La razón de esto es que escanear la memoria es una operación costosa. Por lo tanto, el AV lo hará durante un tiempo después de que comience el proceso, pero eventualmente se detendrá.
Utilice cargas útiles más pequeñas . Cuanto más pequeña sea la carga útil, menos probabilidades habrá de que la detecten. Si usa msfvenom para ejecutar un solo comando en lugar de un shell inverso, al antivirus le resultará más difícil detectarlo. Puedes probarmsfvenom -a x64 -p windows/x64/exec CMD='net user pwnd Password321 /add;net localgroup administrators pwnd /add' -f csharp
y ver qué pasa.
Si la detección no es un problema, incluso puedes utilizar un truco sencillo. Desde su shell inverso, ejecute cmd.exe
nuevamente. El antivirus detectará su carga útil y eliminará el proceso asociado, pero no el nuevo cmd.exe que acaba de generar.
Si bien cada AV se comportará de manera diferente, la mayoría de las veces habrá una forma similar de evitarlos, por lo que vale la pena explorar cualquier comportamiento extraño que notes durante las pruebas.
Si bien no es un método de derivación antivirus , los carpetas también son importantes al diseñar una carga maliciosa que se distribuirá a los usuarios finales. Una carpeta es un programa que fusiona dos (o más) ejecutables en uno solo. A menudo se utiliza cuando desea distribuir su carga útil oculta dentro de otro programa conocido para engañar a los usuarios haciéndoles creer que están ejecutando un programa diferente.
Si bien cada carpeta puede funcionar de manera ligeramente diferente, básicamente agregarán el código de su shellcode dentro del programa legítimo y lo ejecutarán de alguna manera.
Podría, por ejemplo, cambiar el punto de entrada en el encabezado PE para que su código shell se ejecute justo antes del programa y luego redirigir la ejecución nuevamente al programa legítimo una vez que haya finalizado. De esta manera, cuando el usuario haga clic en el ejecutable resultante, su código shell se ejecutará primero de forma silenciosa y continuará ejecutando el programa normalmente sin que el usuario se dé cuenta.
Puede colocar fácilmente una carga útil de su preferencia en cualquier archivo .exe con extensión msfvenom
. El binario seguirá funcionando como de costumbre pero ejecutará una carga útil adicional de forma silenciosa. El método utilizado por msfvenom inyecta su programa malicioso creando un hilo adicional para él, por lo que es ligeramente diferente de lo mencionado anteriormente pero logra el mismo resultado. Tener un hilo separado es incluso mejor ya que su programa no se bloqueará en caso de que su código shell falle por algún motivo.
Para esta tarea, aplicaremos una puerta trasera al ejecutable WinSCP disponible en C:\Tools\WinSCP
.
Para crear un WinSCP.exe con puerta trasera, podemos usar el siguiente comando en nuestra máquina con Windows:
Nota: Metasploit se instala en la máquina Windows para su comodidad, pero puede tardar hasta tres minutos en generar la carga útil (las advertencias producidas se pueden ignorar de forma segura).
El WinSCP-evil.exe resultante ejecutará una carga útil de meterpreter inversa_tcp sin que el usuario se dé cuenta. Antes que nada, recuerde configurar un nc
oyente para recibir el shell inverso. Cuando ejecuta su ejecutable con puerta trasera, debería iniciar un shell inverso mientras continúa ejecutando WinSCP.exe para el usuario:
Las carpetas no harán mucho para ocultar su carga útil de una solución AV . El simple hecho de unir dos ejecutables sin ningún cambio significa que el ejecutable resultante seguirá activando cualquier firma que hizo la carga útil original.
El uso principal de los carpetas es engañar a los usuarios haciéndoles creer que están ejecutando un ejecutable legítimo en lugar de una carga útil maliciosa.
Al crear una carga útil real, es posible que desee utilizar codificadores, criptadores o empaquetadores para ocultar su código shell de los AV basados en firmas y luego vincularlo a un ejecutable conocido para que el usuario no sepa lo que se está ejecutando.
Siéntase libre de intentar cargar su ejecutable vinculado al sitio web de THM Antivirus Check (enlace disponible en su escritorio) sin ningún tipo de embalaje, y debería obtener una detección del servidor, por lo que este método no será de mucha ayuda al intentar obtiene la bandera del servidor por sí solo.
También puede obtener detalles más detallados sobre PE si consulta el sitio web de Documentos del
La tabla anterior nos dice qué valores necesitamos establecer en diferentes registros del procesador para llamar a las funciones sys_write y sys_exit usando syscalls. Para Linux de 64 bits, el registro rax se utiliza para indicar la función en el kernel que deseamos llamar. Configurar rax en 0x1 hace que el kernel ejecute sys_write, y configurar rax en 0x3c hará que el kernel ejecute sys_exit. Cada una de las dos funciones requiere algunos parámetros para funcionar, que se pueden configurar a través de los registros rdi, rsi y rdx. Puede encontrar una referencia completa de las llamadas al sistema Linux de 64 bits disponibles .
Para crear una carga útil preparada, usaremos una versión ligeramente modificada del código preparador proporcionado por . El código completo de nuestro stager se puede obtener aquí, pero también está disponible en su máquina Windows en C:\Tools\CS Files\StagedPayload.cs
:
Para obtener más información sobre el cifrado, le recomendamos que consulte sala Crypto 101.
Usaremos el empaquetador para esta tarea, ya que nuestras cargas útiles están programadas en .NET
. Para su comodidad, puede encontrar un acceso directo a él en su escritorio.