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

爬虫逆向之滑块验证码加密分析(轨迹和坐标)

本文章中所有内容仅供学习交流使用,不用于其他任何目的。否则由此产生的一切后果均与作者无关!

在爬虫开发过程中,滑块验证码常常成为我们获取数据的一大阻碍。而滑块验证码的加密方式多种多样,其中轨迹加密和坐标加密是比较常见的两种方式。本文将详细介绍这两种加密方式的原理以及如何进行逆向分析。

验证码逆向过程分析

第一步,找生成图片的接口(接口可能有加密参数)获取图片url或者图片base64编码,可能还有id,token等值。

第二步,用识别工具识别图片,获取缺口坐标(用函数模拟轨迹)

第三步,获取缺口坐标(轨迹)(可能会进行加密)和第一次接口获取id、token等(可能会进行加密),可能直接携带加密坐标(轨迹)和token、id直接请求页面。也可能作为请求体或者cookie、请求头进行另一个接口请求(验证接口可能不止一个,甚至多级),请求成功返回一个成功的token(可能有时间效期,token可能只能用一次,也可能用多次)。携带token请求你要请求的页面,成功请求。

最好用seesion = requests.session()进行请求

接口不一定一次性返回 图片+id+token

 识别后并不一定直接拿“缺口坐标”(轨迹)就能用,可能会进行加密

验证接口可能不止一个,甚至多级

1.坐标加密

目标网址:aHR0cHM6Ly96YnRiLmdkLmdvdi5jbi8jL2p5Z2c=

大于五页后都会有滑块验证码

抓包分析,图片接口

图片接口请求头字段加密字段

找加密位置非常简单,xhr跟栈就行了

回调第一个就是加密的位置

事实上是搜不到的,对字段进行打乱重组,但是跟栈也轻轻松松

这个值是要求的

求出来这种格式像什么

CryptoJSWordArray 对象的内部格式,哈希加密,.toString()

测试,这个就是原型的SHA256加密,直接用原生库即可

内部进行字符串拼接

最后一个参数需要传验证码接口的请求体

输出为请求体的拼接

直接写死即可(这网站请求详情,则不能写死,因为页数变化,时间变化)

简简单单请求出参数。获取两张图片

在验证码接口一共四个参数有用到

两张图片

secretKey为密钥

token为验证接口携带

用ddddcor求出x距离

ocr = ddddocr.DdddOcr()
img_1 = base64.b64decode(response.json()['data']['repData']['jigsawImageBase64'])
img_2 = base64.b64decode(response.json()['data']['repData']['originalImageBase64'])
token = response.json()['data']['repData']['token']
secretKey = response.json()['data']['repData']['secretKey']
print(ocr.slide_match(img_1,img_2)['target'][0])

第二个接口check

直接搜,比较简单

这个X对于是距离。

进去NO函数。非常容易的AES加密

def aes_encrypted(w, L):data = json.dumps(w, separators=(',', ':')).encode('utf-8')key = L.encode('utf-8')[:16].ljust(16, b'\0')cipher = AES.new(key, AES.MODE_ECB)ct = cipher.encrypt(pad(data, AES.block_size))return base64.b64encode(ct).decode('ascii')

对坐标加密

有个非常巨大的坑,表面L已经写好了,其实传参L不是这个值

恶心。实际L值是第一个接口返回secretKey

最后成功获取token

在详情页携带token,就能顺利请求内容了。

事实上以上例子,直接请求验证码接口获得的token就可以请求页面了。不需要在进行下一个接口的请求,纯属练习。

2.轨迹加密

目标网址:aHR0cHM6Ly9janljLmhiYmlkZGluZy5jb20uY24vaHViZWl5dGgvanl4eC90cmFkZV9pbmZvci5odG1s

验证码接口直接请求即可,非常友好(后面你就知道有一个巨大的坑)

图片处理请求距离

img_1 = base64.b64decode(response.json()['captcha']['templateImage'].split('base64,')[1])
img_2 = base64.b64decode(response.json()['captcha']['backgroundImage'].split('base64,')[1])
ocr = ddddocr.DdddOcr()
x = ocr.slide_match(img_1,img_2)['target'][0]
print(x)

非常友好,瞬间出来

看看第二个验证接口

一眼看出,id是第一个接口请求的,data加密很明显是base64加密

响应cookies为第二个接口求这个值

base64解密看一下

解析一下

'bgImageWidth': 260,
'bgImageHeight': 159,
'sliderImageWidth': 49,
'sliderImageHeight': 159,这四个参数为两张验证码大小

"startSlidingTime":"2025-08-14T09:55:48.568Z","endSlidingTime":"2025-08-14T09:55:50.110Z",

startSlidingTime开始时间,endSlidingTime点击到验证码验证时间

trackList就是轨迹

一大串轨迹,跟栈调试一下

x代表移动距离,y代表上下多动,由x的变化看出,是先慢后快,在慢,t则是时间

