rea-reverse

ASAR 解包与 Electron IPC 分析

打开 Electron 应用的 app.asar,浏览和下载其中文件,查看主进程、preload 和渲染进程入口,并列出应用在进程之间暴露的 IPC 通道和 contextBridge 接口。

归档不会离开你的浏览器. 归档在一个禁止联网的 Web Worker 中本地读取,不上传任何内容。 如何自己验证

在单独页面打开工具(无广告、无统计)

大多数 Electron 应用把 JavaScript、HTML 和各种资源打进一个叫 app.asar 的文件里,放在应用的 resources 目录下。ASAR 是一种很简单的归档格式:开头是一段描述目录树的 JSON 头,后面把各个文件的内容不压缩地依次拼接起来。里面装着决定 Electron 应用安全模型的三部分:拥有完整 Node.js 能力的 main 进程代码;决定网页能碰到什么的 preload 脚本;以及 renderer 页面。三者之间通过 IPC 通道通信。

这个工具在浏览器里打开 app.asar,列出全部文件,可以单独下载或预览其中任何一个已打包的文件,并且把代码里注册和调用的 IPC 通道整理成一张表。归档文件自始至终不会上传。

使用步骤

  1. 找到归档:Windows 和 Linux 在可执行文件旁的 resources/app.asar,macOS 在 .app 包里的 Contents/Resources/app.asar。
  2. 把它拖到上方的拖放区,或点“选择文件”。页面只解析归档头,文件在你打开时才读取。
  3. 先看总览(主进程入口、preload 脚本、头部 SHA-256),再看 IPC 通道和 contextBridge 暴露的接口,然后预览它们指向的文件。

真实输出示例

我们从 Electron 45.2.1 官方 linux-x64 发布包里取出 resources/default_app.asar,在本页打开,再把页面显示的结果复制下来。排版做了压缩(标签和值放在同一行,表格重新对齐,省去“下载”“预览”按钮),所有值都原样保留。

样本: Electron 45.2.1(MIT)自带的默认应用 default_app.asar(110,835 字节,SHA-256 eac55d6c…f510),来自 2026-11-02 下载的 electron-v45.2.1-linux-x64.zip (github.com/electron/electron/releases/tag/v45.2.1)

总览
应用名          electron
版本            —
主进程入口      main.js
preload 脚本    preload.js 设置于 default_app.js:74
渲染进程入口    —
文件            13 个文件,1 个目录,0 个链接
总大小          105.0 KB
归档大小        108.2 KB
头部 SHA-256    e6e13aba82feefcd857797ebf919a7611cae3b5664f0733548898ed3c48cad7d
unpacked 条目   0
Source map      0 个 .map 文件,3 个带 sourceMappingURL 的 JS 文件

最大的文件
icon.png        72.1 KB
index.html      11.8 KB
main.js         9.6 KB
default_app.js  3.9 KB
…

IPC 通道 (2)
通道       方向         调用                进程     文件:行
bootstrap  渲染 → 主    ipcMain.handle      主进程   default_app.js:63
bootstrap  渲染 → 主    ipcRenderer.invoke  preload  preload.js:34

contextBridge 暴露的接口 (1)
window.<名字>       暴露的键    文件:行
electronDefaultApp  initialize  preload.js:59

原生模块 (0)
unpacked 条目 (0)

能做什么

输入。 一个 ASAR 归档,通常是 resources/app.asar。页面一开始只读 16 字节的前缀和 JSON 头,文件内容用到时才读,所以再大的归档,开销也差不多只是读一个头。上限:头部最大 32 MiB,最多 100 万个条目,目录最多嵌套 128 层,分析超时时间 2 分钟。

归档内容。 你会看到:

  • 文件树:每个文件、目录和符号链接,以可折叠、可搜索的树形展示,带大小。导出的 JSON 里还有每个文件的偏移、可执行标志;归档里有逐文件的 integrity 记录时,也包含它的算法和哈希。
  • 统计:文件、目录、链接、unpacked 文件各有多少,总大小,按扩展名的分布,以及最大的 20 个文件。
  • 下载和预览:任何存放在归档里面的文件,都可以单独下载或以文本方式查看,不用把整个归档解开。符号链接会自动跟随。
  • app.asar.unpacked 条目:标为 unpacked 的文件会列出来,但它们的字节并不在 .asar 文件里,Electron 把它们放在旁边的 app.asar.unpacked/ 目录中。原生模块通常就放在那里。

