Electron 桌面应用逆向(asar / CDP / .jsc)

SkillDev tools

Electron 桌面应用逆向:asar 解包、主/渲染进程 JS、V8 字节码(.jsc)边界、CDP 动态调试、反调试对抗。 触发词:Electron、asar、桌面应用逆向、.jsc、V8 快照、CDP、ELECTRON_RUN_AS_NODE、devtools 检测。

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Electron 桌面应用逆向(asar / CDP / .jsc) skill

What this skill tells your AI

The instructions your AI receives, as published by dslsdzc/rev-skills in .claude/skills/re-electron/SKILL.md and read by ahel’s review.

何时使用 / 何时不用

  • 用:Electron 打包桌面应用——resources/app.asar 结构、主/渲染进程 JS 逻辑还原、原生 .node 模块、密钥/通信逻辑提取
  • 用:V8 字节码 .jsc 文件(bytenode 类工具编译产物)的识别与边界判断
  • 用:动态调试——主进程 --inspect、渲染进程 --remote-debugging-port(CDP)、ELECTRON_RUN_AS_NODE 以 Node 模式复用运行时
  • 用:反调试对抗——devtools / inspect 检测识别与绕过(联动 [[re-evasion]])
  • 不用:纯浏览器扩展(走 [[re-browser-ext]])
  • 不用:脱离 Electron 场景的混淆 JS/脚本(走 [[re-script-deob]] / [[re-deobfuscate]])
  • 不用:.jsc 出现在 Cocos 游戏资源场景(那是 Cocos 引擎字节码,走 [[re-game]];本技能只管 Electron 的 V8 字节码)
  • 不用:仅需通用二进制初勘([[re-binary-core]] 通用底座;本技能只补 Electron 打包层语义)
  • 注意:动态执行默认沙箱([[re-analyze/platform-tips]] 最高原则)——跑应用、连 CDP、加载 .jsc 均按动态处理,静态解包可免沙箱

工具准备

所有工具先验证再使用。静态解包可免沙箱;运行应用 / 动态调试默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)。Electron 应用多为跨平台打包,工具链以 npm 生态为主、跨 OS 一致。

Node.js + npm —— 工具底座(asar / CDP 客户端 / bytenode / fuses)

  • Linux: 发行版包 apt install nodejs npm(Debian/Ubuntu 仓库版本偏旧,V8 版本锁场景用 NodeSource 或 nvm 装新版);macOS: brew install node;Windows: 官方安装器或 choco install nodejs;跨平台: nvm(nvm install <version>)按需切换 Node 版本
  • 验证: node --version && npm --version
  • nvm: 官方安装脚本(GitHub nvm-sh/nvm);Windows 用 nvm-windows 或官方安装器

@electron/asar —— asar 归档解包(现维护的官方包)

  • npm install -g @electron/asar 或免安装 npx @electron/asar ...(npx 首次自动下载;旧包名 asar 已弃用但 CLI 兼容仍可用)
  • 验证: npx @electron/asar --help(应列出 pack / list / extract-file / extract 子命令)
  • 用法: npx @electron/asar extract app.asar out/(整包解出)、npx @electron/asar list app.asar(列目录)、npx @electron/asar extract-file app.asar path/to/file(单文件)
  • 免工具直读:asar 头部内嵌 JSON 目录({"files":{...}},每文件含 size/offset/integrity),JSON 字符串自偏移 16 起(前 12 字节为 pickle 嵌套大小字段,offset 12 为头字符串长度,均 uint32 LE)——strings app.asar | grep '"files"' 或小脚本 JSON.parse 定位,无需任何工具

strings / grep —— 文本线索

  • Linux: binutils 自带(apt install binutils 等);macOS: 自带(或 brew install binutils);Windows: WSL 内或 Sysinternals strings
  • 验证: strings --version

chrome-remote-interface —— CDP 客户端(动态调试主力)

  • 项目内 npm install chrome-remote-interface(官方 npm 包,Chrome DevTools Protocol 客户端)
  • 验证: node -e "require('chrome-remote-interface')" 不报错即装好;实际连通见操作步骤 6
  • 备选: 纯 curl + Node 内置 ws,无需装包(curl http://127.0.0.1:<port>/json/listwebSocketDebuggerUrl 后连 WebSocket 发 JSON-RPC)

bytenode —— .jsc 编译/加载理解工具(验证与对照用)

  • npm install -g bytenode 或项目内安装
  • 验证: npx bytenode --help-c 编译、-e/--electron 编译为 Electron 用、-ep/--electron-path 指定 Electron 可执行文件)
  • 用途:用目标版本 Node/Electron 重编译对照样本 .jsc 的魔数与可加载性;本身不是反编译工具(见坑 1)
  • Electron 42+ 主进程场景(electronMain)无 CLI 对应参数,须用 API 调用形态:require('bytenode').compileFile({ electronMain: true, filename: 'main.js' })(与坑 1 的 electronMain 模式呼应)

