news 2026/10/12 21:53:19

Claude Code 二进制里的 Unicode 隐写:用 XOR 还原检测代码的完整实验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Claude Code 二进制里的 Unicode 隐写:用 XOR 还原检测代码的完整实验

1. 从 claude.exe 里挖出 Unicode 隐写:一次本地可复现的 XOR 还原实验

Claude Code 二进制里藏检测代码这件事,真正值得动手复现的不是"有没有"这个结论,而是怎么从 225 MB 的 PE 可执行文件里把 JavaScript 源码捞出来、怎么认出 Unicode 隐写、怎么用 XOR 把黑名单解出来。这套方法本身是通用的:Bun SEA(Single Executable Application)把 JS 源码以字符串形式嵌进原生二进制,不做反编译也能直接搜。你只要会fs.readFileSync加indexOf,就能在本地把整条链路走一遍。

这篇写给三类人:一是想验证自己装的 Claude Code 版本里到底有没有这段逻辑的开发者;二是对二进制字符串提取、XOR 解码、Unicode 隐写这类逆向基础手法感兴趣的安全学习者;三是想搞清楚"隐写标记到底怎么被服务端读出来"的技术好奇者。全程在本地跑,不需要联网抓包,也不需要任何特殊网络环境,一个 Node.js 环境加一个claude.exe就够了。

我会按"定位文件 → 提取字符串 → 识别 XOR 线索 → 试探密钥 → 还原黑名单 → 比对隐写字符"的顺序走,每一步都给可复制的脚本和实际输出。踩过的坑也会标出来,比如为什么直接toString('utf8')会丢字符、为什么 XOR 密钥要从91这个常量反推而不是暴力枚举。

2. TaoToken 前置:把还原出来的检测逻辑接到可控的模型端点上

还原出检测逻辑之后,下一步自然是"那我怎么在一个干净、可控的环境里继续用 Claude 系模型做编码"。这里就涉及接入层的事。TaoToken 提供的是兼容 Anthropic 与 OpenAI 风格的统一 API 入口,你可以把它理解成一个"模型网关":本地工具(Claude Code、Cline、Codex 等)把请求发到统一 Base URL,由网关路由到具体模型。对做这类逆向实验的人来说,它的价值在于请求端点、模型 ID、密钥三者都是你自己配的,链路透明,方便对照观察 System Prompt 里到底带了什么。

先把几个关键地址记下来,后面配置要用:

  • 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
  • API 根地址:https://taotoken.net/api
  • 模型对话(验证模型是否通):https://taotoken.net/api/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
  • Coding Plan(长期编码/Agent 场景):https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
  • 控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
  • API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
  • Claude Code 接入说明:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude_code&utm_campaign=rewrite

为什么做这个实验要先把接入层理清?因为原版检测逻辑里有个关键分支:NN1()会读process.env.ANTHROPIC_BASE_URL,取出 hostname 去匹配那 147 条黑名单。也就是说,你用什么 Base URL,直接决定了known这个标记位是 true 还是 false。如果你把 Base URL 指向一个自建或可信的网关,known分支就不会命中黑名单,隐写字符会退化成正常撇号。这本身就是验证"隐写是否生效"的一个对照实验:同一份二进制,换 Base URL,观察 System Prompt 里撇号码点的变化。

拿 Key 的流程不复杂:进控制台 → API Keys → 新建 → 复制。但注意,Key 只在创建时完整显示一次,关掉页面就只剩掩码,所以建完立刻存到本地环境变量或密码管理器。我一般这么放:

# Linux / macOS,写进 ~/.zshrc 或 ~/.bashrc export TAOTOKEN_API_KEY="sk-你的key" export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="$TAOTOKEN_API_KEY"
# Windows PowerShell,写进 $PROFILE $env:TAOTOKEN_API_KEY = "sk-你的key" $env:ANTHROPIC_BASE_URL = "https://taotoken.net/api" $env:ANTHROPIC_AUTH_TOKEN = $env:TAOTOKEN_API_KEY

