17 KiB
17 KiB
name, description, capabilities
| name | description | capabilities | |
|---|---|---|---|
| re-electron | Electron 桌面应用逆向:asar 解包、主/渲染进程 JS、V8 字节码(.jsc)边界、CDP 动态调试、反调试对抗。 触发词:Electron、asar、桌面应用逆向、.jsc、V8 快照、CDP、ELECTRON_RUN_AS_NODE、devtools 检测。 |
|
Electron 桌面应用逆向(asar / CDP / .jsc)
何时使用 / 何时不用
- 用: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/list拿webSocketDebuggerUrl后连 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;macOSbrew 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 有原生显示,无需
操作步骤
按顺序执行,每步结果存档;动态执行默认沙箱。
-
识别 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获取
- 判定信号:
-
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)
- 免工具:JSON 目录自偏移 16 起,
-
主进程入口与逻辑:
- 解包后读
out/package.json的main字段 → 主进程入口 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_NODE下require('bytenode')加载 .jsc(版本必须匹配,见坑 1)
- 解包后读
-
渲染进程 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 文本解析)
- 渲染代码一般打包在
-
原生模块
.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)
- 定位:
-
动态 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 注入 - 渲染进程连接流程:
用 chrome-remote-interface 连
curl -s http://127.0.0.1:9222/json/list # 列 targets(title/url/webSocketDebuggerUrl)webSocketDebuggerUrl,Runtime.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包一层再启动
- 启动前先
-
反调试对抗与数据提取:
- 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 什么、CDPRuntime.evaluate要选对执行上下文(主 world vs 隔离 world 的 contextId) - 数据提取联动:运行时内存/密钥 re-memdump、硬编码密钥 re-crypto-keys、加密数据还原 re-crypto-decrypt、系统调用/文件行为 re-tracing
- 行为观察:
ELECTRON_ENABLE_LOGGING=1或--enable-logging打开 Chromium 日志,失败诊断先行
- devtools/inspect 检测常见形态:查
跨域联合
- 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 主进程编译(bytenodeelectronMain模式),ELECTRON_RUN_AS_NODE方式编译的会在加载时崩溃(read-only snapshot 校验不匹配)——不确定的边界如实报告,不臆断源码内容- asar 内路径混淆与完整性校验:现象——解出的文件路径与 require 逻辑对不上,或改完 app.asar 应用启动即退出;原因——asar 支持任意文件名(含
../编码变体),且EnableEmbeddedAsarIntegrityValidationfuse 开启时 asar 内容被校验、OnlyLoadAppFromAsar开启时拒绝非 asar 加载;对策——按 JSON 目录的 size/offset 而非路径语义还原;改包前先@electron/fuses read,校验开启时改用运行时 hook(frida)或动态分析,不硬改归档 ELECTRON_RUN_AS_NODE行为差异:现象——设了环境变量应用没按预期当 Node 跑,或主进程.jsc加载崩溃;原因——该模式下 Electron 只提供 Node 运行时(无electronAPI、require('electron')行为异常),且RunAsNodefuse 为 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-brk、ELECTRON_RUN_AS_NODE全部无效,应用像没收到参数;原因——EnableNodeCliInspectArguments/RunAsNodefuse 被翻转关闭;对策——@electron/fuses read先确认;fuse 写入会改二进制并破坏签名(macOS 需重新签名,未签名时用 resetAdHocDarwinSignature 选项),这是改打包产物的最后手段,优先换 frida / 渲染进程 CDP 通道 - 单实例锁与 CDP 端口不确定:现象——带参数二次启动直接退出、/json/list 连不上;原因——
requestSingleInstanceLock()让第二个实例立即退出,CDP 端口可能被应用占用或改掉;对策——用独立--user-data-dir绕过单实例锁;端口以/json/list实际响应为准;窗口未创建时 target 未暴露,先触发 UI 再枚举