¡Muy buenas a todos! Hoy venimos con una última entrega (de momento) sobre el mundo del AMSI. En esta ocasión, vamos a descubrir cómo podríamos evadir el AMSI haciendo uso de una técnica llamada DLL hijacking.
Cuando un binario está compilado con enlaces dinámicos, el sistema operativo buscará la DLL específica para cubrir una funcionalidad en tiempo de ejecución. En muchas ocasiones, el enlace a la DLL no se define con el path completo, sino únicamente con el nombre.
Esto da pie a que Windows, mediante su modo de búsqueda, interprete cual es la DLL que el binario pretendía cargar en ese enlace. El orden de búsqueda es el siguiente:
- El directorio donde se encuentra la aplicación que se está ejecutando
- C:\Windows\System32
- C:\Windows\System
- C:\Windows
- El directorio donde se encuentra
- Todo lo que haya en la variable de entorno %PATH%
Por lo tanto, por culpa de este funcionamiento de Windows, cualquier persona con acceso de escritura en algún lugar del disco podría evadir AMSI. ¿Por qué? Porque si podemos copiar la PowerShell para tenerla en un lugar en el que tengamos permisos de escritura, y creamos un archivo llamado amsi.dll, en el momento que se abra esa PowerShell, cargará esa DLL por tema de preferencia.
En el siguiente ejemplo podemos ver cómo, teniendo la PowerShell en un sitio como el escritorio, podemos hacer que cargue amsi.dll de ese mismo sitio.
El único requisito para utilizar esta técnica es tener una DLL (creada en C/C++) que tenga las funciones definidas de la misma manera que la DLL real. Es decir, no es necesario que la funcionalidad sea la misma, pero de cara a la PowerShell, la llamada a la función se tiene que dar de la misma forma: con el mismo nombre y los mismos argumentos.
PowerShell de 64 bits se puede encontrar en el siguiente directorio:
- C:\Windows\System32\WindowsPowerShell\v1.0
Mientras que PowerShell de 32 bits se puede encontrar en el siguiente directorio:
- C:\Windows\SysWOW64\WindowsPowerShell\v1.0
Una forma sencilla de realizar esta tarea es mediante el parcheo de la DLL original. En otras palabras, podríamos tomar amsi.dll y cambiar las instrucciones pertinentes para que se pierda la funcionalidad original, y no devuelva nunca que hay malware.
Anteriormente en el post Funciones AMSI explicamos para qué servía cada función de AMSI.
Sabemos que AmsiScanBuffer devuelve un HRESULT, que es un código que representa si la función se ha ejecutado de forma exitosa o no. Si cambiamos AmsiScanBuffer para que siempre devuelva 0x80070057, AMSI dejará de funcionar y lo habremos evadido.
La siguiente imagen es una parte de la función AmsiScanBuffer.
El primer if hace una serie de validaciones básicas y si las cumple, significa que los argumentos se han proporcionado de forma inválida y no se podrá realizar el escaneo, por lo que setea uVar2 a 0x80070057.
Pero ¿Qué pasaría si dentro del else también se setea uVar2 a 0x80070057? La respuesta es que AMSI dejaría de funcionar correctamente, porque, a pesar de que AMSI se comunique con Windows Defender para enviar el buffer, si la función devuelve INVALIDARG, el comando se ejecutará de forma normal.
En ensamblador, para devolver un valor en un return, debemos cargar el contenido en un registro de retorno, por lo tanto, para devolver INVALIDARG, podríamos añadir lo siguiente:
- MOV EAX,0x80070057
Si utilizas ghidra, seleccionando en la parte del decompile, la parte que nos interesa, podemos ver dónde se encuentra su ensamblador correspondiente. Para parchear esta función y que siempre devuelva INVALIDARG, podemos cambiar el último call.
Por lo tanto, la función quedaría como la siguiente:
Por último, quedaría exportar la DLL como PE y ya estaría.