第五届鹏城杯 MDriver WriteUp

MDriver

VMP 脱壳

驱动被 VMP 加壳,采用断点 KeRevertToUserAffinityThread 后 Dump 脱壳后驱动程序。

bp KeRevertToUserAffinityThread

断下后使用,查询驱动信息,得到地址和大小。

使用以下命令将驱动 Dump 内存到本地。

.writemem C:\Users\Liv\Desktop\MDriver_Dump.sys fffff80416d90000 L00427000

反调试绕过

直接 g 运行后发现蓝屏,code 是 0x0deaddb9

在脱壳的驱动中搜索相关字节

搜到了调用蓝屏函数的地方。

查找函数交叉调用找到启动位置和调用位置,直接 Nop 该处 Call 即可。

eb MDriver+0x2ef4 90 90 90 90 90

运行则不再出现蓝屏

驱动分析

使用 YDArk 分析,发现该驱动注册了一个 Wfp 回调。

在刚刚调用反调试的地方往上走两层这边有个关键函数。

这里有个字符串解密出来是”[MDriver] Get packet:”

下面这边有个函数

发现有相关类似加密的敏感操作,对输入的字符串进行两次异或加密。

第四个函数里面有一串密文,是和我们输入的字符串加密后的密文进行比对,若比对失败则输出”[MDriver] your input key wrong!”,若成功则传入一个被 VM 虚拟化保护的函数,后续动调发现该黑盒函数就是传入 Key,然后解密返回 Flag。

根据之前发现驱动注册 WFP 回调,可以分析出这边其实就是调用了 FwpsCalloutRegister0 函数注册 Callout,然后核心的那个函数便是 classifyFn。

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS FwpsCalloutRegister0(
[in, out] void *deviceObject,
[in] const FWPS_CALLOUT0 *callout,
[out, optional] UINT32 *calloutId
);

typedef struct FWPS_CALLOUT0_ {
GUID calloutKey;
UINT32 flags;
FWPS_CALLOUT_CLASSIFY_FN0 classifyFn;
FWPS_CALLOUT_NOTIFY_FN0 notifyFn;
FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN0 flowDeleteFn;
} FWPS_CALLOUT0;

根据 ClassifyFn 函数原型还原函数。

1
2
3
4
5
6
7
8
void FwpsCalloutClassifyFn0(
[in] const FWPS_INCOMING_VALUES0 *inFixedValues,
[in] const FWPS_INCOMING_METADATA_VALUES0 *inMetaValues,
[in, out] void *layerData,
[in] const FWPS_FILTER0 *filter,
[in] UINT64 flowContext,
[in, out] FWPS_CLASSIFY_OUT0 *classifyOut
)

这边函数过滤了协议,这边可以看到在 IPV4 下第五个就是 protocol,1 对应的是 ICMP 协议。

说明该 WFP 网络驱动只拦截 ICMP 网络协议数据,并会打印接收到的数据,后续将拦截的字符串数据传入 HandleKey 函数,进行后续的加密操作。

写一个 ICMP 发包代码,发包长度 20 的字符串,ip 填个”8.8.8.8”即可,ClassifyFunction 会断下(代码见 EXP)

断点 MDriver+0x18a0,也就是 HandleKey 函数,运行发包函数断下。

可以看到 rcx 里面就是发包发送的”11111111111111111111”字符串。

断点 EncKey1 函数该处,拿到第一个异或加密用的 Dword 密钥*v10。

然后[rsp+0x28]就是异或用的密钥

0x65F8010F

断点 EncKey2 该处,获取 0x20 长度的异或密钥数组 v4。

第二个异或密钥为

0xbc, 0xfe, 0xc7, 0xfc, 0xa7, 0xfa, 0xae, 0xf8, 0xbe, 0xf6, 0xbb, 0xf4, 0xb7, 0xf2, 0xbe, 0xf0, 0xb8, 0xee, 0xbe, 0xec

密文从校验函数提取即可,两次异或解密得到 Key(代码见 EXP)。

发包发送解密得到的 Key,单步调试 Check 函数发现执行到该 vm 保护的黑盒函数后,传入 Key 会解密出明文 Flag 到寄存器中。

