新工具,旧方法。 我们进行逆向工程,发现1Password的致命缺陷。每个人都喜欢密码管理器。 它们之所以出色,有很多原因。 就个人而言,我在经理中有200多个条目。 在一个地方拥有这么多机密数据的情况下,很重要的一点是,如果记录被盗用,无论是恶意软件,漏洞利用,还是仅是闲置几分钟的计算机,都必须了解损坏的程度。
《华盛顿邮报》最近根据我们的
研究发表了一篇文章。 本文有助于使人们意识到并非所有密码管理器都是相同的。
我坚信锁定的密码管理器受到了很好的保护。 如果有人可以访问我的计算机,那么由于可以从内存中可靠地清除信息,因此最大值可以依靠一堆随机字节。
1Password 4确实如此(请注意,最新版本为今天的第七个)。 在几年前切换到此模式之前,我检查了当管理器处于锁定状态时,确实没有明文密码。 因此,在妥协的情况下,攻击者将不得不处理加密存储。
保险库已锁定!在这种状态下,没有密码条目或主密码。 非常合理和正确,并且1Password 4通过了此测试。 还是不行
为了摆脱无聊的细节,我将立即说:我们能够从1Password 4中锁定的实例中恢复主密码,如下所示。
解锁1密码4并恢复您的主密码动画显示1Password 4首先以常规方式解锁,然后锁定。 之后,我们运行我们的
multipass实用程序,该实用程序成功恢复了密码。 该实用程序利用对1Password 4中的密码输入字段的不正确处理来还原经过混淆的主密码缓冲区,对其进行去模糊处理,自动解锁1Password 4,最后在控制台中显示主密码。
无聊的细节
评估密码管理器的第一步是检查内存中是否有清除的主密码。 在任何能够与过程存储空间进行交互的十六进制编辑器中,这都是可能的。 例如,免费的
HxD编辑器。 使用它来打开1Password 4内存空间。
我们立即进入1Password 4内存空间的第一个可读区域。
HxD内存表示示例没什么特别的。 但是您可以进行搜索。 例如,如果您在1Password 4解锁窗口中输入密码,但是没有单击“解锁”按钮,情况会怎样:
在字段中输入了主密码的情况下锁定了保管库1密码4密码肯定在内存中吗?
我们打开HxD,但是用主密码(“ Z3Superpass#”)搜索一行不会产生结果。
似乎1Password在输入时会以某种方式对表格进行加密或混淆。 如果该程序正常运行,则一切正常。
潜水更深
要了解为什么在解锁对话框中清楚地显示了主密码后,为什么无法在内存中找到主密码,您应该找到与其交互的代码。 有几种方法。 您可以通过本地化“ GetMessage”,“ PeekMessage”,“ GetWindowText”或其他通常用于处理用户输入的Windows API来跟踪键盘和鼠标事件的处理。 因此,我们找到了记录击键的缓冲区,并通过它们进入了加密/模糊处理例程。 但这是一个漫长且容易出错的过程,特别是对于有时管理内存的大型框架而言,因此,您必须进行许多复制和转换才能跟踪缓冲区。
相反,我们使用了自己的Thread Imager工具,该工具旨在在应用程序级别对“怪异”专有协议进行逆向工程。 它将帮助您确定1Password 4在内存中与我们的主密码进行交互的位置。 该工具“自动”识别1Password 4中与混淆密码交互的代码区域(它只是突出显示与感兴趣的数据交互的指令以进行进一步分析)。 结果看起来像这样:
Thread Imager找到与不专心的主密码交互的1Password 4代码由于主密码以混淆形式存储在内存中,因此该工具应首先显示混淆发生的位置。
第一个结果的片段显示,主密码的首次出现伴随着从地址0x7707A75D到0x701CFA10的代码转换。
Thread Imager中的详细条目突出显示了从0x7707A75D到0x701CFA10的代码过渡,而EAX和ECX寄存器使用主密码引用了缓冲区在调试器(x64dbg)中检查此位置0x7707A75D证实了我们的理论。 确实,第一次,当来自ntdll.dll库的解码功能“ RtlRunDecodeUnicodeString”结束时,出现了字符串“ Z3superpass#”。

经过一点分析,很明显,这两个函数用于混淆密码:“ RtlRunEncodeUnicodeString”和“ RtlRunDecodeUnicodeString”。 因此,主密码对于从内存中的原始复制是隐藏的,这就是为什么我们以前无法在十六进制编辑器中找到它的原因。
如果您在RtlRunEncodeUnicodeString函数的末尾研究编码缓冲区,则带有主密码的加密行如下所示:
加密的主密码在RtlRunDecodeUnicodeString'之后被解码:
解密的主密码有趣的是,此区域保存在相同的地址0x00DFA790中,我们可以在1Password 4解锁窗口中输入密码时从字面上观察其变化:
脆弱性
“ RtlRunEncodeUnicodeString”和“ RtlRunDecodeUnicodeString”是使用简单的XOR操作修改字符串的简单函数。 这还不错:它似乎是掩盖所有带有“ ES_PASSWORD”标志的本机Windows编辑控件的标准方法。
问题在于,在解锁1Password 4之后,不会从内存中清除加密的主密码。
更糟糕的是,即使在1Password 4锁定之后,它仍然保留在内存中,也就是说,我们有一个锁定的密码存储,但是在内存中具有加密的主密码。
甚至更糟糕的是,由于我们与主密码输入对话框进行了交互,因此相同的内存区域将以相同的XOR值重用,这使我们可以轻松访问编码缓冲区来创建漏洞利用。
挑战赛
要为1Password 4创建可靠的漏洞利用程序,您需要更清晰地了解程序的工作流程如何处理主密码。 使用上述工具,我们构建了输出数据图(请参见下图)。

该图使您更容易理解在何处以及涉及哪些库,以便可靠地标识内存中可以检索主密码的区域。
利用
我们现在有什么? 我们有一个锁定的存储空间,并且在内存中的某个位置存储了混淆的密码,因为该程序无法正确清理内存。
要检索它,您需要调用1Password 4中的过程,该过程将启动'RtlRunEncodeUnicodeString'和'RtlRunDecodeUnicodeString'。 因此,它将显示带有编码主密码的存储缓冲区的位置。
具有模糊主密码的存储区没有此缓冲区,将不得不陷入内部过程,Windows控件和相关内存管理机制的深渊。 也许通过这种分析可以很容易地找到一个缓冲区,但是我们并没有这样做。
看来,调用“ RtlRunEncodeUnicodeString”和“ RtlRunDecodeUnicodeString”的唯一方法是在对话框的字符中输入主密码。 这样我们就得到了所需的缓冲区。 但是我们不知道密码的长度。
我们通过拦截访问缓冲区第一个字符的代码来解决此问题,从而阻止了更改尝试。 该例程位于comctl32的控制消息循环中,该循环处理相应元素的缓冲区控制。 调用偏移量为0x70191731的“ memmove”会用输入的字符覆盖缓冲区:
(副作用:突出显示的行(黄色)将更新整个密码行)现在,我们终于拥有了创建漏洞所需的一切。 以下步骤将使我们能够提取主密码:
- 钩“ memmove”以防止覆盖主密码的第一个字节。
- 钩“ RtlRunEncodeUnicodeString”以获取混淆的主密码缓冲区的位置。
- 挂钩“ RtlRunDecodeUnicodeString”以访问在上一步中获得的混淆缓冲区。
- 在密码输入字段中输入字符并拒绝步骤1(保存整个主密码),将步骤2重定向到步骤3以对混淆后的主密码进行解码。
要执行所有这些操作,请为所有这些钩子使用处理程序代码创建一个DLL。 该库嵌入在1Password 4进程中,通过启动我们可以拦截的memmove,RtlRunEncodeUnicodeString和RtlRunDecodeUnicodeString步骤,并通过魔术恢复被混淆的主密码,将一个字符发送到主密码对话框。 大多数魔术都发生在DetourRtlRunEncodeUnicodeString中,这是'RtlRunEncodeUnicodeString'函数的钩子,如下所示:
这带给我们最终结果:使用Windows API使用的错误程序解锁任何版本的锁定存储库1Password 4:

总结
当我们第一次深入研究1Password 4的内部时,我们期望会遇到某种复杂的安全系统,并且期望所有敏感信息都将从内存中清除,就像PBKDF2过程和使用主密码的其他区域中那样。 相应的条目也被清除。 但是,由于疏忽,密码字段被视为带有隐藏密码的标准Windows API控件,这会破坏1Password 4的安全性。