
ver https://github.com/cube0x0/CVE-2021-1675
= Print Nightmare Informe de análisis :imagesdir: Figures :toc: :icons: font :figure-caption: Fig. :xrefstyle: short :pdf-theme: basic-theme.yml
El 29 de junio de 2021, se expuso una vulnerabilidad muy grave del servicio de impresión de Windows como un 0day, con una puntuación base de 8.8, publicada en GitHub (ya eliminada). Esta vulnerabilidad es la famosa PrintNightmare: CVE-2021-34527, incluso más peligrosa que EternalBlue.
== Información básica de la vulnerabilidad
La vulnerabilidad 34527 afecta a casi todas las versiones posteriores a Windows 7 y Windows Server 2008. Para obtener más información, consulte <>.
Desde el punto de vista del daño, un atacante puede utilizar la autenticación de un usuario normal para ejecutar código arbitrario de forma remota con privilegios de administrador. En cuanto a la dificultad de explotación, esta vulnerabilidad es muy fácil de explotar, por lo que es extremadamente peligrosa.
En cuanto a las características de la vulnerabilidad, la vulnerabilidad 34527 se basa en CVE-2021-1675. La vulnerabilidad 1675 es una vulnerabilidad de escalada de privilegios local y ejecución remota de código, y tiene muchas similitudes con la vulnerabilidad 34527.
Antes de comprender el principio de funcionamiento de la vulnerabilidad, debemos tener una comprensión general de la arquitectura del subsistema de impresión en segundo plano de Windows, lo que nos ayudará a aclarar la relación entre los módulos involucrados en la vulnerabilidad.
== Flujo de llamadas de CVE-2021-1675
=== Arquitectura del subsistema de impresión en segundo plano de Windows
La arquitectura del spooler se puede representar con <<spooler_arch>>:
[[spooler_arch]] .Arquitectura del Spooler de Impresión image::Print Spooler Architecture.png[]
Específicamente, el spooler de impresión se utiliza para gestionar trabajos de impresión y está compuesto por los siguientes componentes:
winspool.drv:: Archivo de biblioteca de vínculos dinámicos proporcionado al usuario. Este archivo define las API de Win32 relacionadas con el spooler para que las llame el usuario. Todas las API dentro utilizan llamadas a procedimientos remotos para obtener el servicio.
spoolsv.exe:: spoolsv.exe actúa como el servidor en el sistema, siendo el primer programa en procesar las llamadas API. Este diseño permite que el spooler de impresión maneje tanto trabajos de impresión locales como remotos sin distinción.
spoolsv.dll:: Programa de enrutamiento. Envía las solicitudes de impresión recibidas por spoolsv.exe a varios proveedores de impresión y decide qué proveedor procesará finalmente la solicitud. Su función es distinguir si el trabajo de impresión es remoto o local. En máquinas remotas, asigna fijamente la tarea al proveedor de impresión local.
localspl.dll:: Proveedor de impresión local. La tarea principal del proveedor de impresión es satisfacer las necesidades de gestión de trabajos de impresión. La mayoría de las API se implementan dentro de este módulo.
Siguiendo la teoría anterior, por ejemplo, cuando llamamos a la función AddPrinterDriverEx (CVE-2021-1675), pasa por el siguiente flujo:
=== Selección de versión de la función
En primer lugar, esta función es en realidad una macro que selecciona la versión Unicode (W) o Ansi (A) según el entorno de compilación local, como en <>:
[[AddPrinterDriverEx]] .AddPrinterDriverEx image::AddPrinterDriverEx.png[]
Pero tanto la versión de caracteres anchos como la de caracteres estrechos no tienen diferencia en el resultado, porque las cadenas del kernel de Windows usan codificación Unicode, por lo que la llamada de la versión Ansi se convertirá en una llamada de la versión Unicode, como en <>:
[[AnsiToUnicode]] .AnsiToUnicode image::AnsiToUnicode.png[]
Cuando los parámetros de la función Ansi se convierten a la versión Unicode, se llama a una función (<>):
[[AnsiCallUnicode]] .AnsiCallUnicode image::AnsiCallUnicode.png[]
Y esta función es en realidad la versión Unicode de AddPrinterDriverEx (<>):
[[GetUnicodeProcAddress]] .GetUnicodeProcAddress image::GetUnicodeProcAddress.png[]
=== La función API envía una solicitud RPC al servidor spooler
Dentro de la función de la versión Unicode, primero se selecciona el tipo de parámetro de la función según el valor de Level:
image::pDriverInfo.png[]
En esta vulnerabilidad, estableceremos Level en 2, es decir, seleccionamos el tipo del parámetro pDriverInfo como la estructura DRIVER_INFO_2. Luego, Windows procesará los parámetros de la función y, después del procesamiento, continuará procesando la API a través de una llamada a procedimiento remoto:
image::set arguments.png[]
image::NdrClientCall3.png[]
=== Mecanismo MSRPC
El mecanismo de llamada a procedimiento remoto de Microsoft se basa en el estándar DCE. Explicado de manera simple, la llamada a procedimiento remoto consiste en ejecutar procesos en un sistema remoto, los cuales están predefinidos por el programador o el sistema.
La forma específica de RPC es serializar la función que se desea llamar de forma remota, transmitirla a través de la red al sistema remoto, donde se deserializa y se ejecuta. En la arquitectura de Microsoft, TCP/IP y SMB son los protocolos comúnmente elegidos para transportar llamadas RPC.
Para usar MSRPC, primero se debe definir la descripción de la interfaz IDL de la función a llamar, y luego usar la herramienta MIDL para generar el stub de serialización correspondiente para el cliente y el servidor. Para algunas API de Win32, el stub del servidor ya está definido, por lo que solo necesitamos generar y usar el stub del cliente.
MSRPC usa UUID para identificar un tipo de protocolo, como MS-RPRN que describe el protocolo de impresión remota. Todas las funciones relacionadas con la impresión remota son parte de este protocolo. MSPRC usa el UUID 12345678-1234-ABCD-EF00-0123456789AB para identificar este protocolo (<<rprn_uuid>>):
[[rprn_uuid]] .UUID de MS-RPRN image::spoolss uuid.png[]
Luego, sobre esta conexión, se puede usar el número de operación (opnum) para identificar las funciones dentro del protocolo y así llamarlas remotamente. Por ejemplo, AddPrinterDriverEx se identifica a sí misma con el número 89 (<<addPrinterDriverEx_opnum>>):
[[addPrinterDriverEx_opnum]] .AddPrinterDriverEx Opnum image::AddPrinterDriverEx Opnum.png[]
Al usar MSRPC, hay dos puntos a tener en cuenta:
"Como puede ver en su salida, los scripts intentan conectarse al puerto 135 (endpoint mapper) para obtener el puerto TCP/IP donde el endpoint DCOM está escuchando (que es un puerto dinámico)." -- SecureAuthCorp/impacket issue #412
=== spoolsv.exe procesa la solicitud API
[[call_flow]] .Flujo de llamadas de RpcAddPrinterDriverEx image::Function Calls.png[]
De <<call_flow>> se puede ver que spoolsv.exe llama a estas funciones, y desde el análisis interno de las funciones, este módulo no realiza ninguna operación aparte de la inicialización. Finalmente, este módulo llama a la función apuntada por pLocalProvidor, que es la función LocalAddPrinterDriverEx dentro del módulo localspl.dll. localspl, como proveedor de impresión local, es el módulo que realmente implementa la funcionalidad de la API.
=== Lógica de implementación de funciones del proveedor de impresión local
[[LocalAddPrinterDriverEx]] .LocalAddPrinterDriverEx image::LocalAddPrinterDriverEx.png[]
Primero, <> explica que este módulo verifica que el spooler esté funcionando correctamente, y luego salta a la función SplAddPrinterDriverEx.
[[SplAddPrinterDriverEx]] .SplAddPrinterDriverEx image::SplAddPrinterDriverEx.png[]
El interior de la función <> es una posición importante para determinar si la función AddPrinterDriverEx puede ejecutarse con éxito. La primera mitad no es necesario verla porque WPP es una tecnología relacionada con registros; la saltamos por ahora.
En la segunda mitad, Microsoft define una variable v12, una bandera para determinar si la función continúa ejecutándose o sale directamente.
[[bittest]] .bittest dwFileCopyFlags image::bittest in spl.png[]
De <> se puede ver que hay dos condiciones para continuar la ejecución: una es que v12 sea 0, es decir, que el bittest tenga éxito; la otra es que Validate tenga éxito. Validate es una verificación de permisos que no se puede eludir fácilmente. Dentro hay una API llamada OpenProcessToken, que indica que se necesita elevar privilegios en el proceso siguiente, y esto no es posible si no se es administrador.
Por lo tanto, para continuar la ejecución solo se puede eludir la verificación de bittest, y el número evaluado a4 — el cuarto parámetro de la función — es el parámetro explicado en el sitio oficial <>:
|=== |Nombre/valor |Descripción
|APD_STRICT_UPGRADE
0x00000001
|Agregar el controlador de impresión de reemplazo solo si ninguno de los archivos del controlador de reemplazo es más antiguo que los archivos correspondientes del controlador instalado actualmente.
|APD_STRICT_DOWNGRADE
0x00000002
|Agregar el controlador de impresión de reemplazo solo si ninguno de los archivos del controlador instalado actualmente es más antiguo que los archivos correspondientes del controlador de reemplazo.
|APD_COPY_ALL_FILES
0x00000004
|Agregar el controlador de impresión y copiar todos los archivos en el directorio del controlador. Se deben ignorar las marcas de tiempo de los archivos.
|APD_COPY_NEW_FILES
0x00000008
|Agregar el controlador de impresión y copiar en el directorio del controlador los archivos que sean más nuevos que cualquiera de los archivos correspondientes que estén actualmente en uso.
|APD_COPY_FROM_DIRECTORY
0x00000010
|Agregar el controlador de impresión utilizando los nombres de archivo completamente calificados especificados en la estructura _DRIVER_INFO_6. Si se especifica esta bandera, se debe especificar una de las otras banderas de copia en este campo de bits.
|APD_DONT_COPY_FILES_TO_CLUSTER
0x00001000
|Al agregar un controlador de impresión a un clúster de servidores de impresión, no copiar los archivos del controlador al disco compartido del clúster.
|APD_COPY_TO_ALL_SPOOLERS
0x00002000
|Agregar el controlador de impresión a los servidores spooler del clúster.
|APD_INSTALL_WARNED_DRIVER
0x00008000
|Agregar el controlador de impresión, incluso si está en la Lista de Controladores de Impresión Advertidos del servidor.
|APD_RETURN_BLOCKING_STATUS_CODE
0x00010000
|Especifica el código de error específico de la implementación que se devolverá si la instalación del controlador de impresión es bloqueada por la política del servidor.
|===
bittest 16 verifica si el bit 16 de la variable es 1, que corresponde al valor de parámetro 0x8000 (APD_INSTALL_WARNED_DRIVER). Según la definición, este parámetro significa agregar el controlador de impresión al servidor sin verificación.
Se dice que antes de que se corrigiera 1675, este parámetro aún no aparecía en la documentación oficial, lo que no es difícil de ver dónde radica la vulnerabilidad.
=== Método de explotación de la vulnerabilidad
Cuando el método de agregar un controlador de impresión se ejecuta realmente, si se selecciona la estructura DRIVER_INFO_2, ocurren las siguientes cosas:
. Abrir respectivamente DriverFile, ConfigFile y DataFile, y confirmar si estos tres archivos existen. Solo DataFile permite ser una ruta UNC.
ifdef::backend-pdf[] Consulte los resultados de la ejecución del programa malicioso en https://github.com/hahaleyile/my-CVE-2021-1675[mi repositorio]; la animación de demostración es el archivo gif en el directorio Figures. endif::[]
== Parche de Microsoft para la vulnerabilidad 1675
El 8 de junio de 2021, Microsoft parcheó la vulnerabilidad CVE-2021-1675. Los cambios específicos son los siguientes:
[[path_1675]] .Parche para CVE-2021-1675 image::IsElevated.png[]
[[YIsElevationRequired]] .YIsElevationRequired image::YIsElevationRequired.png[]
[[YIsElevated]] .YIsElevated image::YIsElevated.png[]
[[unset_1675]] .Deshacer APD_INSTALL_WARNED_DRIVER image::JudgeIsElevated.png[]
Microsoft agregó una verificación de elevación de privilegios del usuario en la función RpcAddPrinterDriverEx, y el usuario puede eliminar esta restricción en el registro (<<path_1675>>). El usuario solo necesita crear una clave llamada NoWaringNoElevationOnInstall en la ubicación especificada del registro (<>), o que la cuenta RPC pueda obtener el Token de proceso TOKEN_QUERY (<>), para eludir este parche. Si el parche surte efecto, el bit 16 del parámetro dwFileCopyFlags se pondrá a 0 mediante una operación AND, lo que significa que el valor del parámetro APD_INSTALL_WARNED_DRIVER se volverá inválido (<<unset_1675>>).
== Bypass del parche de 1675
Aunque Microsoft parcheó la función RpcAddPrinterDriverEx, aún podemos eludirlo mediante una llamada remota a RpcAsyncAddPrinterDriver. Como se muestra en <<async_send>>, esta función del cliente es una llamada remota directa al servidor:
[[async_send]] .Enviar RpcAsyncAddPrinterDriver image::RpcAsyncAddPrinterDriver Send.png[]
El servidor primero asigna espacio para el hilo y luego continúa la llamada, como se muestra en <<async_receive>>:
[[async_receive]] .Recibir RpcAsyncAddPrinterDriver image::RpcAsyncAddPrinterDriver Receive.png[]
En las funciones llamadas a continuación, todos los parámetros se colocan en la pila y se ejecuta YAddPrinterDriverEx como un hilo (<<async_yadd>>):
[[async_yadd]] .Inicio del hilo YAddPrinterDriverEx image::thread start YAddPrinterDriverEx.png[]
De esta manera, se elude con éxito el parche de Microsoft para RpcAddPrinterDriverEx.
Es decir, mediante la llamada remota a la función RpcAsyncAddPrinterDriver, se puede continuar ejecutando código arbitrario como administrador.
Además, según la declaración de <>, el parche todavía tiene problemas con la verificación del Token, y además será ineficaz en máquinas con UAC completamente deshabilitado. Sin embargo, personalmente no tengo un conocimiento profundo de este mecanismo, por lo que no haré más comentarios.
== Corrección de la vulnerabilidad 34527 por parte de Microsoft
. Si los tres archivos existen, se copian al directorio C:\Windows\System32\spool\drivers\x64\3\New, como se muestra en <<cp_conf_file>> y <<cp_data_file>>: + [[cp_conf_file]] .Copiar archivo de configuración image::copy config file.png[] + [[cp_data_file]] .Copiar archivo de datos image::copy data file.png[]
. La razón para copiarlos a este directorio es ejecutar los archivos correspondientes. 3 indica que este controlador de impresión es de tipo v3. Primero se copian los nuevos archivos al directorio New para evitar sobrescribir los archivos en el directorio 3. Si hay archivos con el mismo nombre en 3, se mueven al directorio Old como copia de seguridad, y luego se copian los archivos de New a 3 para sobrescribir. Esto se puede observar al ejecutar por segunda vez el método RpcAddPrinterDriverEx: + .Segunda llamada RPC image::second time call.png[] + .Archivos de respaldo al directorio Old image::backup file.png[] + .Copiar nuevos archivos al destino image::copy file.png[] + Según este mecanismo, podemos guardar archivos de rutas remotas como archivos de rutas locales, porque los parámetros de archivo de controlador y archivo de configuración en la función solo pueden ser rutas locales, y solo el parámetro de archivo de datos puede ser una ruta remota.
. Según <>, pConfigFile es la biblioteca de vínculos dinámicos de configuración del controlador del dispositivo, por lo que debe cargarse una vez para la inicialización. Esto también se confirma en la operación real. + .Cargar pConfigFile image::Load Image.png[] + De esta manera, solo es necesario escribir una DLL maliciosa, colocar el código malicioso en el punto de entrada de la DLL para ejecutarlo, y se ejecutará código arbitrario con privilegios de administrador. + .spoolsv.exe está bajo privilegio de administrador image::spoolsv user.png[]
. La función CreateInternalDriverFileArray() determina si verificar el directorio de controladores spool según la bandera de operación de archivos. Si la bandera a5 se marca como False, la función de carga del controlador solo verifica si el directorio del usuario contiene los archivos de controlador a copiar. De lo contrario, la función intenta buscar el controlador de destino en el directorio de controladores spool. Esto requiere que dwFileCopyFlags establezca el parámetro APD_COPY_FROM_DIRECTORY al mismo tiempo. + image::APD.png[] + image::APD_1.png[]
== Método de uso del programa de explotación
Este programa se basa en Docker para su construcción, lo que puede resolver eficazmente los problemas de dependencia del entorno.
Primero, el usuario descarga el archivo compose en su propio directorio, y luego crea una carpeta llamada share en ese directorio como directorio de montaje. El usuario puede colocar el programa malicioso en la carpeta share, que se compartirá como ruta SMB de Samba.
Luego, el usuario ingresa el comando en la terminal
para iniciar el clúster (un contenedor). El usuario puede ingresar al contenedor para operar; el entorno dentro del contenedor ya está configurado.
O también puede ingresar el comando directamente en la terminal
para ejecutar el comando.
== Resultados de la ejecución del programa de explotación
¡Gracias a <> por el código de código abierto para mi referencia!
ifndef::backend-pdf[] .Resultado de la ejecución del programa image::exploit.gif[] endif::[]
El 6 de julio de 2021, Microsoft solucionó temporalmente este problema de impresión en segundo plano a través de una nueva ronda de parches.
Según la declaración oficial, este parche hará que la instalación de controladores de impresión en el servidor de impresión solo pueda ser realizada por administradores. Además, Microsoft agregó una directiva de grupo y dos claves de registro para que los usuarios personalicen esta directiva.
A través de la descompilación IDA de <<restrict_async>>, podemos ver que Microsoft agregó verificaciones de Token y entradas de registro en la función Async.
[[restrict_async]] .Restricción en la función asíncrona image::restrict in async.png[]
A través de <<restrict_rpcadd>> podemos ver que Microsoft agregó verificaciones del grupo de usuarios y sus claves de registro en la función RpcAddPrinterDriverEx.
[[restrict_rpcadd]] .Restricción en la función RpcAddPrinterDriverEx image::restrict in rpcadd.png[]
Por lo tanto, probablemente la corrección de la vulnerabilidad 1675 todavía tenga problemas con la verificación del Token.
== Plan del proyecto
. Agregar soporte para la llamada remota a RpcAsyncAddPrinterDriver, para eludir el parche del 8 de junio de Microsoft y lograr el exploit CVE-2021-34527.
. Comprender el mecanismo de UAC y mejorar este análisis de vulnerabilidad.
[bibliography] == Referencias
[[[a,Sitio oficial]]] Vulnerabilidad de ejecución remota de código en el spooler de impresión de Windows https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2021-34527
[[[b,dwFileCopyFlags]]] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/b96cc497-59e5-4510-ab04-5484993b259b
[[[c,Análisis de la vulnerabilidad Windows PrintNightmare (CVE-2021-34527) y su parche]]] https://www.freebuf.com/vuls/279876.html
[[[d,gentilkiwi/mimikatz]]] https://github.com/gentilkiwi/mimikatz
[[[e,Documentación oficial]]] Estructura DRIVER_INFO_2 https://docs.microsoft.com/en-us/windows/win32/printdocs/driver-info-2
[[[f,cube0x0]]] https://github.com/cube0x0
[[[g,James Forshaw]]] https://twitter.com/tiraniddo/status/1410726790994169857
[[[h,Declaración oficial]]] KB5005010: Restricción de instalación de nuevos controladores de impresión después de aplicar las actualizaciones del 6 de julio de 2021 https://support.microsoft.com/en-us/topic/kb5005010-restricting-installation-of-new-printer-drivers-after-applying-the-july-6-2021-updates-31b91c02-05bc-4ada-a7ea-183b129578a7