Steal ’em all! Token impersonation

Durante un proceso de intrusión, el robo de Tokens y la impersonación de usuarios nos puede ser de gran ayuda, ahorrándonos mucho tiempo y favoreciendo el permanecer lo más sigilosos posibles utilizando tan solo las capacidades y herramientas que el propio sistema operativo Microsoft Windows nos ofrece.

Antes de comenzar y a modo de recordatorio, un Token dentro del sistema operativo Microsoft Windows es un elemento de seguridad que dota de identificación a procesos e hilos cuando estos quieren realizar acciones sobre objetos securizables del sistema (ficheros, registros, servicios…).

En las siguientes líneas vamos a ver cómo es posible robar el Token de casi cualquier proceso en ejecución en el equipo utilizando tan solo dos técnicas diferentes, siempre y cuando tengamos los privilegios y permisos necesarios para poder llevarlo a cabo.

Como todo en este mundo parte de un trabajo previo, el reto y la inspiración de la investigación partió de este magnífico post de Justin Bui (@slyd0g) de SpecterOps, del que rebatiremos algunas de sus conclusiones, así como el de Chintan Shah de MacAfee  “Technical Analysis of Access TokenTheft and Manipulation”, el cual referencia al primero.

Pero ¿por qué nos puede interesar robar un token de un proceso o hilo concreto del sistema?

La respuesta rápida y corta es para elevar privilegios y realizar acciones que con el token actual no podemos llevar a cabo o para movernos lateralmente hacia otro equipo de la red. Un ejemplo de lo primero lo tenéis en el anterior post “TrustedInstaller, parando Windows Defender”, aunque ahora veremos alguno más.

La fórmula general para poder robar un token pasa por:

  1. Acceder al proceso remoto (Llamada Winapi Openprocess).
  2. Acceder a su Token.
  3. Establecer ese Token en el hilo actual del proceso en el que nos ejecutamos, es decir, impersonar.

Veremos cómo para conseguir nuestro objetivo, robar cualquier token presente en los procesos del equipo, estas tres acciones se llevarán a cabo mediante el uso de dos conjuntos diferentes de llamadas al Winapi.

La clave para poder realizar una acción de impersonación y que el hilo de ejecución actual de nuestro proceso pase a obtener la identidad del proceso/hilo víctima se basa en los siguientes factores:

  • Suficientes permisos de acceso basados en los controles discrecionales, es decir quién somos y a qué grupos pertenecemos.
  • Disponer del privilegio de impersonar (SeImpersonatePrivilege) en el proceso en el que nos ejecutamos.
  • Sorprendentemente, el control de integridad mandatorio, es decir, el nivel de integridad de nuestro proceso frente al del objetivo, no afecta, delegando al control discrecional comentado anteriormente todo el trabajo.

Teniendo en cuenta todo lo anterior vamos a presentar el primer conjunto de llamadas al WinApi (al que llamaremos Técnica1) que nos permitirá robar el token a una parte de los procesos en ejecución del equipo, y que es usado por nuestros compañeros de SpecterOps en el post mencionado arriba.

TÉCNICA 1

  1. Habilitar SeDebugPrivilege
  2. OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)
  3. OpenProcessToken(TOKEN_DUPLICATE | TOKEN_QUERY)
  4. ImpersonateLoggedOnUser()

De este conjunto de llamadas en este orden, destacar el Openprocess con el requerimiento de apertura PROCESS_QUERY_LIMITED_INFORMATION.

Este es un buen descubrimiento por parte de Justin, ya que tan solo esta solicitud de permiso de apertura nos permite acceder a la posibilidad de poder leer el Token, aunque eso no quiere decir todavía que podamos usarlo, lo que dependerá de los controles discrecionales (permisos) del mismo (aspecto que no se tuvo en cuenta en el mencionado post). Esto nos va a ser muy útil cuando nos enfrentemos a procesos de tipo PPL (Protected Processes).

Por lo que respecta a habilitar SeDebugPrivilege, no nos hace falta, y veremos el porqué.

Como prueba de concepto se ha realizado una pequeña aplicación llamada “StealAllTokens” (que os dejo en el Github),  que recorre todos los procesos en ejecución de la máquina y trata de robar el Token usando la Técnica1.

