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

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

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

验证码逆向过程分析

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

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

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

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

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

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

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

还有一个典型的「状态依赖」问题:

验证码接口依赖会话状态

/captcha/image 只做一件事:根据当前会话生成一张图并返回其token。

紧接着调 /captcha/check 也返回一个token

两个token一模一样

但如果只请求/captcha/image获取token,没有经过/captcha/check 直接去携带token请求页面,是通过不了的。

以下两个例子:

/captcha/image返回的token和/captcha/check返回的token一模一样

但是如果跳过/captcha/check,直接用/captcha/image返回的token去请求页面。是没法通过的。一句话:token 必须被 /captcha/check 把状态从 issued 变成 verified 才能继续用,否则服务端会判定“未经验证的验证码”。

/captcha/image接口

/captcha/check接口

返回响应内容

要请求的接口内容

这三个token一样,但是跳过/captcha/check接口直接用/captcha/image接口的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,就能顺利请求内容了。

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.pswp.cn/diannao/95753.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95753.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序实现导航至目的地

本人做的导航页面相关功能和效果的代码 javascript相关 Page({data: {markers: [],latitude: , // 中心点坐标longitude: ,FIXED_LAT: 31.2304, // 1. 写死的目标点坐标, 示例:上海人民广场FIXED_LNG: 121.4737},onLoad: function () {// 如果要显示地图可以看onLo…

中国科学社简史

中国科学社简史中国科学社,作为中国近代史上第一个民间综合性科学团体,为中国现代科学文化事业的发展做出了卓越贡献。其历程不仅见证了中国科学从萌芽到蓬勃发展的转变,还反映了中国科学体制化的艰难探索与辉煌成就。中国科学社的起源可追溯…

若尔当型,Jordon Form

文章目录一、相似二、若尔当型1.1 认识若尔当型1.2 凯莱-哈密顿定理 (Cayley-Hamilton Theorem)一、相似 Every matrix CB−1ABC B^{-1}ABCB−1AB has the same eigenvalues as A. These C’s are similar to A. 任意一个矩阵C,满足 CB−1ABC B^{-1}ABCB−1AB 都和…

解决uni-app微信小程序编译报错:unexpected character `1`

问题原因在uni-app微信小程序开发中&#xff0c;当template模板中包含英文符号<或>时&#xff0c;微信小程序的编译器会将这些符号误认为是HTML标签的开闭符号&#xff0c;从而导致类似unexpected character 1的编译错误。错误示例<view class"plan-bmi">…

[Linux] RAID存储技术

目录 RAID实现方式 RAID 0 RAID 1 RAID 5 RAID 10 管理RAID0 创建RAID 查看RAID 格式化和挂载 删除RAID 管理RAID1 创建RAID 查看RAID 格式化和挂载 增加热备盘 模拟故障 删除故障磁盘 删除RAID 管理RAID5 创建RAID 查看RAID md5设备划分分区 RAID实现方…

程序设计|C语言教学——C语言基础4:进阶

一、预处理指令预处理指令在编译前执行&#xff0c;除了#include&#xff0c;还有以下常用指令&#xff1a;1. #define 宏定义无参宏&#xff1a;定义常量或代码片段&#xff0c;编译时直接替换&#xff08;无类型检查&#xff09;。#define PI 3.1415926 // 定义常量 #define…

数据结构之heap算法

文章目录前言1. heap结构概述2. push_heap3. pop_heap4. sort_heap5. make_heap前言 heap这种数据结构&#xff0c;允许用户以任何次序将任何数据放入该结构中&#xff0c;但是最后取出数据的时候一定是权值最高&#xff08;或者最低&#xff09;的元素。主要和实现有关&#x…

MCU 软件断点调试注意事项!!!

——为什么调试器会在运行中改我的Flash程序&#xff1f;调试单片机时&#xff0c;很多人都有这样的疑问&#xff1a;明明我在调试前刷进去的固件是好的&#xff0c;为什么加了一个断点之后&#xff0c;调试器居然去改了 Flash&#xff1f; 如果我拔掉调试器&#xff0c;这个固…

启发式合并 + 莫队 恋恋的心跳大冒险

题目来源&#xff1a;2025 Wuhan University of Technology Programming Contest 比赛链接&#xff1a;Dashboard - 2025 Wuhan University of Technology Programming Contest - Codeforces 题目大意&#xff1a; Solution&#xff1a; 首先肯定要预处理出以每个节点为起点…

JCTools 无锁并发队列基础:ConcurrentCircularArrayQueue

ConcurrentCircularArrayQueue ConcurrentCircularArrayQueue 是一个抽象类&#xff0c;它为基于数组的并发循环队列提供了基础功能。从其命名可以看出几个关键特性&#xff1a;​​Concurrent​​&#xff1a;常指无锁并发。​​Circular Array​​&#xff1a;内部使用循环数…

力扣(LeetCode) ——622. 设计循环队列(C语言)

题目&#xff1a;622. 设计循环队列示例1&#xff1a; MyCircularQueue circularQueue new MyCircularQueue(3); // 设置长度为 3 circularQueue.enQueue(1); // 返回 true circularQueue.enQueue(2); // 返回 true circularQueue.enQueue(3); // 返回 true circularQueue.…

在JVM跑JavaScript脚本 | Oracle GraalJS 简介与实践

这是2024年初的 GraalVM 系列博文&#xff0c;当时写了大纲&#xff0c;知道一年半后的现在才得以完成发布&#x1f604; 1、概述 实话说&#xff0c;标题的场景为小众需求&#xff0c;日常开发基本用不到&#xff0c;我是最近在做一个低代码轮子玩具 app-meta 需要实现 FaaS&…

基于 EC 数据与大模型技术实现天气预报:从数据到上线的全栈方法

1. 先校准“EC 数据”与“AI 预报”的语境 EC 数据家族(常用) IFS/HRES:确定性全球模式,水平分辨率约 9 km,常用预报范围 10 天; IFS/ENS:51 成员集合预报,提供 15 天概率信息; ERA5:再分析数据,小时级、0.25,可追溯至 1940 年,用作训练/评测黄金基准。 AI 预报…

迭代器模式及优化

迭代器模式&#xff08;Iterator Pattern&#xff09;是一种行为型设计模式&#xff0c;用于提供一种统一的方式遍历聚合对象&#xff08;如集合、容器&#xff09;中的元素&#xff0c;而无需暴露对象的内部实现细节。它将遍历逻辑与聚合对象分离&#xff0c;使得遍历操作可以…

纯Qt手撕gb28181协议/gb28181协议服务端/gb28181协议设备端/gb28181设备模拟器/gb28181虚拟监控设备

一、前言说明 搞完onvif设备模拟器&#xff0c;总想着把28181设备模拟也实现&#xff0c;因为之前已经花了大力气把28181平台软件端实现了&#xff0c;为了实现这个组件&#xff0c;头发掉了一大把&#xff0c;专门把国标文档看了好几遍&#xff0c;逐行阅读&#xff0c;针对需…

【渗透实战】无下载器环境(curl/wget)下玩转 Metasploit 自动利用

1. 背景与问题场景 在渗透测试或漏洞利用中&#xff0c;Metasploit&#xff08;MSF&#xff09;是业界最常用的框架之一。 其许多 RCE&#xff08;远程代码执行&#xff09;模块在落地 payload&#xff08;如 Meterpreter 或反弹 shell&#xff09;时&#xff0c;采用了 CMD St…

jd-hotkey探测热点key

对任意突发性的无法预先感知的热点数据&#xff0c;包括并不限于热点数据&#xff08;如突发大量请求同一个商品&#xff09;、热用户&#xff08;如恶意爬虫刷子&#xff09;、热接口&#xff08;突发海量请求同一个接口&#xff09;等&#xff0c;进行毫秒级精准探测到。然后…

C#WPF实战出真汁07--【系统设置】--菜品类型设置

1、菜品设置介绍 菜品设置跟餐桌设置的功能目的是相同的&#xff0c;包括了新增&#xff0c;删除&#xff0c;编辑&#xff0c;分页&#xff0c;查询&#xff0c;重置&#xff0c;全选&#xff0c;全消&#xff0c;列表功能&#xff0c;实现流程也是布局设计&#xff0c;后台逻…

aave v3 存款与借款利息的计算方式

本文只涉及到利率计算的数学原理&#xff0c;不作源码解析:存款首先我们假设小明在aave里面存了10000usdt&#xff0c;存的时候年化收益率是5%,那么半年后其存款的利息是多少呢?常规的计算方式如下:利息10000*5%*(存款的时长/一年的时长)这么做有什么问题呢&#xff1f;假设现…

Windows MCP.Net:基于.NET的Windows桌面自动化MCP服务器深度解析

&#x1f4cb; 目录 项目概述 技术架构深度解析 核心功能模块详解 代码实现分析 使用场景与实战案例 性能优化与最佳实践 扩展开发指南 总结与展望 项目概述 什么是Windows-MCP.Net&#xff1f; Windows MCP.Net是一个基于.NET 10.0开发的Windows桌面自动化MCP&…