Secured Personal Vault复现
强网杯的一道misc题,但是是逆向
提取文件
内核!蓝屏!驱动!
首先用Windbg分析dmp文件
!analyze -v

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

下一步应该把引发蓝屏的exe dump出来
pip install volatility3
vol -f E:\MEMORY.dmp windows.filescan
找到了aPersonalVault.exe和personalVaultKernel.sys


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

逆向分析
sys驱动逆向

1 | ds分析:这是一个Windows内核驱动程序的初始化函数(DriverEntry),主要完成以下工作: |
1 | ntoskrnl.exe是Windows操作系统的一个重要内核程序,里面存储了大量的二进制内核代码,用于调度系统时使用,也是操作系统启动后第一个被加载的程序,通常该进程在任务管理器中显示为System |
遍历系统模块,寻找ntoskrnl.exe,获取HalPrivateDispatchTable的地址(从ntoskrnl.exe基地址偏移14682984处),把函数表中的HalTimerConvertAuxiliaryCounterToPerformanceCounter替换成sub_FFFFF80512BF10A0,获取到全局进程表并保存起来
1 | 选择这个函数是攻击者在隐蔽性和功能性之间的平衡选择,既能够实现内核级控制,又不容易被常规安全检测发现。 |
分析通信函数

Signal如果是1就会触发蓝屏部分代码,Signal为2则遍历进程链表,寻找R3通信传来的对应Pid的EProcess,然后获取CreateTime数据返回到R3
通信数据的结构通过分析R3程序可以得到
1 | struct ComData |
ida恢复sys文件的结构体

得到的代码
1 | __int64 __fastcall sub_FFFFF80512BF10A0(ComData *ComData, __int64 a2, __int64 a3) |
1 | 用户态程序调用HalTimerConvertAuxiliaryCounterToPerformanceCounter |
EPROCESS结构:从藏匿到曝光:EPROCESS如何成为恶意程序的终极战场? - 知乎
R3程序逆向
加密流程
之前windbg的分析可以知道系统中有两个apersonalVault进程,其中一个进程触发了蓝屏

main函数负责与内核挂钩函数通信,进入sub_7FF611D11D10分析
这里看到了通信数据的结构体
CreateWindowExW函数创建窗口

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

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

创建邮件槽用于进程通信,存储密文信息
1 | NumberOfBytesWritten = 0; // 创建邮件槽进行进程间通信 |
邮件槽
邮件槽(Mailslot)是Windows操作系统提供的一种简单的单向进程间通信(IPC)机制,只能一个进程写入,另一个进程读取

创建者写入的地址并不是一个内存地址,而是全局唯一的命名管道名称,计算用户名的哈希作为唯一标识,系统在内核中创建了一个名为\\.\mailslot\mailslot_1234ABCD 的邮件槽对象,创建者向这个地址写入加密数据存入邮件槽内核缓冲区。
读取者不知道创建者进程的邮件槽句柄,使用共享内存作为地址簿,创建者再共享内存中注册信息,读取者查找地址,读取数据
解密流程
Check按钮处理
1 | if ( !v5 ) |
获取当前用户名并计算用户名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解密出明文
数据提取

找到了pbBuffer在程序中的地址
windbg中找到两个进程
1 | PROCESS ffffef063fbe8080 |
先使用.process /r /p Eprocess地址,切换到对应进程,然后通过偏移读取数据

lm vm aPersonalVault查看进程模块信息,可以看到aPersonalVault的两个进程基址是0x00007ff611d10000
查看了进程1的密钥,切换进程后查看进程2的密钥

CreateTime获取:dt nt!_EPROCESS ffffef063fbe8080


1 | 进程1:0x01dc3fe6ed454439 |
密文获取
查看邮件槽句柄的地址

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


两个句柄分别是0x27C和0x280
获取该进程0x27c句柄的信息


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


DeviceObject说明这个文件对象 ffffe70b7f057380 关联到设备对象 ffffef063d2d9770,所有对该文件对象的 I/O 操作最终都会路由到这个设备对象处理
点击DeviceObject,发现它是Msfs驱动

Msfs分析
分析本机的msfs.sys

1 | // IRP_MJ_CREATE_NAMED_PIPE (2) - 创建命名管道请求处理 |
转到读处理函数分析

FsContext + 0x118处存储了数据,进入sub_1C000A0A0
1 | _QWORD *__fastcall PerformSyncRead( |
Context+0x18处是其数据的LIST_ENTRY,- 8则是原始数据结构的头部,+ 0x28则是数据指针,+ 0x20则是数据长度
list_entry

data length

data ptr

密文为
1 | 密文_1080: |


解密
需要的数据已经得到,开始解密
首先把48字节和对应的CreateTime异或得到真正的key和IV
1 | import struct |
1 | 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 |
密文1解出来一句话

密文2

flag{Making_challenge_is_hard_manage_a_secure_vault_is_more_difficult} 得到flag


