我用 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 大小 里面是什么
.text 54 MB x64 机器码:Bun 引擎 + Claude Code 源码编译后的字节码
.rdata 21 MB 只读数据:所有字符串常量、System Prompt 文本、JSON Schema 模板
.data 2 MB 全局变量
.pdata <1 MB 异常处理表
.rsrc <1 MB Windows 资源(图标等)
.reloc <1 MB 重定位表
.bun 138 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_verify tengu_sparrow_ledger 是否注入"复现→修改→验证"工作流
act_dont_rederive tengu_cedar_lantern 是否注入"已知事实不要反复推导"
ownership_frame tengu_walnut_prism 影响多个模块的审慎程度
tool_param_json tengu_silent_harbor JSON 参数格式提醒
heron_brook tengu_heron_brook 远程注入任意文本到 System Prompt
autonomy_append tengu_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、日期、当前目录
15 Focus Mode 用户只看最终回复
16 URL 规则 绝对不允许猜测 URL
17 文件规则 没读过不许写,改了别重读
18 Prompt 注入警觉 工具结果里若有可疑内容,先告诉用户

九、认证系统——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

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