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

下载地址:
bdaK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Z5j5i4u0W2i4K6u0r3K9h3&6A6N6q4)9K6c8Y4y4#2M7X3I4Q4x3@1c8x3L8K6b7#2K9@1k6%4f1%4N6J5h3s2A6E0y4e0u0s2c8i4j5#2P5X3c8%4
取码(GAME)
其拿到手是个DMP文件,8G fulldump。
直接看一下堆栈情况,发现是一个从EXE 到 SYS中 并触发蓝屏的情况。

把栈帧切到驱动里面看一下

显然的他是故意触发蓝屏,解引用0指针。
肯定要开始分析了,那么需要把驱动、应用,DUMP出来分析。
显然可以直接.writemem来完成,但是会有问题。

问题就是,内存被换出了,并没有驻留,故此内存dump会缺页。
驱动并不明显,但是到了EXE这里就会出大问题,缺很多东西,那么需要换一种办法dump了。
我选择的是折磨自己的办法,写个windbg插件,这玩意AI竟然写不明白。只能自己摸索。
这里花了大量时间
代码如下:
驱动真正入口点很容易定位,无论是字符串还是什么。

可以往下分析一下。

稍微往下看一下就能发现其获取了ntoskrnl的基地址,并通过nt!HalPrivateDispatchTable表获取了HalTimerConvertAuxiliaryCounterToPerformanceCounter的地址(nt!HalPrivateDispatchTable + 0x398)并替换了指针,显然用于通信。
后面则是拿了全局的进程链表。
那么下一步就是看通信函数了。

其中需要恢复一下结构,其轻而易举。
显然的,当function为1的时候就是蓝屏,当2的时候根据pid拿到该进程的CreateTime。
EPROCESS 的偏移如下:

观察一下所有函数,就可以发现此时驱动已经分析完了,并没有什么有用的函数了。

首先查看一下进程列表,会发现有2个进程名字一样的进程(就是一个exe开了2),其中一个引发了蓝屏。

蓝屏的进程为ffffef063fbc1080(EPROCESS)

这里需要用我的插件dump出来并修复PE结构(我反正是手修的)。把2个EXE都dump出来,并把引发蓝屏的进程称之为A,另一个称之为B。
由于2个进程是一样的,这里选取A进程的dump着重分析。
当修好PE结构后,其导入表应该是都在的。

入口点一眼盯真发现其是创建了一个窗口程序,处理程序为sub_7FF611D11D10。直接点进去分析。
可以发现其创建了2按钮(一个是创建,一个是检查)2个输入的地方(一个是用户名,一个是秘密)。


这里比较长,就直接讲是什么意思。
那么这一时刻又有老铁问了?为什么是AES。
其实FindCrypt已经给了答案。


这里比较长,就直接讲是什么意思。
那么我们需要考虑其为什么蓝屏?
那么为什么会出现这个问题?
我们抓住一个点:其邮件槽的句柄是通过共享内存来获取的,共享内存则是根据用户名一一对应的。 进而当2个进程输入一个同一个用户名则会导致混乱。
此时我们便可以进行现场还原,根据dumpfile。
在引起蓝屏的进程中查看句柄,情况真相大白。

不难看出mailslot_1359e83被引用了2次,其表明是A、B进程都引用了他,进而A进程用自己的密钥,解密B进程密钥加密的数据,导致与自身不匹配引发的蓝屏。
所以要解密其秘密数据,需要先用A进程的密钥加密回去,再用B进程的密钥解密。
而mailslot_e60a23e2是A进程自身的秘密数据,用A进程的密钥即可解密。
此时我们逻辑理清了,那么现在需要数据。Key、IV、CreateTime都可以直接拿到,难点则在邮件槽的数据。
邮件槽(mailslot)是一种跨进程通信的Windows服务,其基于文件。
具体的邮件槽原理在《Windows内核原理与实现》中写的很清楚,这里不在赘述,而是讲述一种分析思路。
我们从只有一个引用的邮件槽开始分析。
首先查看其对象类型,发现其是文件对象。

那么此时先按文件对象的结构进行解析。
此时需要关注其是什么驱动管理了该文件对象。

现在真相大白了,msfs驱动管理了邮件槽的对象,我们需要对其分析。

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

此时转到MsFsdRead函数看一下。

此时可以发现,_FILE_OBJECT的FsContext成员为关键成员,其0x118处存储了数据。
进入MsReadDataQueue进一步分析。

其中会发现在0x18处是其数据的LIST_ENTRY,- 8则是原始数据结构的头部,+ 0x28则是数据指针,+ 0x20则是数据长度。
回归到具体数据上则是这样,0xffffe70b`7e814260是上文中的FsContext

CreateTime只需要dt _EPROCESS的方式就能看到了。
此时我们理一下数据大致如下:
那么请注意,由于AESkey和IV都是亦或过自己的CreateTime,解密时仍然需要。
先看简单的,0x80长度的密文对应的时只有一个引用的(A进程)。
此时需要把AESkey和IV与A进程的CreateTime逐字节亦或并解密AES即可。

可以发现能解出来一句话,说明另一个才有flag。
那么按照刚才的逻辑来捋一下。

此时成功解密:
flag{Making_challenge_is_hard_manage_a_secure_vault_is_more_difficult}
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');
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;
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`);
}
}
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');
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;
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)
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-10-31 13:09
被moshuiD编辑
,原因: