En este post voy a contar el proceso que me llevó a provocar un crash en explorer.exe e inutilizar Windows Defender.
Bueno, pues resulta que estaba leyendo sobre los tipos de acceso que se pueden pedir al intentar abrir un handle a un proceso con la función OpenProcess de la API de Windows. Para quien no lo sepa, en Windows un handle es un identificador necesario para poder acceder a un objeto.
Ahora bien, ¿qué es un objeto?, pues igual que se dice que en Linux todo es un fichero, en Windows, todo es un objeto, por ejemplo, un proceso en Windows se representa con el objeto EPROCESS. Esta sería la definición de un objeto en Windows, más información de lo mencionado en:
- Documentación Oficial de Handles y Objetos en Windows
- Documentación Oficial de tipos de acceso a procesos en Windows
En resumen, no se puede acceder directamente a un objeto sin un handle. Así es como se representa un handle (identificador) en el libro de Windows System Programming parte 1 de Pavel Yosifovich:


Hay muchas formas de ver los handles que tiene abierto un proceso, una sería con Process Explorer o Handle (tools de la suite Sysinternals), con WinDbg, etc.
Volviendo al tema, leyendo sobre los protected processes (Procesos Protegidos), leí esto:


De todos los derechos de acceso que se prohíben desde procesos normales a procesos protegidos, vi que había varios que no están permitidas. Entonces, conociendo todos los tipos de derechos de acceso que existen, por simple omisión deberían estar permitidos los siguientes:
Tanto SYNCHRONIZE como PROCESS_QUERY_LIMITED_INFORMATION no iban a llevarnos a mucho, así que me centré en PROCESS_SUSPEND_RESUME.
Esto de los procesos protegidos esencialmente impide que incluso siendo administrador puedas tocar lo que no debes. Muchos procesos de sistema y casi todos los antivirus y EDRs tienen sus procesos y servicios como protegidos, y Defender no es la excepción.
Estos son todos los procesos protegidos que hay en una máquina virtual con Windows visto desde WinDbg con debugging local de kernel:


Y aquí vemos cómo el servicio de Defender y el proceso son efectivamente objetos protegidos:




Así que me puse a investigar cómo suspender un proceso de manera programática y resultó que se podía hacer con NtSuspendProcess. Al no estar esta función presente en la API de Windows, sabiendo que empieza por Nt sabemos que debe estar en ntdll.dll (esta DLL tiene la Native API y es la que se utiliza en todos los procesos de Windows para comunicarse con el kernel).
Sabiendo esto, procedemos a buscar cómo llamar a esta función, sus argumentos, etc. Usando el enlace del párrafo anterior como referencia, definimos la función de NtSuspendProcessFn, requiriendo como argumento un handle a un proceso:


NtSuspendProcess en NTDLL solo es un wrapper para la función real, que está en el kernel (ntoskrnl.exe), que tiene esta pinta al abrirla en IDA:


Las funciones en Windows normalmente acaban necesitando de DLLs como kernel32.dll o advapi32.dll, las cuales a su vez usan las funciones de la Native API (en ntdll), y finalmente desde esas funciones se hacen las syscalls que permiten ejecutar código en kernel mode:


Nota: este dibujo es una simplificación del proceso, dejo un enlace en las referencias a un post donde se detalla todo el proceso.
Siguiendo con el tema, la función para suspender procesos quedó tal que así:


Esencialmente, lo que se hace es abrir un handle al módulo ntdll.dll que tendrá este proceso en memoria cuando sea ejecutado (todos los procesos lo tienen, como ya he mencionado antes) y con GetProcAddress se buscará la dirección de memoria de la función NtSuspendProcess que está en la EAT (Export Address Table) de ntdll.dll.
También hará falta incluir estas headers:


Y usar esta función para encontrar el PID del proceso cuyo nombre demos como argumento a la función. La función crea un snapshot de todos los procesos usando CreateToolhelp32Snapshot y va iterando sobre la estructura que devuelve esta función y va comparando el campo szExeFile de cada entrada correspondiente a cada proceso con el nombre de proceso que nosotros le hemos pasado. Y finalmente si corresponde el nombre del ejecutable del snapshot con el que nosotros queremos, se devuelve el PID del proceso que nos interesa:


Al intentar hacer una prueba me dio acceso denegado (error 5):


Así que añadí una función para activar el privilegio SeDebugPrivilege en el access token del proceso (eso sí, ese privilegio ya debe estar ahí, es decir, que esto solo funciona si lanzamos nuestra PoC con permisos de administrador).
Para los que no sepan que es un access token, es una estructura que contiene información sobre los privilegios que tiene un proceso o un hilo y que se utiliza para acceder a una serie de recursos. Por ponerlo más sencillo, podemos ver los privilegios que vienen incluidos en el access token de cmd.exe cuando nosotros hacemos whoami /priv (ya que el proceso de whoami.exe se crea con los mismos privilegios que tiene el cmd.exe). El SeDebugPrivilege en concreto, se utiliza por parte tanto de debuggers como incluso de tools ofensivas como es el caso de mimikatz cuando hacemos el famoso privilege::debug.
La función completa para habilitar este privilegio es un poco larga, así que vamos a saltárnosla para ir directamente a la main, que se queda tal que así:


Así que, recapitulando, lo que vamos a hacer es:
- Identificar el PID del proceso de Defender
- Abrir un handle al access token de nuestro proceso
- Habilitar el privilegio SeDebugPrivilege
- Abrir un handle al proceso de Defender con el acceso PROCESS_SUSPEND_RESUME
- Llamar a NtSuspendProcess para ver qué ocurre
Y una vez lanzado, el proceso de Defender se queda en estado suspendido y para más sorpresas, resulta que el entorno gráfico se cuelga (explorer.exe), la única ventana que funciona es la del propio cmd. Quería comprobar que de verdad estuviera suspendido, pero como no tenía herramientas gráficas, usé una tool llamada DTrace. Es una tool open-source que tiene una versión para Windows y tiene esta arquitectura:


Escribiendo scripts en D, se pueden hacer cosas como la que nos interesa ahora, loggear las syscalls hechas por el proceso, y, curiosamente después de un rato sin hacer syscalls (lo que es de suponer en un proceso suspendido), de repente MsMpEng.exe empezó a hacer syscalls, no está claro el por qué, aunque puede ser que esto sea por algún driver que esté haciendo algo con el proceso de Defender que nosotros no vemos.
Este es el script en D que usé para loggear las syscalls de ese proceso:


Así es como se ve antes de suspender el proceso:


Y así es como se ve un rato después (al principio no efectúa ninguna syscall):


Reiniciando la máquina, en Eventviewer revisando los eventos recientes vemos que se ha producido un evento 1002 (cuelgue de aplicación) en explorer.exe:


Inicialmente pensé que habría algún handle abierto desde MsMpEng a explorer, sin embargo, al ver los handles de MsMpEng no vi handles a explorer.exe
Nota: Este PID (5004) es en mi máquina anfitrión, en la máquina virtual es 3068, o bfc en hexadecimal


Únicamente había handles a sí mismo, así que puede que no sea por eso. Le dediqué algo de tiempo a intentar averiguar el motivo del crasheo de explorer, aunque no saqué nada en claro. Así que por el momento lo dejo aquí por si alguien quiere profundizar más.
Y en conclusión, así es como nos cargamos Defender 😀 (eso si, con privs de admin).
Nota: cuando tenga un rato, publicaré la tool para que podáis trastear.
Referencias
- Reversing Windows Internals (Part 1) – Digging Into Handles, Callbacks & ObjectTypes
- Backstab – A tool to kill antimalware protected processes
- Windows 10 System Programming, Part 1
- DTrace en Windows
- Anatomy of the thread suspension mechanism in Windows (Windows Internals)
- A Syscall Journey in the Windows Kernel