强网杯的一道misc题,但是是逆向

提取文件

内核!蓝屏!驱动!

首先用Windbg分析dmp文件

!analyze -v

image-20251027163132323

综合所有信息,崩溃过程很可能是这样的:

  1. 用户 a 运行了桌面上的 aPersonalVault.exe 程序。
  2. 该程序加载了同目录下的 personalVaultKernel.sys 驱动程序到系统内核。
  3. 这个驱动程序存在严重的编程缺陷,在执行其功能时(很可能是在处理某个系统服务请求时),发生了内存访问违规(c0000005)。
  4. 由于这个错误发生在具有最高权限的内核模式,Windows 内核无法安全地处理它,为了阻止数据损坏或安全漏洞,立即触发了蓝屏死机(BugCheck 3B),并生成了你现在分析的 MEMORY.DMP 文件。
  5. 崩溃发生后,aPersonalVault.exepersonalVaultKernel.sys 可能被用户手动删除,或被安全软件作为可疑文件清除了。

!process 0 0列出所有进程,发现当前运行的是存在两个aPersonalVault.exe进程

image-20251027163339863

下一步应该把引发蓝屏的exe dump出来

pip install volatility3

vol -f E:\MEMORY.dmp windows.filescan

找到了aPersonalVault.exe和personalVaultKernel.sys

image-20251027232124194

image-20251027231811551

然后进行转储得到原始文件

image-20251027232517665

逆向分析

sys驱动逆向

image-20251027234845891

1
2
3
4
5
6
7
ds分析:这是一个Windows内核驱动程序的初始化函数(DriverEntry),主要完成以下工作:

创建设备对象和符号链接 - 建立驱动程序与用户态的通信接口
设置派遣函数 - 处理IRP请求和卸载例程
枚举系统模块 - 查找ntoskrnl.exe内核模块
函数挂钩 - 替换PsTerminateSystemThread系统函数
错误处理 - 完善的资源清理机制
1
ntoskrnl.exe是Windows操作系统的一个重要内核程序,里面存储了大量的二进制内核代码,用于调度系统时使用,也是操作系统启动后第一个被加载的程序,通常该进程在任务管理器中显示为System

遍历系统模块,寻找ntoskrnl.exe,获取HalPrivateDispatchTable的地址(从ntoskrnl.exe基地址偏移14682984处),把函数表中的HalTimerConvertAuxiliaryCounterToPerformanceCounter替换成sub_FFFFF80512BF10A0,获取到全局进程表并保存起来

1
选择这个函数是攻击者在隐蔽性和功能性之间的平衡选择,既能够实现内核级控制,又不容易被常规安全检测发现。

分析通信函数

image-20251028095206440

Signal如果是1就会触发蓝屏部分代码,Signal为2则遍历进程链表,寻找R3通信传来的对应Pid的EProcess,然后获取CreateTime数据返回到R3

通信数据的结构通过分析R3程序可以得到

1
2
3
4
5
6
7
8
struct ComData
{
DWORD MagicNum;
DWORD Signal;
DWORD64 Pid;
DWORD64 RetData;
CHAR PAD[216];
};

ida恢复sys文件的结构体

image-20251028102924257

得到的代码

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
__int64 __fastcall sub_FFFFF80512BF10A0(ComData *ComData, __int64 a2, __int64 a3)
{
__int64 (__fastcall *v3)(ComData *, __int64, __int64); // rbp
DWORD n2; // eax
_QWORD *v8; // rax
DWORD64 Pid; // rcx

v3 = (__int64 (__fastcall *)(ComData *, __int64, __int64))qword_FFFFF80512BF3348;
if ( ExGetPreviousMode() && ComData->MagicNum == 0x30303030 )
{
n2 = ComData->Signal;
if ( n2 == 1 )
{
process_list = 0LL;
qword_FFFFF80512BF3348 = 0LL;
ntoskrnl_base = 0LL;
MEMORY[0] = 0; // 触发蓝屏
}
else if ( n2 == 2 )
{
v8 = (_QWORD *)process_list;
Pid = ComData->Pid;
if ( *(_QWORD *)(process_list + 464) != Pid )
{
do
v8 = (_QWORD *)(v8[59] - 472LL);
while ( v8[58] != Pid );
}
ComData->RetData = v8[63];
}
}
return v3(ComData, a2, a3);
}
1
2
3
4
5
6
7
8
9
用户态程序调用HalTimerConvertAuxiliaryCounterToPerformanceCounter

