强网杯Secured Personal Vault预期解WP

moshui Lv3

背景

该题唯一解是非预期。
然后该题为蓝屏DUMP,需要分析内核,那必须搞一下。
image.png

预期解

其拿到手是个DMP文件,8G fulldump。

直接看一下堆栈情况,发现是一个从EXE 到 SYS中 并触发蓝屏的情况。
image.png
把栈帧切到驱动里面看一下
image.png
显然的他是故意触发蓝屏,解引用0指针。
肯定要开始分析了,那么需要把驱动、应用,DUMP出来分析。

DUMP

显然可以直接.writemem来完成,但是会有问题。
image.png
问题就是,内存被换出了,并没有驻留,故此内存dump会缺页。

驱动并不明显,但是到了EXE这里就会出大问题,缺很多东西,那么需要换一种办法dump了。

我选择的是折磨自己的办法,写个windbg插件,这玩意AI竟然写不明白。只能自己摸索。

这里花了大量时间
代码如下:

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

function initializeScript()
{
return [new host.functionAlias(safeDumpMemory, "MDump")];
}


async function safeDumpMemory(filePath, startAddress, imageSize)
{
const pageSize = 4096;
let totalBytesWritten = 0;
let log = host.diagnostics.debugLog;

log(`[*] 开始安全转储内存...\n`);
log(` - 输出文件: ${filePath}.py\n`);
log(` - 起始地址: 0x${startAddress.toString(16)}\n`);
log(` - 总大小: 0x${imageSize.toString(16)} bytes\n`);

try
{
// 创建或覆盖目标文件
let file = host.namespace.Debugger.Utility.FileSystem.CreateFile(filePath + ".py", "CreateAlways");
var textWriter = host.namespace.Debugger.Utility.FileSystem.CreateTextWriter(file,'Utf8');
// 创建一个4KB的全零缓冲区,用于填充坏页
let nullBuffer = new ArrayBuffer(pageSize);
let nullView = new Uint8Array(nullBuffer);
textWriter.WriteLine(`# python ${filePath}.py
buffer = [
`);
// 逐页循环
for (let offset = host.Int64(0); offset.compareTo(imageSize) < 0; offset = offset.add(pageSize))
{
let currentAddress = startAddress.add(offset);
let bytesToWrite = (imageSize.subtract(offset).compareTo(pageSize) < 0) ? imageSize.subtract(offset) : host.Int64(pageSize);

try
{
// 尝试读取当前内存页
let buffer = host.memory.readMemoryValues(currentAddress,bytesToWrite);
textWriter.WriteLine(buffer);
textWriter.WriteLine(",");
}
catch(e)
{
log(`[!] 错误: ${e}\n`);
// 如果读取失败,就写入零
log(`[!] 无法读取地址: 0x${currentAddress.toString(16)}。正在写入0x${bytesToWrite.toString(16)}字节的0... \n`);
textWriter.WriteLine(nullView);
textWriter.WriteLine(",");

}

totalBytesWritten += bytesToWrite;
// 每转储1页打印一次进度
if (offset.bitwiseAnd(0xFFFFF) == 0 && offset.compareTo(0) > 0) {
log(` ... 已写入 0x${offset.toString(16)} bytes\n`);
}
}
textWriter.WriteLine(`]
f = open('${filePath}','wb')
f.write(bytearray(buffer))`);
file.Close();
log(`\n[+] 转储完成! 总共写入 0x${imageSize.toString(16)} 字节 请运行${filePath}.py\n`);
}
catch(e)
{
log(`[!] 发生严重错误: ${e}\n`);
}
}

驱动分析

驱动真正入口点很容易定位,无论是字符串还是什么。
image.png
可以往下分析一下。
image.png
稍微往下看一下就能发现其获取了ntoskrnl的基地址,并通过nt!HalPrivateDispatchTable表获取了HalTimerConvertAuxiliaryCounterToPerformanceCounter的地址(nt!HalPrivateDispatchTable + 0x398)并替换了指针,显然用于通信

后面则是拿了全局的进程链表。

那么下一步就是看通信函数了。
image.png
其中需要恢复一下结构,其轻而易举。

1
2
3
4
5
6
7
struct CommuStruct
{
char sign[4];
int function;
__int64 pid;
__int64 *processCreateTime;
};

显然的,当function为1的时候就是蓝屏,当2的时候根据pid拿到该进程的CreateTime。

EPROCESS 的偏移如下:
image.png
观察一下所有函数,就可以发现此时驱动已经分析完了,并没有什么有用的函数了。
image.png

应用分析

首先查看一下进程列表,会发现有2个进程名字一样的进程(就是一个exe开了2),其中一个引发了蓝屏。
image.png
蓝屏的进程为ffffef063fbc1080(EPROCESS)
image.png
这里需要用我的插件dump出来并修复PE结构(我反正是手修的)。把2个EXE都dump出来,并把引发蓝屏的进程称之为A,另一个称之为B

由于2个进程是一样的,这里选取A进程的dump着重分析。

当修好PE结构后,其导入表应该是都在的。
image.png
入口点一眼盯真发现其是创建了一个窗口程序,处理程序为sub_7FF611D11D10。直接点进去分析。

可以发现其创建了2按钮(一个是创建,一个是检查)2个输入的地方(一个是用户名,一个是秘密)。
image.png

创建逻辑分析

image.png
这里比较长,就直接讲是什么意思。

  1. 通过与R0通信,获取本进程的CreateTime
  2. 随机生成AESkey与IV
  3. 采用AES CBC + Padding的方式来加密输入的东西
  4. 把AESkey和IV与CreateTime进行亦或,本质就是加密一下AESkey和IV
  5. 创建邮件槽(IPC通信方式之一)并把密文写入其中。
  6. 创建共享内存(根据输入的用户名),把自己的Pid和邮件槽的句柄写入其中。

那么这一时刻又有老铁问了?为什么是AES。
其实FindCrypt已经给了答案。
image.png

检查逻辑分析

image.png
这里比较长,就直接讲是什么意思。

  1. 通过用户名读取共享内存,获取邮件槽句柄
  2. 查看邮件槽中的数据,并读出来(此时为密文)
  3. 通过与R0通信,获取本进程的CreateTime
  4. 通过CreateTime解密AESkey与IV
  5. 解密密文,并重新加密AESKey与IV
  6. 判断解密是否成功
  7. 成功则弹出信息框,不成功则把解密失败的内容写入邮件槽,并引发蓝屏

那么我们需要考虑其为什么蓝屏?

  • 很显然是没解密出来正确的密文

那么为什么会出现这个问题?

  • 需要动静结合阐述这个问题。

我们抓住一个点:其邮件槽的句柄是通过共享内存来获取的,共享内存则是根据用户名一一对应的。 进而当2个进程输入一个同一个用户名则会导致混乱。

此时我们便可以进行现场还原,根据dumpfile。
在引起蓝屏的进程中查看句柄,情况真相大白。
image.png
不难看出mailslot_1359e83被引用了2次,其表明是A、B进程都引用了他,进而A进程用自己的密钥,解密B进程密钥加密的数据,导致与自身不匹配引发的蓝屏

所以要解密其秘密数据,需要先用A进程的密钥加密回去,再用B进程的密钥解密。
(主要是他也不符合padding的结果,肯定是需要做其他操作)

mailslot_e60a23e2是A进程自身的秘密数据,用A进程的密钥即可解密。

此时我们逻辑理清了,那么现在需要数据。Key、IV、CreateTime都可以直接拿到,难点则在邮件槽的数据。

邮件槽分析

邮件槽(mailslot)是一种跨进程通信的Windows服务,其基于文件。

具体的邮件槽原理在《Windows内核原理与实现》中写的很清楚,这里不在赘述,而是讲述一种分析思路。

我们从只有一个引用的邮件槽开始分析。

首先查看其对象类型,发现其是文件对象
image.png
那么此时先按文件对象的结构进行解析。
此时需要关注其是什么驱动管理了该文件对象。
image.png
现在真相大白了,msfs驱动管理了邮件槽的对象,我们需要对其分析。
image.png

直接把本地的驱动丢IDA里看一下。
既然邮件槽的对象归msfs驱动管理,我们需要关注其IRP处理函数,就可以知道读写邮件槽时如何对其操作了
image.png

此时转到MsFsdRead函数看一下。
image.png
此时可以发现,_FILE_OBJECTFsContext成员为关键成员,其0x118处存储了数据。

进入MsReadDataQueue进一步分析。
image.png
其中会发现在0x18处是其数据的LIST_ENTRY- 8则是原始数据结构的头部,+ 0x28则是数据指针,+ 0x20则是数据长度。

