强网杯S9 Secured Personal Vault WP
Secured Personal Vault
题目考点
取证、进程&驱动逆向、msfs.sys逆向、Mailslot结构逆向
0x1 取证阶段
下载题目附件,得到MEMORY.DMP,是Windows的系统转储文件格式,使用Windbg打开该文件。
点击!analyze -v,进行dmp分析。

可以发现系统是触发了蓝屏,并且能看到蓝屏时保存的栈信息,是由aPersonalVault+0x2a4b开始调用,然后到最后personalVaultKernel+0x10f5代码触发蓝屏。

跳转到触发蓝屏的汇编代码,可以看到是xor清零了eax,然后后续又访问了[rax],导致空指针异常。

可以看到起始Call是在aPersonalVault.exe进程中,而触发蓝屏异常的是personalVaultKernel.sys,所以接下来就是Dump转储这两个文件进行分析。

使用!process 0 0命令遍历当前所有进程,发现当前运行的是存在两个aPersonalVault.exe进程。
1 | PROCESS ffffef063fbe8080 |

直接通过Windbg的.writemem指令发现无法转储成功完整的进程和sys,所以尝试使用Volatility3进行直接原文件转储。
使用Volatility3命令python .\vol.py -f .\MEMORY.DMP windows.filescan遍历所有文件,找到对应的几个文件的地址,再使用python .\vol.py -f .\MEMORY.DMP windows.dumpfiles --virtaddr 地址进行转储即可得到原始文件。

0x2 逆向阶段
1. R0驱动逆向分析
找到关键入口点函数,发现这边是遍历了进程,寻找ntoskrnl.exe,获取到HalPrivateDispatchTable函数表,最后将函数表中的HalTimerConvertAuxiliaryCounterToPerformanceCounter替换成sub_1400010A0,并且获取到全局进程表保存起来。

这边提前提一下R3进程的部分代码,实际是驱动将该函数进行Hook,将NtConvertBetweenAuxiliaryCounterAndPerformanceCounter作为R3-R0通信函数,通信数据的结构通过R3代码可以分析出来。
1 | struct ComData |

R0这边Hook替换的函数分析,可以看到Signal如果是1就会触发蓝屏部分代码,Signal为2则遍历进程链表,寻找R3通信传来的对应Pid的EProcess,然后获取CreateTime数据返回到R3。

上文EPROCESS结构通过Windbg的dt nt!_EPROCESS命令可以获得,可以通过偏移知道代码中实际对应是什么数据,若在0x1cb偏移前,则再需要dt nt!_KPROCESS,获取第一部分的结构。


实际用到的只有如下数据:
1 | struct _EPROCESS |
至此R0驱动程序分析完毕。
2. R3程序逆向分析
main函数创建了一个窗口,并且获取了NtConvertBetweenAuxiliaryCounterAndPerformanceCounter函数作为通信函数使用,下一步分析WndProc函数。

存在两个输入框以及按钮,按下Create按钮则会获取输入的用户名以及Secret。

加密流程分析
调用该函数与R0驱动进行通信,传递Signal是2,传过去的Pid是GetCurrentProcessId,也就是当前进程ID,驱动会返回当前进程的CreateTime数据。
然后调用BCryptGenRandom随机初始化一个48字节的pbBuffer,用于后续的加密使用。

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

加密完,将48字节异或上驱动返回的CreateTime数据,加密了密钥和IV。

将输入的用户名进行Hash加密,作为Mailslot路径名和映射空间名的后缀,调用CreateMailslotA创建一个邮槽并将返回句柄存在全局的hObject内存位置,将加密的密文写入到邮槽中,再创建一个内存映射,将句柄保存的内存地址进行映射。