传递包含MagicNum和命令的结构体

挂钩函数验证认证并处理命令

返回内核数据或执行清理操作

调用原始函数保持系统正常

EPROCESS结构:从藏匿到曝光:EPROCESS如何成为恶意程序的终极战场? - 知乎

R3程序逆向

加密流程

之前windbg的分析可以知道系统中有两个apersonalVault进程,其中一个进程触发了蓝屏

image-20251028104347859

main函数负责与内核挂钩函数通信,进入sub_7FF611D11D10分析

image-20251028104647171

这里看到了通信数据的结构体

CreateWindowExW函数创建窗口

image-20251028110151836

Create按钮处理:检查用户名和Secret是否为空,初始化ComData数据结构,调用内核函数进行通信,传递Signal是2,传过去的Pid是GetCurrentProcessId,也就是当前进程ID,驱动会返回当前进程的CreateTime数据,然后调用BCryptGenRandom随机初始化一个48字节的pbBuffer,用于后续的加密使用。

将随机的48字节作为两部分使用,前32字节作为Key,后16字节作为IV,对用户输入的Secret进行AES-CBC加密。

image-20251028110612103

加密完成后,将48字节异或驱动传过来的数据,相当于加密了密钥和IV

image-20251028110638154

创建邮件槽用于进程通信,存储密文信息

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
NumberOfBytesWritten = 0;           // 创建邮件槽进行进程间通信
*(_OWORD *)Name = 0LL;
v19 = 1;
v20 = 0;
v71 = 0LL;
v21 = -1LL;
v72 = 0LL;
v73 = 0LL;
v74 = 0LL;
do
++v21;
while ( String[v21] ); // 计算用户名哈希
if ( v21 )
{
v22 = String;
do
{
v23 = *v22++;
++v20;
v19 = v23 * v19 + 257;
}
while ( v20 < v21 );
}
sub_7FF611D11AD0(Name, 0x50uLL, "\\\\.\\mailslot\\mailslot_%x");// 创建唯一邮件槽名称
if ( hObject )
CloseHandle(hObject);
hObject = CreateMailslotA(Name, 0, 0xFFFFFFFF, 0LL);
FileA = CreateFileA(Name, GENERIC_WRITE, FILE_READ_DATA, 0LL, OPEN_EXISTING, FILE_READ_ATTRIBUTES, 0LL);// 写入加密数据到邮件槽
WriteFile(FileA, lpBuffer_2, Size, &NumberOfBytesWritten, 0LL);
CloseHandle(FileA);
memset(lpBuffer_2, 0, Size);
*(_OWORD *)Buffer = 0LL; // 创建共享内存映射
v76 = 0LL;
v77 = 0LL;
v78 = 0LL;
v79 = 0LL;
sub_7FF611D11AD0(Buffer, 0x50uLL, "Local\\mapping_%x");
if ( hObject_0 )
CloseHandle(hObject_0);
hObject_0 = CreateFileMappingA((HANDLE)0xFFFFFFFFFFFFFFFFLL, 0LL, PAGE_READWRITE, 0, 0x1000u, Buffer);
lpBaseAddress = MapViewOfFile(hObject_0, 6u, 0, 0, 0x1000uLL);// 在共享内存中存储邮件槽信息和进程ID
CurrentProcessId = GetCurrentProcessId();
*lpBaseAddress = hObject;
lpBaseAddress[1] = CurrentProcessId;
UnmapViewOfFile(lpBaseAddress);
n64 = 64;
Success = L"Success";
User_created_successfully. = L"User created successfully.";
goto LABEL_62;
}
}

邮件槽

邮件槽(Mailslot)是Windows操作系统提供的一种简单的单向进程间通信(IPC)机制,只能一个进程写入,另一个进程读取

export_ibstfb