轨迹生成的函数

    def gen_track( gap_x, gap_y=0, seed=None):# ran_x = random.randint(19, 40)"""模拟轨迹生成生成「慢→快→慢」三段式轨迹gap_x : 缺口 x 像素gap_y : y 轴最大抖动像素seed  : 随机种子,方便调试"""if seed:random.seed(seed)# 总步数 & 总耗时steps = random.randint(40, 60)  # 步数少一点更平滑total_t = random.randint(2800, 3500)  # 总耗时 2.8~3.5 strack = []x0, y0 = 0, 0t0 = 2383  # 起始时间戳gap_x = gap_xfor i in range(steps + 1):# 1. 三段式 S 曲线映射t = i / steps# 三次贝塞尔缓动:慢→快→慢ratio = 3 * t ** 2 - 2 * t ** 3if i != 0:# 2. 计算本次坐标x = int(round(x0 + gap_x * ratio)) + 1y = y0 + random.randint(-1, 1)if i == 0:x = 0y = 0# 3. 时间分布也按 S 曲线:开始稀疏、中间密集、末尾稀疏dt = int(total_t * (0.8 + 1.2 * (1 - math.sin(math.pi * t))) / steps)t0 += dt# 4. 事件类型if i == 0:ev_type = "down"elif i == steps:ev_type = "up"else:ev_type = "move"track.append({"x": x, "y": y, "type": ev_type, "t": t0})# 提前到达终点就停if x >= gap_x:track[-1]['x'] = gap_xtrack[-1]['type'] = "up"breakreturn track

y上下抖动,X先慢后快在慢,t时间不能太快,传入x距离即可,t实际为毫秒,随机累加

ocr = ddddocr.DdddOcr()
x = ocr.slide_match(img_1,img_2)['target'][0]
track = gen_track(x)

在把时间整理一下

tart_iso = datetime.datetime.utcnow().isoformat(timespec='milliseconds') + 'Z'
end_iso = (datetime.datetime.utcnow() +datetime.timedelta(milliseconds=track[-1]['t'])).isoformat(timespec='milliseconds') + 'Z'

全部求出来,再用base64编码

ef gen_track(gap_x, gap_y=0, seed=None):# ran_x = random.randint(19, 40)"""模拟轨迹生成生成「慢→快→慢」三段式轨迹gap_x : 缺口 x 像素gap_y : y 轴最大抖动像素seed  : 随机种子,方便调试"""if seed:random.seed(seed)# 总步数 & 总耗时steps = random.randint(40, 60)  # 步数少一点更平滑total_t = random.randint(2800, 3500)  # 总耗时 2.8~3.5 strack = []x0, y0 = 0, 0t0 = 2383  # 起始时间戳gap_x = gap_xfor i in range(steps + 1):# 1. 三段式 S 曲线映射t = i / steps# 三次贝塞尔缓动:慢→快→慢ratio = 3 * t ** 2 - 2 * t ** 3if i != 0:# 2. 计算本次坐标x = int(round(x0 + gap_x * ratio)) + 1y = y0 + random.randint(-1, 1)if i == 0:x = 0y = 0# 3. 时间分布也按 S 曲线:开始稀疏、中间密集、末尾稀疏dt = int(total_t * (0.8 + 1.2 * (1 - math.sin(math.pi * t))) / steps)t0 += dt# 4. 事件类型if i == 0:ev_type = "down"elif i == steps:ev_type = "up"else:ev_type = "move"track.append({"x": x, "y": y, "type": ev_type, "t": t0})# 提前到达终点就停if x >= gap_x:track[-1]['x'] = gap_xtrack[-1]['type'] = "up"breakreturn track
ocr = ddddocr.DdddOcr()
x = ocr.slide_match(img_1,img_2)['target'][0]
track = gen_track(x)
tart_iso = datetime.datetime.utcnow().isoformat(timespec='milliseconds') + 'Z'
end_iso = (datetime.datetime.utcnow() +datetime.timedelta(milliseconds=track[-1]['t'])).isoformat(timespec='milliseconds') + 'Z'
payload = {'bgImageWidth': 260,'bgImageHeight': 159,'sliderImageWidth': 49,'sliderImageHeight': 159,'startSlidingTime': tart_iso,'endSlidingTime': end_iso,'trackList': track
}
data = base64.b64encode(json.dumps(payload, separators=(',', ':')).encode()).decode()
url = "https://cjyc.hbbidding.com.cn/captcha/check2"
payload = {"id": id ,"data":data
}
response = requests.post(url, headers=headers, cookies=cookies, data=payload)
print(response.text)

最终结果请求失败,请求多次还是失败

我在想,这么完美的请求方式,为什么错了,最后调试好久,对比距离得出

最后x的距离是网页验证码的距离,不是实际下载图片的距离

实际下载图片这么大

最终的大小按页面大小算

所以还得把图片修改一下

用opencv,改一下图片大小,再识别距离

 def _resize(b64: str, w: int, h: int) -> str:"""把 base64 图片缩放成指定宽高后再 base64 编码"""img = cv2.imdecode(np.frombuffer(base64.b64decode(b64), np.uint8), cv2.IMREAD_COLOR)img = cv2.resize(img, (w, h), interpolation=cv2.INTER_AREA)return base64.b64encode(cv2.imencode('.jpg', img)[1]).decode()

