rea-reverse

ASAR 解包:提取并查看 Electron 应用里的文件

基于 REA 6.4.0更新于 约 7 分钟读完
本页目录

Electron 应用把 JavaScript、HTML 和各种资源都打进一个文件:resources/app.asar。想调试自己应用的打包版本、确认某次发布到底装了什么,或者研究一个开源 Electron 项目,第一步都是打开这个文件。本文是一份快速上手指南:app.asar 在各个平台上放在哪里,怎样在浏览器或命令行里解包,打开之后先看什么。完整的分析流程(REA 的应用图、preload 桥接、IPC 配对)见 用 REA 逆向分析 Electron 应用。

适合谁读:分析自己的应用、开源应用或已获授权评估的应用的 Electron 开发者和审查者。命令行部分需要 Node.js,浏览器部分什么都不用装。

文中的例子是专门为本文写的一个迷你 Electron 应用 notes-shell:一个 main 进程、一个 preload 脚本、一个 renderer 页面,外加一个占位用的 .node 文件。打包时用 --unpack "*.node" 让它留在归档外面。

ASAR 是什么格式

ASAR 是一种很简单的归档格式:开头是一小段二进制前缀和一段描述目录树的 JSON 头,后面把文件内容不压缩地依次拼接起来。头里的每个文件条目有 size、offset(存成十进制字符串),新版 @electron/asar 写出的归档还带一条 integrity 记录,里面是 SHA-256 哈希。下面是示例里一个文件的真实头部条目:

{"size":300,"offset":"1219","integrity":{"algorithm":"SHA256","hash":"022ffed9…","blockSize":4194304,"blocks":["022ffed9…"]}}

有些条目标着 "unpacked": true,它们的字节根本不在 .asar 里,而是放在旁边的 app.asar.unpacked/ 目录中,因为这些文件必须以真实文件的形式存在于磁盘上。最常见的就是原生 Node 扩展(.node)。复制应用来分析时,记得把归档和这个目录放在一起。

app.asar 在哪里

归档位于应用的 resources 目录。常见位置:

  • macOS:/Applications/<App>.app/Contents/Resources/app.asar。在访达里右键应用,选显示包内容就能进去。
  • Windows:按用户安装的一般在 %LOCALAPPDATA%\Programs\<App>\resources\app.asar,为所有用户安装的一般在 C:\Program Files\<App>\resources\app.asar。
  • Linux:安装包常放在 /opt/<App>/resources/app.asar。AppImage 要先解开:带 --appimage-extract 参数运行它,再到 squashfs-root/resources/ 下找。

有的应用没有 app.asar,直接带一个普通的 resources/app/ 目录。那就没什么可解包的,直接打开目录即可。

开始之前,先把 app.asar 和 app.asar.unpacked/ 复制到一个工作目录。这样已安装的应用自动更新时,不会影响你的分析。

方法一:在浏览器里解包

打开 ASAR 解包与 Electron IPC 分析工具,把 app.asar 拖进去。归档在你电脑上的 Web Worker 里解析,不会上传。页面一开始只读前缀和头部,所以再大的归档也打开得很快。你会得到一棵可搜索的文件树,任何打包在里面的文件都能单独预览或下载,不用解开整个归档。

对 notes-shell,报告显示 7 个文件、3 个目录、1 个 unpacked 条目。除了文件树,工具还替你读出了 Electron 的结构:

报告部分 notes-shell 的结果
package.json notes-shell 0.3.0,main: src/main.js
preload 脚本 src/preload.js,在 src/main.js:10 配置
renderer 入口 renderer/renderer.js,来自 renderer/index.html
contextBridge notes,键为 list、save、onSaved(src/preload.js:3)
原生模块 native/fast-hash.node,30 字节,unpacked

IPC 通道表一共 6 行:src/main.js 第 18、19 行用 ipcMain.handle 处理的 notes:list 和 notes:save;第 21 行用 webContents.send 发出的 notes:saved;以及 src/preload.js 第 4 到 6 行里对应的 ipcRenderer.invoke 和 ipcRenderer.on 调用。每一行都标出了方向,以及发起调用的代码属于哪个进程(main 还是 preload)。

浏览器方式有两个限制:一次只能下载一个文件;unpacked 条目的字节不在归档里,所以拿不到。要完整解包,用命令行。

方法二:用 npx @electron/asar 解包

@electron/asar 是 Electron 项目维护的打包库,不用全局安装就能运行:

npx @electron/asar list app.asar
npx @electron/asar extract app.asar out
npx @electron/asar extract-file app.asar src/preload.js

list 列出归档里的所有路径。示例的输出(4.3.1 版):

/native
/native/fast-hash.node
/package.json
/renderer
/renderer/index.html
/renderer/renderer.js
/src
/src/main.js
/src/preload.js
/src/store.js

extract 把整棵树写到 out/,还会从 app.asar.unpacked/ 把 unpacked 条目复制过来,所以 out/native/fast-hash.node 也在。extract-file 把单个文件以它的文件名写到当前目录,这里就是 preload.js。