创建者写入的地址并不是一个内存地址,而是全局唯一的命名管道名称,计算用户名的哈希作为唯一标识,系统在内核中创建了一个名为\\.\mailslot\mailslot_1234ABCD 的邮件槽对象,创建者向这个地址写入加密数据存入邮件槽内核缓冲区。

读取者不知道创建者进程的邮件槽句柄,使用共享内存作为地址簿,创建者再共享内存中注册信息,读取者查找地址,读取数据

解密流程

Check按钮处理

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
if ( !v5 )
{
Success = L"Input required";
User_created_successfully. = L"Please enter username.";
goto LABEL_61;
}
NumberOfBytesRead = 0; // 读取共享内存获取邮件槽信息
v30 = 1;
NumberOfBytesWritten = 0;
v31 = 0;
NextSize = 0;
*(_OWORD *)Name = 0LL;
v71 = 0LL;
v72 = 0LL;
v73 = 0LL;
v74 = 0LL;
v32 = String; // 计算用户哈希
do
{
v33 = *v32++;
++v31;
v30 = v33 * v30 + 257;
}
while ( v31 < v5 );
*(_OWORD *)Buffer = 0LL; // 打开共享内存映射
v76 = 0LL;
v77 = 0LL;
v78 = 0LL;
v79 = 0LL;
sub_7FF611D11AD0(Buffer, 0x50uLL, "Local\\mapping_%x");
hFileMappingObject = OpenFileMappingA(6u, 0, Buffer);
if ( !hFileMappingObject )
{
Success = L"Error!";
User_created_successfully. = L"No username found...";
goto LABEL_61;
}
v35 = MapViewOfFile(hFileMappingObject, 6u, 0, 0, 0x1000uLL);// 获取邮件槽句柄和进程id
hSourceHandle = *(void **)v35;
hSourceProcessHandle = OpenProcess(PROCESS_DUP_HANDLE, 0, v35[2]);
CurrentProcess = GetCurrentProcess();
if ( !DuplicateHandle(
hSourceProcessHandle,
hSourceHandle,
CurrentProcess,
&TargetHandle,
0,
0,
DUPLICATE_SAME_ACCESS) )
goto LABEL_60;
GetMailslotInfo(TargetHandle, 0LL, &NextSize, &NumberOfBytesWritten, 0LL);
sub_7FF611D11A70("cbMessage: %llx; cMessage: %llx\n", NextSize, NumberOfBytesWritten);
if ( NextSize == -1 )
{
Success = L"No secret found!";
User_created_successfully. = L"No secret is in the box right now!!!";
goto LABEL_61;
}
if ( NumberOfBytesWritten != 1 )
{
LABEL_60:
Success = L"Secret system is corrupted!";
User_created_successfully. = L"Something is wrong...";
goto LABEL_61;
}
lpBuffer_3 = (char *)j__malloc_base(NextSize);// 读取加密数据
ReadFile(TargetHandle, lpBuffer_3, NextSize, &NumberOfBytesRead, 0LL);
memset(&v80[2], 0, 0xF0uLL); // 调用内核函数获取解密密钥
v80[0] = 0x230303030LL;
v40 = GetCurrentProcessId();
v66 = 0LL;
v80[1] = v40;
v67 = v80;
NtConvertBetweenAuxiliaryCounterAndPerformanceCounter(1LL, &v67, &v66, 0LL);
v41 = v80[2];
v42 = BYTE1(v80[2]);
*(_WORD *)(&pbBuffer + 5) ^= *(_WORD *)((char *)&v80[2] + 5);// 解密密钥
*(&pbBuffer + 7) ^= HIBYTE(v80[2]);
*(_WORD *)(&pbBuffer + 13) ^= *(_WORD *)((char *)&v80[2] + 5);
*(&pbBuffer + 15) ^= HIBYTE(v80[2]);
*(_WORD *)((char *)&xmmword_7FF611D36BB8 + 5) ^= *(_WORD *)((char *)&v80[2] + 5);
BYTE7(xmmword_7FF611D36BB8) ^= HIBYTE(v80[2]);
*(_WORD *)((char *)&xmmword_7FF611D36BB8 + 13) ^= *(_WORD *)((char *)&v80[2] + 5);
HIBYTE(xmmword_7FF611D36BB8) ^= HIBYTE(v80[2]);
*(_WORD *)((char *)&xmmword_7FF611D36BC8 + 5) ^= *(_WORD *)((char *)&v80[2] + 5);
BYTE7(xmmword_7FF611D36BC8) ^= HIBYTE(v80[2]);
*(_WORD *)((char *)&xmmword_7FF611D36BC8 + 13) ^= *(_WORD *)((char *)&v80[2] + 5);
pbBuffer ^= LOBYTE(v80[2]);
*(_DWORD *)(&pbBuffer + 1) ^= *(_DWORD *)((char *)&v80[2] + 1);
*(&pbBuffer + 8) ^= LOBYTE(v80[2]);
*(_DWORD *)(&pbBuffer + 9) ^= *(_DWORD *)((char *)&v80[2] + 1);
LOBYTE(xmmword_7FF611D36BB8) = LOBYTE(v80[2]) ^ xmmword_7FF611D36BB8;
*(_DWORD *)((char *)&xmmword_7FF611D36BB8 + 1) ^= *(_DWORD *)((char *)&v80[2] + 1);
BYTE8(xmmword_7FF611D36BB8) ^= LOBYTE(v80[2]);
*(_DWORD *)((char *)&xmmword_7FF611D36BB8 + 9) ^= *(_DWORD *)((char *)&v80[2] + 1);
LOBYTE(xmmword_7FF611D36BC8) = LOBYTE(v80[2]) ^ xmmword_7FF611D36BC8;
*(_DWORD *)((char *)&xmmword_7FF611D36BC8 + 1) ^= *(_DWORD *)((char *)&v80[2] + 1);
BYTE8(xmmword_7FF611D36BC8) ^= LOBYTE(v80[2]);
*(_DWORD *)((char *)&xmmword_7FF611D36BC8 + 9) ^= *(_DWORD *)((char *)&v80[2] + 1);
HIBYTE(xmmword_7FF611D36BC8) ^= HIBYTE(v80[2]);
v60 = 0LL;
v59 = xmmword_7FF611D36BB8;
v58 = *(_OWORD *)&pbBuffer;
*((_QWORD *)&v43 + 1) = *((_QWORD *)&xmmword_7FF611D36BC8 + 1);
*(double *)&v43 = sub_7FF611D11000(v81, &v58);
lpBuffer_4 = lpBuffer_3; // AES解密
v81[15] = v43;
if ( NextSize )
{
v45 = (char *)((char *)&v81[15] - lpBuffer_3);
v46 = (((unsigned __int64)NextSize - 1) >> 4) + 1;
do
{
v47 = *lpBuffer_4;
sub_7FF611D118E0(lpBuffer_4, v81);
n16_1 = 16LL;
do
{
*(_BYTE *)lpBuffer_4 ^= *((_BYTE *)lpBuffer_4 + (_QWORD)v45);
lpBuffer_4 = (_OWORD *)((char *)lpBuffer_4 + 1);
--n16_1;
}
while ( n16_1 );
v45 -= 16;
v81[15] = v47;
--v46;
}
while ( v46 );
v42 = BYTE1(v80[2]);
v41 = v80[2];
}
*((_DWORD *)&pbBuffer + 1) ^= HIDWORD(v80[2]);
*((_DWORD *)&pbBuffer + 3) ^= HIDWORD(v80[2]);
DWORD1(xmmword_7FF611D36BB8) ^= HIDWORD(v80[2]);
HIDWORD(xmmword_7FF611D36BB8) ^= HIDWORD(v80[2]);
DWORD1(xmmword_7FF611D36BC8) ^= HIDWORD(v80[2]);
HIDWORD(xmmword_7FF611D36BC8) ^= HIDWORD(v80[2]);
pbBuffer ^= v41;
*(&pbBuffer + 1) ^= v42;
*((_WORD *)&pbBuffer + 1) ^= WORD1(v80[2]);
*(&pbBuffer + 8) ^= v41;
*(&pbBuffer + 9) ^= v42;
*((_WORD *)&pbBuffer + 5) ^= WORD1(v80[2]);
LOBYTE(xmmword_7FF611D36BB8) = v41 ^ xmmword_7FF611D36BB8;
BYTE1(xmmword_7FF611D36BB8) ^= v42;
WORD1(xmmword_7FF611D36BB8) ^= WORD1(v80[2]);
BYTE8(xmmword_7FF611D36BB8) ^= v41;
BYTE9(xmmword_7FF611D36BB8) ^= v42;
WORD5(xmmword_7FF611D36BB8) ^= WORD1(v80[2]);
LOBYTE(xmmword_7FF611D36BC8) = v41 ^ xmmword_7FF611D36BC8;
BYTE1(xmmword_7FF611D36BC8) ^= v42;
WORD1(xmmword_7FF611D36BC8) ^= WORD1(v80[2]);
BYTE8(xmmword_7FF611D36BC8) ^= v41;
BYTE9(xmmword_7FF611D36BC8) ^= v42;
WORD5(xmmword_7FF611D36BC8) ^= WORD1(v80[2]);
memset(v80, 0, sizeof(v80));
if ( NextSize )
{
if ( (NextSize & 0xF) == 0 )
{
v49 = &lpBuffer_3[NextSize - 1];
v50 = (unsigned __int8)*v49;
if ( (unsigned __int8)(v50 - 1) <= 0xFu )
{
v51 = 0LL;
if ( !(_BYTE)v50 )
{
LABEL_55:
v52 = NextSize - v50;
goto LABEL_57;
}
while ( *v49 == (_BYTE)v50 )
{
++v51;
--v49;
if ( v51 >= v50 )
goto LABEL_55;
}
}
}
}
v52 = 0;
LABEL_57:
if ( v52 ) // 显示解密结果
{
lpBuffer_3[v52] = 0;
MessageBoxA(hWndParent, lpBuffer_3, "Secret", 0x40u);
}
else // 解密失败
{
sub_7FF611D11AD0(Name, 0x50uLL, "\\\\.\\mailslot\\mailslot_%x");
hFile = CreateFileA(Name, GENERIC_WRITE, FILE_READ_DATA, 0LL, OPEN_EXISTING, FILE_READ_ATTRIBUTES, 0LL);
WriteFile(hFile, lpBuffer_3, NextSize, lpNumberOfBytesWritten, 0LL);
CloseHandle(hFile);
memset(lpBuffer_3, 0, NextSize);
memset((char *)v81 + 8, 0, 0xF8uLL);
*(_QWORD *)&v81[0] = 0x130303030LL;
v69 = v81;
v68 = 0LL;
NtConvertBetweenAuxiliaryCounterAndPerformanceCounter(1LL, &v69, &v68, 0LL);
}
return DefWindowProcW(hWndParent, Msg, wParam, lParam);
}
PostQuitMessage(0);
}
return 0LL;
}

获取当前用户名并计算用户名hash,打开创建邮件槽时映射的内存,拿到邮件槽的句柄,调用ReadFile获取密文

与内核通信获取当前进程的CreateTime,异或解密刚刚被异或加密的pbBuffer,也就是(Key+IV)共48字节。

将解密的数据与输入的secret进行比对,正确则输出secret窗口,不正确就打开当前用户名的邮件槽,将解密数据写回邮件槽,设置Signal=1,触发了驱动的地址异常,触发了蓝屏

所以触发蓝屏的原因是:输入用户名解密secret时,解密出的数据与当时输入的明文不匹配。我们从windbg中发现内存中有两个apersonalVault进程,两个进程分别输入了两个不同的用户名创建了两个邮件槽,其中一个进程输入了另一个进程的用户名,得到了另一个进程的密文,这个密文不是用它的密钥加密的,所以肯定无法正确解密,比对失败后Signal=1,触发蓝屏

如何解密:获取两个进程中被加密的48字节数据(key+IV),再获取两个进程的CreateTime,异或解密出真正的key和IV

获取两个进程的邮件槽中存储的密文

进程1的密文直接用进程1的key和IV进行AES-CBC解密出明文

进程2的密文由于被进程1误解密过一次并覆盖到邮件槽,所以先用进程1的key和IV加密一次,再用进程2的Key和IV解密出明文