Electron 分析。 报告还包括:

  • package.json:name、version 和 main。main 入口按 Node 的规则解析,默认是 index.js。
  • preload 脚本:赋给 preload 属性的字符串,例如 webPreferences: { preload: path.join(__dirname, 'preload.js') },解析成归档里的文件,并注明它是在哪个文件的哪一行配置的。
  • renderer 入口:归档中 HTML 文件里的 <script src>,解析成归档内的路径;远程 URL 会被忽略。
  • IPC 通道表:所有对 ipcMain.handle、ipcMain.on、ipcMain.once、ipcRenderer.invoke、ipcRenderer.send、ipcRenderer.sendSync、ipcRenderer.on 和 webContents.send 的调用(win.webContents.send 这种写法也算)。每一行给出通道名、API、方向(renderer 到 main,或 main 到 renderer)、所属进程,以及 文件:行号。通道不是单纯的字符串字面量时,这一行标为动态,并显示原始表达式。
  • contextBridge 暴露:每个 contextBridge.exposeInMainWorld(name, { ... }),列出暴露的名字和 API 对象的键。这正是网页能调用的全部接口。
  • 原生模块:.node 文件(编译成 Mach-O、ELF 或 PE 的 Node 扩展),带大小和是否 unpacked。
  • source map:归档里的 .map 文件,以及被扫描的 JavaScript 文件中有多少个以 sourceMappingURL 注释结尾。
  • 警告:名字可疑的条目(含 ..、斜杠或绝对路径)、指向归档外部的链接、格式不对的条目、超出归档末尾的文件,以及只扫描了一部分的 JavaScript 文件。

Electron fuse 与 ASAR 完整性。 同一个拖放区还接受 Electron 应用里的另外两种文件:

  • Electron 可执行文件,用来读出它的 fuse:Windows 是应用的主 .exe,Linux 是主可执行文件,macOS 是 Contents/Frameworks/Electron Framework.framework/Electron Framework(fuse 在 framework 里,不在 Contents/MacOS/<应用名> 里)。页面列出每个 fuse 是开、关还是已移除,它管什么(比如 RunAsNode 打开时,设置 ELECTRON_RUN_AS_NODE=1 就能让应用变成普通 Node.js 进程),以及是否符合 Electron 文档给出的加固取值。macOS 通用二进制按架构切片各给一张表。请把它当作加固检查,用在你自己构建或获授权审计的应用上。
  • XML 格式的 macOS Info.plist,显示其中的 ElectronAsarIntegrity 条目(路径、算法、哈希)。打开 app.asar 时,总览里会显示它的头部 SHA-256;EnableEmbeddedAsarIntegrityValidation fuse 打开时,Electron 拿来和 plist 里的哈希比较的就是这个值。

原理

归档开头是两个 Chromium 的 “pickle” 记录:第一个给出头部的大小,第二个把头部存成一个 JSON 字符串。页面先检查这些长度是否互相吻合,再解析 JSON,遍历其中的 files 树。已打包文件的位置是“数据区起点 + offset”,而 ASAR 把 offset 存成十进制字符串。格式处理对照 @electron/asar 4.3.1 核对过,前缀检查参考了 REA(morluto 的开源项目,MIT 许可)。

