Userland-Hooking using suspended processes
¡Hola!
Hoy os traemos un artículo en el que analizaremos una de las técnicas más comunes utilizadas por los sistemas EDR y antivirus, las cuales se utilizan para monitorizar las actividades de aquellos procesos ejecutados en sistemas operativos Windows.
Actualmente los EDR cuentan con diferentes posibilidades para realizar esta monitorización. Por un lado, en algunos casos se utilizan funciones expuestas por parte del kernel de Windows. Por otro lado, se utilizan las fuentes de Threat Intelligence que proporciona el propio sistema operativo, como EtwTI, estas últimas pueden ser listadas mediante el comando “logman query providers”, tal y como se muestra a continuación.
Finalmente, y casi con total probabilidad, es la técnica más conocida y documentada hasta el momento, que consiste en hacer uso de UserLand Hooking para realizar monitorización sobre diferentes funciones internas de Windows.
Lo más común es encontrar esta monitorización implantada directamente sobre las llamadas al sistema existente en NTDLL.
Esto es debido a que NTDLL es la DLL expuesta a la capa de usuario encargada de realizar el “Exchange” entre el modo de usuario y el kernel del propio equipo, tal y como se puede observar en este gráfico.
Sabemos que todos los procesos ejecutados en los sistemas operativos Windows han de cargar en memoria una versión de NTDLL para poder llamar de forma efectiva a las capacidades expuestas por la API de Windows.
Analizando el procedimiento de creación de procesos documentado en el libro Windows Internals, se observa que una vez un proceso es iniciado mediante llamadas a NtCreateProcess, NtCreateUserProcess, éste se encargará de realizar los siguientes pasos:
- Validación de parámetros, operaciones de Windows Subsystem, etc.
- Abre el fichero .exe, la imagen del fichero PE.
- Inicializa las estructuras del proceso, tanto en kernel como en user mode (_EPROCESS, _KPROCESS, _PEB, etc)
- Crea el hilo inicial.
- Realiza operaciones posteriores a la creación del proceso, así como operaciones relacionadas con la inicialización del subsistema de Windows.
- Inicia la ejecución del primer hilo, salvo que se haya creado el proceso en estado suspendido.
- En el contexto de nuevos procesos e hilos, completa la inicialización del espacio de memoria del proceso (por ejemplo, carga las DLL que necesite el proceso, resolución de IAT...) e inicia la ejecución del Entry-Point.
Estos pasos pueden ser visualizados de forma gráfica:
Observando detenidamente estos pasos, se observa que inicialmente, antes de llamarse al EntryPoint del programa, se llamará a la función RtlUserThreadStart registrada en NTDLL, la cual será la encargada de realizar los pasos del punto 7.
Esto es debido a que el primer hilo creado durante la inicialización de un proceso se corresponde con el procesamiento de funciones presentes en la DLL NTDLL.
Esto puede ser visto de forma gráfica mediante Windbg, utilizando la siguiente configuración de inicio:
windbg -xe ld:ntdll.dll explorer.exe
Y analizando las DLLs que tiene cargadas en este punto el proceso, se puede evidenciar que NTDLL se encuentra cargada y que la función en la que se encuentra detenida la ejecución es RtlUserThreadStart.
También puede comprobarse cómo en este punto la estructura PEB (Process Environment Block) no ha sido completamente inicializada, siendo inviable localizar referencias de las DLLs cargadas por el proceso mediante el análisis de la estructura (PPEB_LDR_DATA), que en procesos completamente inicializados contiene una referencia a las DLLs cargadas.
Mediante este análisis, se llega a la conclusión de que un proceso en estado suspendido contendrá una copia en memoria de la NTDLL del sistema, ésta no habrá sido modificada por los sistemas de los EDRs, que implementan como mecanismo de seguridad diferentes hooks en las llamadas a la API de Windows expuestas mediante esta DLL. Esto puede ser confirmado mediante el análisis de un sistema con un EDR instalado, el cual implementa monitorización en diferentes funciones, como NtReadVirtualMemory o NtQueueUserAPC.
Para observar esto, comparemos la diferencia entre la estructura de una llamada al sistema (Syscall) entre un equipo que cuenta con el antivirus de Windows Defender, que no implementa técnicas de userland-hooking.
Comparándolo posteriormente con una llamada al sistema monitorizada por un EDR.
Como se puede observar, en la llamada monitorizada por el EDR, existe un salto incondicional (jmp) que, en caso de seguirlo, se entra en la DLL inyectada en el proceso por el EDR, que aplicará operaciones con el fin de detectar si esta llamada se realiza por un proceso legítimo, o si en cambio tiene un fin malicioso.
En cambio, analizando las llamadas existentes en la DLL de un proceso en estado suspendido, observamos que no se han llegado a inyectar los saltos (jmp) implementados por el EDR.
Para esto utilizaremos x64dbg y añadiremos un breakpoint en “System DLL Load”.
Se puede comprobar que el proceso se detiene en la función RtlUserThreadStart, la cual es la misma que vimos previamente mediante WinDBG.
Y que en este punto tan solo se encuentra cargada la DLL de NTDLL y la propia imagen del ejecutable.
De esta manera, se abre la puerta a copiar la memoria de dicha NTDLL, que no se encuentra monitorizada y copiarla a un proceso malicioso, el cual haya sido inyectada con la DLL del EDR y que esté siendo monitorizado mediante la técnica de userland-hooking, sin necesidad de reemplazar la memoria específica de ciertas syscall, sino directamente reemplazando el total de la memoria, haciendo esta técnica cross-compatible con todas las versiones de Windows.
Para hacer esto se siguen los siguientes pasos:
- Se crea un proceso suspendido (diferenciando entre 32 y 64 bits)
- Se normaliza la estructura PEB de dicho proceso
- Se localiza la ImageBaseAddress de dicho proceso.
- Se llama a VirtualQueryEx, localizando aquellas regiones mapeadas con estado MEM_COMMIT y MEM_IMAGE
- Se lee dicha región y se buscan los magic bytes de un fichero PE32.
- Si es la primera coincidencia con dichos magic bytes, se actualiza la posición del puntero que se está leyendo, saltando al final de dicha región, dado que se trata de la imagen del propio proceso suspendido.
- Se continúa hasta encontrar la segunda coincidencia con dichos magic bytes.
- Una vez localizada una nueva coincidencia, ésta se tratará de la propia NTDLL.
- Se lee la memoria de dicha sección, enumerando las diferentes secciones de dicha DLL, hasta localizar la sección text.
- Se obtiene el tamaño de la sección text, y se copia el contenido de la misma en la sección text de la NTDLL del proceso “padre”, cuya NTDLL quedará limpia.
De esta forma se logra limpiar el proceso y poder usarlo sin ser detectado por el EDR.
A continuación, se observa cómo se limpia mediante esta técnica un proceso previamente parcheado por uno de los EDRs testeados en el equipo de Red Team de Innotec Security.
En la imagen previa se observa cómo la función ZwQueueUserApcThread se encuentra monitorizada.
Seguidamente, contemplamos la ejecución de la función unhook, encargada de limpiar los hooks mediante la técnica descrita.
Se ha añadido un breakpoint en la función memcpy encargada de reemplazar la memoria de la NTDLL monitorizada, con la memoria de la NTDLL del proceso suspendido.
También se ha añadido un breakpoint de hardware en la dirección en la que se encuentra ZwQueueUserApcThread, por lo que una vez modificada la ejecución, se detendrá y se podrá observar cómo se reemplaza la memoria de la misma.
Una vez ejecutado, se observa que la ejecución se ha detenido al escribir en la dirección de ZwQueueUserApcThread y cómo este hook ha sido reemplazado por la llamada real.
Si se compara esta llamada al sistema con una realizada por un programa en un entorno sin hooks implementados, se puede observar cómo coinciden absolutamente, evadiendo de esta manera los controles implantados por el sistema EDR.
A modo de ejemplo, veamos una inyección en memoria sin realizar unhooking.
Y, por otro lado, abusando de esta técnica de unhooking.
PD: Después de implementar esta técnica, se localizó que en el blog de Sektor7 ya se hablaba previamente de ella, por ello se hace referencia a su blog en este artículo.
Esperamos que os haya resultado útil y, sobre todo, lo hayáis disfrutado. ¡Nos vemos en el siguiente!
Referencias:
- https://blog.sektor7.net/#!res/2021/perunsfart.md
- https://www.ired.team/offensive-security/defense-evasion/how-to-unhook-a-dll-using-c
- https://twitter.com/aionescu/status/1066014417903439872
- https://s3cur3th1ssh1t.github.io/A-tale-of-EDR-bypass-methods/
- https://undev.ninja/introduction-to-threat-intelligence-etw/