@electron/fuses —— 运行能力开关检查/翻转

  • npm install -g @electron/fuses(官方包)
  • 验证: npx @electron/fuses read --app <应用可执行文件或 .app 路径>——输出各 fuse 状态(RunAsNode / EnableNodeCliInspectArguments / EnableEmbeddedAsarIntegrityValidation / OnlyLoadAppFromAsar 等)
  • 翻转: npx @electron/fuses write --app <路径> RunAsNode=off EnableNodeCliInspectArguments=off(写前先 read 评估;翻转改动二进制、破坏签名,macOS 需重新签名)
  • 免工具查看:strings 搜 fuse 哨兵串 dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX,其后为 fuse 版本、长度与 wire 字节(0x30(0)=禁用、0x31(1)=启用、0x72(r)=移除)

通用反编译器 —— 原生 .node 模块

  • Ghidra(官方 release 包,需 JDK;部分发行版仓库有 apt install ghidra / pacman -S ghidra;macOS brew install --cask ghidra;Windows 官方 zip;验证 analyzeHeadless -help
  • IDA:商业版;Freeware 版架构支持范围以官方页面为准
  • .node 本质是 ELF/Mach-O/PE 动态库,导入反编译器前先用 file/readelf -h 确认架构(联动 [[re-format-elf]] / [[re-format-macho]] / [[re-format-pe]])

(可选)xvfb-run —— Linux 无显示环境跑 GUI 应用

  • Linux: apt install xvfb / dnf install xvfb / pacman -S xvfb
  • 验证: xvfb-run -a node --version;无显示服务器时 xvfb-run -a electron . 运行目标
  • macOS/Windows 有原生显示,无需

操作步骤

按顺序执行,每步结果存档;动态执行默认沙箱。

  1. 识别 Electron 与版本

    ls resources/                    # 打包应用应有 app.asar(+ app.asar.unpacked/)
    strings <可执行文件> | grep -i -E 'electron|chrome' | head
    
    • 判定信号:resources/app.asar 结构、进程列表中多进程(主进程 + GPU/渲染子进程)、二进制内 Electron/Chromium 版本串
    • 版本确认:打包应用 --version 常无输出——可靠途径是动态阶段用 CDP 执行 process.versions(electron/chrome/v8/node/modules 全字段);静态只能靠二进制 strings 版本串辅助
    • Electron → Chromium/Node 对应关系查官方 electron-timelines 发布表(只列 Chromium/Node,无 V8 列);V8 版本只能运行时 process.versions.v8 获取
  2. asar 解包

    npx @electron/asar list app.asar                       # 先看目录结构
    npx @electron/asar extract app.asar out/               # 整包解出
    npx @electron/asar extract-file app.asar package.json  # 单文件快速取
    
    • 免工具:JSON 目录自偏移 16 起,strings app.asar | grep '"files"' 或脚本按 size/offset 拼接即可还原全部文件
    • app.asar.unpacked/ 目录是未打进归档的原生模块,原样在磁盘上,直接分析
    • 改包前先 npx @electron/fuses read --app <可执行文件>EnableEmbeddedAsarIntegrityValidation / OnlyLoadAppFromAsar——完整性校验开启时替换 app.asar 会导致启动失败(见坑 2)
  3. 主进程入口与逻辑

    • 解包后读 out/package.jsonmain 字段 → 主进程入口 JS(默认 main.js
    • 主进程是 Node 环境:require 链、app/BrowserWindow/ipcMain 调用即业务骨架;IPC handler(ipcMain.handle/on)是主/渲染通信枢纽,先列全
    • 主进程逻辑可被 ELECTRON_RUN_AS_NODE=1 <可执行文件> script.js 以纯 Node 方式复用(同一 V8/Node ABI,见坑 3 的边界)——不依赖 electron API 的部分可离线跑通观察行为
    • 主进程打包为 .jsc 的场景:ELECTRON_RUN_AS_NODErequire('bytenode') 加载 .jsc(版本必须匹配,见坑 1)
  4. 渲染进程 JS 还原

    • 渲染代码一般打包在 out/ 某子目录(webpack/browserify/rollup bundle,常混淆、压缩成一行)
    • 还原顺序:确定入口 HTML(BrowserWindow 的 loadFile/loadURL 参数)→ 找 <script> 引用的 bundle → 美化(js-beautify 类)→ 字符串定位(strings/xxd)→ 混淆层走 [[re-script-deob]] / [[re-deobfuscate]]
    • 渲染进程密钥/逻辑提取以字符串引用(硬编码密钥、API 域名、特征常量)为锚点展开
    • .jsc 文件出现时跳到坑 1 的边界处理(不可当 JS 文本解析)
  5. 原生模块 .node

    • 定位:app.asar.unpacked/**/*.node(以及解包后 require 路径指向的 .node)
    • 识别:file xxx.node(ELF/Mach-O/PE + 架构)、readelf -h(Linux);.node 即动态库,走 [[re-format-elf]] / [[re-format-macho]] / [[re-format-pe]] 前置 → [[re-ghidra]] / [[re-ida]] / [[re-radare2]] 反编译,C++ 语义走 [[re-cpp-abi]]
    • NODE_MODULE_VERSION(process.versions.modules)与 Electron 版本一一对应,用错版本加载 .node 会报 ABI 错误——可借此确认目标 Electron 版本
    • 导出函数即 N-API/V8 接口层,从导出表 + JS 侧调用点交叉定位业务函数(联动 [[re-imports]])
  6. 动态 CDP 调试

    # 主进程(Node/V8 Inspector,默认端口 9229)
    <可执行文件> --inspect=9229
    <可执行文件> --inspect-brk=9229        # 启动即暂停,等调试器
    # 渲染进程(Chromium CDP,默认端口 9222)
    <可执行文件> --remote-debugging-port=9222
    
    • 启动前先 npx @electron/fuses read --app <可执行文件>EnableNodeCliInspectArguments 为 Disabled 时 --inspect/--inspect-brk 被忽略(SIGUSR1 也不再开 inspector)——此时只能走渲染进程 CDP 或 [[re-frida]] 注入
    • 渲染进程连接流程:
      curl -s http://127.0.0.1:9222/json/list    # 列 targets(title/url/webSocketDebuggerUrl)
      
      用 chrome-remote-interface 连 webSocketDebuggerUrlRuntime.evaluate 执行任意 JS、Console.enable 收日志、Page.captureScreenshot 截图;主进程同法连 --inspect 端口
    • 端口以 /json/list 实际响应为准,别猜固定端口(应用可能占用/修改);窗口未创建时 target 不会出现,先在 UI 触发再枚举
    • 单实例锁(app.requestSingleInstanceLock() 常见)导致二次启动直接退出——用 --user-data-dir=<新目录> 或先结束现有实例
    • 浏览器页面里连 CDP 的 WebSocket 受 Origin 检查(Chrome 111+):加 --remote-allow-origins=*;Node 客户端无 Origin 头不受影响
    • 运行时信息取证:Runtime.evaluate 执行 process.versions(版本)、process.env(环境变量)、require.cache(已加载模块)——比静态字符串可靠
    • Linux 无显示环境用 xvfb-run -a 包一层再启动
  7. 反调试对抗与数据提取

    • devtools/inspect 检测常见形态:查 process.argv/process.execArgv 里的 inspect 参数、DevTools 打开检测(窗口尺寸 outerWidth - innerWidth、console 计时)、debugger 语句、process.versions 异常校验
    • 定位检测点 → patch 跳过(改 bundle 后重新 pack asar,注意坑 2 完整性)或运行时 hook([[re-frida]])、CDP 层 Debugger.enable 前先绕过
    • 注入面判断:contextIsolation(Electron 12 起默认 true)、sandbox(Electron 20 起默认 true)、nodeIntegration(默认 false)决定 preload/渲染进程能 require 什么、CDP Runtime.evaluate 要选对执行上下文(主 world vs 隔离 world 的 contextId)
    • 数据提取联动:运行时内存/密钥 [[re-memdump]]、硬编码密钥 [[re-crypto-keys]]、加密数据还原 [[re-crypto-decrypt]]、系统调用/文件行为 [[re-tracing]]
    • 行为观察:ELECTRON_ENABLE_LOGGING=1--enable-logging 打开 Chromium 日志,失败诊断先行

跨域联合

  • [[re-managed]]:托管代码网关路径(re-managed → 本技能),Electron 的 JS 层归其编排
  • [[re-script-deob]] / [[re-deobfuscate]]:渲染进程混淆 JS 还原
  • [[re-evasion]]:devtools/inspect 检测对抗方法论(本技能出目标特征,绕过策略联动)
  • [[re-format-elf]] / [[re-format-macho]] / [[re-format-pe]]:原生 .node 模块格式解析前置
  • [[re-cpp-abi]]:原生模块 C++ 语义(RTTI/虚表/STL)恢复
  • [[re-imports]]:.node 导出表与库指纹、Electron 版本确认辅助
  • [[re-ghidra]] / [[re-ida]] / [[re-radare2]]:原生模块反编译
  • [[re-frida]]:fuse 禁 inspect 场景的动态注入替代通道
  • [[re-memdump]] / [[re-crypto-keys]] / [[re-crypto-decrypt]]:运行时密钥与加密数据提取
  • [[re-tracing]]:动态行为观察(文件/网络/系统调用)
  • [[re-variant]]:多版本对比(补丁定位、逻辑差异)
  • [[re-game]]:Cocos 场景 .jsc(JS 引擎字节码)与 Electron .jsc(V8 字节码)的区分
  • [[re-sandbox]]:一切动态执行强制前置([[re-analyze/platform-tips]] 默认沙箱原则)
  • 配套:[[re-patching]](改 bundle 字节补丁)、[[re-triage]](初勘前置)

常见坑与陷阱

  • .jsc 是 V8 字节码快照,不是 JS 文本:现象——解出 .jsc 文件后 grep/美化/解析全无效,内容是不可读二进制;原因——bytenode 类工具把 JS 编译成 V8 字节码并移除源码(Function.prototype.toString() 被替换为桩代码,这是识别特征之一);边界——V8 字节码格式未文档化、随 V8 版本变化,目前无公开反编译工具;node --print-bytecode(官方 Node 构建可用)只能打印「运行中」编译出的字节码助记符,不是源码还原;对策——先确认样本 .jsc 的编译环境(魔数/加载器提示、process.versions.v8 对照),能加载就跑行为分析(strace/网络/内存),需要源码语义就找未编译的 .js 副本或 bundle map;版本锁——.jsc 必须由同 V8 版本、同平台/架构的 Node 或 Electron 编译才能加载,跨版本加载直接崩;Electron 42+(V8 ≥ 14.8)主进程加载的 .jsc 需用真实 Electron 主进程编译(bytenode electronMain 模式),ELECTRON_RUN_AS_NODE 方式编译的会在加载时崩溃(read-only snapshot 校验不匹配)——不确定的边界如实报告,不臆断源码内容
  • asar 内路径混淆与完整性校验:现象——解出的文件路径与 require 逻辑对不上,或改完 app.asar 应用启动即退出;原因——asar 支持任意文件名(含 ../编码变体),且 EnableEmbeddedAsarIntegrityValidation fuse 开启时 asar 内容被校验、OnlyLoadAppFromAsar 开启时拒绝非 asar 加载;对策——按 JSON 目录的 size/offset 而非路径语义还原;改包前先 @electron/fuses read,校验开启时改用运行时 hook(frida)或动态分析,不硬改归档
  • ELECTRON_RUN_AS_NODE 行为差异:现象——设了环境变量应用没按预期当 Node 跑,或主进程 .jsc 加载崩溃;原因——该模式下 Electron 只提供 Node 运行时(无 electron API、require('electron') 行为异常),且 RunAsNode fuse 为 Disabled 时该环境变量被完全忽略(连带 process.fork() 失效);对策——先 read fuses 确认 RunAsNode 状态;该模式只用于复跑不依赖 electron API 的逻辑,GUI/窗口代码必须走正常启动
  • contextIsolation / sandbox 决定注入面:现象——preload 脚本里 require 报错、CDP evaluate 拿不到页面变量;原因——Electron 12 起 contextIsolation 默认 true(渲染进程与 preload 上下文隔离)、Electron 20 起 sandbox 默认 true(preload 无完整 Node 能力)、nodeIntegration 默认 false;对策——CDP Runtime.evaluate 先枚举执行上下文(Runtime.enable 后看 contextId),区分主 world 与隔离 world 再求值;注入/读取面按这三个开关的实际配置设计
  • fuses 改变运行能力(调试入口可能整体关闭):现象——--inspect--inspect-brkELECTRON_RUN_AS_NODE 全部无效,应用像没收到参数;原因——EnableNodeCliInspectArguments / RunAsNode fuse 被翻转关闭;对策——@electron/fuses read 先确认;fuse 写入会改二进制并破坏签名(macOS 需重新签名,未签名时用 resetAdHocDarwinSignature 选项),这是改打包产物的最后手段,优先换 frida / 渲染进程 CDP 通道
  • 单实例锁与 CDP 端口不确定:现象——带参数二次启动直接退出、/json/list 连不上;原因——requestSingleInstanceLock() 让第二个实例立即退出,CDP 端口可能被应用占用或改掉;对策——用独立 --user-data-dir 绕过单实例锁;端口以 /json/list 实际响应为准;窗口未创建时 target 未暴露,先触发 UI 再枚举

Signals

GitHub stars
57
Forks
8
Last commit
Sep 2026

ahel review

  • K1binfo
    installs-packages

Automated review, not a security audit. Ruleset v1+k2.

Advanced
Catalog kind
skill
Gateway key
re-electron
Source
github.com/dslsdzc/rev-skills