Electron app.asar 解包只有部分资源的原因与逆向方法

为什么解包只有部分资源

1. 文件在 app.asar.unpacked 而不在 asar 里

Electron 会把不适合打包进 asar 的文件放到同级目录:

resources/
 ├─ app.asar          ← 前端代码、JS bundle
 └─ app.asar.unpacked/ ← 原生模块、大文件

通常放在 unpacked 里的有:

  • .node 原生扩展
  • ffmpeg.dll / ffmpeg.so
  • AI 模型、WASM 文件
  • 浏览器内核组件

解包时要把 app.asarapp.asar.unpacked 都看一遍。

2. webpack 把所有代码打成了 bundle

现代 Electron 应用几乎都用 webpack 打包,解包后看到的不是:

src/pages/index.js
src/components/Button.js

而是:

main.js
renderer.js
chunk.a1b2c3.js

所有模块都合并进了 bundle,没有”缺文件”,只是代码被合并了。

3. 资源在运行时从网络下载

部分 AI 软件、IDE 工具首次启动时会在线下载:

  • 语言包
  • 插件资源
  • 模型文件
  • JS chunk

下载后存到用户目录,不在安装包里:

Windows:
%AppData%\应用名\          ← 配置、sqlite、token
%LocalAppData%\应用名\     ← 缓存、大文件、下载资源

4. JS 被编译为 V8 字节码(.jsc)

商业 Electron 应用常用 bytenode 把 JS 编译成 .jsc

main.js → main.jsc

普通 asar 解包后可以看到文件,但无法读取源码。

5. 应用对资源做了加密

运行时解密:

// 应用内部流程(无法直接看到)
const encrypted = fs.readFileSync('xxx.bin');
const code = decrypt(encrypted, key);
eval(code);

解包后只能看到加密的 .bin 文件。

解包工具

# 查看 asar 文件列表
npx asar list app.asar

# 解包到指定目录
npx asar extract app.asar ./out

解包后搜索这些关键字找 webpack 入口:

webpackChunk
__webpack_require__

Electron 应用资源分布

位置通常存放
resources/app.asar前端页面、JS bundle、html、css
resources/app.asar.unpacked/.node 模块、dll、ffmpeg、WASM
%AppData%/应用名/用户配置、sqlite、历史记录
%LocalAppData%/应用名/缓存、下载的大文件、模型
%AppData%/.../Code Cache/V8 字节码缓存
%AppData%/.../IndexedDB/结构化数据

逆向流程

1. 解包 asar

npx asar extract app.asar ./out

2. 检查 app.asar.unpacked

ls resources/app.asar.unpacked/

3. 找 preload.js

preload 是 Electron 主进程和渲染进程之间的桥梁,通常包含 IPC 通信和权限暴露逻辑:

out/main/preload.js
out/dist-electron/preload.js

4. 找用户数据目录

# Windows
dir %AppData%\应用名
dir %LocalAppData%\应用名

5. Hook fs 抓解密内容

对于运行时解密的资源,hook fs.readFileSync 把解密后的结果 dump 出来:

const fs = require('fs');
const orig = fs.readFileSync;

fs.readFileSync = function (path, options) {
  const result = orig.apply(this, arguments);
  if (path.includes('.bin') || path.includes('.jsc')) {
    console.log('[readFileSync]', path, result.toString('hex').slice(0, 64));
  }
  return result;
};

6. 检查是否是 V8 字节码

.jsc 文件开头有 V8 magic bytes,可以用 bytenodewrap 机制在运行时拿到原始代码,但需要和目标 Node.js/Electron 版本完全匹配。