这几条命令在 Windows、macOS、Linux 上用法相同。Windows 下路径有空格要加引号,例如 npx @electron/asar extract "C:\Program Files\Notes\resources\app.asar" out。

缺了 .unpacked 目录会怎样:只把 app.asar 复制到另一个目录再运行 extract,4.3.1 版会报 Error: Unable to extract some files:,接着是 app.asar.unpacked/native/fast-hash.node 的 ENOENT: no such file or directory。打包在归档里的文件照样写出来了,只是缺了原生模块。把那个目录放回归档旁边,重新运行即可。

方法三:交给 REA

REA(morluto 维护的开源项目)可以直接读取 ASAR 归档。装好 REA 6.4.0(见 安装与接入)后:

rea inspect-artifact /absolute/path/work/app.asar --json > inventory.json
rea extract-artifact /absolute/path/work/app.asar --json
rea analyze-javascript-application /absolute/path/work/app.asar --json > app-evidence.json
  • inspect-artifact 返回按内容寻址的清单:路径、大小、SHA-256 摘要,外加完整性检查结果和建议的下一步探查。它不解包任何文件。
  • extract-artifact 把所有普通文件写进 REA 新建的临时目录(系统临时目录下的 rea-extracted-<uuid>),并在结果里告诉你位置。它会拒绝路径穿越和符号链接逃逸,也从不覆盖已有文件。
  • analyze-javascript-application 在不运行应用的情况下构建应用图:入口、模块、import、preload 桥接和 IPC。怎么读这张图,见 Electron 分析 一文。

REA 会拿每个条目去核对归档里声明的 SHA-256,对不上时默认直接失败,unpacked 的文件也会核对。如果某次发布在打包之后合理地改动过 unpacked 文件(比如给原生模块签了名),加上 --integrity-policy record-and-continue。REA 会把这些不一致明确记录为不可信,而不是悄悄忽略。

打开之后先看什么

文件到手后,按下面的顺序看,能最快回答大部分问题:

  1. package.json。main 指出 main 进程的入口,没写时按 Node 的默认规则用 index.js。name 和 version 告诉你手上是哪个版本。
  2. main 入口。找 new BrowserWindow(...) 和它的 webPreferences:用的是哪个 preload 脚本,contextIsolation、sandbox、nodeIntegration 的设置是否符合你对自己应用的预期。
  3. preload 脚本。contextBridge.exposeInMainWorld 精确规定了网页能调用什么。在示例里,网页只能调用 window.notes.list、save 和 onSaved,别的都调不到。
  4. IPC 处理函数。每个 ipcMain.handle 或 ipcMain.on 通道,都是 main 进程接受 renderer 输入的入口。逐个确认处理函数有没有校验收到的数据。示例里的 store.save 会把笔记内容转成字符串并截断长度。
  5. source map 和原生模块。归档里的 .map 文件能把压缩代码还原成源码,浏览器工具会统计它们的数量。.node 文件是编译后的代码,要用 Ghidra 这类原生工具分析(见 REA + Ghidra)。

如果 main 或 preload 代码被压缩过,浏览器工具的 IPC 表可能大半是空的,因为它靠字面名称 ipcMain、ipcRenderer、webContents 来匹配。REA 的分析基于语法树,不受这个限制。如果归档里还带了 .map 文件,可以用 source map 还原工具 重建原始文件;拿它来还原自家应用的崩溃堆栈,见 还原压缩后的报错堆栈。

重新打包自己的构建

调试自己的应用时,你可能想改一个文件,放回打包好的版本里试试:

npx @electron/asar pack out app.asar --unpack "*.node"

做本地快速实验没问题,但有两个 Electron 特性可能会拦住你。开启 EnableEmbeddedAsarIntegrityValidation 熔断开关(fuse)后,Electron 会拿归档头去比对打包时记录的哈希,重新打包的归档会被拒绝加载。开启 OnlyLoadAppFromAsar 后,零散的 resources/app/ 目录会被忽略。这两项都是发布版的加固设置,不要为了让补丁跑起来而关掉它们。正确的做法是改源码,再用你平时的打包工具(Electron Forge、electron-builder)重新构建。

常见问题

能在线解包 ASAR 吗?

可以。ASAR 解包工具 在浏览器里运行,不上传归档,你可以在 DevTools 的 Network 面板里自己验证。要连同 unpacked 文件一起完整解包,用 npx @electron/asar extract。

app.asar 是加密的吗?

不是。这个格式没有任何加密,内容按原始字节存放,头部就是 JSON。有些应用会在打包前把 JavaScript 混淆或压缩,那是另一回事。

为什么有的文件标为 unpacked,却下载不了?

它的字节在 app.asar.unpacked/ 里,不在归档中。直接打开那个目录就行。

该用哪个工具?

想快速看一眼、了解 IPC 全貌,用浏览器工具;要把文件完整解到磁盘上,用 @electron/asar;需要校验完整性、得到可以引用的 Evidence 和应用图,用 REA。

下一步