数据提取

image-20251028132711060

找到了pbBuffer在程序中的地址

windbg中找到两个进程

1
2
3
4
5
6
7
8
9
PROCESS ffffef063fbe8080
SessionId: none Cid: 0fa8 Peb: 9f83185000 ParentCid: 14b0
DirBase: 840fe000 ObjectTable: ffffa687a7241740 HandleCount: 161.
Image: aPersonalVault.exe

PROCESS ffffef063fbc1080
SessionId: none Cid: 26f0 Peb: 9216e1a000 ParentCid: 14b0
DirBase: 1296dc000 ObjectTable: ffffa687a6dd3b40 HandleCount: 165.
Image: aPersonalVault.exe

先使用.process /r /p Eprocess地址,切换到对应进程,然后通过偏移读取数据

image-20251028133350490

lm vm aPersonalVault查看进程模块信息,可以看到aPersonalVault的两个进程基址是0x00007ff611d10000

查看了进程1的密钥,切换进程后查看进程2的密钥

image-20251028134025384

CreateTime获取:dt nt!_EPROCESS ffffef063fbe8080

image-20251028134502257

image-20251028134704545

1
2
进程1:0x01dc3fe6ed454439
进程2:0x01dc3fe6f02a77eb

密文获取

查看邮件槽句柄的地址

