某东常用验证码逆向流程分析
# 声明本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!
# 目标
解决JD常用的验证码
# 前言
这里常用的就三种接口
1. 登录接口的滑块
2. cfe链接接口中的滑块
3. cfe链接接口中的点选
还有两个验证码
- 旋转验证码
- 这个验证码我一直没有触发。听说触发了这个验证码 就离毁号不远了。且爬且珍惜吧。
- 手势验证码
- 只有固定接口才有。
# 三种验证
## 登录的滑块接口
接口链接
> aHR0cHM6Ly9wYXNzcG9ydC5qZC5jb20vdWMvbG9naW4/bHR5cGU9bG9nb3V0
如上图所示。
这里就不多说废话了。直接刷新看接口
这里可以看到 有两个接口请求发出。分别为
- s: 负责验证请求
- g:负责获取图片和参数
### g请求
这里看g请求。直接进第二个栈。
这里看到 很多值已经给了我们值了。这里我们重点观察两个值。分别是`e` 和 `j`
这里打断点进栈
好了 然后再进去。然后发现到一个虚拟机里了 这里包含了e的生成。
然后就是`j`
j的生成不必多说。看下图就知道了。
其他的值。
- **appId**:写死就行
- **scene**: 同上
- **product:** 同上
- **lang:** 同上
- **callback:** 一个callback随机数算法 写死就行
然后看返回值
上述请求有几个值需要提出来。方便后续s调用
### S请求
这里继续看s请求。
这里和上图结果图作为比较。可以发现。
c好像就是challenge。
这里话不多说了。其他的值都不重要 。
最主要是这个`d`参数。
我们重点来看这个`d`
如下图所示。很多值的生成都能直接看到。
这里d 值的生成 则是由一个方法传参加密了一堆数组。
那这个数组是啥东西。其实我们不难发现这就是个轨迹。通过getCoordinate这个方法 才变成了请求接口中的d这个样子。
d方法如下:
```js
'getCoordinate': function(a) {
var b = this;
var c = new Array();
for (var d = 0x0; d < a['length']; d++) {
if (d == 0x0) {
c['push'](b['pretreatment'](a < 0x3ffff ? a : 0x3ffff, 0x3, !![]));
c['push'](b['pretreatment'](a < 0xffffff ? a : 0xffffff, 0x4, !![]));
c['push'](b['pretreatment'](a < 0x3ffffffffff ? a : 0x3ffffffffff, 0x7, !![]));
} else {
var e = a - a;
var f = a - a;
var g = a - a;
c['push'](b['pretreatment'](e < 0xfff ? e : 0xfff, 0x2, ![]));
c['push'](b['pretreatment'](f < 0xfff ? f : 0xfff, 0x2, ![]));
c['push'](b['pretreatment'](g < 0xffffff ? g : 0xffffff, 0x4, !![]));
}
}
return c['join']('');
}
```
这个方法很简单。缺什么扣什么。
然后传值轨迹。
至于轨迹的生成。可以通过某些算法去实现这个列表数组的生成。
这里是通过举例然后偏移出数组变量
这里贴个方法用于生成数组。
```python
def offer(distance):
index = 0
slide = []
indexTime = str(int(time.time()))[:9]
for item in self.base_slide:
index += 1
item = int(indexTime + str(item)[-4:])
if int(item) >= (distance + int(self.base_slide)):
slide = self.base_slide[:index]
slide.append(
)), item, item + 700 + int(random.random() * 1000)])
break
last = int(slide[-1].split('.'))
pIndex = 0
for item in self.push_slide:
if pIndex == 0 or pIndex == len(self.push_slide) - 1:
times = slide[-1]
else:
times = slide[-1] + (self.push_slide - self.push_slide)
slide.append( + last), '369', times])
pIndex += 1
# print(json.dumps(slide))
return slide
```
最后通过`getCoordinate` 生成加密值。
最后伪装成功参数 然后请求就行了 返回下图代表成功。
## cfe接口滑块
下面接口网站如下
> aHR0cHM6Ly9jZmUubS5qZC5jb20vcHJpdmF0ZWRvbWFpbi9yaXNrX2hhbmRsZXIvMDMxMDE5MDAvP3JldHVybnVybD1odHRwcyUzQSUyRiUyRml0ZW0uamQuY29tJTJGMTAwMDQ4MjcyNzYyLmh0bWwmcnFob3N0PWh0dHBzJTNBJTJGJTJGYXBpLm0uamQuY29tJnJwaWQ9cnAtMTg2NTQ5NDkwLTEwMDU2LTE3MDk3MTMyMDgzNzEmZXZ0eXBlPTImZXZhcGk9Y29sb3JfcGNfZGV0YWlscGFnZV93YXJlQnVzaW5lc3Mmc291cmNlPTEmZm9yY2VDdXJyZW50Vmlldz0x
如下图页面。这就到了另一个滑块的接口。
这里我们点击快速验证。然后完整的走一遍流程看看。具体的走向是如何的
如下图所示。
这里首先筛选xhr请求。请求太多
如下图所示。图中经过了这么多的请求。
我们一个一个看。
第一个m?std 请求 返回值是0 目测只是判断状态的请求。
第二个api请求。如下图 返回了一个值。请求参数中enbody加密了
第三个api请求也是加密了 enbody。返回值没东西。应该也是个请求判定。
第四个fp请求。请求需要伪装si 和 ct。 而这个si 刚好和第二个api请求的si相同。 返回了 fp 和 st。
第五个请求: web_jcap_report 返回了 code为0 无用请求。
第六个请求:请求传参中带有上图fp参数的加密 还多了个tk加密。返回值 返回了 缺口图和背景图。
第七个请求:无用请求
第八个请求:即是第二个check请求。同第六个请求相似。但是返回值不一样。
那这样我们大概就能明白个具体流程了。
1. api请求得到si
2. 通过fp请求 请求出fp和st
3. 通过上述这些返回值 以及其他参数获得check的请求参数加密。
4. 通过第一次check请求得到图片。进行识别
5. 然后通过识别到的信息再次请求check。完成滑块加密。
### api请求
这里我们首先去看一下api请求。
如上图。所示 只有这个enbody值是通过加密获得的。那我们去搜索一下 或者走栈。
如下图所示 直接获取到了 enbody加密的地方。
这里简化一下代码
```js
result = encrypt(JSON.stringify(param), 'rhiasnkdhandrisk', 'r-s-h-n_r_isnkdk')
```
然后过一下断点。这里可以看到 加密的值分别为
**eid**: 可以为空
**evType:** 应该是验证码类型 2
**requestId**: 由另一个接口605返回的requestID
**shshshfpx**:是一个随机值的算法。包含随机生成以及时间戳的拼接。
第二次api请求。这里第二次请求加密的值是 第一次的返回值 已经第一次的那个请求参数。其实可以不需要。这里也展示一下
### fp请求
这里fp请求。不太好搜索。我们直接进栈
进栈然后搜索 `.si` 这里可以发现。ct的算法 已经出来了。
这里可以看到 。这个值应该就是加密的值。
然后把几个函数互相扣一下就行。
这个x函数的算法如下
```js
function s(t, e, n) {
var r = t
, a = o;
e && (i = e);
return c(r, a)
}
```
这里层层扣即可。
然后把第一次`api`返回的那个`data `和这个新生成的`ct `
去请求 得到`fp `和 `st`
### 两次check
**第一次**
断点继续走 走到下图所在位置
如上图所示。
- si即是 第一次请求的返回的data
- lang: 写死。
- tk:上述乱七八糟的值拼接加密的值
- ct: 字符串拼接 包含环境代码。
- version: 写死
- client: 写死
这里需要注意的是 这里的t 暂时是不传值的。这里t是什么先留个悬念。
然后进行加密就得到了tk和ct。
然后请求。得到两张以base64为编码的图片。
**第二次check请求**
这里其他值加密都是一样的。
唯一不同就是这个 JSON.stringify(t)如下图所示
这里传值是轨迹。通过两张图片识别出来的距离 最终通过算法生成轨迹。
这里轨迹简单看看 第一个是移动的距离值。
第二个和第三个 如下图js所示的位置
这里可以通过算法去实现 也可以通过自建库来实现。
最后通过这些值加密 得到请求参数即可完成滑块。整体流程如下
## cfe 点选验证
下面接口网站如下
> aHR0cHM6Ly9jZmUubS5qZC5jb20vcHJpdmF0ZWRvbWFpbi9yaXNrX2hhbmRsZXIvMDMxMDE5MDAvP3JldHVybnVybD1odHRwcyUzQSUyRiUyRml0ZW0uamQuY29tJTJGMTAwMDQ4MjcyNzYyLmh0bWwmcnFob3N0PWh0dHBzJTNBJTJGJTJGYXBpLm0uamQuY29tJnJwaWQ9cnAtMTg2NTQ5NDkwLTEwMDU2LTE3MDk3MTMyMDgzNzEmZXZ0eXBlPTImZXZhcGk9Y29sb3JfcGNfZGV0YWlscGFnZV93YXJlQnVzaW5lc3Mmc291cmNlPTEmZm9yY2VDdXJyZW50Vmlldz0x
和滑块一个连接 不同的是 验证码不一样。
触发方式应该是与爬取的等级而定。
如上图所示。
其实上图大体流程都是一样的。这里唯一有一点区别。
如下图所示。这里JSON.stringify(t) 里面传值的不是轨迹。而是 点选的坐标。
这里可以通过两种方式去解决
1. 通过打码平台识别——亲测识别率很低
2. 通过训练。这里可以导出几百张数据集。然后通过yolov5识别
这里还有一点需要注意。
这里点选的 验证码
第一次check返回的tp值是22 。
而滑块是30
可以通过这个去区分验证码。 第二次check返回的数据,验证失败有两种情况:
s_code":16130和s_code":16190
貌似16130是距离有问题,16190是轨迹数据有问题
我缺口距离用的是ddddocr,貌似有点不准。轨迹是自己大概地写了个函数,前几次验证可以,提交几次后就一直返回16190 这个好啊,通过该分析挺多网站的验证码都能搞了
这个好啊,通过该分析挺多网站的验证码都能搞了 好东西,研究研究 非常详细,很有启发 mark多谢分享 分析的很详细,好好学习下 学到了,感谢楼主 好东西啊 学习学习,谢谢分析@!