WikifitaGitHub live67e8de5
projeto · claude_desktop/bezetacil-toolset

Bezetacil Toolset — Electron Runtime Introspection

Pipeline ASAR, deobfuscation, harness runtime, debugger spy, CDP extraction

Baixar raw

Bezetacil Toolset — Electron Runtime Introspection

Toolset for reverse engineering, patching, and runtime introspection of Electron apps (developed for Claude Desktop).

Overview

┌─────────────────────────────────────────────────────────┐
│ 1. ASAR Pipeline     │ Extract → Patch → Repack → Sign  │
│ 2. Deobfuscation     │ webcrack → deloom → analysis     │
│ 3. Bezetacil Harness │ Runtime injection + hot reload   │
│ 4. Debugger Spy      │ Network capture + WebSocket      │
│ 5. CDP Tools         │ Python PEP 723 scripts           │
│ 6. Bridge Server     │ Bidirectional TCP Python ↔ Node  │
└─────────────────────────────────────────────────────────┘

1. ASAR Pipeline

Tools

# Install
npm install -g @electron/asar deloom webcrack

# Binary locations
~/.hermes/node/bin/asar
~/.hermes/node/bin/deloom
~/.hermes/node/bin/webcrack

Complete Pipeline

# 1. Backup
cp /Applications/Claude.app/Contents/Resources/app.asar /tmp/app.asar.backup

# 2. Extract (with symlink to unpacked)
ln -sf /Applications/Claude.app/Contents/Resources/app.asar.unpacked /tmp/app.asar.backup.unpacked
asar extract /tmp/app.asar.backup /tmp/extracted/

# 3. Patch (Python)
python3 -c "
with open('/tmp/extracted/.vite/build/index.js') as f: content = f.read()
content = content.replace('old', 'new')
with open('/tmp/extracted/.vite/build/index.js', 'w') as f: f.write(content)
"

# 4. Repack (Node.js API — preserves native binaries)
cat > /tmp/build.mjs << 'EOF'
import { createPackageWithOptions, getRawHeader } from '@electron/asar/lib/asar.js';
import { createHash } from 'crypto';
const src = '/tmp/extracted', dest = '/tmp/app.asar.patched';
await createPackageWithOptions(src, dest, { unpack: '{*.{node,dylib},spawn-helper}' });
const hash = createHash('SHA256').update(getRawHeader(dest).headerString).digest('hex');
console.log('Hash:', hash);
EOF
node /tmp/build.mjs

# 5. Update Info.plist (9 files!) with header hash
python3 -c "
import plistlib, os
HASH = '...'
for p in [list of 9 plists]:
    with open(p,'rb') as f: pl = plistlib.load(f)
    pl['ElectronAsarIntegrity'] = {'Resources/app.asar': {'algorithm': 'SHA256', 'hash': HASH}}
    with open(p,'wb') as f: plistlib.dump(pl, f)
"

# 6. Deploy + Sign