image-20251028140353307

切换进程,读取进程1和进程2中邮件槽句柄

image-20251028141459562

image-20251028141408189

两个句柄分别是0x27C和0x280

获取该进程0x27c句柄的信息

image-20251028141709390

image-20251028162827492

\mailslot_e60a23e2是邮件槽的唯一标识名,Object地址是ffffe70b7f057380,按文件对象的结构进行解析

image-20251028142706844

image-20251028162930357

DeviceObject说明这个文件对象 ffffe70b7f057380 关联到设备对象 ffffef063d2d9770,所有对该文件对象的 I/O 操作最终都会路由到这个设备对象处理

点击DeviceObject,发现它是Msfs驱动

image-20251028145500937

Msfs分析

分析本机的msfs.sys

image-20251028160411866

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// IRP_MJ_CREATE_NAMED_PIPE (2) - 创建命名管道请求处理
DriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)&DispatchCreateNamedPipe;

// IRP_MJ_READ (3) - 读请求处理
DriverObject->MajorFunction[3] = (PDRIVER_DISPATCH)DispatchRead;

// IRP_MJ_WRITE (4) - 写请求处理
DriverObject->MajorFunction[4] = (PDRIVER_DISPATCH)&DispatchWrite;

// IRP_MJ_QUERY_INFORMATION (5) - 查询信息请求处理
DriverObject->MajorFunction[5] = (PDRIVER_DISPATCH)&DispatchQueryInformation;

// IRP_MJ_SET_INFORMATION (6) - 设置信息请求处理
DriverObject->MajorFunction[6] = (PDRIVER_DISPATCH)&DispatchSetInformation;

// IRP_MJ_QUERY_VOLUME_INFORMATION (10) - 查询卷信息请求处理
DriverObject->MajorFunction[10] = (PDRIVER_DISPATCH)DispatchQueryVolumeInformation;

// IRP_MJ_DIRECTORY_CONTROL (18) - 目录控制请求处理
DriverObject->MajorFunction[18] = (PDRIVER_DISPATCH)&DispatchDirectoryControl;

// IRP_MJ_DEVICE_CONTROL (12) - 设备控制请求处理
DriverObject->MajorFunction[12] = (PDRIVER_DISPATCH)DispatchDeviceControl;

转到读处理函数分析

image-20251028161022717

FsContext + 0x118处存储了数据,进入sub_1C000A0A0

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
_QWORD *__fastcall PerformSyncRead(
_QWORD *IoStatusBlock,
ULONG_PTR FileControlBlock,
int ReadFlags,
__int64 UserBuffer,
unsigned int RequestedLength,
_DWORD *BytesNeeded)
{
ULONG_PTR DataBlock; // r14
__int64 DataBuffer; // rdx
unsigned int DataSize; // esi
unsigned int BytesToCopy; // eax
__int64 CopiedBytes; // rdi
ULONG_PTR ErrorParameter; // rax

// 初始化IO状态块
*IoStatusBlock = 0LL;
IoStatusBlock[1] = 0LL;

// 获取数据块指针(文件控制块+24处的指针减去8)
DataBlock = *(_QWORD *)(FileControlBlock + 24) - 8LL;

// 获取数据缓冲区和数据大小
DataBuffer = *(_QWORD *)(DataBlock + 40);
DataSize = *(_DWORD *)(DataBlock + 32);

// 检查请求长度是否足够
if ( RequestedLength < DataSize )
{
if ( ReadFlags != 4 ) // 如果不是部分读取模式
{
// 返回缓冲区太小的错误
*(_DWORD *)IoStatusBlock = STATUS_BUFFER_TOO_SMALL; // 0xC0000023
return IoStatusBlock;
}

// 部分读取模式,设置状态为缓冲区溢出
*(_DWORD *)IoStatusBlock = STATUS_BUFFER_OVERFLOW; // 0x80000005
BytesToCopy = RequestedLength;
}
else
{
// 请求长度足够,复制全部数据
BytesToCopy = *(_DWORD *)(DataBlock + 32); // DataSize
}

// 实际字节数
CopiedBytes = BytesToCopy;

// 执行内存复制:从数据缓冲区复制到用户缓冲区
MemoryCopy(UserBuffer, DataBuffer, BytesToCopy);

// 设置需要的字节数(原始数据大小)
*BytesNeeded = *(_DWORD *)(DataBlock + 32);

// 如果是完整读取且缓冲区足够大
if ( DataSize <= RequestedLength )
{
if ( ReadFlags != 4 ) // 如果不是部分读取模式
{
// 验证数据块的完整性
ErrorParameter = ValidateDataBlock(FileControlBlock, DataBlock);
if ( ErrorParameter )
{
// 如果验证失败,触发系统崩溃(BugCheck)
KeBugCheckEx(
0x52u, // FAT_FILE_SYSTEM
1uLL, // 错误代码
ErrorParameter, // 错误参数1
FileControlBlock, // 错误参数2
DataBlock // 错误参数3
);
}
}

// 设置成功状态
*(_DWORD *)IoStatusBlock = STATUS_SUCCESS; // 0
}

// 设置实际复制的字节数
IoStatusBlock[1] = CopiedBytes;

return IoStatusBlock;
}

Context+0x18处是其数据的LIST_ENTRY- 8则是原始数据结构的头部,+ 0x28则是数据指针,+ 0x20则是数据长度

list_entry

image-20251028161747407

data length

image-20251028161929790

data ptr

image-20251028162030011

密文为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
密文_1080:
95 c2 4e 06 41 92 cf 1c-52 18 01 32 86 4e 38 5e
e2 ee 24 7b 68 0f dc be-7e 14 3b ee 47 fd 22 13
b3 71 1c 1b 40 08 e2 b3-ff 78 34 eb e5 3e 7d 53
48 53 59 16 74 46 f5 8b-eb 9d 96 1c 57 13 5e bb
2a 18 fa 30 98 29 23 db-99 73 d6 ac 4b 01 88 c5
7e 38 b0 a5 8d 3e 71 b0-fe c9 8e 7b d8 e3 0c 2c
27 07 b9 94 94 68 53 af-71 98 84 3b 89 ac 21 de
56 67 7c c3 e6 cf 43 b8-d6 d5 39 61 5c 76 31 79
密文_8080:
3c 5b 7d 22 a8 62 ff 7e-89 ef f9 6d 26 e3 d3 e6
2f c3 9d ff 8a c5 4c 0b-e2 d1 02 47 39 45 96 83
46 ce 20 68 f6 0a f0 65-2b 5b 85 be 4f dc f5 63
ee e2 74 9f 4e 07 71 29-03 94 7e 5a b0 bf 66 75
6c 4b 9c d7 c2 10 fd 45-4d a9 65 36 84 02 f9 8e

image-20251028162155076

image-20251028163207874

解密