回归到具体数据上则是这样,0xffffe70b`7e814260是上文中的FsContext
image.png

解密

CreateTime只需要dt _EPROCESS的方式就能看到了。

此时我们理一下数据大致如下:

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
0298: Object: ffffe70b7f0673c0  GrantedAccess: 00160089 (Protected) Entry: ffffa687a73fba60
Object: ffffe70b7f0673c0 Type: (ffffe70b77835850) File
ObjectHeader: ffffe70b7f067390 (new version)
HandleCount: 2 PointerCount: 32768
Directory Object: 00000000 Name: \mailslot_1359e83 {Mailslot}
data:
ffffa687`a8d145d8 3c 5b 7d 22 a8 62 ff 7e-89 ef f9 6d 26 e3 d3 e6 <[}".b.~...m&...
ffffa687`a8d145e8 2f c3 9d ff 8a c5 4c 0b-e2 d1 02 47 39 45 96 83 /.....L....G9E..
ffffa687`a8d145f8 46 ce 20 68 f6 0a f0 65-2b 5b 85 be 4f dc f5 63 F. h...e+[..O..c
ffffa687`a8d14608 ee e2 74 9f 4e 07 71 29-03 94 7e 5a b0 bf 66 75 ..t.N.q)..~Z..fu
ffffa687`a8d14618 6c 4b 9c d7 c2 10 fd 45-4d a9 65 36 84 02 f9 8e lK.....EM.e6....


027c: Object: ffffe70b7f057380 GrantedAccess: 00160089 (Protected) (Audit) Entry: ffffa687a73fb9f0
Object: ffffe70b7f057380 Type: (ffffe70b77835850) File
ObjectHeader: ffffe70b7f057350 (new version)
HandleCount: 1 PointerCount: 1
Directory Object: 00000000 Name: \mailslot_e60a23e2 {Mailslot}
data:
ffffa687`a5efa7a8 95 c2 4e 06 41 92 cf 1c-52 18 01 32 86 4e 38 5e ..N.A...R..2.N8^
ffffa687`a5efa7b8 e2 ee 24 7b 68 0f dc be-7e 14 3b ee 47 fd 22 13 ..${h...~.;.G.".
ffffa687`a5efa7c8 b3 71 1c 1b 40 08 e2 b3-ff 78 34 eb e5 3e 7d 53 [email protected]..>}S
ffffa687`a5efa7d8 48 53 59 16 74 46 f5 8b-eb 9d 96 1c 57 13 5e bb HSY.tF......W.^.
ffffa687`a5efa7e8 2a 18 fa 30 98 29 23 db-99 73 d6 ac 4b 01 88 c5 *..0.)#..s..K...
ffffa687`a5efa7f8 7e 38 b0 a5 8d 3e 71 b0-fe c9 8e 7b d8 e3 0c 2c ~8...>q....{...,
ffffa687`a5efa808 27 07 b9 94 94 68 53 af-71 98 84 3b 89 ac 21 de '....hS.q..;..!.
ffffa687`a5efa818 56 67 7c c3 e6 cf 43 b8-d6 d5 39 61 5c 76 31 79 Vg|...C...9a\v1y


PROCESS ffffef063fbc1080 ->A(bsod) CreateTime: 0x01dc3fe6f02a77eb
SessionId: none Cid: 26f0 Peb: 9216e1a000 ParentCid: 14b0
DirBase: 1296dc000 ObjectTable: ffffa687a6dd3b40 HandleCount: 165.
Image: aPersonalVault.exe

unsigned char aes_key[] =
{
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
};
unsigned char iv[] =
{
0xAF, 0x40, 0xDF, 0x21, 0xDA, 0x73, 0x21, 0x01, 0x3A, 0xFA,
0x99, 0x1C, 0xE6, 0x56, 0x69, 0x00
};


PROCESS ffffef063fbe8080 ->B CreateTime: 0x01dc3fe6ed454439
SessionId: none Cid: 0fa8 Peb: 9f83185000 ParentCid: 14b0
DirBase: 840fe000 ObjectTable: ffffa687a7241740 HandleCount: 161.
Image: aPersonalVault.exe

unsigned char key[] =
{
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
};
unsigned char iv[] =
{
0xC9, 0x62, 0xDD, 0x9C, 0xF2, 0xEE, 0x60, 0x5E, 0xA0, 0x6B,
0x4C, 0xCF, 0xB5, 0xEF, 0x0D, 0x82
};

那么请注意,由于AESkey和IV都是亦或过自己的CreateTime,解密时仍然需要。

先看简单的,0x80长度的密文对应的时只有一个引用的(A进程)。
此时需要把AESkey和IV与A进程的CreateTime逐字节亦或并解密AES即可。
image.png
可以发现能解出来一句话,说明另一个才有flag。

那么按照刚才的逻辑来捋一下。

  1. 需要用A的AESkey和IV先亦或一下CreateTime,得到真正的AESkey和IV
  2. 由于炸了之前把解密错误的文本写入了邮件槽,则需要把密文用A的key和IV加密
  3. 用B的AESkey和IV先亦或一下CreateTime,得到真的AESkey和IV,并解密刚刚加密的密文

image.png
此时成功解密:
flag{Making_challenge_is_hard_manage_a_secure_vault_is_more_difficult}

  • 标题: 强网杯Secured Personal Vault预期解WP
  • 作者: moshui
  • 创建于 : 2025-10-22 00:54:19
  • 更新于 : 2025-10-22 10:12:50
  • 链接: https://www.moshui.eu.org/2025/10/22/qwb-SecuredPersonalVault-wp/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论