IPC 表的来源是:用一个轻量的 JavaScript 词法分析器(tokenizer)扫描归档里所有 .js、.cjs、.mjs 文件,先扫应用自己的文件,node_modules 放在最后。词法分析会跳过注释、字符串内容和正则表达式,所以字符串里出现的 "ipcMain.handle('x')" 这种文字不会被误报。然后匹配 ipcRenderer.invoke( 这样的成员调用,读取第一个参数。只有当这个参数是单个字符串,或者不含插值的模板字符串时,通道才算字面量。

判断一个文件属于哪个进程:页面从 main 入口、已解析的 preload 脚本和 renderer 脚本出发,沿着相对路径的 require 和 import 往下走。没被这样走到的文件,就根据它用的 API 推断:ipcMain 和 webContents 只存在于 main 进程;调用了 contextBridge 的文件当作 preload。都不满足的,进程显示为未知。

读 fuse 时,页面查找 Electron 放在 fuse 字节前面的哨兵字符串 dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX,文件按 4 MiB 分片读取,200 MB 的 framework 也不会一次读进内存。哨兵后面是 1 字节版本、1 字节长度,然后每个 fuse 一个字节:ASCII 0(关)、1(开)或 r(已移除)。fuse 的名字和顺序取自 @electron/fuses 2.1.3 的 FuseV1Options。我们用官方 Electron 发布包里的 11 个二进制和 npx @electron/fuses read 的输出逐项对比:44.9.1 和 20.3.12 原样的 Windows x64、Linux x64、macOS x64 构建,外加 44.9.1 的 macOS arm64;用 @electron/fuses 加固过的三个副本(44.9.1 的 Linux 和 Windows、20.3.12 的 Windows);以及用 lipo 合成后再翻转过的 44.9.1 通用 framework。全部一致。Electron 44 有 9 个 fuse,Electron 20 有 7 个。

隐私是怎么保证的

归档文件不会被上传:

  • 解包工具运行在一个单独的框架(iframe)里,来源是 tools.rea-reverse.com。下面说的处理都在这个框架里、在你的设备上完成。
  • 这个页面在正文之后展示一个广告。广告脚本运行在 rea-reverse.com 上,而框架属于另一个源(origin),浏览器不允许广告脚本或本页的任何其他脚本读取你拖入的归档或结果。
  • 框架的内容安全策略拦截它的脚本能发起的所有请求(connect-src 'none',也不加载任何第三方资源)。框架不能自行打开窗口或让本页跳转;只有你点击报告里的链接时才会打开页面。它传给本页的只有自身高度和下面这些统计事件。
  • 解析、扫描和每一次文件下载,都在你设备上的 Web Worker 里进行。Worker 通过浏览器的文件 API,只读取头部和需要的那几段字节。
  • 统计事件只有粗粒度信息:成功或错误码、文件数量区间(例如 11-100)、读 fuse 时的容器类型(ELF、PE、Mach-O、通用 Mach-O 或 plist)、文件大小区间、耗时区间,绝不包含文件名、文件路径、通道名或任何其他内容。框架把事件交给本页,由本页发送。完整清单见隐私政策。
  • 不想要任何广告和统计?在单独页面打开解包工具。

如何自己验证

  1. 打开浏览器开发者工具的 Network(网络) 面板,加载本页。发往 tools.rea-reverse.com 的请求就是框架:它的 HTML、样式、字体和脚本,以及 Worker 脚本 /_astro/asar-worker-*.js。发往 Google 域名的请求是本页的广告。
  2. 清空日志,然后分析一个归档,再从中下载一个文件。在过滤框里输入 domain:tools.rea-reverse.com:最多只会再出现一次 Worker 脚本 /_astro/asar-worker-*.js(为下一次运行准备的新 Worker),没有任何请求带着你的数据。广告请求可能还会继续,但它们来自本页,不带框架里的任何内容。
  3. 点开来自 tools.rea-reverse.com 的 asar-electron/ 文档,查看 Headers:它的 Content-Security-Policy 里有 connect-src 'none' 和 frame-ancestors https://rea-reverse.com。在 Chrome 里,Application(应用)> Frames 也能看到框架的源和策略。Worker 运行在 default-src 'none'; script-src 'self'; connect-src 'none' 这条策略下,浏览器不允许它发出任何网络请求。
  4. 检查隔离:在 Console(控制台) 里,上下文选 top,运行 document.getElementById('tool-frame').contentDocument,结果是 null;frames[0].document 会抛出 SecurityError。广告脚本拿到的也是这个结果。
  5. 或者先打开页面再断网(关掉 Wi-Fi,或在 Network 面板勾选 Offline),然后分析文件,照样能用。

局限

  • 是词法扫描,不是完整的语法分析。 只认 ipcMain、ipcRenderer、webContents 这几个名字后面直接跟方法调用的写法。别名不会被追踪:const { ipcRenderer: r } = require('electron'); r.send(...),或者 const r = ipcRenderer,都识别不了;ipc[name](...)、ipcMain['handle'](...) 这种计算属性调用也识别不了。把这些对象改了名的压缩(minify)代码,大部分行都会丢失。
  • 只覆盖上面列出的 API。 ipcRenderer.once、ipcRenderer.postMessage、ipcRenderer.sendToHost、ipcMain.removeHandler,以及基于 MessagePort 的通道,都不在表里。
  • 动态通道不会被求值。 由变量拼出来的通道只显示表达式,不会算出具体的值。
  • 扫描预算。 每个 JavaScript 文件只扫描前 4 MiB,整个归档总共最多扫描 128 MiB。超出预算的文件(通常在 node_modules 里)会在警告里列出。
  • 不扫描的内容。 unpacked 的 JavaScript(字节不在归档里)、HTML 里的内联 <script> 代码块、TypeScript 源文件,以及超过 1 MiB 的 package.json 或 HTML 文件。
  • integrity 只显示,不校验。 导出的 JSON 里带有头部记录的每个文件的哈希,但不会逐个重新计算。头部 SHA-256 和 Info.plist 里的哈希会分别显示出来,由你自己对照;Windows .exe 里的完整性资源不读,二进制格式的 Info.plist 要先用 plutil -convert xml1 转换。
  • fuse 只读不写。 页面不会改动二进制。fuse 只说明 Electron 运行时允许什么,不能说明应用有没有用别的方式打开调试器或 Node.js。
  • 进程归属是推断的。 不会追踪裸包名的导入,所以只能通过某个 npm 包到达的代码,可能显示为未知进程。
  • 原生代码不在范围内。 .node 文件只列出来,请用 Ghidra 这类原生工具分析。

合规说明

请只分析你自己开发的应用、你获得授权评估的应用,或者开源应用。请遵守所分析软件的许可协议(包括其中限制逆向工程的条款)以及适用于你的法律法规。本工具不提供任何绕过授权或其他保护措施的建议。另见免责声明。

对应的命令行

用官方 @electron/asar 命令行(4.3.1,用 bunx 运行;也可以用 npx @electron/asar 运行同一个包)处理同一个归档。列出文件:

$ bunx @electron/asar list default_app.asar
/default_app.js
/icon.png
/index.html
/main.js
/octicon
/octicon/book-24.svg
…
/package.json
/preload.js
/styles.css

整体解包,再找出页面报告的 IPC 调用,文件和行号一致:

$ bunx @electron/asar extract default_app.asar out
$ grep -n -E "ipcMain\.handle|ipcRenderer\.invoke|exposeInMainWorld" out/*.js
out/default_app.js:63:ipcMain.handle('bootstrap', (event) => {
out/preload.js:34:    const electronPath = await ipcRenderer.invoke('bootstrap');
out/preload.js:59:contextBridge.exposeInMainWorld('electronDefaultApp', {

常见问题

怎样在线解包 app.asar,又不上传文件?

把文件拖到本页。归档在你的浏览器里读取:先解析归档头,每个文件只在你预览或下载时才从你本地的副本里读出来,不会发到任何服务器。

能把整个归档解出来吗?

页面一次下载一个文件。要完整解包,请在自己的电脑上运行 npx @electron/asar extract app.asar out/,见上面的命令。

逆向分析 Electron 应用从哪里入手?

从总览里的主进程入口和 preload 脚本开始,再看 IPC 表:渲染进程能调用的每个通道和负责响应的处理函数,就是不可信输入碰到高权限代码的地方。可以在这里预览这些文件,也可以解包后用编辑器打开。

能检查 Electron fuse 吗?

能,但要拖入可执行文件而不是归档:Windows 拖入主 .exe,Linux 拖入主可执行文件,macOS 拖入 Contents/Frameworks 里的 Electron Framework。打开完整性 fuse 时,app.asar 的头部 SHA-256 就是要和 ElectronAsarIntegrity 对照的值。

为什么某个通道显示为动态?

第一个参数不是单个字符串字面量,例如带变量的模板字符串,或者在别处定义的常量。打开对应文件的那一行,看它最终是什么值。

应用明明用了 IPC,表却是空的?

多半是代码被压缩、对象改了名,或者 IPC 调用位于 unpacked 的文件或超大的文件里。看一下警告和扫描统计。

更新于

相关教程