什么是邮件槽
邮槽(MailSlot)是Windows提供的一种单向进程间通信机制,只由客户端向服务端单向发送数据。特点是单向通信以及广播通信。
流程:
- 客户端通过CreateMailslotA创建一个邮槽对象。
- 客户端通过WriteFile函数写入消息到邮槽中。
- 服务端通过CreateMailslot创建邮槽,并读取相应数据。
命名格式是”.\mailslot\路径名”
由于邮槽是有自己的一个数据储存结构,后续会对这点进行逆向分析。
解密流程分析
点击Check Secret按钮,会调用以下部分代码。
获取当前的用户名,然后将用户名Hash作为映射空间名后缀,打开刚刚同用户名创建邮槽数据时映射的内存,拿到hObject数据,也就是Mailslot的句柄,再调用GetMailslotInfo获取邮槽中的数据大小,调用ReadFile获取邮槽中的密文数据。

与R0驱动进程通信再次获取当前进程的CreateTime数据,异或解密刚刚被异或加密的pbBuffer,也就是(Key+IV)共48字节数据。

使用解密的Key和IV对刚刚获取的密文进行AES-CBC解密,与加密是对称的,解密完毕后又将(Key+IV)48字节异或CreateTime进行数据加密。

最后红框处将解密文本与窗口输入的Secret文本进行比对,若匹配则弹出”Secret”信息框,不匹配则调用下面部分代码。
打开对应用户名的邮槽对象,将当前解密的数据写回邮槽中,并且设置Signal为1,与R0驱动进行通信,也就触发了R0驱动的那个[0]地址赋值异常,触发蓝屏。

3. 题目流程分析
已知题目DMP附件是由于驱动触发蓝屏时转储的,驱动触发蓝屏原因是:R3程序请求用户名获取的对应密文,解密后不匹配当前窗口中的Secret。
而取证阶段,我们通过!process 0 0遍历进程发现了存在两个aPersonalVault.exe,所以会存在一种情况造成蓝屏:
两个进程分别通过不同的原用户名创建了两份邮槽储存密文,然后其中一个进程误输错用户名,访问了另一个进程的邮槽,获取到了不是属于他密钥加密的密文,自然也无法解密出明文数据,比对失败后则通信Signal为1的消息,触发蓝屏。
整体流程:
两个进程分别用生成的不同随机密钥加密了两份Secret,储存到了两个邮槽当中。
进程1通过错误用户名访问到了进程2的邮槽,使用进程1的密钥进行了数据解密,匹配失败,将解密后数据写回到进程2的邮槽中,然后触发蓝屏。
逆向流程:
获取两个进程中被加密的48字节(Key+IV)数据,然后再获取两个进程的
CreateTime,将48字节和CreateTime进行异或得到两份AES的密钥和IV。获取两个进程创建的邮槽中的密文数据。
进程1的密文是可以直接通过进程1的密钥和IV进行AES-CBC解密出明文1。
进程2的密文由于被进程1解密过一次,并且重写覆盖到邮槽数据中,所以需要先使用进程1的密钥和IV进行AES-CBC加密一次,再用进程2的密钥和IV进行AES-CBC解密,最终得到明文2.
所以可以直接提取两个进程的被加密的(Key+IV),以及各自的CreateTime,从而解密出原始的两份(Key+IV)。
以及最重要的是接下来需要分析邮槽相关API以及数据结构,从而提取两份密文。
4. 密钥提取
可以从R3程序找到(Key+IV)储存的偏移,即exe+0x26BA8

以下是刚刚遍历得到的两个进程数据,Eprocess分别是ffffef063fbe8080、ffffef063fbc1080。
1 | PROCESS ffffef063fbe8080 |
需要先使用.process /r /p Eprocess地址,切换到对应进程,才能通过当前进程Base地址+偏移读取到目标程序中的数据。

再通过lm vm aPersonalVault命令查看进程模块信息,获取到进程基地址,两个进程都是同一个基地址,所以需要切换进程才能访问对应进程的数据。

db 00007ff611d10000+0x26BA8查询字节,得到48字节被加密的(Key+IV),切换到另一个进程同样操作,即可以得到两份被加密的(Key+IV)。


可以使用.writemem C:\Users\admin\Desktop\Key_and_IV.dat 00007ff611d36ba8 L30命令导出目标地址0x30长度的字节到文件中。
5. 进程CreateTime获取
通过获取到的两个进程的Eprocess地址,转成_EPROCESS结构查看,即可找到CreateTime数据。
dt nt!_EPROCESS ffffef063fbe8080


