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 di funzionalità estese nel corso degli anni hanno creato una superficie di attacco generosa. Nel febbraio 2025, James Forshaw (@tiraniddo) di Google Project Zero ha pubblicato un post di blog che descrive un approccio innovativo per abusare della tecnologia da remoto Distributed COM (DCOM), in cui gli oggetti COM intrappolati possono essere utilizzati per eseguire codice gestito .NET nel contesto di un processo DCOM lato server. Forshaw evidenzia diversi casi d'uso per l'escalation dei privilegi e l'aggiramento di Protected Process Light (PPL).
Sulla base della ricerca di Forshaw, Mohamed Fakroud (@T3nb3w) ha pubblicato un'implementazione della tecnica per aggirare 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 perché interagiscano 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 da remoto che consente ai client COM di comunicare con i server COM tramite comunicazione tra processi (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 bersaglio di ricerca per valutare l'esposizione delle vulnerabilità e scoprire le caratteristiche di cui si potrebbe abusare. Un oggetto COM intrappolato è una classe di bug in cui un client COM istanzia una classe COM in un server DCOM fuori dal processo, dove il client controlla l'oggetto COM tramite un puntatore di oggetto con 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 aggiramento PPL in cui l'interfaccia IDispatch, come esposta nella classe COM WaaSRemediation, viene manipolata per l'abuso di oggetti COM intrappolati e l'esecuzione di codice .NET. WaaSRemediation è implementato nel servizio WaaSMedicSvc, che viene eseguito come 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 proof of concept.
Il nostro percorso di ricerca è iniziato con l’esplorazione della 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 delle interfacce e dei tipi degli oggetti che utilizzano definiti in fase di compilazione. Invece, il late binding permette al client di scoprire e chiamare metodi sull’oggetto nel tempo di esecuzione. IDispatch include il metodo GetTypeInfo, che restituisce un’interfaccia ITypeInfo . ITypeInfo dispone di metodi che possono essere utilizzati per scoprire le informazioni di tipo per l’oggetto che lo implementa.
Se una classe COM utilizza una libreria di tipi, può essere interrogata dal client tramite ITypeLib (ottenuta da ITypeInfo-> GetContainingTypeLib) per recuperare le informazioni sui tipi. Inoltre, le librerie di tipi possono anche fare riferimento ad altre librerie di tipi per ulteriori informazioni 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 modificando la chiave del registro TreatAs, la classe punterà a un’altra classe COM di nostra scelta, come System.Object nel framework .NET. Da notare che Forshaw sottolinea che StdPicture non è valido poiché questo oggetto esegue un controllo per istanziazioni fuori processo, quindi abbiamo mantenuto il focus sull’uso di StdFont.
Gli oggetti .NET ci interessano per via del metodo GetType di System.Object. Attraverso GetType, possiamo eseguire la riflessione .NET per accedere infine ad Assembly.Load. Sebbene sia stato scelto System.Object , questo tipo è la radice della gerarchia dei tipi in .NET. Pertanto, è possibile utilizzare qualsiasi oggetto COM .NET.
Nella fase iniziale, erano necessari altri due valori DWORD sotto la chiave HKLM\Software\Microsoft\.NetFramework per trasformare il nostro caso d’uso percepito in realtà:
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 Registro di sistema remoto per manipolare i valori chiave del registro .NetFramework e dirottare l'oggetto StdFont sul computer di destinazione. Successivamente, abbiamo sostituito CoCreateInstance con CoCreateInstanceEx per istanziare l'oggetto COM WaaSRemediation sulla destinazione remota 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 "gestibile da remoto" CreateInstance sull'interfaccia ITypeInfo per reindirizzare il flusso di link dell'oggetto StdFont (tramite precedente manipolazione 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. Dato che usiamo Assembly.Load su DCOM, questa tecnica di movimento laterale è completamente senza file poiché il trasferimento dei byte assembly è gestito dalla magia del remoting DCOM. Per una spiegazione approfondita di questo flusso tecnico dall'istanziazione dell'oggetto alla riflessione, fai riferimento al seguente diagramma:
Il nostro primo e principale problema è stato chiamare Assembly.Load_3, tramite IDispatch->Invoke. Invoke passa un array di oggetti alla funzione target, e Load_3 è il sovraccarico di Assembly.Load che richiede un array di un singolo byte. Quindi, dovevamo avvolgere il SAFEARRAY di byte all'interno di un altro SAFEARRAY di VARIANTI – inizialmente cercavamo di passare un singolo SAFEARRAY di byte.
Un altro problema è stato trovare il sovraccarico Assembly.Load giusto. 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 dati un puntatore di tipo, il nome del metodo e il suo numero di parametri. Assembly.Load ha due sovraccarichi statici che richiedono un singolo argomento; di conseguenza, alla fine abbiamo usato una soluzione arrangiata. 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 in esecuzione sotto la svchost.exe del computer remoto. Abbiamo provato diverse soluzioni, come far sì che il nostro assembly .NET bloccasse indefinitamente il thread principale, eseguisse lo shellcode in un altro thread e facesse in modo che ForsHops.exe lasciasse il thread di utilizzo in sospeso, ma nulla era elegante.
Nello stato attuale, ForsHops.exe viene eseguito finché il beacon non esce, dopodiché rimuove le operazioni del registro. Ci sono opportunità di miglioramento, ma lasciamo che sia qualcosa da fare per chi legge.
Le linee guida di rilevamento proposte da Samir Bousseaden (@SBousseaden) dopo che Mohamed Fakroud ne ha pubblicato l'implementazione si applicano 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 lo standard ForsHops.exe eseguibile:
regola Detect_Standard_ForsHops_PE_By_Hash
La nostra implementazione estende leggermente l'abuso di COM spiegato nel blog di Forshaw sfruttando oggetti COM intrappolati per il movimento laterale piuttosto che l'esecuzione locale per l'aggiramento PPL. Pertanto, è ancora suscettibile 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 grazie a Dwight Hohnstein (@djhohnstein) e Sanjiv Kawa (@sanjivkawa) per aver dato un feedback su questa ricerca e per aver fornito recensioni dei post di blog.