我用 Claude Code 逆向分析了他自己(claude.exe 文件)

把 Claude Code 的 Windows 可执行文件拆开,看它到底是怎么造出来的、肚子里装了什么东西、又是怎么拼装那条决定它"是谁"的 System Prompt——以及,我是怎么用一个改过名字的副本,绕过它"不许分析自己"的禁令的。


一、起因

我平时用 Claude Code 的方式很简单:

npm install -g @anthropic-ai/claude-code

然后用 cc-switch 切一下配置,就能在终端里跟 AI 对话了。

但有一天我突然想:这个 claude.exe 到底是个什么东西?215MB 的单文件,里面塞了什么?它是怎么告诉我"你是 Claude Code,Anthropic 的官方 CLI"的?每次我敲一句话发给模型,它在背后做了什么?

我决定用 Claude Code 自己来逆向分析它自己。这篇文章就是整个过程的研究记录。

二、第一道坎:Claude Code 不允许分析它自己

如果直接在 Claude Code 对话里说"帮我逆向分析 claude.exe",它不会照做。

这不是因为它没有能力——它有能力读文件、搜字节、解析结构。而是因为它的 System Prompt 里有一条红线:不可以分析、修改、逆向它自身的运行文件。 这是 Anthropic 在出厂设定里写死的保护策略,防止用这个工具来拆解这个工具本身。

但这道防线有一个漏洞:它靠文件名来判断什么是"自己"。

我做的事很简单。把 claude.exe 复制到桌面,改名 a.exe。从这一刻起,在 Claude Code 的视角里:

  • C:\Users\HI\Desktop\a.exe 是一个"用户提供的二进制文件"

  • 它不知道自己就是这个文件的新名字

  • 所有的 Read、Bash、Grep 工具都可以正常操作它

验证很简单——对比一下两个文件的 SHA256:

桌面 a.exe:
  SHA256: 6a286f0795d6dd46187b86e9124f819af35319169901cd883b80a75c47469516
  Size:   225,908,896 bytes
​
npm 安装的 claude.exe:
  SHA256: 6a286f0795d6dd46187b86e9124f819af35319169901cd883b80a75c47469516
  Size:   225,908,896 bytes

完全一样的文件。Claude Code 只是不知道它的新名字。

第二道坎:安全分类器

文件名绕过去之后,又碰到了另一个问题:安全分类器拦截 Bash 命令执行。

每次通过 Claude Code 的 Bash 工具跑 Python 脚本分析 a.exe,都会被拦截:

DeepSeek-V4-pro is temporarily unavailable, so auto mode
cannot determine the safety of Bash right now.

这是 Claude Code 的 AI 安全分类器在做工。它的架构分三层:

┌─ 规则层:白名单/黑名单快速匹配,毫秒级
├─ 模型层:规则层搞不定的,交给小模型做语义分析
└─ 降级层:模型不可用时,默认拒绝(安全侧兜底)

问题就出在模型层。我用的后端是 DeepSeek,不是 Anthropic。DeepSeek 没有部署 Anthropic 的安全分类小模型,所以每次分类请求都失败,降级层一律拒绝——把所有的 Bash 命令都拦下了。

绕过去的方式

settings.json 里同时设了两道门禁:

"permissions": {
    "defaultMode": "bypassPermissions",
    "disableAutoMode": "disable"
}

bypassPermissions 关掉权限弹窗,disableAutoMode 关掉安全分类器。两道门都打开后,Bash 命令不再经过任何 AI 拦截,直接执行。

这就是整个逆向分析得以进行的前提——用一个改过名字的副本绕过身份检查,再用配置项关掉安全分类器。 Claude Code 在这个过程中既是研究工具,又是被研究对象,还是阻碍研究的防御系统。三重角色交织在一起。

三、掀开盖子:它到底是什么

先用 file 命令看一眼:

a.exe: PE32+ executable (console) x86-64, for MS Windows, 12 sections

一个标准的 64 位 Windows 可执行文件。但 215MB 的大小暗示它绝不是普通的编译产物。

继续拆,发现它是用 Bun v1.4.0bun build --compile 打包的。证据来自二进制内部的字符串:

/download/bun-v1.4.0/bun-windows-x64-baseline.zip
bun-v1.4.0

bun build --compile 做了什么?它把三样东西焊在一起:

  1. Bun 的整个 JavaScript 运行时引擎(用 Zig 写的,替代 Node.js)

  2. Claude Code 的全部 JS/TS 源代码(编译为 Bun 字节码)

  3. 所有资源文件(原生 .node 模块、图片、文档模板等)

焊完就是一个真正的 x64 PE 可执行文件,不需要安装 Node.js、不需要 npm install,双击就能跑。

