El modelo de objetos componentes (COM) ha sido una piedra angular del desarrollo de Microsoft Windows desde principios de la década de 1990 y sigue siendo muy frecuente en los sistemas operativos y aplicaciones modernos de Windows. La dependencia de los componentes COM y el amplio desarrollo de características a lo largo de los años ha creado una generosa superficie de ataque. En febrero de 2025, James Forshaw (@tiraniddo) de Google Project Zero publicó una entrada en su blog en la que detallaba un nuevo método para abusar de la tecnología de comunicación remota Distributed COM (DCOM), mediante el cual los objetos COM atrapados pueden utilizarse para ejecutar código .NET gestionado en el contexto de un proceso DCOM del lado del servidor. Forshaw destaca varios casos de uso para la escalada de privilegios y la elusión del proceso protegido ligero (PPL).
Según la investigación de Forshaw, Mohamed Fakroud (@T3nb3w) publicó una implementación de la técnica para eludir las protecciones PPL a principios de marzo de 2025. Jimmy Bayne (@bohops) y yo realizamos una investigación similar en febrero de 2025, que nos ha llevado a desarrollar una técnica de prueba de concepto de movimiento lateral sin archivos mediante el abuso de objetos COM atrapados.
COM es un estándar de interfaz binaria y un nivel de servicio de middleware que permite la exposición de componentes modulares distintos para interactuar entre sí y con las aplicaciones, independientemente del lenguaje de programación subyacente. Por ejemplo, los objetos COM desarrollados en C++ pueden interactuar fácilmente con una aplicación .NET, lo que permite a los desarrolladores integrar diversos módulos de software de manera efectiva. DCOM es una tecnología de comunicación remota que permite a los clientes COM comunicarse con servidores COM a través de la comunicación entre procesos (IPC) o llamadas a procedimientos remotos (RPC). Muchos servicios de Windows implementan componentes DCOM a los que se puede acceder local o remotamente.
Las clases COM suelen estar registradas y contenidas en el Registro de Windows. Un programa cliente interactúa con un servidor COM creando una instancia de la clase COM, conocida como objeto COM. Este objeto proporciona un puntero a una interfaz estandarizada. El cliente utiliza este puntero para acceder a los métodos y propiedades del objeto, lo que facilita la comunicación y la funcionalidad entre el cliente y el servidor.
Los objetos COM suelen ser objetivos de investigación para evaluar la exposición a vulnerabilidades y descubrir características susceptibles de abuso. Un objeto COM atrapado es una clase de error en la que un cliente COM instancia una clase COM en un servidor DCOM fuera de proceso, donde el cliente controla el objeto COM mediante un puntero de objeto marshaleado por referencia. En función de las condiciones, este vector de control puede presentar fallos lógicos relacionados con la seguridad.
El blog de Forshaw describe un caso de uso de bypass PPL en el que la interfaz IDispatch, tal como se expone en la clase COM de WaaSRemediation, se manipula para abusar de objetos COM atrapados y ejecutar código .NET. WaaSRemediation se implementa en el servicio WaaSMedicSvc, que se ejecuta como un proceso de svchost.exe protegido en el contexto de NT: AUTHOR\SYSTEM. El excelente recorrido de Forshaw fue la base de nuestra investigación aplicada y el desarrollo de una técnica de prueba de concepto de movimiento lateral sin archivos.
Nuestro viaje de investigación comenzó explorando la clase COM WaaSRemediation que soporta la interfaz IDispatch. Esta interfaz permite a los clientes realizar la vinculación tardía. Normalmente, los clientes COM tienen definidas en tiempo de compilación las definiciones de interfaz y de tipo de los objetos que utilizan. En cambio, el enlace tardío permite al cliente descubrir y llamar a los métodos del objeto en tiempo de ejecución. IDispatch incluye el método GetTypeInfo, que devuelve una interfaz ITypeInfo. ITypeInfo tiene métodos que pueden utilizarse para descubrir información sobre el tipo del objeto que lo implementa.
Si una clase COM utiliza una biblioteca de tipos, puede ser consultada por el cliente a través de ITypeLib (obtenida de ITypeInfo-> GetContainingTypeLib) para recuperar la información de tipos. Además, las bibliotecas de tipos también pueden hacer referencia a otras bibliotecas de tipos para obtener información adicional sobre los tipos.
Según la entrada de blog de Forshaw, WaaSRemediation hace referencia a la biblioteca de tipos WaaSRemediationLib, que a su vez hace referencia a stdole (automatización OLE). WaaSRemediationLib utiliza dos clases COM de esa biblioteca, StdFont y StdPicture. Al realizar un secuestro COM en el objeto StdFont modificando su clave de registro TreatAs , la clase apuntará a otra clase COM de nuestra elección, como System.Object en .NET Framework. Cabe destacar que Forshaw señala que StdPicture no es viable, ya que este objeto realiza una comprobación de instanciación fuera de proceso, por lo que nos mantuvimos centrados en el uso de StdFont.
Los objetos .NET nos interesan por el método GetTypede System.Object . A través de GetType, podemos realizar una reflexión de.NET para, finalmente, acceder a Assembly.Load. Aunque se eligió System.Object, este tipo resulta ser la raíz de la jerarquía de tipos en .NET. Por lo tanto, se podría utilizar cualquier objeto .NET COM.
Con la etapa inicial establecida, había otros dos valores DWORD en la clave HKLM\Software\Microsoft\.NetFramework necesarios para hacer realidad nuestro caso de uso percibido:
Al confirmar que se podía cargar la última versión del CLR y.NET en nuestras pruebas iniciales, supimos que íbamos por buen camino.
Centrándonos en los aspectos programáticos remotos, primero usamos Remote Registry para manipular los valores de la clave de registro de .NetFramework y secuestrar el objeto StdFont en la máquina de destino. A continuación, cambiamos CoCreateInstance por CoCreateInstanceEx para instanciar el objeto COM WaaSRemediation en el objetivo remoto y obtener un puntero a la interfaz IDispatch.
Con un puntero a IDispatch, llamamos al método miembro GetTypeInfo para obtener un puntero a la interfaz ITypeInfo, que está atrapada en el servidor. Los métodos miembro llamados a partir de entonces se producen en el lado del servidor. Después de identificar la referencia de interés de la biblioteca de tipos contenida (stdole) y derivar la referencia de objeto de clase posterior de interés (StdFont), finalmente utilizamos el método "remotable" CreateInstance en la interfaz ITypeInfo para redirigir el flujo de enlace de objetos StdFont (a través de la manipulación previa de TreatAs) para crear una instancia de System.Object.
Dado que AllowDCOMReflection está correctamente configurado, podemos realizar una reflexión .NET sobre DCOM para acceder a Assembly.Load y cargar un ensamblado .NET en el servidor COM. Dado que utilizamos Assembly.Load sobre DCOM, esta técnica de movimiento lateral no tiene archivos, ya que la transferencia de bytes del ensamblado la gestiona la magia de comunicación remota de DCOM. Para obtener una explicación detallada de este flujo técnico desde la instanciación de objetos hasta la reflexión, consulte el siguiente diagrama:
Nuestro primer y principal problema era llamar a Assembly.Load_3, a través de IDispatch->Invoke. Invoke pasa una matriz de objetos de argumentos a la función de destino, y Load_3 es la sobrecarga de Assembly.Load que toma una única matriz de bytes. Por tanto, necesitábamos envolver el SAFEARRAY de bytes dentro de otro SAFEARRAY de VARIANTs – inicialmente, intentábamos pasar un solo SAFEARRAY de bytes.
Otro problema era encontrar la sobrecarga de Assembly.Load adecuada. Las funciones auxiliares se tomaron del código CVE-2014-0257 de Forshaw, que incluía la función GetStaticMethod. Esta función utilizaba la reflexión de .NET sobre DCOM para encontrar un método estático dado un puntero de tipo, el nombre del método y su conteo de parámetros. Assembly.Load tiene dos sobrecargas estáticas que toman un único argumento, por lo que acabamos utilizando una solución poco elegante. Nos dimos cuenta de que la tercera instancia de Load con un solo argumento era nuestra elección correcta.
Uno de los mayores inconvenientes que observamos con esta técnica fue que la baliza generada tendría su vida útil limitada al cliente COM; en este caso, la vida útil de la aplicación de nuestro binario de armamento "ForsHops.exe" (con un nombre elegante, por supuesto). Por lo tanto, si ForsHops.exe limpiaba sus referencias COM o se cerraba, también lo haría la baliza que se ejecutaba en el svchost.exe del equipo remoto. Probamos diferentes soluciones, como bloquear indefinidamente el subproceso principal de nuestro ensamblado .NET, ejecutar código shell en otro subproceso y hacer que ForsHops.exe dejara bloqueado el subproceso del exploit, pero ninguna resultó elegante.
En su estado actual, ForsHops.exe se ejecuta hasta que se cierra la baliza, momento en el que elimina sus operaciones de registro. Hay oportunidades de mejora, pero lo dejaremos como ejercicio para el lector.
La guía de detección propuesta por Samir Bousseaden (@SBousseaden) tras la publicación de su implementación por Mohamed Fakroud también se aplica a esta técnica de movimiento lateral:
Además, recomendamos implementar los siguientes controles adicionales:
Además, aproveche la siguiente regla YARA de prueba de concepto para detectar el ejecutable estándar ForsHops.exe:
regla Detect_Standard_ForsHops_PE_By_Hash
Nuestra implementación amplía ligeramente el abuso de COM explicado en el blog de Forshaw al aprovechar los objetos COM atrapados para el movimiento lateral en lugar de la ejecución local para eludir la PPL. Por lo tanto, sigue siendo susceptible a las mismas detecciones que las implementaciones que realizan ejecución local.
Puede encontrar el código de movimiento lateral de prueba de concepto de ForsHops.exe aquí.
Un agradecimiento especial a Dwight Hohnstein (@djhohnstein) y Sanjiv Kawa (@sanjivkawa) por dar su opinión sobre esta investigación y por proporcionar una reseña del contenido de las entradas del blog.