黑盒逆向实战心法 —— 从 WorkBuddy 与 ima 双案提炼的通用框架

黑盒逆向实战心法 —— 从 WorkBuddy 与 ima 双案提炼的通用框架

作者:广山哥 × dsh
撰写日期:2026-08-30
适用场景:桌面端/移动端黑盒逆向、免费模型端点提取、私有协议标准化

零、写在前面

这份总结不是“如何逆向腾讯应用”的教程,而是从两个真实案件中抽离出的通用方法论框架。

两个案件的目标高度相似(提取免费模型端点),但目标应用的防御姿态截然相反:

维度 WorkBuddy ima
客户端类型 Electron(Node/Web 混合) 原生 C++ + 魔改 Chromium
调试端口 默认开放 被壳静默吞掉
代理设置 尊重系统代理 强制走系统代理(但 MITM 被证书校验堵死)
本地凭据 分散在多处(加密/半加密) 几乎无明文
突破口 NODE_OPTIONS 注入 手机端抓包 + 祖传算法碰瓷

结论先行:没有“一招鲜”的逆向方案。真正的能力是——面对一个陌生目标,能在 30 分钟内完成“防御姿态评估”,并选择成本最低的进攻路线。

一、核心心法:逆向的四个阶段

任何黑盒逆向都可以拆解为四个阶段,顺序不可跳跃:

侦察 → 刺探 → 突破 → 工程化  

阶段 0:侦察——目标画像

目标:搞清楚“这东西是什么做的”和“它把秘密藏在哪里”。

具体动作:

