Sin dai primi anni '90, Component Object Model (COM) ha rappresentato una pietra miliare dello sviluppo di Microsoft Windows e ancor oggi è molto diffuso nelle applicazioni e nei sistemi operativi Windows moderni. La dipendenza dai componenti COM e lo sviluppo esteso di funzionalità nel corso degli anni hanno creato una superficie d'attacco generosa. Nel febbraio 2025, James Forshaw (@tiraniddo) di Google Project Zero ha pubblicato un post sul blog che descrive un approccio innovativo per abusare della tecnologia di remoting Distributed COM (DCOM), in cui gli oggetti COM intrappolati possono essere utilizzati per eseguire codice gestito da .NET nel contesto di un processo DCOM lato server. Forshaw evidenzia diversi casi d'uso per l'escalation dei privilegi e il bypass del Protected Process Light (PPL).
Sulla base della ricerca di Forshaw, Mohamed Fakroud (@T3nb3w) ha pubblicato un'implementazione della tecnica per bypassare le protezioni PPL all'inizio di marzo 2025. Jimmy Bayne (@bohops) ed io abbiamo condotto una ricerca simile nel febbraio 2025, che ci ha portato a sviluppare una tecnica di movimento laterale senza file proof-of-concept sfruttando oggetti COM intrappolati.
COM è uno standard di interfaccia binaria e un livello di servizio middleware che consente l'esposizione di componenti distinti e modulari per interagire tra loro e con le applicazioni, indipendentemente dal linguaggio di programmazione sottostante. Ad esempio, gli oggetti COM sviluppati in C++ possono interfacciarsi facilmente con un'applicazione .NET, consentendo agli sviluppatori di integrare efficacemente diversi moduli software. DCOM è una tecnologia di remoting che consente ai client COM di comunicare con i server COM tramite comunicazione inter-processo (IPC) o chiamate di procedura remota (RPC). Molti servizi Windows implementano componenti DCOM accessibili localmente o da remoto.
Le classi COM sono tipicamente registrate e contenute all'interno del Registro di Windows. Un programma client interagisce con un server COM creando un'istanza della classe COM, nota come oggetto COM. Questo oggetto fornisce un puntatore a un'interfaccia standardizzata. Il client utilizza questo puntatore per accedere ai metodi e alle proprietà dell'oggetto, facilitando la comunicazione e la funzionalità tra il client e il server.
Gli oggetti COM sono spesso bersagli di ricerca per valutare l'esposizione alla vulnerabilità e scoprire caratteristiche abusabili. Un oggetto COM intrappolato è una classe di bug in cui un client COM crea un'istanza di una classe COM in un server DCOM fuori processo, dove il client controlla l'oggetto COM tramite un puntatore a un oggetto sottoposto a marshalling per riferimento. A seconda della condizione, questo vettore di controllo può presentare difetti logici legati alla sicurezza.
Il blog di Forshaw descrive un caso d'uso di bypass PPL in cui l'interfaccia IDispatch, esposta nella classe COM WaaSRemediation, viene manipolata per l'abuso di oggetti COM e l'esecuzione di codice .NET. WaaSRemediation è implementata nel servizio WaaSMedicSvc, che viene eseguito come un processo svchost.exe protetto nel contesto di NT AUTHORITY\SYSTEM. L'eccellente guida di Forshaw è stata la base per la nostra ricerca applicata e per lo sviluppo di una tecnica di prova di movimento laterale senza file.
Il nostro percorso di ricerca è iniziato esplorando la classe COM WaaSRemediation che supporta l'interfaccia IDispatch . Questa interfaccia consente ai client di eseguire il late binding. Normalmente, i client COM hanno le definizioni di interfaccia e tipo per gli oggetti che utilizzano definite in fase di compilazione. Invece, il binding tardivo permette al client di scoprire e chiamare metodi sull'oggetto al tempo di esecuzione. IDispatch include il metodo GetTypeInfo , che restituisce un'interfaccia ITypeInfo. ITypeInfo dispone di metodi che possono essere utilizzati per scoprire informazioni sul tipo di oggetto che lo implementa.
Se una classe COM utilizza una libreria di tipi, può essere interrogata dal client tramite ITypeLib (ottenuto da ITypeInfo-> GetContainingTypeLib) per recuperare le informazioni sui tipi. Inoltre, le librerie di tipi possono anche fare riferimento ad altre librerie di tipi per ottenere informazioni aggiuntive sui tipi.
Secondo il post sul blog di Forshaw, WaaSRemediation fa riferimento alla libreria di tipi WaaSRemediationLib, che a sua volta fa riferimento a stdole (automazione OLE). WaaSRemediationLib utilizza due classi COM di quella libreria, StdFont e StdPicture. Eseguendo il COM Hijacking sull'oggetto StdFont tramite modifica della chiave del registro TreatAs, la classe punterà a un'altra classe COM di nostra scelta, come System.Object nel framework .NET. È importante notare che Forshaw sottolinea che StdPicture non è fattibile poiché questo oggetto esegue un controllo per l'istanziazione fuori processo, quindi abbiamo continuato a concentrarci sull'utilizzo di StdFont.
Gli oggetti .NET sono interessanti per noi grazie al metodo GetTypedi System.Object. Attraverso GetType, possiamo eseguire la riflessione .NET per accedere infine ad Assembly.Load. Sebbene sia stato scelto System.Object, questo tipo risulta essere la radice della gerarchia dei tipi in .NET. Pertanto, è possibile utilizzare qualsiasi oggetto COM .NET.
Con la fase iniziale impostata, c'erano altri due valori DWORD nella chiave HKLM\Software\Microsoft\.NetFramework necessari per rendere reale il nostro caso d'uso percepito:
Dopo aver confermato che l'ultima versione del CLR e di .NET poteva essere caricata nei nostri primi test, abbiamo capito di essere sulla strada giusta.
Spostando la nostra attenzione sugli aspetti programmatici remoti, abbiamo prima utilizzato Remote Registry per manipolare i valori delle chiavi del registro .NetFramework e dirottare l'oggetto StdFont sulla macchina di destinazione. Successivamente, abbiamo sostituito CoCreateInstance con CoCreateInstanceEx per istanziare l'oggetto COM WaaSRemediation sul target remoto e ottenere un puntatore all'interfaccia IDispatch .
Con un puntatore a IDispatch, chiamiamo il metodo membro GetTypeInfo per ottenere un puntatore all'interfaccia ITypeInfo, che è intrappolata nel server. I metodi membro chiamati successivamente si verificano sul lato server. Dopo aver identificato il riferimento della libreria di tipi contenuta di interesse (stdole) e aver derivato il successivo riferimento all'oggetto di classe di interesse (StdFont), abbiamo infine utilizzato il metodo "remotable" CreateInstance sull'interfaccia ITypeInfo per reindirizzare il flusso di link degli oggetti StdFont (tramite manipolazione precedente TreatAs ) per istanziare System.Object.
Poiché AllowDCOMReflection è impostato correttamente, possiamo eseguire la riflessione .NET su DCOM per accedere ad Assembly.Load e caricare un assembly .NET nel server COM. Poiché utilizziamo Assembly.Load su DCOM, questa tecnica di movimento laterale è completamente senza file, in quanto il trasferimento dei byte dell'assembly è gestito dalla magia del remoting DCOM. Per una spiegazione approfondita di questo flusso tecnico dall'istanziazione dell'oggetto alla riflessione, consulta il seguente diagramma:
Il nostro primo e principale problema è stato chiamare Assembly.Load_3 tramite IDispatch->Invoke. Invoke passa un array di argomenti alla funzione di destinazione, mentre Load_3 è l'overload di Assembly.Load che accetta un array di byte singoli. Pertanto, abbiamo dovuto racchiudere il SAFEARRAY di byte all'interno di un altro SAFEARRAY di VARIANT: inizialmente, abbiamo continuato a provare a passare un singolo SAFEARRAY di byte.
Un altro problema è stato trovare il giusto sovraccarico di Assembly.Load. Le funzioni di supporto sono state prese dal codice CVE-2014-0257 di Forshaw, che includeva la funzione GetStaticMethod. Questa funzione utilizza la riflessione di .NET su DCOM per trovare un metodo statico dato un puntatore di tipo, il nome del metodo e il suo numero di parametri. Assembly.Load ha due overload statici che richiedono un solo argomento; pertanto, abbiamo finito per utilizzare una soluzione complicata. Abbiamo notato che la terza istanza di Load con un singolo argomento era la scelta giusta.
Uno dei maggiori svantaggi che abbiamo osservato con questa tecnica è stato che il beacon generato avrebbe avuto una durata limitata al client COM; in questo caso, la durata dell'applicazione del nostro binario di armamento "ForsHops.exe" (dal nome elegante, ovviamente). Quindi, se ForsHops.exe puliva i riferimenti COM o usciva, lo faceva anche il beacon che girava sotto la svchost.exe della macchina remota. Abbiamo provato diverse soluzioni, come ad esempio far sì che il nostro assembly .NET bloccasse indefinitamente il suo thread principale, eseguisse lo shellcode in un altro thread e facesse sì che ForsHops.exe lasciasse sospeso il thread dell'exploit, ma niente di elegante.
Nello stato attuale, ForsHops.exe viene eseguito finché il beacon non esce, dopodiché rimuove le operazioni del registro. Esistono margini di miglioramento, ma lo lasceremo come esercizio per il lettore.
La guida per il rilevamento proposta da Samir Bousseaden (@SBousseaden) dopo che Mohamed Fakroud ne ha pubblicato l'implementazione si applica anche a questa tecnica di movimento laterale:
Inoltre, consigliamo di implementare i seguenti controlli aggiuntivi:
Inoltre, utilizza la seguente regola YARA proof-of-concept per rilevare l'eseguibile standard ForsHops.exe:
regola Detect_Standard_ForsHops_PE_By_Hash
La nostra implementazione estende leggermente l'abuso del COM spiegato nel blog di Forshaw sfruttando oggetti COM intrappolati per il movimento laterale piuttosto che l'esecuzione locale per il bypass PPL. Pertanto, è ancora soggetta agli stessi rilevamenti delle implementazioni che eseguono l'esecuzione locale.
Puoi trovare qui il codice ForsHops.exe proof-of-concept per il movimento laterale.
Un ringraziamento speciale a Dwight Hohnstein (@djhohnstein) e Sanjiv Kawa (@sanjivkawa) per aver fornito feedback su questa ricerca e per aver fornito una recensione dei contenuti dei post del blog.