以下为核心代码。图片大小,轨迹生成

response = requests.get(url, headers=headers, cookies=cookies, params=params)
id = response.json()['id']def _resize(b64: str, w: int, h: int) -> str:"""把 base64 图片缩放成指定宽高后再 base64 编码"""img = cv2.imdecode(np.frombuffer(base64.b64decode(b64), np.uint8), cv2.IMREAD_COLOR)img = cv2.resize(img, (w, h), interpolation=cv2.INTER_AREA)return base64.b64encode(cv2.imencode('.jpg', img)[1]).decode()
slider_b64 = _resize(response.json()['captcha']['templateImage'].split(',', 1)[-1], 49, 159)
bg_b64 = _resize(response.json()['captcha']['backgroundImage'].split(',', 1)[-1], 260, 159)
target_bytes = base64.b64decode(slider_b64)
bg_bytes = base64.b64decode(bg_b64)
img_1 = base64.b64decode(slider_b64)
img_2 = base64.b64decode(bg_b64)
def gen_track(gap_x, gap_y=0, seed=None):# ran_x = random.randint(19, 40)"""模拟轨迹生成生成「慢→快→慢」三段式轨迹gap_x : 缺口 x 像素gap_y : y 轴最大抖动像素seed  : 随机种子,方便调试"""if seed:random.seed(seed)# 总步数 & 总耗时steps = random.randint(40, 60)  # 步数少一点更平滑total_t = random.randint(2800, 3500)  # 总耗时 2.8~3.5 strack = []x0, y0 = 0, 0t0 = 2383  # 起始时间戳gap_x = gap_xfor i in range(steps + 1):# 1. 三段式 S 曲线映射t = i / steps# 三次贝塞尔缓动:慢→快→慢ratio = 3 * t ** 2 - 2 * t ** 3if i != 0:# 2. 计算本次坐标x = int(round(x0 + gap_x * ratio)) + 1y = y0 + random.randint(-1, 1)if i == 0:x = 0y = 0# 3. 时间分布也按 S 曲线:开始稀疏、中间密集、末尾稀疏dt = int(total_t * (0.8 + 1.2 * (1 - math.sin(math.pi * t))) / steps)t0 += dt# 4. 事件类型if i == 0:ev_type = "down"elif i == steps:ev_type = "up"else:ev_type = "move"track.append({"x": x, "y": y, "type": ev_type, "t": t0})# 提前到达终点就停if x >= gap_x:track[-1]['x'] = gap_xtrack[-1]['type'] = "up"breakreturn track
ocr = ddddocr.DdddOcr()
x = ocr.slide_match(img_1,img_2)['target'][0]
track = gen_track(x)

请求失败

多试几次就请求成功了。请求概率挺高的

请求成功

http://www.lryc.cn/news/620606.html

相关文章:

  • Redis 03 redis 缓存异常
  • 嵌入式学习笔记--MCU阶段--DAY12实时操作系统rt_thread1
  • C语言零基础第16讲:内存函数
  • 华为实验WLAN 基础配置随练
  • 【奔跑吧!Linux 内核(第二版)】第6章:简单的字符设备驱动(三)
  • 使用AI编程自动实现自动化操作
  • 考研408《计算机组成原理》复习笔记,第三章(6)——Cache(超级重点!!!)
  • [免费]基于Python的影视数据可视化分析系统(Flask+echarts)【论文+源码+SQL脚本】
  • 财务自动化软件敏感数据泄露风险评估与防护措施
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘arviz’问题
  • Flutter 顶部导航标签组件Tab + TabBar + TabController
  • Polyak-Ruppert 平均
  • 第四天~什么是ARXML?
  • Eureka故障处理大汇总
  • Java研学-RabbitMQ(八)
  • 李沐-第六章-LeNet训练中的pycharm jupyter-notebook Animator类的显示问题
  • 【LeetCode 热题 100】295. 数据流的中位数——最大堆和最小堆
  • 基于Django的福建省旅游数据分析与可视化系统【城市可换】
  • AI 编程实践:用 Trae 快速开发 HTML 贪吃蛇游戏
  • 【经验分享】如何在Vscode的Jupyter Notebook中设置默认显示行号
  • vscode的wsl环境,ESP32驱动0.96寸oled屏幕
  • 【面板数据】各省及市省级非物质文化遗产数据合集(2005-2024年)
  • 【JavaEE】多线程 -- 初识线程
  • Java应用快速部署Tomcat指南
  • **超融合架构中的发散创新:探索现代编程语言的挑战与机遇**一、引言随着数字化时代的快速发展,超融合架构已成为IT领域的一种重要趋势
  • ts概念讲解
  • 网络原理-HTTP
  • 一致性哈希Consistent Hashing
  • 【代码随想录day 20】 力扣 669. 修剪二叉搜索树
  • 力扣-64.最小路径和