小橙子 65a042bd08
fix: detect & isolate fake tool_use leaks from non-Anthropic providers (#4)
* feat(plugin): add reverse-engineering plugin v0.4.0 + project-level codegraph

Bundles a multi-platform RE toolkit as a local marketplace plugin that
clone-and-enable installs in cc-haha desktop. Covers static (Ghidra,
radare2, JADX, apktool, binwalk via Bash) and the gap a previous review
called out: dynamic debugging is the lane that matters most for
AI-driven RE, but Frida alone cannot single-step or set real
breakpoints. This commit fills that gap with three non-overlapping
dynamic lanes -- Frida (instrumentation, mobile, broad surveys), GDB
(cross-arch single-step + breakpoints, embedded firmware), LLDB (Apple
platforms, ObjC/Swift).

Architecture coverage spans MIPS / ARM (incl. Cortex-M Thumb) /
PowerPC (incl. e200 VLE) / 68k (incl. Mac Toolbox A-line traps) /
SuperH / RISC-V / x86-64 / AArch64. The agent picks the right ISA,
base address, and load configuration in skills/firmware-blob, then
hands off to skills/pe-elf-macho with per-architecture decompilation
notes (MIPS delay slots, ARM Thumb interworking, PowerPC TOC/SDA,
68k register banks, etc).

Plugin layout (.claude-plugin marketplace + plugin manifests):

  plugins/.claude-plugin/marketplace.json
  plugins/reverse-engineering/
    .claude-plugin/plugin.json     v0.4.0, GHIDRA_INSTALL_DIR + ARTIFACT_DIR userConfig
    agents/reverse-engineer.md     triage -> static -> optional dynamic -> report
    skills/triage/                 file-type identification, packing, routing
    skills/pe-elf-macho/           Ghidra/r2 + per-arch notes for non-x86
    skills/firmware-blob/          raw blob -> ISA + base address + Ghidra/r2 load config
    skills/apk-analysis/           JADX + apktool, Android attack surface
    skills/ios-analysis/           IPA bundle + Mach-O, FairPlay-aware
    skills/dynamic-debug-overview/ decision matrix Frida vs GDB vs LLDB
    skills/frida-dynamic/          hooks + Memory + CpuContext + Stalker + watchpoints
    skills/gdb-debug/              cross-arch via gdbserver / qemu-user / qemu-system
    skills/lldb-debug/             macOS / iOS device / Linux, ObjC/Swift symbols
    skills/crackme-keygen/         CTF / self-owned binary; ships keygen, not patch
    skills/re-report/              final structured report with IOCs + confidence
    commands/triage.md             /reverse-engineering:triage <path>
    commands/report.md             /reverse-engineering:report <sample-id>
    mcp/servers.json               7 MCP servers: ghidra (pyghidra-mcp PyPI),
                                   radare2 (npm), gdb (mcp-gdb npm), lldb
                                   (stass/lldb-mcp git), jadx, apktool, frida
                                   (kahlo-mcp git, all @main pinned)
    hooks/hooks.json               empty placeholder
    scripts/validate.ts            offline schema check via project's
                                   validatePluginManifest + validatePluginContents
    scripts/dev-link.ts            Windows mklink /J cache -> repo source so SKILL
                                   edits don't require version bump (--restore undoes)
    scripts/smoke.ts               end-to-end: marketplace register -> enable ->
                                   update -> reload -> assert detail counts.
                                   Counts derived from on-disk source (glob),
                                   not hardcoded; idempotent across reruns.
    README.md                      install, prerequisites per MCP, dynamic-capabilities
                                   matrix, architecture coverage, quickstart with
                                   busybox sample, dev-link / smoke workflows

Project-level MCP (clone-and-go for any contributor):

  .mcp.json                        codegraph (npx -y codegraph serve --mcp);
                                   project scope, picked up automatically.
                                   Pre-existing user-level codegraph install
                                   still wins for users who have one.

userConfig of the plugin is honest -- only knobs that actually do
something (GHIDRA_INSTALL_DIR substituted into the ghidra MCP env;
ARTIFACT_DIR resolved relative to agent cwd at run time). Earlier
ENABLE_* booleans were removed because they didn't actually toggle
anything; users disable individual MCP servers from Settings -> MCP
at runtime.

End-to-end verification (server on :3456, vite on :1420):
  - bun run plugins/reverse-engineering/scripts/validate.ts
    -> 0 fail / 0 warn (marketplace + plugin schema both clean)
  - bun run plugins/reverse-engineering/scripts/smoke.ts
    -> 13 passed / 0 failed (marketplace registration, enable, update
       0.3.0->0.4.0, reload, detail assertions including 11 skill ids
       matching the on-disk glob)
  - GET /api/plugins/detail
    -> commands=2 / agents=1 / skills=11 / mcpServers=7 / errors=[]
  - GET /api/mcp
    -> codegraph appears with scope=project,
       configLocation=.mcp.json
  - Desktop UI Settings -> Plugins
    -> v0.4.0 listed in enabled group, "11 skills 1 Agent 7 MCP"

External tool prerequisites (each MCP needs its underlying tool on
PATH; users install per-MCP independently -- none required all at
once): Ghidra (Java 17+), r2, GDB (gdb-multiarch for cross-arch),
LLDB, frida-tools, JADX (Java 17+), apktool. Codegraph requires
either a local global install (npm i -g codegraph) or relies on npx
to fetch on first run.

Constraint: Cannot single-step from Frida alone -- that's the gap GDB
and LLDB fill in this commit. dynamic-debug-overview SKILL is the
canonical decision guide for which lane to use.
Confidence: high
Scope-risk: narrow (additive plugin under plugins/, additive
.mcp.json; zero changes to existing source). Plugin loads via the
existing PluginManifestSchema path -- no new server-side code added.
Tested: bun run plugins/reverse-engineering/scripts/validate.ts (pass),
bun run plugins/reverse-engineering/scripts/smoke.ts (13/13 pass), live
chrome-devtools MCP smoke against running desktop UI verifying plugin
appears with correct version + counts.
Not-tested: real underlying-tool integration (Ghidra/r2/JADX/GDB/LLDB
must be installed by the user; the plugin only validates manifest
shape and component loading). Reverse-debugging (rr) flow is
documented in skills/gdb-debug but not exercised in smoke.

* fix: detect & isolate fake tool_use leaks from non-Anthropic providers

Some third-party Anthropic-compatible gateways (mimo, lgfzer, certain
proxies) don't relay native tool_use blocks: when the model wants to
call a tool, it ends up emitting an XML-style `<tool_use ...>{...}
</tool_use>` element as plain text inside content_delta. The desktop
chat renders that as markdown, marked passes the unknown element
through as raw HTML, the browser collapses whitespace, and the user
sees garbage like `<tool_useid="..."` followed by a smushed shell
command. Worse, the model has no signal the call never executed and
follows up with apologies ("工具用错了, 重来:") and retries — every
"call" is text and nothing runs.

Three layers of defense, lowest to highest in the stack:

1. **Source-side priming reduction** (server). The specialistRouter
   docstring already warned that `key="value"` attribute fragments in
   model-facing strings make some gateway models switch from real
   tool_use blocks into XML mode. Three model-facing nudges still
   embedded `subagent_type="verification"` in their reminders — fix
   them to use prose ("set the subagent_type parameter to verification")
   so we stop priming the failure mode:
     - src/utils/messages.ts (verification_gate_reminder)
     - src/tools/TodoWriteTool/TodoWriteTool.ts
     - src/tools/TaskUpdateTool/TaskUpdateTool.ts
   New regression test src/utils/noKeyValueNudges.test.ts pins all
   three call sites so a future "typo fix" doesn't accidentally
   re-introduce the attribute form.

2. **Detection + sanitization** (desktop). New
   desktop/src/lib/fakeToolUseDetection.ts extracts XML-style
   `<tool_use>` blocks (closed, half-open mid-stream, multiple
   consecutive retries, `name`/`id` in either order, quoted or
   unquoted, inside or outside fenced code blocks) and returns
   `{ cleanText, blocks[] }`. AssistantMessage uses this before
   handing content to MarkdownRenderer so the user never sees the
   XML garbage; copyText also uses cleanContent so users don't paste
   broken markup. Fenced code blocks (```xml ... ```) are preserved
   so legitimate documentation examples render as-is.

3. **Provider compatibility tracking** (desktop). New
   desktop/src/stores/providerCompatStore.ts counts fake tool_use
   leaks per provider id, persisted to localStorage. After 3 leaks
   it fires a one-time toast suggesting the user switch providers.
   Settings → Provider list shows a "工具调用异常" badge on offending
   providers; saving an edit on a flagged provider re-arms the
   warning (clearProvider). Above-threshold detection is silent
   afterward so we don't spam — the badge stays as the persistent
   signal.

The new FakeToolUseNotice component renders an inline grey card
above the assistant bubble naming the attempted tool ("Bash") and
making it clear nothing ran, so the user knows not to trust any
follow-up "Done." claim that depended on the call's output.

Verified:
- desktop: bun run lint clean
- desktop: 28 new tests + existing AssistantMessage.linkrouting (15
  total) pass
- server: 31 tests across verificationGate, specialistRouter, and the
  new noKeyValueNudges regression all pass

Tested: bun run lint (desktop), bunx vitest run (3 desktop suites),
        bun test (3 server suites)
Confidence: high
Scope-risk: narrow

---------

Co-authored-by: cc-haha <cc-haha@users.noreply.github.com>
Co-authored-by: 你的姓名 <you@example.com>
2026-06-10 20:01:32 +08:00

10 KiB

reverse-engineering plugin

Multi-platform reverse engineering toolkit for cc-haha. Bundles five MCP servers, one orchestration agent, six skills, and two slash commands behind a single plugin install.

What it gives you

Surface Item
Agent reverse-engineer — orchestrates triage → static → optional dynamic → report
Skills triage, pe-elf-macho, firmware-blob, apk-analysis, ios-analysis, dynamic-debug-overview, frida-dynamic, gdb-debug, lldb-debug, crackme-keygen, re-report
Commands /reverse-engineering:triage <path>, /reverse-engineering:report <sample-id>
MCP servers ghidra (pyghidra-mcp), radare2 (radareorg/radare2-mcp), gdb (mcp-gdb), lldb (stass/lldb-mcp), jadx (zinja-coder/jadx-mcp-server), apktool (zinja-coder/apktool-mcp-server), frida (FuzzySecurity/kahlo-mcp)
Hooks placeholder (add a fileCreated hook locally if you want SOC-style auto-triage)

Dynamic capabilities (what AI can actually drive)

This is the lane that matters most for AI-driven RE. Static analysis has limited ROI when reading optimised, obfuscated, or stripped code; runtime observation turns hypotheses into facts. The plugin ships three dynamic lanes that don't overlap:

Capability Frida GDB LLDB
Read/write process memory
Read/write GP registers inside hook
Call stack
Function-level hook via breakpoint via breakpoint
Address-level hook (any instruction)
Instruction-level trace Stalker (cheap) ⚠️ stepi loop (slow) ⚠️ thread step-inst loop (slow)
Real single-step (instruction)
Real software/hardware breakpoints ⚠️ trampoline only
Watchpoint (byte granularity) ⚠️ page only
Reverse-debug rr / record full ⚠️ limited
Java method hook
ObjC method hook
Cross-arch (MIPS/PPC/68k/SH) ⚠️ via frida-server gdb-multiarch + qemu ⚠️ no PPC32/68k
iOS device frida-server jailbroken ⚠️ via debugserver via debugserver

The agent reads dynamic-debug-overview first to pick the right lane. For "single-step through MIPS router firmware" → GDB. For "what URL does this Android app POST to" → Frida. For "step into ObjC method on iOS" → LLDB.

Architecture coverage

The reverse-engineering decompilers (Ghidra, radare2) are multi-arch by design. The pe-elf-macho and firmware-blob skills cover:

  • x86 / x86-64 — Windows PE, Linux ELF, macOS Mach-O (the default case)
  • ARM — ARMv4-v8, Thumb/Thumb2 interworking, AArch64. Cortex-M (Thumb-only) flash images load via firmware-blob using the vector-table heuristic.
  • MIPS — MIPS32/64, big and little endian, MIPS16e/microMIPS. Common in routers, PSX, older PIC32, embedded Linux.
  • PowerPC — PPC32/PPC64, plus VLE (e200, NXP MPC57xx automotive). Common in Wii/GameCube, Xbox 360, older Macs, network gear.
  • Motorola 68k — M68000 through 68060, ColdFire. Old Macs, Atari ST, Amiga, Sega Genesis. Recognises Mac Toolbox A-line traps when applicable.
  • SuperH — SH-2 (Sega Saturn) and SH-4 (Dreamcast).
  • RISC-V — RV32/RV64 with C/M/A/F/D extensions.
  • Smaller ISAs Ghidra/r2 also handle — AVR (Arduino), MSP430, 6502 (NES), Z80, TriCore, Hexagon, Xtensa.

The firmware-blob skill specifically handles raw blobs (no PE/ELF/Mach-O header) — router firmware, Cortex-M flash dumps, U-Boot uImages, console ROMs, ECU dumps — by identifying the ISA + endianness + base address before loading into Ghidra/r2 with the right processor module.

Install

From the repo root, add the marketplace by directory:

# inside cc-haha checkout
$env:CC_HAHA_PLUGIN_MARKETPLACE='C:\Users\70641\cc-haha\plugins'
# Then in the desktop UI: Settings → Plugins → Add marketplace → paste the path,
# install "reverse-engineering", enable.

Or via the CLI:

./bin/claude-haha plugin marketplace add C:\Users\70641\cc-haha\plugins
./bin/claude-haha plugin install reverse-engineering@cc-haha-builtin

Validate the manifest at any time:

./bin/claude-haha plugin validate plugins/reverse-engineering

Quickstart — first real run

Once the plugin is enabled and at least one of the underlying tools is on your PATH (Ghidra or radare2 covers most native cases), pick a small, non-malicious open-source binary to drive the workflow. busybox is a good first target — it's a single static ELF, big enough to be interesting, small enough to finish quickly.

# 1. Get a sample
mkdir samples
curl -L -o samples/busybox 'https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64'

# 2. Triage — identifies file type, packing, picks the next skill
#    (in chat) /reverse-engineering:triage samples/busybox

# 3. Static analysis happens automatically once triage routes to pe-elf-macho.
#    For a non-x86 sample (firmware blob, MIPS router image, Cortex-M flash dump),
#    triage routes to firmware-blob first, which identifies the ISA and base
#    address before handing back to pe-elf-macho.

# 4. Final report
#    (in chat) /reverse-engineering:report <sample-id>

Expected products under ${ARTIFACT_DIR}/<sample-id>/:

triage.md            — file type, entropy, routing decision
static-native.md     — imports, key functions decompiled, strings, decoded constants
report.md            — verdict + findings table + IOCs + open questions

Confidence is honest: static-only conclusions about runtime behaviour cap at medium. To upgrade to high you have to run frida-dynamic against a target you've authorised.

Development workflow (changing skills / agent prompts)

The plugin loader caches each plugin under ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/ keyed on the manifest version. That means a naive "edit SKILL.md, reload" loop will not see your changes until the version is bumped.

Two options:

Option A — version bump (publishing flow)

# Edit plugin sources, then:
# 1. Bump "version" in plugins/reverse-engineering/.claude-plugin/plugin.json
# 2. Re-materialise:
Invoke-RestMethod -Method POST -Uri http://127.0.0.1:3456/api/plugins/update `
  -ContentType 'application/json' `
  -Body '{"id":"reverse-engineering@cc-haha-builtin","scope":"user"}'
Invoke-RestMethod -Method POST -Uri http://127.0.0.1:3456/api/plugins/reload `
  -ContentType 'application/json' -Body '{}'

Option B — dev junction (fast iteration loop)

# Replace the cached version dir with a junction to the in-repo source.
bun run plugins/reverse-engineering/scripts/dev-link.ts

# Now editing any SKILL.md / agent / command takes effect after just:
Invoke-RestMethod -Method POST -Uri http://127.0.0.1:3456/api/plugins/reload `
  -ContentType 'application/json' -Body '{}'

# When done, restore the real cache before publishing:
bun run plugins/reverse-engineering/scripts/dev-link.ts --restore

dev-link.ts is Windows-only (uses mklink /J); on macOS/Linux a manual ln -s does the same thing.

Smoke test

End-to-end check after manifest changes — assumes server (:3456) and vite (:1420) are running (start them as documented in docs/desktop/10-local-mcp-testing.md):

bun run plugins/reverse-engineering/scripts/smoke.ts

The script registers the marketplace, enables the plugin, runs /api/plugins/update + /reload, and asserts that detail returns the right version, zero errors, and the expected component counts (counted from the on-disk source, not hardcoded). Exits non-zero on any mismatch.

External tool prerequisites

The plugin doesn't ship the underlying tools. You need them on your machine (installable independently — none are required all at once):

MCP What you need Install
ghidra Ghidra (NSA), Java 17+, uvx (from uv) https://ghidra-sre.org + set GHIDRA_INSTALL_DIR
radare2 r2 on PATH, Node https://rada.re
gdb GDB on PATH (gdb-multiarch for cross-arch), Node apt install gdb gdb-multiarch / brew install gdb
lldb LLDB on PATH, uvx macOS: built-in via Xcode CLT; Linux: apt install lldb; Windows: LLVM installer
jadx Java 17+, uvx jadx-mcp-server pulls JADX itself
apktool Java 17+, uvx, apktool jar https://ibotpeaches.github.io/Apktool/
frida frida-tools, frida-server on the target device, uvx pip install frida-tools

You can disable individual MCP servers (e.g., turn off Frida if you only do static work) from the desktop MCP settings page (Settings → MCP) — the plugin's job is to bundle the configurations; per-server enable/disable is a runtime decision, not a manifest one.

User-config knobs

Key Default Purpose
GHIDRA_INSTALL_DIR (env fallback) Path to Ghidra install. Substituted into the ghidra MCP server's env at launch.
ARTIFACT_DIR artifacts/re-runs Where reports and intermediates go. Resolved relative to the agent's current working directory at run time.

Scope and rules

  • Read-only on samples. No skill in this plugin will execute a sample on the host. Frida runs only on user-authorised targets (sandboxed device or VM).
  • No public uploads. No VirusTotal, no malware-bazaar pushes.
  • No commercial license cracking. The crackme-keygen skill is for CTFs and self-owned binaries.
  • Confidence is honest. Static-only conclusions about runtime behaviour cap at medium; high requires confirmation by another channel.

References