需要的数据已经得到,开始解密

首先把48字节和对应的CreateTime异或得到真正的key和IV

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
import struct

def main():
EncKeyIV_8080 = [0x45, 0x71, 0x9c, 0x96, 0xf3, 0x11, 0x80, 0x2e, 0x9d, 0xac, 0xf2, 0x94, 0x64, 0x4e, 0xc7, 0xe5,
0x52, 0xa4, 0x46, 0x35, 0x5e, 0x86, 0x72, 0x75, 0xa9, 0xb7, 0xbf, 0x80, 0x52, 0xd0, 0x06, 0xa2,
0xc9, 0x62, 0xdd, 0x9c, 0xf2, 0xee, 0x60, 0x5e, 0xa0, 0x6b, 0x4c, 0xcf, 0xb5, 0xef, 0x0d, 0x82]
CreateTime_8080 = 0x01dc3fe6ed454439

EncKeyIV_1080 = [0x20, 0x51, 0xb5, 0x07, 0x07, 0x70, 0xb8, 0x0e, 0xfc, 0xa3, 0x9c, 0x30, 0x54, 0x92, 0xd6, 0x44,
0x9d, 0x08, 0xe2, 0x02, 0xfe, 0x81, 0xd1, 0xf6, 0x70, 0xb6, 0x86, 0x35, 0x20, 0xb4, 0xa6, 0x6e,
0xaf, 0x40, 0xdf, 0x21, 0xda, 0x73, 0x21, 0x01, 0x3a, 0xfa, 0x99, 0x1c, 0xe6, 0x56, 0x69, 0x00]
CreateTime_1080 = 0x01dc3fe6f02a77eb

# 对8080数据进行异或解密
for i in range(0, 48, 8):
# 将8个字节转换为64位整数
current_value = struct.unpack('<Q', bytes(EncKeyIV_1080[i:i+8]))[0]
# 异或操作
xored_value = current_value ^ CreateTime_1080
# 将结果转换回字节并替换原数据
xored_bytes = struct.pack('<Q', xored_value)
for j in range(8):
EncKeyIV_1080[i + j] = xored_bytes[j]

# 对1080数据进行异或解密
for i in range(0, 48, 8):
# 将8个字节转换为64位整数
current_value = struct.unpack('<Q', bytes(EncKeyIV_8080[i:i+8]))[0]
# 异或操作
xored_value = current_value ^ CreateTime_8080
# 将结果转换回字节并替换原数据
xored_bytes = struct.pack('<Q', xored_value)
for j in range(8):
EncKeyIV_8080[i + j] = xored_bytes[j]

print("AES Key_1(8080): ", end="")
for i in range(32):
print(f"{EncKeyIV_1080[i]:02X} ", end="")
print()

print("AES IV_1(8080): ", end="")
for i in range(32, 48):
print(f"{EncKeyIV_1080[i]:02X} ", end="")
print()

print("AES Key_2(1080): ", end="")
for i in range(32):
print(f"{EncKeyIV_8080[i]:02X} ", end="")
print()

print("AES IV_2(1080): ", end="")
for i in range(32, 48):
print(f"{EncKeyIV_8080[i]:02X} ", end="")
print()

if __name__ == "__main__":
main()


1
2
3
4
AES Key_1(8080): 7C 35 D9 7B 15 2E 5C 2F A4 E8 B7 79 82 71 1B E4 6B E0 03 D8 B8 B9 AE 74 90 F3 FA 6D B4 EF DA A3 
AES IV_1(8080): F0 26 98 71 14 D1 BC 5F 99 2F 09 22 53 D0 D1 83
AES Key_2(1080): CB 26 9F F7 E1 4F 64 0F 17 D4 B6 C0 B2 AD 0A 45 76 7F C8 F2 18 BE 0D F7 9B C1 AC C5 C6 8B 7A 6F
AES IV_2(1080): 44 37 F5 D1 3C 4C FD 00 D1 8D B3 EC 00 69 B5 01

密文1解出来一句话

image-20251028163945644

密文2

image-20251028164812895

flag{Making_challenge_is_hard_manage_a_secure_vault_is_more_difficult} 得到flag