1 | 0x01dc3fe6ed454439 |
6. 邮槽逆向分析
前置分析
注意到R3程序创建邮槽的句柄是保存在自身进程的全局地址中,所以可以直接像上文提取密钥相关数据一样操作,切换进程,读取对应偏移数据,得到两个进程的邮槽句柄。


获取到两个句柄分别是0x27C和0x280。
ffffef063fbc1080:

ffffef063fbe8080:

使用!handle 27c f ffffef063fbc1080命令获取该进程0x27C句柄的信息。
可以看到Name是\mailslot_e60a23e2,也就是R3看到的创建的邮槽的名字,后面的是用户名的Hash值,Object地址是ffffe70b7f057380。

此类型的句柄都是_FILE_OBJECT结构类型,使用dt nt!_FILE_OBJECT ffffe70b7f057380将目标地址转成指定结构查看。

点击DeviceObject,发现设备是Msfs驱动,也就是msfs.sys,是Windows R0层控制邮槽操作的驱动,所以接下来就对msfs.sys进行逆向分析。

msfs.sys逆向分析
由于从DMP中Dump出来的msfs.sys IAT有点小问题,所以这边使用自己主机中的msfs.sys进行逆向分析。
DriverEntry函数,这边初始化了各种IRP函数,主要看的是ReadFile对应回调的MsFsdRead,R3层通过ReadFile获取邮槽中的数据,因为只需要获取到邮槽中的数据,所以只需要分析MsFsdRead函数,得到邮槽结构,即可寻址找到邮槽数据。

MsFsdRead调用了MsCommonRead,然后再调用了MsReadDataQueue,先从当前的邮槽FileObject获取FsContext指针,加上0x88传入函数。
后续UserBuffer和Length就是要返回的数据指针以及读取大小。

可以从memcpy的传参返回去看,可以找到对应的数据指针以及数据长度的寻址计算方法,所以就可以通过上文得到的两个FileObject地址,寻址找到两个邮槽密文数据,以及数据长度。
pData = *(void**)(*(QWORD*)(FileObject->FsContext + 0x88 + 0x18) - 8 + 0x28)
Datalength = *(DWORD*)(*(QWORD*)(FileObject->FsContext + 0x88 + 0x18) - 8 + 0x20)

注意一点,上面是对主机的msfs.sys进行逆向分析,不是目标DMP系统中的msfs.sys,所以相关偏移可能不太一样,需要同样使用Volatility3进行Dump出msfs.sys,用ida分析。
发现这边实际是FsContext+0x118,并且MsReadDataQueue内的的寻址是和上文一致,只需要修改FsContext添加的偏移即可。


最终数据和数据长度寻址如下:
pData = *(void**)(*(QWORD*)(FileObject->FsContext + 0x118 + 0x18) - 8 + 0x28)
Datalength = *(DWORD*)(*(QWORD*)(FileObject->FsContext + 0x118 + 0x18) - 8 + 0x20)
7. 密文提取
通过上文获取的两个邮槽的FileObject,获取FsContext,寻址得到邮槽数据以及数据长度。

Eprocess ffffef063fbc1080:

Eprocess ffffef063fbe8080:

使用命令导出密文到文件即可。
.writemem C:\Users\admin\Desktop\EncData_1.dat ffffa687a5efa7a8 L80
.writemem C:\Users\admin\Desktop\EncData_2.dat ffffa687a8d145d8 L50
0x3 解密
通过两份CreateTime值和两份(Key+IV)值异或,得到两份AES密钥和IV。
解密代码:
1 |
|
Output:
1 | AES Key_1(1080): 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 |
上文流程分析可知,一个密文可以被其中一对密钥+IV直接解密,而另一个密文需要被一对密钥+IV加密后再用另一对密钥+IV解密,直接尝试配对解密即可。
可直接解密密文:

被解密过的密文:

flag{Making_challenge_is_hard_manage_a_secure_vault_is_more_difficult}