当前位置: 首页 > news >正文

JD Cloud 验证码逆向

JD Cloudflare 验证码逆向踩坑记录

搞国航机票搜索时遇到的京东云验证码,记录一下从一头雾水到跑通的全过程。
环境:纯 Node.js,不依赖浏览器/无头浏览器


目录

  1. 验证码流程长什么样
  2. 抓包看接口
  3. 参数是怎么拼出来的
  4. 加密算法是啥
  5. WASM 卡住了,换个路子
  6. asm.js 回退才是出路
  7. FP 请求实现
  8. Check 请求:获取图片 vs 提交验证
  9. 完整流程
  10. 踩坑总结

1. 验证码流程长什么样

国航的机票查询页,点搜索之后会弹出一个京东云验证码。样式就是那种"在背景图里找出某某图标点一下"的点击式验证码。

整个流程拆开来看其实就两步:

  1. 拿验证码图片(背景大图 + 指引小图)
  2. 提交点击坐标让服务器判断对不对

后端只有两个接口,三步操作:

Step 1: FP 请求 → 拿到临时 token (st) Step 2: Check 请求(传空数据)→ 拿到验证码图片 Step 3: Check 请求(传点击坐标)→ 提交验证

有个有意思的点:获取图片和提交验证用的是同一个接口,区别只在于加密参数tk里面塞的数据不同。


2. 抓包看接口

2.1 FP 请求

FP 请求是第一步,用来获取一个临时 token(st)。需要传一个加密后的设备指纹ct,以及一个认证头x-jdcloud-captcha-auth,格式是;时间戳;32位hex

参数说明:

参数说明
sisessionId,浏览器的 localStorage 里拿b2c-web_sid
ct加密后的设备指纹
x-jdcloud-captcha-auth认证头
其他version=2,lang=1,client=m都是固定的

返回的st就是后续请求需要的 token。

2.2 Check 请求(获取图片)

Check 请求比 FP 多了一个tk参数,它是加密后的验证数据。获取图片的时候传的是空数据加密后的结果。

返回的img字段里有两张 base64 图片:一张背景大图(b1)和一张拼图指引(b2)。


3. 参数是怎么拼出来的

3.1 核心函数 Q

在 SDK 里翻到一个核心函数,所有 check 请求都是它发出去的。它的逻辑大致是:

先把验证数据用encodeURI编码一下,然后按固定格式拼接明文。明文里包含了时间戳、sessionId、st(token)、验证数据、设备指纹等信息,还会在特定位置插入随机长度的随机串。拼接完成后用加密函数加密,带上认证头发送 POST 请求。

3.2 参数拼接规则

两个关键参数tkct的明文拼接规则不一样:

  • ct的明文:随机前缀 + sessionId长度(4位固定宽度) + sessionId + 设备指纹 + 时间戳
  • tk的明文:时间戳 + sessionId长度(4位固定宽度) + sessionId + st长度(4位固定宽度) + st + 验证数据长度(6位固定宽度) + 验证数据 + 触摸信息 + 随机后缀

随机前缀的长度由时间戳 % 19决定,随机后缀的长度由时间戳 % 41决定。

3.3 常量

SDK 里藏了几个关键常量,包括默认的加密密钥、tdat_ctx 上下文、md5 salt 等。具体值就不贴了,感兴趣的可以自己去 SDK 里找。


4. 加密算法是啥

在 SDK 里看到0x9e3779b9这个数,熟悉的人应该一眼就能认出来——这是XXTEA 算法的 delta 常数。

加密链路大致是:明文先做 UTF-8 编码,然后转成 32 位无符号整数数组,走 XXTEA 加密轮,最后转成二进制串用 URL-safe 的 Base64 编码输出。它的 Base64 字母表把标准版的+/换成了-_

至于 auth 头的 hash 算法,尝试用纯 JS 去复现,但输出跟浏览器里的不一致。后来发现这个 hash 的计算在 WASM 模块里,涉及 C++ 的整数运算逻辑,JS 模拟不了。


5. WASM 卡住了,换个路子

加密核心的两个函数都在 WASM 模块里。当时试了几条路:

  1. 直接下载 WASM 文件→ 返回 404,WASM 被内联到 SDK 里了
  2. 用 jsdom 加载完整 SDK→ WASM 编译在 Node 里跑不起来
  3. 纯 JS 手写 XXTEA→ 输出跟 WASM 不一致,C++ 和 JS 的整数运算有差异

三条路都走不通的时候差点想放弃了。后来仔细看了 SDK 代码,发现它在 WASM 编译失败的时候会降级到一个 asm.js 版本。

那能不能让它强制走 asm.js 回退?


6. asm.js 回退才是出路

6.1 问题在哪

SDK 加载时会检测是不是 Node.js 环境。如果检测到是 Node.js,它会走不同的初始化路径,那个路径里不包含我们需要的两个函数。

6.2 怎么解决

思路很简单:

  1. 把这个检测结果 patch 成false,让它以为自己在浏览器里
  2. 禁用 WebAssembly,逼它降级到 asm.js
  3. mock 几个浏览器全局对象(documentwindow之类的)

具体做法就是读取 SDK 的 JS 文件,把检测 process 的那段代码替换掉,然后在执行前把WebAssembly.instantiateStreaming设成一个直接 reject 的函数,再补上global.documentglobal.window的 mock。

然后 eval 执行 patch 后的 SDK 代码,轮询等待 asm.js 初始化完成,就能拿到加密函数了。

6.3 验证

跑了个测试,asm.js 版的加密输出和浏览器 WASM 版完全一致。到这里核心问题就破了。


7. FP 请求实现

拿到加密函数之后,FP 请求就很简单了。

流程就是:按规则拼出ct的明文 → 调用加密函数加密 → 生成 auth 头 → 发起 POST 请求。返回的st就是后续需要的 token。


8. Check 请求:获取图片 vs 提交验证

Check 请求的接口、参数结构完全一样,区别只在于tk里加密的数据不同。

场景tk 里加密的 data
获取图片空数据
提交验证点击坐标数据

坐标数据就是xy和点击时间戳ts组成的数组。

Check 的tk明文拼接规则和 FP 的ct类似,只是多了 st 和验证数据的部分。

响应解析也很简单:code为 0 且有img字段说明拿到图片了(或者验证失败返回了新图片),code为 0 且没有img说明验证通过。其他错误码对应不同的异常情况。


9. 完整流程

FP 请求 → 拿 st → Check 请求(空数据)→ 拿图片 → Check 请求(点击坐标)→ 验证结果
  • sessionId 从浏览器 localStorage 获取
  • st 有效期几分钟,每次验证前重新 FP
  • tdat_ctx、sensor 从浏览器抓一次就能重复用


最后说两句

整个逆向过程最折腾的就是 WASM 那块,试了好几种方案都没成,最后发现 SDK 自带的 asm.js 回退才是最省事的方案——不用自己重写加密,不用折腾 WASM 运行时,patch 几行代码就能直接用。

对了,sessionId 和设备指纹都是从浏览器抓的固定值,实际用的时候记得换成你自己的。


运行结果示例

注意:本文只涉及验证码的协议逆向与加密突破,不包含验证码图片识别(找图、坐标计算等)。

Running] node "f:\project\crawler\验证码\国际航空\work\1.js" === JD Cloudflare Captcha 纯Node.js验证 === [1/4] 初始化加密模块... ✓ jcap asm.js就绪 [2/4] FP请求(获取token)... ✓ FP成功 st=sY87rOSMAGjYO2iI [3/4] 获取验证码图片... ✓ 背景图已保存 ✓ 拼图已保存 [4/4] 提交验证... === 验证结果 === { "st": "", "code": 16807, "s_code": 16102, "msg": "验证失败,请重新验证" } ❌ 验证失败(坐标不对): 验证失败,请重新验证
http://www.jsqmd.com/news/1132354/

相关文章:

  • 【全文系列目录】风控PM记
  • Burp Suite Intruder 4种攻击模式实战:Sniper/Cluster Bomb 对比与 3 个典型场景应用
  • LLM的“类人认知“,到底是能力涌现还是统计模仿?
  • XCA 2.9.0:企业级PKI证书管理的技术架构与实战解决方案
  • 私密科普:女性经后淋漓不尽,别当成普通经期残留
  • 终极指南:企业级Docker化邮件中继服务部署与架构实践
  • 机房故障换机后应急验证:24 小时 SpeedCE 点检 SOP
  • AI编程助手实战指南:从原理到应用,GitHub Copilot与Cursor深度测评
  • 【操作系统】页面置换算法(CLOCK/改进型CLOCK)
  • Redis--Redis分布式系统的原理与实操
  • 你的前端代码打包后究竟经历了什么?
  • 白话MVP
  • Agent出现LLM因为历史工具调用消息而误解工具调用方式的问题
  • Gromacs 分子动力学 远程安装介绍 全网最详细的Gromacs安装前说明 该怎么选择合适的安装方式 Windows直接可用的Gromacs(预编译版)有什么危害?Gromacs安装需要准备什么?
  • Langchain文本切割器在RAG中的使用
  • K/R/F/S 四大系列斜齿轮减速机的区别与选型要点?
  • Cline 配置 Claude Sonnet 5 实战指南:思考深度调优与切换 Fable 5 的时机
  • 3步解锁Text-to-CAD:如何用文字描述生成专业机械设计
  • Pandoc Lua 过滤器:免外部工具,高效处理文档转换!
  • 图最短路评测:Dijkstra 对了,也可能用错场景
  • [实例] SPI接口的ADC芯片全通道纯硬件驱动——基于HAL库和TLA2518芯片
  • 74LS73 异步计数器设计实战:2片芯片实现4位二进制与8421BCD电路对比
  • Claude Code的完美国产替代小米 MiMo Code安装指南
  • 前端应用的离线暂停更新策略:原理、实现与最佳实践
  • 星火插件:面向资深开发者的认知增强型IDE插件
  • [特殊字符] Git 协作指南
  • Recursive vs. Recurrent RNN 结构辨析:从链式到树状的3种建模范式
  • Vben精讲:06-Vben环境变量配置
  • VS code 连接 remote SSH 一些基本教程
  • CameraGraph™全域相机拓扑推理引擎 视频孪生跨镜目标连续追踪核心支撑 空间相机关系张量建模:根治跨镜头目标ID跳变、身份混淆底层算法拆解