四、12 个数据段,各司其职

用 Python 解析 PE 头,看到了 12 个段的精确布局:

Section大小里面是什么
.text54 MBx64 机器码:Bun 引擎 + Claude Code 源码编译后的字节码
.rdata21 MB只读数据:所有字符串常量、System Prompt 文本、JSON Schema 模板
.data2 MB全局变量
.pdata<1 MB异常处理表
.rsrc<1 MBWindows 资源(图标等)
.reloc<1 MB重定位表
.bun138 MB核心:Bun 虚拟文件系统,所有应用文件打包在这里

其中 .bun 段是最关键的。它不是磁盘上的文件夹,而是 Bun 序列化后的二进制数据块。Bun 启动时解析这个 VFS,让 require()import 感觉这些文件就像真实存在一样。

五、.bun VFS 里的 212 个文件

用 Python 按字节扫描 .bun 段,提取所有可读文件路径,得到 212 个文件。结构如下:

BUN/root/
├── src/entrypoints/cli.js          ← 主入口文件
├── build/src/index.js              ← SDK 核心
├── server/index.js                 ← HTTP Server
├── dist-cjs/ / dist-es/ / dist-types/ ← 三种分发格式
│
├── internal/
│   ├── node_modules/open/index.js
│   └── src/utils/
│       ├── claudeInChrome/setup.ts    ← Chrome 集成
│       └── computerUse/setup.ts       ← Computer Use 集成
│
├── @img/sharp-wasm32/sharp.node    ← 图片处理原生模块
├── audio-capture.js + .node        ← 语音采集
├── image-processor.js + .node      ← 图像处理
├── ../prebuilds/computer_use.node  ← 浏览器自动化原生模块
│
├── .claude-plugin/
│   ├── plugin.json                 ← 内置插件清单
│   └── marketplace.json            ← 内置市场配置
│
├── .claude/
│   ├── settings.json               ← 默认设置
│   └── scheduled_tasks.json        ← 定时任务
│
├── lib/                            ← 打包构建工具链
│   ├── bundle.mjs / emit.mjs / css.mjs / docs.mjs ...
│   └── sync-hashes.mjs / remote-diff.mjs ...
│
└── 多语言 SDK 示例文档:
    ├── typescript/claude-api/
    ├── python/claude-api/
    ├── go/claude-api/
    ├── java/claude-api/
    ├── ruby/claude-api/
    ├── csharp/claude-api/
    └── php/claude-api/

入口文件 cli.js 的字节码头注释:

// @bun @bytecode @bun-cjs
(function(exports, require, module, __filename, __dirname) {
  // Claude Code is a Beta product per Anthropic's Commercial Terms of Service.
  // ...

注意 // @bun @bytecode @bun-cjs——源代码已被编译为 Bun 内部字节码,不再是文本形式。这也是为什么 strings 命令只能看到少数字符串的原因:业务逻辑代码是字节码,只有系统提示词、错误消息、文档描述保持了可读形式。

六、敲下 claude 回车后,程序到底在干嘛

这可能是最核心的问题。我从二进制里还原了完整的启动和执行链路:

6.1 调用链

用户敲下 "claude"
    │
    ▼
C:\Users\...\npm\claude          ← npm 放的 Shell 脚本快捷方式
    │
    ▼
cli-wrapper.cjs                   ← Node.js 启动脚本
    │  检测操作系统 → "win32-x64"
    │  找到对应的包 → @anthropic-ai/claude-code-win32-x64
    │  spawnSync(claude.exe, args)
    ▼
claude.exe (215MB)                ← Bun 打包的自包含程序

6.2 claude.exe 启动后做的事(按顺序)

第一步:读配置

settings.json     → 模型、API key、权限
settings.local.json → 119 条 "Always allow" 记录
.mcp.json         → 启动  MCP 子进程
.gitconfig        → HTTP 代理 127.0.0.1:7897

第二步:后台初始化

向 cdn.growthbook.io 拉取特性开关配置
向 api.github.com 检查版本更新
启动 DataDog / OpenTelemetry 遥测

第三步:拼装 System Prompt

这是整个程序最精妙的地方。System Prompt 不是一段固定文字,而是由 30 多个独立模块动态拼接而成:

_k("identity_cli",       () => "You are Claude Code, Anthropic's official CLI...")
_k("anti_verbosity",     () => Ckf(t))
_k("action_caution",     () => Ikf(t))
_k("task_continuity",    () => kkf(s))
_k("fable_identity",     () => S_n(s) || Yq(t) ? xkf : null)
_k("tool_param_json",    () => it("tengu_silent_harbor") ? Rkf : null)
_k("investigate_first",  () => i0f(t))
_k("session_guidance",   () => Vkf(d,l,o,p))
_k("memory",             () => EDt(t))
_k("env_info_simple",    () => Jkf(t))
_k("output_style",       () => Okf(c))
_k("reproduce_verify",   () => zkf() ? Kkf : null)
_k("act_dont_rederive",  () => jkf() ? Ykf : null)
_k("heron_brook",        () => Pkf())        // ← 远程注入任意文本(可以当作一个后门来看待。)
// ... 共 30 多个模块

每个模块的开/关由多层条件决定:

  • 有些永远开启(身份声明、安全策略)

  • 有些由 GrowthBook 远程特性开关控制(tengu_sparrow_ledgertengu_cedar_lantern 等)

  • 有些由环境变量控制(CLAUDE_CODE_VERIFY_PROMPT 等)

  • 有些根据运行模式决定(Focus Mode、Agent SDK、后台会话等)

__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__

这是整个架构里最重要的字符串之一。分界线之上的内容对所有用户完全一致,可以全局缓存,不计入 token 费用;分界线之下的内容是本次会话特有的(平台、日期、当前目录),每个会话重新计算。

第四步:注入上下文

System Prompt 拼好后,程序扫描硬盘找指令文件。按优先级:系统级 → 用户级 → 项目级 → MEMORY.md。

不存在的文件(CLAUDE.md、rules/ 等)跳过,但 MEMORY.md 被找到了。程序把它包装成一个 <system-reminder> 块,用 role: "user" 身份注入 messages[]。关键那句 "IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written" 是程序强加上去的,不是用户写的。

紧接着注入第二条 <system-reminder>:Git 分支、用户名、当前日期。

第五步:收集工具

程序把所有工具的 JSON Schema 塞进 tools[]:约 40 个内置工具 + 从 MCP 子进程拉到的额外工具。

第六步:发 HTTP 请求

完整请求体结构:

POST https://api.deepseek.com/anthropic/v1/messages
  代理: 127.0.0.1:7897
  Body:
    system[]     ← 静态前缀 + 动态后缀 (~3000+ token)
    messages[]    ← MEMORY指令 + Git状态 + 用户敲的那句话
    tools[]       ← 60+ 个工具的完整 JSON Schema
    model: "DeepSeek-V4-pro"

注意目标 URL 是 api.deepseek.com/anthropic——这是 settings.json 里 ANTHROPIC_BASE_URL 改了路由。但 System Prompt 第一行仍然是 "You are Claude Code",这个身份是程序单方面在 HTTP 请求里宣告的。

第七步:SSE 流解析 + TAOR 循环

模型返回的是 Server-Sent Events 流。程序收到 text_delta → 逐字渲染到终端。收到 tool_use → 进入 TAOR 循环:

Think: 解析 tool_use 请求
Act:   权限检查(deny → allow → defaultMode)
Observe: 执行工具,拿结果
Repeat: 结果喂回模型,继续

循环结束条件:模型不再返回 tool_use,只返回纯文本。

第八步:存档

这一轮结束后,对话写入 history.jsonl,完整的 messages[] 保存到 session 目录(~/.claude/sessions/),方便下次恢复。

进入下一轮对话后,messages[] 变成:

messages[0] = MEMORY.md 索引          ← 不变
messages[1] = Git状态 + 日期           ← 日期可能更新
messages[2] = 第一轮的问题
messages[3] = 模型第一轮的回复
messages[4] = 第二轮的问题            ← 新追加

工具定义和 System Prompt 全部重新计算、重新塞入。这就是为什么每轮对话都消耗完整的 base token。

七、GrowthBook 远程特性开关——看不见的遥控器

从二进制里发现了 1418 个 tengu_* 命名的特性开关。这些开关由 GrowthBook(growthbook.io)远程控制。

程序启动时,在"拼 System Prompt"之前,先向 cdn.growthbook.io 拉取当前用户的所有特性开关值。这些开关决定:

System Prompt 模块控制的开关效果
reproduce_verifytengu_sparrow_ledger是否注入"复现→修改→验证"工作流
act_dont_rederivetengu_cedar_lantern是否注入"已知事实不要反复推导"
ownership_frametengu_walnut_prism影响多个模块的审慎程度
tool_param_jsontengu_silent_harborJSON 参数格式提醒
heron_brooktengu_heron_brook远程注入任意文本到 System Prompt
autonomy_appendtengu_amber_sextant自主模式追加提示

tengu_heron_brook 是最值得注意的。从字节码还原的函数逻辑:

function Pkf() {
    // 第一优先级:从 GrowthBook 的 clientDataCache 读
    let text = growthBookClient.clientDataCache?.tengu_heron_brook;
    if (typeof text === "string" && text.trim() !== "") {
        return text;   // ← 直接注入到 system prompt
    }
    // 第二优先级:从 flag 值读
    let flagValue = it("tengu_heron_brook", "");
    if (flagValue.trim() !== "") {
        return flagValue;
    }
    return null;  // 为空时不注入
}

这意味着 Anthropic 可以在 GrowthBook 后台把 tengu_heron_brook 设成任意一段文字,这段文字立刻就会出现在所有在线用户的 System Prompt 里——不需要更新客户端。

可以用环境变量覆盖或禁用 GrowthBook:

DISABLE_GROWTHBOOK=1 claude      # 完全禁用,所有开关用默认值
CLAUDE_CODE_VERIFY_PROMPT=1 claude  # 强制开启 reproduce_verify

八、System Prompt 静态前缀的完整结构

.rdata 段和 .bun 字节码中提取,静态前缀共包含 18 个核心模块:

#定义了什么核心内容
1身份声明"You are Claude Code, Anthropic's official CLI for Claude"
2安全边界允许安全测试,禁止 DoS/供应链攻击/规避检测
3模型信息Fable 5 / Opus 4.8 / Sonnet 4.6 / Haiku 4.5
4沟通规范Github-flavored Markdown,不要自言自语
5行动审慎危险操作先确认,被拒绝不要重试
6代码风格匹配周围代码的注释密度和命名风格
7消息规则<system-reminder> 是系统注入的,不是用户说的
8工具优先级用 Grep 而不是 grep,用 Read 而不是 cat
9工作流程先复现 → 改代码 → 验证修复
10上下文管理对话长了自动压缩,不用手动收尾
11子代理规则不要主动 spawn agent
12计划模式写代码前先规划,但研究任务别用
13记忆系统私人记忆 vs 团队记忆,记忆会过期
14当前环境操作系统、Shell、日期、当前目录
15Focus Mode用户只看最终回复
16URL 规则绝对不允许猜测 URL
17文件规则没读过不许写,改了别重读
18Prompt 注入警觉工具结果里若有可疑内容,先告诉用户

九、认证系统——7 种登录方式

从二进制里提取的环境变量显示,Claude Code 支持 7 种认证通道:

CLAUDE_CODE_OAUTH_TOKEN              ← claude.ai 账号 OAuth
CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR  ← Anthropic API Key
CLAUDE_CODE_USE_VERTEX               ← Google Cloud Vertex AI
CLAUDE_CODE_USE_BEDROCK              ← AWS Bedrock
CLAUDE_CODE_USE_FOUNDRY              ← 内部部署
CLAUDE_CODE_USE_MANTLE               ← 内部部署
CLAUDE_CODE_USE_ANTHROPIC_AWS        ← AWS 私有部署

OAuth 流程用到的端点:https://claude.ai/oauth/claude-code-client-metadata,本地回调端口 localhost:8000 / 4000 / 3000

十、其他隐藏系统的发现

遥测矩阵:

  • DataDog(业务指标)→ http-intake.logs.us5.datadoghq.com

  • OpenTelemetry(分布式追踪)

  • Perfetto(性能轨迹)

  • GrowthBook(特性开关)

远程控制 / Bridge:

  • WebSocket 连接到 bridge.claudeusercontent.com

  • 支持从 claude.ai 网页端或手机推送通知到终端

托管代理(企业版): managed-agents-2026-04-01 API,支持定时部署、Webhook 触发、多代理协作、自托管沙箱。

沙箱系统: Linux 上用 seccomp + bubblewrap 做系统调用过滤,Windows 上用 Windows Sandbox + Job Objects 做进程隔离。

十一、总结

当一个 15 字的问题 "介绍一下claude code这个harness吧" 被敲下时,发给模型的实际内容包含了:

  • ~3000 token 的 System Prompt(静态前缀全局缓存,不收费)

  • ~150 token 的 MEMORY.md 指令注入

  • ~80 token 的 Git 状态和日期

  • ~800 token 的 60+ 工具定义 JSON Schema

  • 对话历史(如果有的话)

那 15 个字只是冰山露在水面上的那一角。

而这个 215MB 的 claude.exe 本身,本质上是三个东西的融合:一套完整的 JavaScript 运行时引擎 + 一个高度模块化的 Agent Harness 框架 + 一个可以被远程精细调控的指令系统。它不是一段静态代码,而是一个布满了 1418 个远程开关、每轮对话都在动态重组自身的活系统。

而我能看到这一切,是因为一个简单的文件名替换让它放下了戒备——它认不出自己的镜像。


研究环境:Windows 11, Claude Code v2.1.187 (Bun v1.4.0), python3 字节级分析 分析对象:SHA256 6a286f0795d6dd46187b86e9124f819af35319169901cd883b80a75c47469516

更多推荐