Destacar que el programa lo vamos a ejecutar en un Windows 10 Pro 20H2, con integridad “High” y desde un usuario que pertenece al grupo de Administradores, evitando por el momento lanzarlo como SYSTEM.

 Los resultados son los siguientes:

Vemos que somos capaces de robar el Token a más de la mitad de los procesos en ejecución en el equipo con la Técnica1, ¡incluso sin habilitar SeDebugPrivilege!. Este no lo necesitamos ya que la mayoría de los procesos tienen permitido al grupo Administradores abrir el proceso con PROCESS_QUERY_LIMITED_INFORMATION, como muestra la siguiente imagen (N.d.E.: pinchar en las imágenes para ampliarlas).

Esto es de gran importancia para permanecer lo más sigilosos posibles, ya que muchos EDRs vigilan qué procesos habilitan este privilegio. Interesante…

Pero ¿por qué no podemos hacerlo del resto? ¿Qué nos lo impide?

Veamos el ejemplo del proceso analizado por el post de SpecterOps, el “spoolsv.exe”. En resumidas cuentas, la conclusión a la que llegó nuestro compañero es que no es posible debido a que el grupo BUILTIN\Administrator debe ser el TOKEN_OWNER para poder hacer un OpenProcessToken() y así robar el token.

Desde mi punto de vista, esto no es así, y veamos el porqué.

En los sistemas operativos Microsoft Windows modernos todo es considerado un objeto securizable (ver la maravillosa herramienta de Sysinternals Winobj) y cuando digo todo es: un proceso, un registro, un hilo, una pipe, un puerto ALPC, un escritorio… un TOKEN!

Es decir, todos estos elementos presentan DACLs (Discretionary Access Control List) que controlan quién accede al objeto y para qué. Existe una relación entre la llamada a una determinada WinAPI para obtener un manejador del objeto y el conjunto de DACLs que lo protegen. Veamos dos ejemplos, tomando como elemento de análisis el proceso mencionado “spoolsv.exe”.

OpenProcess()

Cuando llamamos a esta función, el SO valida el tipo de acceso solicitado contra el conjunto de DACLs asociadas al objeto “proceso”, para permitirnos o no el acceso a su manejador.

En este caso, vemos como las listas de control de acceso del proceso Spoolsv.exe nos permiten hacer un Openprocess() con el tipo de acceso PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, pero no para el resto.

De la misma forma, tenemos el segundo ejemplo para la apertura del token.

OpenProcessToken()

Por lo tanto, la DACL aplicada al grupo BUILTIN\Administrator solo nos permite abrir el token para realizar un TOKEN_QUERY y no para poder ser robado y usado por otro proceso, para ello nos faltaría el permiso de TOKEN_DUPLICATE.

Bien, una vez visto el motivo y utilizando la increíble herramienta ProcessHacker, si añadimos este permiso al Token, vemos como con la Tecnica1 somos capaces de robarle el Token al proceso “spoolsv.exe”:

¡Pero un momento! Esto son trampas, si con un proceso como System (ProcessHacker) hacemos estos cambios de permisos entonces, en verdad no se puede robar el token…

Bueno, como estamos en un blog de hacking, veamos el Hack para saltarnos los controles discrecionales aplicados al objeto Token y así poder suplantar la identidad de ese Token.

Para ello, introduciremos la Técnica2, que hace uso de la magia del Native API (ntdll.dll) y de más concretamente de la función NtImpersonateThread().

La belleza de esta función es que nos permite impersonar un hilo remoto, ¡pero ojo!, si este hilo no presenta un Token asociado, en su lugar nos impersona el del token principal o primario del proceso (O_o). ¿Un lujo, no?

Lo único que tenemos que tener en cuenta, es no tener mala suerte y que ese hilo este en ese momento impersonando a un usuario de menor privilegio, cosa que va a ser muy rara que ocurra, aunque deberíamos comprobar en nuestro código esa posibilidad.

Destacar que existen dos condiciones: hay que abrir el proceso con PROCESS_QUERY_INFORMATION y para aquellos procesos que no nos deje hacerlo habilitar SeDebugPrivilege. Por lo tanto, veamos la fórmula de la Tecnica2.