断点该 call,记录当前 rdx,步过执行完,查看 rdx 内存,其中就是 flag。

flag{Y0r_Ar3_W1nKern3l_Mas7er!*}

EXP

解密 Key:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

int main()
{
uint8_t xorKey[]{0xbc, 0xfe, 0xc7, 0xfc, 0xa7, 0xfa, 0xae, 0xf8, 0xbe, 0xf6, 0xbb, 0xf4, 0xb7, 0xf2, 0xbe, 0xf0, 0xb8, 0xee, 0xbe, 0xec};

uint8_t xorKey2[]{0x0f, 0x01, 0xf8, 0x65};

uint8_t EncKey[]{0xd4, 0x90, 0x60, 0xed, 0xc7, 0xa4, 0x30, 0xf4, 0xdf, 0x93, 0x1c, 0xe5, 0xd0, 0x96, 0x19, 0xf3, 0xdb, 0x8e, 0x21, 0xa8};

for (int i = 0; i < 20; i++)
EncKey[i] ^= xorKey[i];

for (int i = 0; i < 20; i++)
EncKey[i] ^= xorKey2[i % 4];

for (int i = 0; i < 20; i++)
printf("0x%X,", EncKey[i]);

printf("\n");
printf("%.20s\n", EncKey);
return 0;
}

发包程序

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <stdio.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")

typedef struct _ICMP_HEADER {
UCHAR Type;
UCHAR Code;
USHORT Checksum;
USHORT Id;
USHORT Sequence;
} ICMP_HEADER, * PICMP_HEADER;

typedef struct _IP_HEADER {
UCHAR ip_hl_v;
UCHAR ip_tos;
USHORT ip_len;
USHORT ip_id;
USHORT ip_off;
UCHAR ip_ttl;
UCHAR ip_p;
USHORT ip_sum;
struct in_addr ip_src;
struct in_addr ip_dst;
} IP_HEADER, * PIP_HEADER;

USHORT CalculateChecksum(USHORT* pBuf, int len) {
long sum = 0;
USHORT* w = pBuf;
int nleft = len;
USHORT temp = 0;

while (nleft > 1) {
sum += *w++;
nleft -= 2;
}

if (nleft == 1) {
*(UCHAR*)(&temp) = *(UCHAR*)w;
sum += temp;
}

sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return (USHORT)(~sum);
}

int main()
{
WSADATA wsaData;
SOCKET rawSocket = INVALID_SOCKET;
struct sockaddr_in destAddr;
int iResult;

char sendBuffer[1024];
IP_HEADER* ipHeader = (IP_HEADER*)sendBuffer;
ICMP_HEADER* icmpHeader = (ICMP_HEADER*)(sendBuffer + sizeof(IP_HEADER));

unsigned char Key[] = "go_to_find_the_flag!";
int icmpDataLen = 21;

const char* destinationIp = "8.8.8.8";

iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0)
{
return 1;
}

rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (rawSocket == INVALID_SOCKET) {
WSACleanup();
return 1;
}
ZeroMemory(&destAddr, sizeof(destAddr));
destAddr.sin_family = AF_INET;

iResult = inet_pton(AF_INET, destinationIp, &destAddr.sin_addr);
if (iResult != 1)
{
closesocket(rawSocket);
WSACleanup();
return 1;
}

ZeroMemory(sendBuffer, sizeof(sendBuffer));

icmpHeader->Type = 8;
icmpHeader->Code = 0;
icmpHeader->Id = (USHORT)GetCurrentProcessId();
icmpHeader->Sequence = 1;

RtlCopyMemory(icmpHeader + 1, Key, icmpDataLen);

icmpHeader->Checksum = CalculateChecksum((USHORT*)icmpHeader, sizeof(ICMP_HEADER) + icmpDataLen);

iResult = sendto(rawSocket,
(char*)icmpHeader,
sizeof(ICMP_HEADER) + icmpDataLen,
0,
(struct sockaddr*)&destAddr,
sizeof(destAddr));

closesocket(rawSocket);
WSACleanup();

std::cout << "Sending key succeed." << std::endl;
system("pause");
return 0;
}