免责声明:
背景
在红队演习期间,通常会在受感染的工作站中维护命令和控制 (C2) 并在个人驱动器上搜索文件。有时,有可能发现密码 Excel 文件,但尝试打开时会出现密码提示。此时,人们可能会使用 office2hashcat 提取 Excel 密码哈希并尝试使用 hashcat 破解它,这可能很有挑战性,具体取决于密码的复杂性。
被入侵的用户正在他的工作站上打开受密码保护的 Excel 文件。我的队友想出了一个绝妙的主意,检查 Excel 进程的内存,看看密码是否存储在那里,这就是我们的研究之旅的开始。
解Excel如何处理输入的密码的思考过程
在本节中,我们将引导您完成对“Excel.exe”进行逆向工程的过程,以深入了解加密电子表格的密码处理方式。这种理解可能使我们能够在 Excel 进程中检索受密码保护的 Excel 文件的密码(如果该文件正在打开)。
在开始逆向 Excel 之前,先使用“高度安全”的密码“ VERYSECUREPASSWORD ”作为示例创建了一个受密码保护的 Excel 文件。
之后,我们开始对“Excel.exe”进行逆向工程,以确定它开始处理输入密码的点。幸运的是,“Excel.exe”的 PDB 可用,这使我们能够在 IDA 中搜索与密码相关的函数。一个引起我们注意的函数是“PasswordDialog”。
然后我们启动Windbg在这个函数上设置一个断点,以观察当出现密码提示时它是否会被触发。
包含“密码”的搜索功能
当我打开受密码保护的 Excel 时,断点按预期触发。从那里,我们能够跟踪调用堆栈并到达“HrFullSaveFlushPkgEx”函数。
“PasswordDialog”函数的调用堆栈
经过多次尝试,我们发现“HrFullSaveFlushPkgEx”调用了一个名为“mso20win32client!MsoHrLoadCryptSession”的函数,该函数似乎负责处理密码。它将输入的密码作为第一个参数。
输入的密码被用作“MsoHrLoadCryptSession”函数的第一个参数
由于这个函数没有文档记录,我决定在 GitHub 上搜索更多信息。这让我找到了 Windows XP 的源代码,在那里我们发现了关于该函数的注释。检查注释后,很明显我们的密码很可能被用来生成密钥。
Windows XP 源代码提供了该函数的描述
在为受密码保护的 Excel 文件提供有效密码后,我们执行了 crypt 函数及其内部函数“HrCheckPwd”。返回值 0 表示密码验证成功。
MsoHrLoadCryptSession 的内部函数
密码有效时返回 0
相反,提供不正确的密码会导致返回错误代码,这并不奇怪。
密码不正确时返回0xe0040603
现在我们对 Excel 如何验证输入的密码有了更好的了解,下一步就是确定明文密码是否存储在进程的其他地方,例如堆中,或者是否会被擦除。
继续追踪流程,我们最终找到一个以密码作为参数的函数。
以输入的密码作为参数的函数
仔细检查后,您可能会注意到该函数不仅将密码作为参数,而且还在实际密码之前包含两个额外的字节,似乎代表密码长度,正如我们在测试中观察到的那样。
密码结构
以下是该结构在伪代码中的示例:
typedef struct Password {
DWORD passwordLength;
WCHAR password[MAX_PASSWORD_LENGTH];
} Password;
进一步分析,我们发现了一个有趣的函数,叫做“FHpAllocCore”:
出现一个分配内存的函数
在“FHpAllocCore”函数中,有一个名为“AllocateEx”的内部函数,似乎与内存分配有关。
“FHpAllocCore”中的“AllocateEx”
进一步检查,我们发现它是 NTDLL RtlAllocateHeap 的包装函数。此函数将从堆中分配一个大小为 0x26(十进制为 38)的块,该块的大小正好是密码长度(2)+ WCHAR 中的密码长度(36)的大小。然后我们获得了新分配块的堆地址作为返回值。(在本例中为 0x1a35537a370)
NTDLL RtlAllocateHeap 的包装函数
完成“FHpAllocCore”函数并继续前进几步后,我们到达另一个名为“BltB”的函数,该函数接受三个参数(密码、堆地址和密码长度)。可以合理地假设此函数将对输入的密码执行某些操作。
从 FHpAllocCore 到 BltB 的 IDA 视图
调用 BltB 时注册设置
BltB 函数内部有一个名为“memmove”的函数。显然,该函数会尝试将值从一个内存地址移动到另一个内存地址。
通过在“memmove”函数处设置断点,我们观察到它试图将密码结构(密码长度+密码)从堆栈复制到堆地址,即之前分配的堆块地址。
执行 memmove 将密码结构从堆栈复制到堆
经过多次重新运行并打开受密码保护的Excel文件后,我们确认我们的假设是正确的。输入的密码确实保留在堆中。
密码存储在堆中
值得一提的是,错误的密码并不会保存在堆中,而且我们经过多次测试发现,密码保存在内存中是因为再次保存Excel文件时,Excel会使用堆内存中的这个密码,通过调用“MsoHrCreateCryptSession”函数重新加密文件。
确认输入的密码确实保存在堆内存中后,我们的下一步是确定如何以编程方式找到它。虽然可以搜索每个堆块以查找密码,但这种方法过于耗时。
幸运的是,我们能够在堆中存储密码的地址附近发现一个签名模式。由于密码的堆地址也存储在堆本身中,因此这一发现大大简化了我们的搜索过程。
存储密码的地址存储在堆中
将另一个 Excel 实例与内存中存储的密码进行比较后,我们发现两个实例共享一个共同的签名。此外,它们与存储密码的地址的偏移量(0x30)完全相同。
签名寻找存储密码的地址
因此,我们进行了额外的逆向工程,以更好地理解此签名。进一步的调查发现,签名的一部分(0x2375d68f)是硬编码的。
签名存在于汇编代码中
我们发现的签名是 SH::SH 函数参数的一部分
这个反汇编代码更清楚地显示了这个构造函数中硬编码签名是如何构造的。
SH::SH 函数的反汇编代码
此外,签名值被传递给名为“LogObjectLifetimeEvent”的函数。在“LogObjectLifetimeEvent”的反汇编代码中,“0x05”和“0xff”代表LifeTimeObjectType,而“0x2375d68f”代表ulong类型。
“LogObjectLifetimeEvent” 的反汇编代码
根据以上信息,我们可以得出结论,签名是一个硬编码值,而“0x05”和“0xFF”是 LifeTimeObjectType 的枚举值。此外,“0x2375D68F”表示此日志对象的静态 ulong 值。
总结和概念证明
根据我们的研究,我们发现当在 Excel 文件中输入并验证受密码保护的电子表格的密码时,该密码将存储在进程的堆内存中。此外,我们在内存中存储密码的地址附近发现了一个通用签名,可用于更有效地定位密码。
基于此研究,我们创建了一个名为“officedump”的工具,目的是利用讨论到的签名和偏移量从进程内存中提取密码。
officedump 的 POC
目前我们仅在安装了 Microsoft Office 365 的 Windows 10/11 上进行了测试。请随时告诉我您的测试结果。
作为一项额外功能,“officedump”还具有转储 Word 进程内存中受密码保护的 Word 文档密码的额外功能。但需要注意的是,与 Excel 相比,此进程使用的偏移量和签名不同。最后但并非最不重要
该工具的链接如下:
https://github.com/elephacking/officedump
打个广子
我们用友专业的团队,可承接渗透测试,攻防演练,应急响应、钓鱼演练、ctf培训等比赛项目
原文始发于微信公众号(影域实验室):红队技术-揭示存储在进程内存中的 Excel 密码秘密
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论