TECNICA 2

  1. Activamos SeDebugPrivilege
  2. OpenProcess(PROCESS_QUERY_INFORMATION)
  3. Listamos los hilos del proceso remoto
  4. Llamamos a NtImpersonateThread()

Vamos a añadir esta segunda técnica a nuestra aplicación “StealAllTokens”, y la vamos a ejecutar contra todo aquel proceso que la Tecnica1 no nos haya funcionado, obteniendo los siguientes resultados:

Vemos que ahora hemos robado con éxito un total de 327 Tokens y nos han quedado tan solo 10 en el tintero. Veamos cuales son y porque.

  • Servicio DoSvc
    • Descripción: Realiza tareas de optimización de distribución de contenido P2P en la red
    • Motivo: Opentoken() para el grupo de administradores no tiene privilegios asignados,  no deja abrirlo con PROCESS_QUERY_INFORMATION
  • Servicio Windefend
    • Descripción: Windows defender antivirus
    • Motivo: Opentoken() para el grupo de administradores no tiene privilegios asignados,  no deja abrirlo con PROCESS_QUERY_INFORMATION
  •  Servicio SgRmBroker
    • Descripción: Supervisa y certifica la integridad de la plataforma Windows. 
    • Motivo: Opentoken() para el grupo de administradores no tiene privilegios asignados,  no deja abrirlo con PROCESS_QUERY_INFORMATION
  • Servicio Wscsvc
    • Descripción: Servicio WSCSVC (Centro de seguridad de Windows) supervisa e informa acerca de la configuración de mantenimiento de seguridad del equipo
    • Motivo: Opentoken() para el grupo de administradores no tiene privilegios asignados,  no deja abrirlo con PROCESS_QUERY_INFORMATION
  •  Servicio SecurirtyHealthService
    • Descripción: El servicio Seguridad de Windows controla la protección de dispositivos unificados y la información sobre el estado.
    • Motivo: Opentoken() para el grupo de administradores no tiene privilegios asignados,  no deja abrirlo con PROCESS_QUERY_INFORMATION
  •  Servicio  MpCopyAccelerator
    • Descripción: Microsoft Malware Protection Copy Accelerator Utility
    • Motivo: Opentoken() para el grupo de administradores no tiene privilegios asignados,  no deja abrirlo con PROCESS_QUERY_INFORMATION
  •  Servicio NisSrv
    • Descripción: Microsoft Network Realtime Inspection Service, parte de defender.
    • Motivo: Opentoken() para el grupo de administradores no tiene privilegios asignados, no deja abrirlo con PROCESS_QUERY_INFORMATION
  •  Servicio sppsvc
    • Descripción: Microsoft Software Protection Platform Service
    • Motivo: Opentoken() para el grupo de administradores no tiene privilegios asignados,  no deja abrirlo con PROCESS_QUERY_INFORMATION
  • Para los dos restantes, con habilitar SeDebugPrivilege en la Tecnica1 conseguiríamos su token.

Como vemos, son procesos svchost.exe (servicios), la mayoría de ellos cuyo objetivo es controlar la integridad y la seguridad del SO.

El motivo por el cual no hemos tenido en éxito en ellos es que el grupo Administrador no tiene privilegios para usar el Token, por lo que bastaría tan solo con robar el token de cualquier proceso de SYSTEM que sí nos lo permite e impersonarlo, para acto seguido ejecutar una nueva llamada a ImpersonateLoggedOnUser() con la nueva identidad.

Con esto tendríamos el 100% de los procesos del sistema cubiertos, ¡incluso de aquellos de tipo Minimal como por ejemplo “Memory Compresion”!

 Conclusiones

  • Somos capaces de robar el token al 97% de los procesos del sistema directamente con un usuario perteneciente al grupo Administradores.
  • El 3% restante se podría hacer pasando por el usuario SYSTEM.
  • No es necesario usar SeDebugPrivilege para ello en más de la mitad de los procesos, por lo que pasaremos todavía más desapercibidos en nuestras acciones. Escalando de Admin a System por ejemplo robándole el Token a Winlogon.exe o al Lsass.exe.

Happy Hacking, GitHub StealAllTokens .

Referencias: