兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
黑盒逆向实战心法 —— 从 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-cookie`、`x-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 应用) ```bash NODE_OPTIONS="--require /path/to/hook.js" /path/to/electron-app ``` Hook 脚本示例(拦截所有 https.request 出站): ```js 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 不通 → `bkn` 是 `token` 的函数。 然后用情报匹配算法(腾讯系 5381、阿里系、通用 HMAC)逐一尝试。 武器 3:报错诱导(协议探测) 发畸形请求,让服务端告诉你“正确姿势”: ```bash # 空体请求 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 | 可观测性接入 | ☐ | 五、终极感悟 逆向的本质不是“读懂别人的代码”,而是“理解别人的设计”。 当一个应用在桌面端层层设防时,它同时也在告诉攻击者:“我的弱点不在这里。” 真正有价值的信息不在二进制里,而在系统的边界处——日志、报错、前端源码、移动端的疏忽。 找秘密,不要去开发者想让你找的地方。去开发者觉得“没人会看那里”的地方。 附录:关键命令速查 ```bash # 查看进程启动参数 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
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章