这里有个容易混的点:Claude Code 认的是ANTHROPIC_BASE_URL+ANTHROPIC_AUTH_TOKEN(或ANTHROPIC_API_KEY),而很多第三方工具认的是OPENAI_BASE_URL+OPENAI_API_KEY。做实验时建议两套都显式设好,避免工具读错变量导致 401。设完之后用echo $ANTHROPIC_BASE_URL确认一下,别让旧的代理配置残留——这一点在排障章节还会展开。

3. 可复制配置:定位二进制、提取字符串、还原 XOR 黑名单

这一节是全文的技术核心,全部可复制。先明确目标文件路径。Claude Code 用 Bun SEA 打包,Windows 下典型位置是:

D:\nodejs\node_global\node_modules\@anthropic-ai\claude-code\bin\claude.exe

macOS / Linux 下通常是:

/usr/local/bin/claude ~/.npm/_npx/node_modules/@anthropic-ai/claude-code/bin/claude

注意bin/claude有时是个几十 KB 的启动器(launcher),真正的 225 MB 二进制在node_modules里。所以脚本里要先判断文件大小,小于 10 MB 就顺着目录往上找真正的二进制。下面这个find_code.cjs就是干这个的,同时扫描关键字符串:

// find_code.cjs —— 定位并扫描 claude 二进制中的检测代码关键词 const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); function resolveRealBinary(p) { try { if (fs.statSync(p).size > 10 * 1024 * 1024) return p; } catch {} const dir = path.dirname(p); const exe = process.platform === 'win32' ? '.exe' : ''; for (const rel of [ 'node_modules/@anthropic-ai/claude-code/bin/claude' + exe, '../node_modules/@anthropic-ai/claude-code/bin/claude' + exe, ]) { const full = path.resolve(dir, rel); try { if (fs.statSync(full).size > 10 * 1024 * 1024) return full; } catch {} } return null; } function scanFile(exePath) { if (!fs.existsSync(exePath)) { console.error('文件不存在:', exePath); process.exit(1); } const data = fs.readFileSync(exePath); const text = data.toString('utf8'); const stats = fs.statSync(exePath); console.log('文件:', exePath); console.log('大小:', (stats.size / 1024 / 1024).toFixed(1), 'MB'); console.log('---\n'); const keywords = [ 'Asia/Shanghai', 'Asia/Urumqi', 'function EN1()', 'function hN1(H,q)', 'function ff7(H)', 'function _f7(H)', 'function NN1()', 'cnTZ', 'labKw', 'DO NOT mention this', ]; let found = 0; keywords.forEach(k => { const idx = text.indexOf(k); if (idx >= 0) { found++; const start = Math.max(0, idx - 20); const end = Math.min(text.length, idx + k.length + 80); let ctx = ''; for (let i = start; i < end; i++) { const c = text.charCodeAt(i); ctx += (c >= 0x20 && c <= 0x7E) ? text[i] : '.'; } console.log(`✓ "${k}"\n ${ctx}\n`); } else { console.log(`✗ "${k}" → 未找到\n`); } }); console.log('---\n结论: ' + found + '/' + keywords.length + ' 命中'); } const arg = process.argv[2]; if (arg) { scanFile(resolveRealBinary(arg) || arg); } else { // 简化版:直接尝试 npm root -g try { const root = execSync('npm root -g', { encoding: 'utf8' }).trim(); const exe = process.platform === 'win32' ? '.exe' : ''; scanFile(root + '/@anthropic-ai/claude-code/bin/claude' + exe); } catch (e) { console.error('未找到,请手动指定路径: node find_code.cjs <path>'); } }

跑起来:

node find_code.cjs # 或指定路径 node find_code.cjs "D:\nodejs\node_global\node_modules\@anthropic-ai\claude-code\bin\claude.exe"