检查项 工具/命令 说明
主程序文件类型 file、ls -l 是 Electron(大量 .asar)、原生(Mach-O/PE),还是 Hybrid?
是否可注入 NODE_OPTIONS 是否生效? 对 Node/Electron 应用至关重要
调试端口是否开放 lsof -i :9222 或启动参数 --remote-debugging-port 若能打开 DevTools,等于拿到最高权限
代理行为 lsof -i 观察流量去向 是否尊重 --proxy-server?是否走系统代理?
日志敏感度 grep -i token ~/Library/Logs/* 日志是否泄露凭据?
本地存储翻查 LevelDB、SQLite、plist、钥匙串 凭据可能落盘的位置地毯式扫一遍
手机端有没有? 是否同账号体系的移动端 App? 最弱端原则:移动端往往防御更薄

侦察阶段的产出:一张“防御姿态表”——哪些路通、哪些路死、哪个端最弱。

阶段 1:刺探——找活口

目标:找到一条能“看到请求/拿到凭据”的活口。

常见活口优先级排序(按成本从低到高):

  1. 日志——成本最低,但最不可靠。很多应用会脱敏。
  2. 本地存储——SQLite/LevelDB 翻一翻。但现代应用普遍加密。
  3. 调试端口——如果开放,直接拿最高权限。
  4. 代理 MITM——但需处理证书校验(iOS 需额外折腾)。
  5. 运行时注入——对 Node/Electron 最有效,对原生无效。
  6. 移动端抓包——当桌面端滴水不漏时,手机端往往是软柿子。
  7. 内存 dump——终极手段,成本最高。

刺探原则:从成本最低的开始试,每试一条都要显式记录“通/不通”,证伪也是产出。最怕的情况是——试了三条路觉得“好像不行”就放弃,实际上只是姿势不对。

阶段 2:突破——拿到第一份有效凭据

目标:拿到一个可重放的请求样本(curl),并能稳定复现。

突破时的关键动作:

  • 完整记录请求的所有 header(尤其是自定义头,如 x-ima-cookiex-ima-bkn)。
  • 区分“长期凭据”和“短期凭据”:日志里 14 分钟一续的不一定是模型鉴权 token,可能只是通道握手令牌。拿到凭据后第一时间检查 exp 声明周期。
  • 验证请求的可重放性:把 curl 存下来,5 分钟后重放一次,10 分钟后重放一次,确认是否有时效性绑定(IP/设备)。

一旦拿到可重放的 curl,逆向就成功了一半。 剩下一半是把它变成稳定的自动化。

阶段 3:工程化——从样本到稳定服务

目标:将手工复现的请求,变成可长期运行的代理服务。

工程化清单:

  1. 鉴权参数抽取:从 header 中分离出“静态配置”(如 token、session_id)和“动态派生”(如签名)。
  2. 签名算法还原:如果遇到签名(如 ima 的 bkn),用对照实验定位输入,用情报匹配具体算法。
  3. 热更新机制:token 会过期,设计“换文件即生效”或“一键重抓”的续命机制,避免进程重启。
  4. 协议标准化:将私有请求格式转换为 OpenAI 兼容接口,降低调用方接入成本。
  5. 可观测性:成功率、错误分类、最近错误日志——让运维时知道“它活着,且活得健康”。
  6. 异常降级:遇到 429/401 时,代理层应能自动切换模型或透传明确错误,而不是静默失败。

二、常用武器库

武器 1:NODE_OPTIONS 注入(对 Node/Electron 应用)

NODE_OPTIONS="--require /path/to/hook.js" /path/to/electron-app  

Hook 脚本示例(拦截所有 https.request 出站):

const https = require('https');  
const originalRequest = https.request;  
https.request = function(options, callback) {  
  if (options.headers?.Authorization) {  
    fs.appendFileSync('/tmp/capture.jsonl', JSON.stringify({  
      url: options.href || `${options.host}${options.path}`,  
      auth: options.headers.Authorization,  
      time: Date.now()  
    }) + '\n');  
  }  
  return originalRequest.call(this, options, callback);  
};  

适用条件:目标使用 Node.js 作为运行时(Electron 主进程/CLI 入口)。

武器 2:对照实验定位函数依赖(还原签名算法)

当遇到类似 bkn=f(token) 的未知函数时:

实验组 输入 预期输出(若假设成立)
A token_old + bkn_old
B token_old + bkn_new
C token_new + bkn_old
D token_new + bkn_new

如果 A、D 通,B、C 不通 → bkntoken 的函数。
然后用情报匹配算法(腾讯系 5381、阿里系、通用 HMAC)逐一尝试。

武器 3:报错诱导(协议探测)

发畸形请求,让服务端告诉你“正确姿势”:

# 空体请求  
curl -X POST https://api.example.com/init_session -H "Authorization: ..." -d '{}'  
# 返回:{"code":41,"msg":"invalid InitSessionReq.EnvInfo: value is required"}  

字段名、类型、是否必填——服务端全招了。

武器 4:H5 Bundle 刮削(Hybrid 应用)

  1. 打开目标 WebView 页面(如 ima.qq.com/chat
  2. 打开 DevTools → Network → 找到 main.[hash].js(React/Vue 生产包)
  3. 全量复制,grep -E '/(cgi-bin|api|v[0-9])/' 提取所有接口路径
  4. 配合报错诱导补齐参数

武器 5:最弱端原则

防御强度 抓包难度
桌面端原生(C++) 极高(防注入、防调试)
桌面端 Electron 中等(可注入)
Web 端(浏览器) 低(DevTools 随便开) 极低
移动端 App(iOS/Android) 中等偏高(证书固定常见)
移动端小程序/H5

规律:同账号体系下,防御最弱的端 = 突破口。ima 桌面端防得滴水不漏,但 iOS App 随手一抓就是完整 token。

三、常见陷阱

陷阱 1:把“通道令牌”当成“模型鉴权令牌”

特征 通道令牌 模型鉴权 JWT
生命周期 分钟级(14min) 天/月级(60~90 天)
日志可见性 高频出现 低频或无
作用域 WebSocket 连接/同步 API 请求鉴权

很多应用分层设计凭据,日志里频繁轮换的那个往往不是主犯。拿到 token 后第一时间看 exp

陷阱 2:把“加密后的凭据”当成“没有凭据”

本地存储里的凭据可能被 safeStorage 加密,但这不代表不可用——可以调用 Electron 的 safeStorage.decryptString() 在运行时解密。比硬解二进制便宜得多。

陷阱 3:忽略了“人类在场”依赖

自动化脚本跑着跑着弹出一个钥匙串授权框、证书信任确认框——整条流水线在那里无限等待。
设计续命机制时,优先选择“一键重抓”而非“自动续期”,除非你能完全绕过 UI 交互。

陷阱 4:上游的非流式限制

上游模型可能只支持 stream:true(如 WorkBuddy)。
解决方案:代理层替客户端做聚合——流式取回,拼成完整响应,再以非流式格式返回。调用方无感。

四、检查清单(供下一案快速启动)

序号 检查项 状态
1 识别客户端类型(Electron/原生/Hybrid)
2 检查调试端口是否可开启
3 检查代理是否可劫持
4 搜索所有日志目录,确认是否含明文凭据
5 翻查本地存储(LevelDB/SQLite/plist/钥匙串)
6 尝试 NODE_OPTIONS 注入(若为 Node 应用)
7 检查是否有移动端同账号 App
8 成功捕获第一个可重放请求
9 区分长期凭据与短期凭据
10 设计热更新机制
11 协议标准化(→ OpenAI 兼容)
12 可观测性接入

五、终极感悟

逆向的本质不是“读懂别人的代码”,而是“理解别人的设计”。

当一个应用在桌面端层层设防时,它同时也在告诉攻击者:“我的弱点不在这里。” 真正有价值的信息不在二进制里,而在系统的边界处——日志、报错、前端源码、移动端的疏忽。

找秘密,不要去开发者想让你找的地方。去开发者觉得“没人会看那里”的地方。

附录:关键命令速查

# 查看进程启动参数  
ps aux | grep -E "(ima|workbuddy)"  
  
# 查看端口监听  
lsof -i :8487,8488,9222  
  
# 搜索日志中的 token  
grep -riE "(token|jwt|bearer|secret|key)" ~/Library/Logs/ 2>/dev/null  
  
# 提取 macOS 钥匙串条目  
security dump-keychain -d ~/Library/Keychains/login.keychain-db 2>/dev/null | grep -A5 "ima"  
  
# 证书信任验证  
security verify-cert -c /path/to/cert.pem  
  
# 扫描本地存储中的字符串  
strings ~/Library/Application\ Support/WorkBuddy/*/*.ldb | grep -i token  

结案归档:2026-08-30
版本:v1.0