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 grande desenvolvimento de funcionalidades ao longo dos anos criaram uma ampla superfície de ataque. Em fevereiro de 2025, James Forshaw (@tiraniddo) do Google Project Zero publicou um post de blog detalhando uma nova abordagem para explorar a tecnologia de comunicação remota Distributed COM (DCOM), em que objetos COM interceptados podem ser usados para executar código gerenciado .NET no contexto de um processo DCOM do lado do servidor. Forshaw destaca vários casos de uso para escalonamento de privilégios e contorno 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, explorando objetos COM interceptados.
O COM é um padrão de interface binária e uma camada de serviço middleware que permite a exposição de componentes distintos e modulares 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. O 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 são normalmente registradas e armazenadas 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.
Os objetos COM são frequentemente alvos de pesquisa para avaliar a exposição a vulnerabilidades e descobrir funcionalidades que podem ser exploradas. Um objeto COM aprisionado é uma classe de bug na qual um cliente COM instancia uma classe COM em um servidor DCOM fora de processo, em que o cliente controla o objeto COM por meio de um ponteiro de objeto marshaled-by-reference. 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 bypass PPL em que a interface IDispatch, conforme exposta na classe COM WaaSRemediation, é manipulada para exploração de objetos COM interceptados e execução de código .NET. O WaaSRemediation é 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 arquivo de prova de conceito.
Nossa jornada de pesquisa começou explorando a classe COM WaaSRemediation que suporta a interface IDispatch . Essa interface permite que os clientes realizem 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 invoque métodos no objeto em tempo de execução. O IDispatch inclui o método GetTypeInfo , que retorna uma interface ITypeInfo. O ITypeInfo tem métodos que podem ser usados para descobrir informações de tipo para o objeto que a implementa.
Se uma classe COM utiliza uma biblioteca de tipos, ela pode ser consultada pelo cliente através da ITypeLib (obtida através da ITypeInfo->GetContainingTypeLib) para recuperar informações sobre tipos. Além disso, bibliotecas de tipos também podem fazer referência a outras bibliotecas para obter informações adicionais.
De acordo com o post no blog de Forshaw, a WaaSRemediation faz referência à biblioteca de tipos WaaSRemediationLib, que por sua vez faz referência a stdole (automação OLE). A WaaSRemediationLib utiliza duas classes COM dessa biblioteca, StdFont e StdPicture. Ao realizar o COM Hijacking no objeto StdFont modificando sua chave de registro TreatAs, a classe apontará para outra classe COM de nossa escolha, como System.Object no framework .NET. É importante observar que Forshaw ressalta que o StdPicture não é viável, pois esse objeto executa uma verificação de instanciação fora do processo, portanto, mantivemos nosso foco no uso do StdFont.
Os objetos .NET são interessantes para nós por causa do método GetType do System.Object. Por meio do GetType, podemos executar a reflexão do .NET para acessar eventualmente o Assembly.Load. Embora o System.Object tenha sido escolhido, esse tipo acaba sendo a raiz da hierarquia de tipos no .NET. Portanto, qualquer objeto COM .NET pode ser usado.
Com a etapa inicial definida, eram necessários outros dois valores DWORD na chave HKLM\Software\Microsoft\.NetFramework para tornar o caso de uso que idealizamos uma realidade:
Ao confirmar que a versão mais recente do CLR e do .NET poderia ser carregada em nossos testes iniciais, sabíamos que estávamos no caminho certo.
Voltando nossa atenção para os aspectos programáticos remotos, primeiro usamos o Registro Remoto para manipular os valores das chaves de registro do .NetFramework e sequestrar o objeto StdFont na máquina de destino. Em seguida, trocamos o CoCreateInstance pelo CoCreateInstanceEx para instanciar o objeto COM WaaSRemediation no alvo remoto e obter um ponteiro para a interface IDispatch .
Com um ponteiro para IDispatch, chamamos o método de membro GetTypeInfo para obter um ponteiro para a interface ITypeInfo , que está interceptada no servidor. Os métodos de membro chamados a partir daí ocorrem no lado do servidor. Após identificar a referência da biblioteca de tipos de interesse (stdole) e derivar a referência do objeto de classe subsequente de interesse (StdFont), finalmente usamos o método “remotable” CreateInstance na interface ITypeInfo para redirecionar o fluxo de vinculação do objeto StdFont (por meio de manipulação TreatAs anterior) para instanciar o System.Object.
Como o AllowDCOMReflection está definido corretamente, podemos executar a reflexão do .NET sobre o DCOM para acessar o Assembly.Load e carregar um assembly do .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 dos bytes do assembly é tratada pela "mágica" do remoting do DCOM. Para obter uma explicação detalhada desse fluxo técnico, desde a instanciação do objeto até a reflexão, consulte o diagrama a seguir:
Nosso primeiro e principal problema foi chamar o Assembly.Load_3, através de IDispatch->Invoke. O Invoke passa um array de objetos com argumentos para a função de destino, e o Load_3 é a sobrecarga do Assembly.Load que recebe um único array de bytes. Assim, precisávamos envolver 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 retiradas do código CVE-2014-0257 do Forshaw. que incluía a função GetStaticMethod. Essa função utilizou a reflexão do .NET sobre o DCOM para localizar um método estático com um ponteiro de tipo, o nome do método e a contagem de parâmetros. O Assembly.Load tem duas sobrecargas estáticas que recebem um único argumento; por isso, acabamos usando uma solução improvisada. Percebemos que a terceira instância de Load , com um único argumento, era a escolha certa.
Uma das maiores desvantagens que observamos nessa técnica foi que o beacon gerado teria sua vida útil limitada ao cliente COM; nesse caso, ao tempo de execução do nosso binário armado "ForsHops.exe" (com um nome elegante, é claro). Portanto, se o ForsHops.exe limpasse suas referências COM ou saísse, o mesmo aconteceria com o beacon que estava sendo executado no svchost.exe da máquina remota. Tentamos diferentes soluções, como fazer com que nosso assembly .NET travasse indefinidamente sua thread principal, executar shellcode em outra thread e fazer com que o ForsHops.exe deixasse a thread de exploração travada, mas nada foi elegante.
Em seu estado atual, o ForsHops.exe é executado até que o beacon seja desativado, momento em que 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 essa técnica de movimento lateral:
Além disso, recomendamos implementar os seguintes controles adicionais:
Além disso, utilize a seguinte regra YARA de prova de conceito para detectar o executável ForsHops.exe padrão:
regra Detect_Standard_ForsHops_PE_By_Hash
Nossa implementação amplia um pouco a exploração de COM explicado no blog de Forshaw, aproveitando objetos COM interceptados para movimento lateral em vez de execução local para contornar a PPL. Portanto, continua suscetível à mesma detecção que as implementações que executam localmente.
Você pode encontrar o código de movimento lateral da prova de conceito ForsHops.exe aqui.
Um agradecimento especial a Dwight Hohnstein (@djhohnstein) e Sanjiv Kawa (@sanjivkawa) pelo feedback sobre esta pesquisa e pela revisão do conteúdo do post no blog.