executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
re-electronElectron 桌面应用逆向:asar 解包、主/渲染进程 JS、V8 字节码(.jsc)边界、CDP 动态调试、反调试对抗。 触发词:Electron、asar、桌面应用逆向、.jsc、V8 快照、CDP、ELECTRON_RUN_AS_NODE、devtools 检测。
Independent GitHub snapshot · Automated classification · Original documentation governs
resources/app.asar 结构、主/渲染进程 JS 逻辑还原、原生 .node 模块、密钥/通信逻辑提取.jsc 文件(bytenode 类工具编译产物)的识别与边界判断--inspect、渲染进程 --remote-debugging-port(CDP)、ELECTRON_RUN_AS_NODE 以 Node 模式复用运行时.jsc 出现在 Cocos 游戏资源场景(那是 Cocos 引擎字节码,走 [[re-game]];本技能只管 Electron 的 V8 字节码).jsc 均按动态处理,静态解包可免沙箱所有工具先验证再使用。静态解包可免沙箱;运行应用 / 动态调试默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)。Electron 应用多为跨平台打包,工具链以 npm 生态为主、跨 OS 一致。
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 --versionnpm 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(单文件){"files":{...}},每文件含 size/offset/integrity),JSON 字符串自偏移 16 起(前 12 字节为 pickle 嵌套大小字段,offset 12 为头字符串长度,均 uint32 LE)——strings app.asar | grep '"files"' 或小脚本 JSON.parse 定位,无需任何工具apt install binutils 等);macOS: 自带(或 brew install binutils);Windows: WSL 内或 Sysinternals stringsstrings --versionnpm install chrome-remote-interface(官方 npm 包,Chrome DevTools Protocol 客户端)node -e "require('chrome-remote-interface')" 不报错即装好;实际连通见操作步骤 6curl http://127.0.0.1:<port>/json/list 拿 webSocketDebuggerUrl 后连 WebSocket 发 JSON-RPC)npm install -g bytenode 或项目内安装npx bytenode --help(-c 编译、-e/--electron 编译为 Electron 用、-ep/--electron-path 指定 Electron 可执行文件).jsc 的魔数与可加载性;本身不是反编译工具(见坑 1)electronMain)无 CLI 对应参数,须用 API 调用形态:require('bytenode').compileFile({ electronMain: true, filename: 'main.js' })(与坑 1 的 electronMain 模式呼应)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 需重新签名)dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX,其后为 fuse 版本、长度与 wire 字节(0x30(0)=禁用、0x31(1)=启用、0x72(r)=移除)apt install ghidra / pacman -S ghidra;macOS brew install --cask ghidra;Windows 官方 zip;验证 analyzeHeadless -help).node 本质是 ELF/Mach-O/PE 动态库,导入反编译器前先用 file/readelf -h 确认架构(联动 [[re-format-elf]] / [[re-format-macho]] / [[re-format-pe]])apt install xvfb / dnf install xvfb / pacman -S xvfbxvfb-run -a node --version;无显示服务器时 xvfb-run -a electron . 运行目标按顺序执行,每步结果存档;动态执行默认沙箱。
识别 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 版本串辅助process.versions.v8 获取asar 解包:
npx @electron/asar list app.asar # 先看目录结构
npx @electron/asar extract app.asar out/ # 整包解出
npx @electron/asar extract-file app.asar package.json # 单文件快速取
strings app.asar | grep '"files"' 或脚本按 size/offset 拼接即可还原全部文件app.asar.unpacked/ 目录是未打进归档的原生模块,原样在磁盘上,直接分析npx @electron/fuses read --app <可执行文件> 看 EnableEmbeddedAsarIntegrityValidation / OnlyLoadAppFromAsar——完整性校验开启时替换 app.asar 会导致启动失败(见坑 2)主进程入口与逻辑:
out/package.json 的 main 字段 → 主进程入口 JS(默认 main.js)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_NODE 下 require('bytenode') 加载 .jsc(版本必须匹配,见坑 1)渲染进程 JS 还原:
out/ 某子目录(webpack/browserify/rollup bundle,常混淆、压缩成一行)<script> 引用的 bundle → 美化(js-beautify 类)→ 字符串定位(strings/xxd)→ 混淆层走 [[re-script-deob]] / [[re-deobfuscate]].jsc 文件出现时跳到坑 1 的边界处理(不可当 JS 文本解析)原生模块 .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]]process.versions.modules)与 Electron 版本一一对应,用错版本加载 .node 会报 ABI 错误——可借此确认目标 Electron 版本动态 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 连 webSocketDebuggerUrl,Runtime.evaluate 执行任意 JS、Console.enable 收日志、Page.captureScreenshot 截图;主进程同法连 --inspect 端口/json/list 实际响应为准,别猜固定端口(应用可能占用/修改);窗口未创建时 target 不会出现,先在 UI 触发再枚举app.requestSingleInstanceLock() 常见)导致二次启动直接退出——用 --user-data-dir=<新目录> 或先结束现有实例--remote-allow-origins=*;Node 客户端无 Origin 头不受影响Runtime.evaluate 执行 process.versions(版本)、process.env(环境变量)、require.cache(已加载模块)——比静态字符串可靠xvfb-run -a 包一层再启动反调试对抗与数据提取:
process.argv/process.execArgv 里的 inspect 参数、DevTools 打开检测(窗口尺寸 outerWidth - innerWidth、console 计时)、debugger 语句、process.versions 异常校验Debugger.enable 前先绕过contextIsolation(Electron 12 起默认 true)、sandbox(Electron 20 起默认 true)、nodeIntegration(默认 false)决定 preload/渲染进程能 require 什么、CDP Runtime.evaluate 要选对执行上下文(主 world vs 隔离 world 的 contextId)ELECTRON_ENABLE_LOGGING=1 或 --enable-logging 打开 Chromium 日志,失败诊断先行.node 模块格式解析前置.jsc(JS 引擎字节码)与 Electron .jsc(V8 字节码)的区分.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 校验不匹配)——不确定的边界如实报告,不臆断源码内容../编码变体),且 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/窗口代码必须走正常启动Runtime.evaluate 先枚举执行上下文(Runtime.enable 后看 contextId),区分主 world 与隔离 world 再求值;注入/读取面按这三个开关的实际配置设计--inspect、--inspect-brk、ELECTRON_RUN_AS_NODE 全部无效,应用像没收到参数;原因——EnableNodeCliInspectArguments / RunAsNode fuse 被翻转关闭;对策——@electron/fuses read 先确认;fuse 写入会改二进制并破坏签名(macOS 需重新签名,未签名时用 resetAdHocDarwinSignature 选项),这是改打包产物的最后手段,优先换 frida / 渲染进程 CDP 通道requestSingleInstanceLock() 让第二个实例立即退出,CDP 端口可能被应用占用或改掉;对策——用独立 --user-data-dir 绕过单实例锁;端口以 /json/list 实际响应为准;窗口未创建时 target 未暴露,先触发 UI 再枚举