cp /tmp/app.asar.patched /Applications/Claude.app/Contents/Resources/app.asar
cp -r /tmp/app.asar.patched.unpacked/* /Applications/Claude.app/Contents/Resources/app.asar.unpacked/
sudo codesign --remove-signature /Applications/Claude.app
sudo codesign -s - /Applications/Claude.app --deep --force --entitlements /tmp/entitlements.plist

Lessons Learned

  • --unpack-dir CLI only accepts 1 value → use glob {...} or Node.js API
  • 9 copies of ElectronAsarIntegrity spread across Info.plist files
  • Hash is SHA256 of headerString (JSON), not the entire file
  • --unpack CLI is buggy for .node → use API createPackageWithOptions({unpack})
  • Native binaries (.node, .dylib, spawn-helper) must stay in unpacked/
  • Code signing: remove original signature, re-sign ad-hoc with entitlements
  • Minimum entitlements: com.apple.security.virtualization, com.apple.security.device.audio-input, com.apple.security.cs.allow-jit

Applied Patches (V17c final)

#PatchPurpose
1function Uy(A){return false}1p mode — claude.ai voice UI
2voice:{buildGate:()=>!0}Enable voice feature
3if(false)return{...entitlement_missing...}Bypass entitlements verification
4Audio proxy handler in array CProxy /v1/audio/* → gateway
5Sqi="localhost:8443"Redirect CSP to Caddy (not effective)
6require("../../harness-bootstrap")Inject harness at boot

2. Deobfuscation

Pipeline

# Deminify + unpack webpack bundle
node ~/.hermes/node/bin/webcrack index.js -o /tmp/demified/

# Result: 19MB deobfuscated.js + extracted modules
ls /tmp/demified/
# 333.js  486.js  515.js  747.js  823.js  949.js  bundle.json  deobfuscated.js

Key Code Discoveries

SymbolMeaningLine
Uy()deploymentModeIs3p — returns true in 3p150368
_cr1p deployment mode class216220
nkapp://localhost — URL in 3p149817
Cmn()Registers protocol handler app://482078
k3A()Blocked paths checker481540
qpnList of blocked paths in 3p481538
Emn()API stub handler (array C)481925
Sqia-api.anthropic.com (CSP)144596
jr.quickAccess.dictationSwift bridge dictation318974
fKr()Initializes Swift bridge319000

3. Bezetacil Harness

Location

~/.bezetacil/
└── scripts/
    └── 001-debugger-spy.js   ← auto-loaded at boot

Harness Bootstrap (injected into index.js)

require("../../harness-bootstrap");

Loads automatically when the app starts. The harness:

  1. Reads ~/.bezetacil/scripts/*.js in alphabetical order
  2. Executes each script via eval() in the main process context
  3. Hot reload: fs.watch() on directory — editing file = automatic reload
  4. Exposes global API: __HARNESS__.eval(), __HARNESS__.log(), __HARNESS__.reload()

Harness API

// Execute code in main process
__HARNESS__.eval("require('electron').BrowserWindow.getAllWindows().length")

// Manual hot reload
__HARNESS__.reload()

// Status

__HARNESS__.status()
// → { log: '/tmp/claude-harness.log', scriptsDir: '...', pid: 89917 }

Development Cycle

1. Edit ~/.bezetacil/scripts/001-my-script.js
2. Save
3. ~3s later: automatic hot reload
4. Check /tmp/claude-harness.log for logs

Zero repacks! Only need to rebuild if changing the bootstrap itself.

Naming Convention and Hot Reload

CRITICAL RULE: Bezetacil scripts must NEVER be versioned with incremental numeric suffixes (e.g., v001-script.js, v002-script.js, 007-capture.js).

Why: The harness loads ALL *.js from the directory in alphabetical order. Each hot reload adds new handlers via eval() without removing previous ones. Versioning with incremental names causes multiple versions of the same script to coexist in memory, each registering their own event listeners — causing handler duplication, race conditions, and files generated with incorrect names.

Correct format: Use fixed descriptive names.

✅ 001-debugger-spy.js        ← fixed name, rewritten when updated
✅ capture-tts-audio.js       ← fixed descriptive name
✅ supports1m-autodiscovery.js ← fixed descriptive name

❌ 001-script.js, 002-script.js, 003-script.js  ← NEVER do this
❌ v1-spy.js, v2-spy.js                          ← NEVER do this
❌ 007-capture-tts-audio.js                      ← NEVER do this

Guard pattern: Every script must start with a guard to prevent multiple instances after hot reload:

(function() {
  // Guard: prevents multiple instances after hot reload
  if (globalThis.__MY_SCRIPT_LOADED__) return;
  globalThis.__MY_SCRIPT_LOADED__ = true;
  // ... script ...
})();

Additional conventions:

  • .js — active script, loaded at boot
  • .js.disabled — disabled script (ignored by harness)
  • .js.inject_first — injected BEFORE normal scripts (hooks that need to be in place before initialization)
  • .js.inject_last — injected AFTER normal scripts (observers, spy, loggers)

Lifecycle:

1. Edit ~/.bezetacil/scripts/my-script.js    ← always the SAME name
2. Save
3. ~3s later: automatic hot reload
4. Check /tmp/claude-harness.log for logs
5. Restart app if needed to clear old handlers from memory

4. Debugger Spy

Script: 001-debugger-spy.js

Uses webContents.debugger + Fetch.enable + Network.enable to capture:

  • Fetch/XHR: URL, method, headers, postData, responseBody
  • WebSocket: frames sent and received (full payload)
  • Network: all requests with URL and method

Output

  • /tmp/voice-captures.json — array of captures with timestamp
  • /tmp/claude-harness.log — debug logs [SPY]

Capture Example

{
  "ts": "2026-06-23T13:56:35.268Z",
  "type": "ws",
  "method": "Network.webSocketFrameReceived",
  "payload": "{\"type\": \"TranscriptText\", \"data\": \"Houston, we may have a problem.\"}"
}

WebContents covered

  • BrowserWindow.getAllWindows() → main webContents
  • webContents.getAllWebContents() → ALL webContents (including WebContentsView)
  • BrowserWindow.getBrowserViews() → child views

5. CDP Tools

Python Scripts (PEP 723 — uv run)

Location: /tmp/cdp-*.py

ScriptFunction
cdp-eval.pyExecute JS in main process via CDP
cdp-harness.pyUse harness API via CDP
cdp-fetch.pyLoad scripts from file server
cdp-load-scripts.pyOrchestrate injection

Usage Pattern

# Connect to CDP (must be active in UI)
curl -s http://localhost:9229/json | jq '.[0].webSocketDebuggerUrl'

# Execute via harness
uv run /tmp/cdp-eval.py

File Server

cd /tmp/scripts && python3 -m http.server 8765

Allows loading scripts from within Electron without string escaping.


6. Bridge Server (TCP)

Concept

Bidirectional TCP connection between Python and the Electron main process for real-time commands.

Files

  • /tmp/scripts/bridge-server-v2.py — async Python server (port 9876)
  • /tmp/scripts/harness-bridge.js — TCP client in Electron

How It Works

Python (bridge-server) ←→ TCP :9876 ←→ Electron (harness-bridge)
                            │
                            ├─ command: {"id":1, "code":"1+1"}
                            └─ response: {"id":1, "result":"2"}

7. Entitlements

File: /tmp/entitlements.plist

<key>com.apple.security.cs.allow-jit</key><true/>
<key>com.apple.security.virtualization</key><true/>
<key>com.apple.security.device.audio-input</key><true/>
<key>com.apple.security.device.camera</key><true/>
<key>com.apple.security.device.bluetooth</key><true/>
<key>com.apple.security.device.usb</key><true/>
<key>com.apple.security.device.print</key><true/>
<key>com.apple.security.personal-information.location</key><true/>
<key>com.apple.security.personal-information.photos-library</key><true/>

Extracted from the original DMG at /Users/alefita/Downloads/Claude.dmg.


8. ASAR Versions

VersionPatchesStatus
V0Original (backup)Clean base
V8Uy=false, voice gate, entitlement, audio proxy3p mode, no voice UI
V9V8 + Uy=falseStable — 1p mode, voice works
V15V9 + harness v1Initial harness
V16V15 + Sqi localhostRedirect attempt (not effective)
V17cV16 + harness v3 (bezetacil)Current — hot reload, debugger spy

9. Scripts Quick Reference

# Full rebuild
cd /tmp/claude-v17 && node /tmp/deploy-v17c.mjs

# Hot reload — just edit
vim ~/.bezetacil/scripts/001-debugger-spy.js

# View logs
tail -f /tmp/claude-harness.log

# View captures
cat /tmp/voice-captures.json | python3 -m json.tool | head -50

# Re-sign (after rebuild)
sudo codesign --remove-signature /Applications/Claude.app
sudo codesign -s - /Applications/Claude.app --deep --force --entitlements /tmp/entitlements.plist