命中Asia/Shanghai、cnTZ、labKw、function EN1()这些关键词,就说明这段逻辑在。接下来是还原 XOR 黑名单。原逻辑里有个常量var GN1 = 91;,这是 XOR 密钥,配合 Base64 使用:

// decode_blacklist.cjs —— 还原 Base64 + XOR(91) 编码的域名黑名单 // 密文需要从二进制里提取,这里用占位符演示流程 const GN1 = 91; // XOR 密钥,来自二进制中的常量 function _f7(H) { const q = Buffer.from(H, 'base64'); let $ = ''; for (const K of q) $ += String.fromCharCode(K ^ GN1); return $.split(','); } // 从 claude.exe 中搜索形如 "aHR0c..." 的长 Base64 串,替换到下面 const cipher = '把从二进制里提取到的Base64密文粘到这里'; const list = _f7(cipher); console.log('解密后条目数:', list.length); console.log(list.slice(0, 20).join('\n'));

怎么找密文?在二进制里搜_f7(或GN1附近的字符串,通常能看到一个很长的、只含A-Za-z0-9+/=的串。把它整段复制出来。如果密钥不是 91,可以写个小循环试探:

// 密钥试探:对同一段密文尝试 0-255,看哪个密钥解出可读域名 const cipher = '你的Base64密文'; const buf = Buffer.from(cipher, 'base64'); for (let key = 0; key < 256; key++) { let s = ''; for (const b of buf) s += String.fromCharCode(b ^ key); // 可读域名通常含 .com / .cn / .ai 且大部分是可打印字符 const printable = [...s].filter(c => c.charCodeAt(0) >= 0x20 && c.charCodeAt(0) <= 0x7E).length; if (printable / s.length > 0.95 && /\.(com|cn|ai|net|org)/.test(s)) { console.log('候选密钥:', key, '→', s.slice(0, 120)); } }

实测下来,密钥 91 解出来的结果里域名占比很高,且逗号分隔规整,基本一眼能确认。这一步的关键是先看可打印字符比例,再看是否含域名后缀,两个条件同时满足才值得人工核对,否则 256 个候选里会有一堆噪声。

4. 验证请求与成功结果:对照隐写字符的码点变化

还原出黑名单只是第一步,真正要验证的是"隐写标记到底怎么被写进 System Prompt"。原逻辑里hN1(H, q)根据known和labKw两个布尔值返回四个不同的撇号字符:

knownlabKw返回字符Unicode 码点视觉表现
falsefalse'U+0027正常撇号
truefalse’U+2019右单引号
falsetrueʼU+02BC修饰字母撇号
truetrueʹU+02B9修饰字母上撇号

这四个字符在终端里几乎看不出区别,但码点完全不同。验证方法就是构造一个最小请求,把 System Prompt 打出来,逐字符看码点。下面这段脚本模拟ff7的拼接逻辑,并打印每个字符的码点:

// verify_stego.cjs —— 复现日期字符串拼接并检查撇号码点 function hN1(known, labKw) { if (!known && !labKw) return "'"; // U+0027 if (known && !labKw) return "\u2019"; // U+2019 if (!known && labKw) return "\u02BC"; // U+02BC return "\u02B9"; // U+02B9 } function ff7(dateStr, known, labKw, cnTZ) { const $ = hN1(known, labKw); const K = cnTZ ? dateStr.replaceAll('-', '/') : dateStr; return `Today${$}s date is ${K}.`; } // 场景 A:非中国时区,无代理 console.log('A:', ff7('2026-06-30', false, false, false)); // 场景 B:中国时区,命中黑名单 const b = ff7('2026-06-30', true, false, true); console.log('B:', b); console.log('B 的码点:', [...b].map(c => 'U+' + c.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')).join(' '));

跑出来的结果,场景 A 是Today's date is 2026-06-30.,撇号是 U+0027,日期用横杠;场景 B 是Today’s date is 2026/06/30.,撇号变成 U+2019,日期分隔符变成斜杠。服务端只要检查这两个特征,就能还原出known和cnTZ两个状态位——这就是隐写的全部机制,没有独立上报通道,标记搭在本来就存在的 System Prompt 里。

要验证真实请求里带的是什么,可以在接入层加一个本地日志代理,或者直接用 TaoToken 的模型对话页面发一条请求,观察返回是否正常。如果只是想确认模型端点通不通,用 curl 打一发:

curl https://taotoken.net/api/v1/messages \ -H "x-api-key: $TAOTOKEN_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "max_tokens": 64, "messages": [{"role": "user", "content": "reply with OK only"}] }'

返回里带content数组且stop_reason正常,说明 Base URL、Key、Model ID 三件套配对了。这一步的意义在于:把接入层固定下来之后,你才能确定 System Prompt 里的隐写字符是本地二进制注入的,而不是网关改的。对照实验的变量控制就在这里。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

做这类实验最容易卡在环境配置上,而不是逆向本身。下面几个报错我都遇到过,按现象对号入座。

401 Unauthorized / invalid x-api-key。九成是 Key 没设对或变量名写错。Claude Code 读ANTHROPIC_AUTH_TOKEN,有些工具读ANTHROPIC_API_KEY,还有的读OPENAI_API_KEY。先echo一遍确认非空,再确认没有多余空格或引号。如果 Key 是从网页复制的,注意别把首尾的换行带进去。另外,Key 只在创建时完整显示,如果你存的是掩码版本,必然 401,回控制台重新建一个。

local proxy failed / connection refused。这个报错通常出现在你设了ANTHROPIC_BASE_URL指向本地某个端口,但那个本地服务没起来。做隐写实验时,如果你用本地日志代理抓 System Prompt,代理挂了就会报这个。排查顺序:先curl一下那个本地地址看通不通,再确认代理进程还在。如果不用本地代理了,记得unset ANTHROPIC_BASE_URL,否则 Claude Code 会一直往一个死端口发请求。

reading 'choices' of undefined。这是 OpenAI 风格响应解析失败的典型报错,多出现在用 OpenAI 兼容工具打 Anthropic 风格端点(或反过来)的时候。choices是 OpenAI 的字段,Anthropic 返回的是content。检查你的工具期望哪种格式,Base URL 是否带了正确的路径后缀(有的要/v1,有的不要)。TaoToken 的接入文档里对两种风格都有说明,对照着改。

OAuth / authentication_error。Claude Code 某些版本会走 OAuth 流程,如果你同时设了ANTHROPIC_AUTH_TOKEN和 OAuth 凭据,可能冲突。做实验时建议清掉旧的 OAuth 缓存(通常在用户目录的配置文件夹里),只保留环境变量这一条路径,减少变量。

Codex auth.json / Cline MCP 配置。如果你用 Codex 或 Cline 做对照实验,注意它们的配置文件位置不同。Codex 读~/.codex/auth.json,Cline 的 MCP 配置在 VS Code 的设置里。无论哪个,Base URL、Key、Model ID 三件套必须同时写全,缺一个都会报错。以 Codex 为例:

{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的key", "model": "claude-sonnet-4-5" }

Cline 的 MCP 配置则是 JSON 里嵌mcpServers,每个 server 单独给command、args、env。env 里把ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN显式传进去,别指望它继承 shell 环境。

最后一个坑:二进制里搜不到关键词不代表没有检测代码。Bun SEA 打包时可能对字符串做了压缩或分片,function EN1()这种带空格的写法可能变成function EN1(){或干脆被 minify 掉。所以扫描时除了精确匹配,也试试EN1、cnTZ这种短标识符。如果短标识符也搜不到,再考虑版本差异——v2.1.91 之前和 v2.1.197 之后本来就没有这段逻辑。

6. 继续用 Claude 系模型做编码:把接入层固定成可复现的基线

实验做完,你手里应该有三样东西:一份从二进制提取的关键词命中报告、一份 XOR 还原出的域名列表、一份隐写字符码点的对照输出。这三样构成了一个可复现的基线。之后无论你升级 Claude Code、换接入端点,还是换工具,都可以拿这套脚本重新跑一遍,看基线有没有变化。

如果你打算长期用 Claude 系模型做编码或 Agent 任务,建议把接入层固定下来,别每次手动改环境变量。TaoToken 的 Coding Plan 就是为这种长期场景准备的,配好之后 Base URL 和 Key 一次设定,Claude Code、Cline、Codex 都能复用同一套凭据。具体接入步骤在 Claude Code 接入说明里有,模型 ID 和路径后缀的对应关系在接入文档里查。

验证模型是否可用,最直接的是模型对话页面,发一条短请求看返回。需要管理多个 Key 或看用量,去控制台。新建或轮换 Key 在 API Keys 页面。这几个入口分工明确,做实验时按需取用就行。

最后留一个实用习惯:每次升级 Claude Code 之后,先跑一遍find_code.cjs,把输出存成带日期的文件,比如scan_20260703.txt。这样你就有了一条时间线,哪个版本引入了什么、哪个版本移除了什么,一目了然。逆向这件事,单次结论价值有限,可复现的对照记录才是真正有用的东西。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 21:52:37

基于动态滑动窗口的多轮长会话流式交互状态恢复机制

在生成式人工智能向各行各业纵深挺进的进程中&#xff0c;“多轮超长会话&#xff08;Multi-turn Long Context Sessions&#xff09;”正逐渐成为专业级工作流的标配。无论是在律师事务所针对数十万字卷宗的连续质询、在科研机构面向百篇前沿文献的交互式研读、还是在金融分析…

作者头像 李华
网站建设 2026/10/12 21:51:54

多模态智能体全流程实战:SGE流量抓取、关键词拓词与排名维稳

1. 多模态智能体规划的核心架构与设计思路多模态智能体规划这件事&#xff0c;说白了就是让一个系统同时具备"看懂、想清、执行"三种能力。我接触过不少团队做智能体&#xff0c;最常见的误区是一上来就堆模型、堆接口&#xff0c;结果跑起来发现各模块之间根本对不上…

作者头像 李华
网站建设 2026/10/12 21:51:22

Python importlib 深度解析:动态导入、热重载与插件系统实战

1. 为什么 importlib 值得你花时间搞懂很多人写 Python 好几年&#xff0c;对 import 的理解还停留在“写个 import xxx 就完事了”的阶段。直到某天遇到这些场景&#xff1a;想根据配置文件动态加载不同模块、想重载一个已经改过的模块而不用重启进程、想自己写一个插件系统让…

作者头像 李华
网站建设 2026/10/12 21:49:12

open-code-review:自托管代码审查协作系统的设计与落地实践

代码审查这件事&#xff0c;做了不等于做到。团队里挂几十个 commit&#xff0c;靠人肉翻 diff&#xff0c;拉上同事坐会议室一行一行念代码——这种场景我太常见了。效率低是一方面&#xff0c;更麻烦的是审查标准完全靠个人经验&#xff0c;A 盯规范、B 只关心跑不跑得通&…

作者头像 李华
网站建设 2026/10/12 21:47:33

一人公司内容获客:五工位AI工作流,告别工具堆叠

1. 一人公司做内容获客&#xff0c;为什么“工具堆叠”反而拖垮效率一个人撑起一家公司&#xff0c;最稀缺的从来不是想法&#xff0c;而是能稳定产出的时间。我见过太多独立开发者、自由职业者、小工作室主理人&#xff0c;电脑里躺着十几个AI工具&#xff1a;写文案的、做图的…

作者头像 李华
网站建设 2026/10/12 21:44:56

MCP真实价值再思考:TaoToken视角下从工具到生态的跨越

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华