O Component Object Model (COM) tem sido um pilar do desenvolvimento do Microsoft Windows desde o início dos anos 1990 e continua muito presente nos sistemas operacionais e aplicações modernos da Microsoft. A dependência de componentes COM e o extenso desenvolvimento de funcionalidades ao longo dos anos criou uma superfície de ataque generosa. Em fevereiro de 2025, James Forshaw (@tiraniddo) do Google Project Zero lançou um post de blog detalhando uma nova abordagem para abusar da tecnologia remota Distributed COM (DCOM), onde objetos COM presos podem ser usados para executar código gerenciado .NET no contexto de um processo DCOM de servidor. Forshaw destaca vários casos de uso para escalonamento de privilégios e contornamento do Protected Process Light (PPL).
Com base na pesquisa de Forshaw, Mohamed Fakroud (@T3nb3w) publicou uma implementação da técnica para contornar as proteções de PPL no início de março de 2025. Jimmy Bayne (@boops) e eu realizamos uma pesquisa semelhante em fevereiro de 2025, o que nos levou a desenvolver uma técnica de movimento lateral sem arquivos de prova de conceito, abusando de objetos COM presos.
O COM é um padrão de interface binária e uma camada de serviço de middleware que permite a exposição de componentes modulares distintos para interagir entre si e com aplicações, independentemente da linguagem de programação subjacente. Por exemplo, objetos COM desenvolvidos em C++ podem interagir facilmente com uma aplicação .NET, permitindo que os desenvolvedores integrem diversos módulos de software de forma eficaz. DCOM é uma tecnologia remota que permite que clientes COM se comuniquem com servidores COM via comunicação entre processos (IPC) ou chamadas de procedimento remoto (RPC). Muitos serviços do Windows implementam componentes DCOM que podem ser acessados local ou remotamente.
As classes COM normalmente são registradas e contidas no Registro do Windows. Um programa cliente interage com um servidor COM criando uma instância da classe COM, conhecida como objeto COM. Esse objeto fornece um ponteiro para uma interface padronizada. O cliente utiliza esse ponteiro para acessar os métodos e propriedades do objeto, facilitando a comunicação e a funcionalidade entre o cliente e o servidor.
Objetos COM são frequentemente alvos de pesquisa para avaliar a exposição a vulnerabilidades e descobrir funcionalidades abusáveis. Um objeto COM preso é uma classe de bug na qual um cliente COM instancia uma classe COM em um servidor DCOM fora de processo, onde o cliente controla o objeto COM por meio de um ponteiro de objeto empacotado por referência. Dependendo da condição, esse vetor de controle pode apresentar falhas lógicas relacionadas à segurança.
O blog de Forshaw descreve um caso de uso de desvio de PPL em que a interface IDispatch, conforme exposto na classe COM WaaSRemediation, é manipulada para abuso de objetos COM presos e execução de código .NET. OWaaSRemediation é implementado no serviço WaaSMedicSvc, que é executado como um processo svchost.exe protegido no contexto de NT AUTHORITY\SYSTEM. O excelente passo a passo de Forshaw foi a base para nossa pesquisa aplicada e desenvolvimento de uma técnica de movimento lateral sem arquivos como prova de conceito.
Nossa jornada de pesquisa começou explorando a classe COM do WaaSRemediation, compatível com a interface co IDispatch. Essa interface permite que os clientes realizem a vinculação tardia. Normalmente, os clientes COM têm as definições de interface e tipo para os objetos que estão usando definidas no tempo de compilação. Em vez disso, a vinculação tardia permite que o cliente descubra e chame métodos no objeto em tempo de execução. IDispatch inclui o método GetTypeInfo, que retorna uma interface ITypeInfo. ITypeInfo tem métodos que podem ser usados para descobrir informações de tipo para o objeto que a implementa.
Se uma classe COM usar uma biblioteca de tipos, ela poderá ser consultada pelo cliente via ITypeLib (obtido de ITypeInfo-> GetContainingTypeLib) para recuperar informações de tipo. Além disso, bibliotecas de tipos também podem fazer referência a outras bibliotecas de tipos para obter informações adicionais de tipos.
De acordo com o post de blog de Forshaw, o WaaSRemediation faz referência à biblioteca de tipos WaaSRemediationLib, que, por sua vez, faz referência ao stdole (automação de OLE). O WaaSRemediationLib utiliza duas classes COM dessa biblioteca, StdFont e StdPicture. Ao executar o sequestro de COM no objeto StdFont modificando sua chave de registro TreatAs, a classe apontará para outra classe COM de nossa escolha, como System.Object na.NET Framework. Digno de nota, Forshaw destaca que StdPicture não é viável, pois esse objeto executa uma verificação de instanciação fora de processo, então mantivemos nosso foco no uso de StdFont.
Objetos .NET são interessantes para nós por causa do método GetType do System.Object. Por meio do GetType, podemos realizar a reflexão do .NET para acessar o Assembly.Load eventualmente. Embora System.Object tenha sido escolhido, esse tipo passa a ser a raiz da hierarquia de tipos em.NET. Portanto, qualquer objeto COM .NET pode ser usado.
Com o estágio inicial definido, havia dois outros valores DWORD sob a chave HKLM\Software\Microsoft\.NetFramework necessários para tornar nosso caso de uso percebido em realidade:
Ao confirmar que a versão mais recente do CLR e do .NET poderia ser carregada em nossos esforços iniciais de teste, sabíamos que estávamos no caminho certo.
Mudando nossa atenção para focar em aspectos programáticos remotos, primeiro usamos o Remote Registry para manipular os arquivos. valores de chave de registro do NetFramework e sequestra o objeto StdFont na máquina de destino. Em seguida, trocamos CoCreateInstance por CoCreateInstanceEx para instanciar o objeto COM WaaSRemediation no destino remoto e obter um ponteiro para a interface IDispatch.
Com um ponteiro para IDispatch, chamamos o método do membro GetTypeInfo para obter um ponteiro para a interface ITypeInfo, que está presa no servidor. Os métodos-membros chamados a partir daí ocorrem no lado do servidor. Depois de identificar a referência de interesse da biblioteca de tipos contida (stdole) e derivar a referência de objeto de classe subsequente de interesse (StdFont), acabamos usando o método CreateInstance "remotable" na interface ITypeInfo para redirecionar o fluxo de link do objeto StdFont (via manipulação TreatAs ) anterior para instanciar System.Object.
Com o AllowDCOMReflection está definido corretamente, podemos executar a reflexão .NET sobre DCOM para acessar Assembly.Load para carregar um assembly .NET no servidor COM. Como estamos usando Assembly.Load em vez de DCOM, essa técnica de movimento lateral é completamente sem arquivos, pois a transferência de bytes de assembly é tratada pela magia remota DCOM. Para uma explicação detalhada desse fluxo técnico da instanciação do objeto até a reflexão, consulte o diagrama a seguir:
Nosso primeiro e principal problema era chamar Assembly.Load_3, via IDispatch->Invoke. Invoke passa uma matriz de argumentos de objeto para a função de destino e Load_3 é a sobrecarga de Assembly.Load que utiliza uma matriz de byte único. Assim, precisávamos agrupar o SAFEARRAY de bytes dentro de outro SAFEARRAY de VARIANTs – inicialmente, continuamos tentando passar um único SAFEARRAY de bytes.
Outro problema era encontrar a sobrecarga adequada do Assembly.Load. As funções auxiliares foram extraídas do código CVE-2014-0257 da Forshaw, que incluía a função GetStaticMethod . Essa função utilizou a reflexão do .NET sobre DCOM para encontrar um método estático dado um ponteiro de tipo, o nome do método e sua contagem de parâmetros. Assembly.Load tem duas sobrecargas estáticas que recebem um único argumento; como tal, acabamos usando uma solução hacky. Percebemos que a terceira instância de Load com um único argumento foi a nossa escolha certa.
Uma das maiores desvantagens que observamos com essa técnica foi que o beacon gerado teria sua vida útil limitada ao cliente COM; neste caso, o tempo de vida da aplicação do nosso binário de armamento "ForsHops.exe" (com um nome elegante, é claro). Portanto, se o ForsHops.exe limpasse suas referências COM ou saísse, o mesmo faria o beacon que estava sendo executado sob o svchost.exe da máquina remota. Tentamos soluções diferentes, como fazer com que nosso assembly .NET travasse indefinidamente seu thread principal, executar shellcode em outro thread e fazer com que o ForsHops.exe deixasse o thread de exploração travado, mas nada era elegante.
Em seu estado atual, ForsHops.exe é executado até que o beacon termine, quando então remove suas operações de registro. Há oportunidades de melhoria, mas deixaremos isso como um exercício para o leitor.
A orientação de detecção proposta por Samir Bousseaden (@SBousseaden) após Mohamed Fakroud publicar sua implementação também se aplica a esta técnica de movimento lateral:
Além disso, recomendamos implementar os seguintes controles adicionais:
Além disso, aproveite a seguinte regra de YARA de prova de conceito para detectar o executável ForsHops.exe padrão:
regra Detect_Standard_ForsHops_PE_By_Hash
Nossa implementação estende ligeiramente o abuso de COM explicado no blog de Forshaw, aproveitando objetos COM presos para movimento lateral em vez de execução local para desvio de PPL. Portanto, ainda está suscetível às mesmas detecções que as implementações que realizam a execução local.
Você pode encontrar o código de movimento lateral de prova de conceito do ForsHops.exe aqui.
Agradecemos a Dwight Hohnstein (@djhohnstein) e Sanjiv Kawa (@sanjivkawa) pelo feedback sobre esta pesquisa e pela revisão do conteúdo do post de blog.