最近因为要采小红书的数据,接触到了数美滑块验证码,所以就有了这篇文章来记录下分析过程。
本文数美滑块以数美官网为例,滑块链接地址:https://www.ishumei.com/trial/captcha.html。
数美滑块版本号:v1.0.3-153
一、请求分析
1.1 验证码申请
打开网站后通过抓包发现,第一步先是请求了https://captcha.fengkongcloud.cn/ca/v1/conf?lang=zh-cn&callback=sm_1649907313036&appId=default&model=slide&rversion=1.0.3&sdkver=1.1.3&organization=RlokQwRlVjUrTUlkIqOg&channel=DEFAULT这个链接,获取滑块的一些js、css信息等等。
其中请求中的organization参数是认证参数,每个网站认证参数都不一样,需要根据你采集的网站来。
返回的“/pr/auto-build/v1.0.3-153/captcha-sdk.min.js”文件是js加密文章,这个经常更新,每个版本加密参数都不一样,需要经常更新维护。
1.2 验证码注册
返回滑块的基本信息:
其中bg表示背景图片,fg表示缺口图片,rid类似于滑块session,后续请求要带上。
1.3 验证码验证
滑过滑块后,请求了一个验证接口:https://captcha.fengkongcloud.cn/ca/v2/fverify,返回通过与否,PASS表示通过。
这一步请求的参数比较多,每个版本且不一样,153版本有下面这些参数:
除个别参数外,大部份参数都经常了加密,我会在后面会对如何加密进行讲解。
二、JS逆向分析-参数加密
首先直接查看fverify接口的Initiator,如下
通过分析,我们直接追到这个位置,打断点看下
可以看到,到这里的时候,已经生成了所有的加密参数,_0x534dc2对象就是加密结果。
其中eo、jj、oh参数都经过了this[_0x163e17(0x4c9)]这个函数加密。我们直接看下这个函数,在return的地方打个断点看下。
可以看到,这个地方_0x452523(0x32e)是DES,我们大胆猜测下,这可能就是一个DES加密,两个入参分别是加密字段和key。
我直接从网上找了一个DES加密代码测试了下。
import base64
from pyDes import des, ECB
def pad(b):
"""
块填充
"""
block_size = 8
while len(b) % block_size:
b += b'\0'
return b
def get_encrypt_content(message, key, flag):
"""
接口参数的加密、解密
"""
des_obj = des(key.encode(), mode=ECB)
if flag:
content = pad(str(message).replace(' ', '').encode())
return base64.b64encode(des_obj.encrypt(content)).decode('utf-8')
else:
return des_obj.decrypt(base64.b64decode(message)).decode('utf-8')
最终发现两边加密结果是一致的,这就是一个DES加密。
到这里,eo、jj、oh这三个参数就解决了,加密字段分别对应“default”、“DEFAULT”、“zh-cn”,加密的key每个版本不同,自己扣下就好。
还剩几个参数,我们继续往上找
剩下的几个加密参数都是在这生成的。
其中几个加密字段解释如下:
lq:滑动距离/300
ro:滑动轨迹
ed:滑动结束时间戳-滑块开始时间戳
px:300,背景图显示宽度
rm:150,背景图显示高度
lw:-1
iu:0
gh:1
ps:
这里有一点要注意下,获取的到背景图大小一般为600*300,实际显示图片大小为300*150,计算滑动距离的时候需要缩放下。
三、部分代码
3.1 图像缺口识别
import cv2
import numpy as np
def get_distance(fg, bg):
"""
计算滑动距离
"""
target = cv2.imdecode(np.fromstring(fg, np.uint8), cv2.IMREAD_COLOR)
template = cv2.imdecode(np.fromstring(bg, np.uint8), cv2.IMREAD_COLOR)
result = cv2.matchTemplate(target, template, cv2.TM_CCORR_NORMED)
_, distance = np.unravel_index(result.argmax(), result.shape)
return distance
3.2 轨迹生成
import random
def get_trajectory_1(distance):
'''
# 滑动轨迹模拟、上下的抖动
:param distance:
:return:
'''
ge = []
ge.append([0, 0, 0])
for i in range(10):
x = 0
y = random.randint(-1, 1)
t = 100 * (i + 1) + random.randint(0, 2)
ge.append([x, y, t])
for items in ge[1:-5]:
items[0] = distance // 2
for items in ge[-5:-1]:
items[0] = distance + random.randint(1, 4)
ge[-1][0] = distance
return ge, ge[-1][2]
四、最终效果