【Python 逆向简书滑块】逆向简书滑块,速度可比自动化快,但别玩过头了
用Python + Node.js 逆向简书的滑块验证,过程略微复杂,要切割背景图,还要OCR识别,但速度快,无需使用自动化工具
·
文章日期:2024.07.25
使用工具:Python、Node.js
文章类型:逆向简书滑块
文章全程已做去敏处理!!! 【需要做的可联系我】
AES解密处理(直接解密即可)(crypto-js.js 标准算法):在线AES加解密工具
【点赞 收藏 关注 】仅供学习,仅供学习。
如果打开练习网站发现不是滑块验证界面,请多刷新几次网页即可
来先看视频,直接验证滑块验证【逆向过简书滑块 - 简单程度】

1、打开博主准备的练习网站(请使用文章开头的AES在线工具解密):ZEdAQ5T22nm/WVg5hghtC4sGCDCyfMpD8mRce5QQ7moeJ6kjHy3KowxP9zqDrJCl
2、打开网站后,我们多刷新几次滑块验证码,是刷新滑块,不是刷新网页,然后我们可以看到这个接口就是用来请求滑块数据的,切记,凡是有数据的,就一定有用,人家不会返回没用的数据的,所有你都要记住。大致分析一下【secretKey】这个参数很明显就是一个密钥。【jigsawImageBase64】【originalImageBase64】这两个参数是滑块背景图和小拼图。【token】这个参数的名字已经告诉我们了,他就是用来认证的,相当于本数据的认证标识,此参数会随着你验证的结束的数据一起返回给服务器做验证,如果token对不上就直接返回错误,如果token没用问题,服务器才会检测你的滑块是否成功对接上。一个token对应一次的滑块验证,也是为了防止数据冲突。

3、然后我们尝试随便滑动错误,看看他传递给服务器的数据是什么,黑了个嘿,【clientUid】【captchaType】是固定参数,【ts】时间戳我们可以自己生成,【token】接口里直接拿着用,【pointJson】这个参数是我们要逆向的,因为里面都是你滑动滑块的数据信息,传递给服务器要验证的,也是关键性参数。

4、好,我们直接全局搜索【pointJson】,然后打断点,打完断点在滑动一下滑块,不要滑动准确,我们要滑动错误,去看断点能不能断住。断住了以后,我们要分析一下,看图里,很明显的AES加密方法,然后把token和滑块距离都加密了,然后我也看了一下,他的AES用的是标准库,没用魔改,所以这就简单了


5、【重点来了】,这个滑块距离怎么搞呢,OCR能识别到距离,但有些人可要注意了,这个专门坑聪明人但有不是很聪明的人,看图说话,这个是什么意思呢
【用selenium直接操作浏览器滑动】:由于你获取的是浏览器源代码里的图片,OCR识别的距离仅用于源代码图片的大小,那你想要实现自动化滑动,就必须要在原有的距离上在运算一下【OCR识别距离 / 400 * 310】
【直接用请求发送识别验证滑块】:由于你获取的是浏览器源代码里的图片,OCR识别的距离仅用于源代码图片的大小,和浏览器无关,浏览器里的图片是前端进行操作的,他在传输后端前也会将滑块距离进行运算,所以我们通过OCR识别出来的距离可以直接经过加密传输后端验证


【附上代码】代码拿走吧,注释写的很明白,不懂可以留言哦
import numpy as np
from PIL import Image
import io
import time
import execjs
import base64
# 安装库 pip install ddddocr
import ddddocr
import requests
import json
# 运行脚本实列化一次即可,不要重复实列化,会浪费时间
det = ddddocr.DdddOcr(det=False, ocr=False)
class Book_slider:
def __init__(self):
# 请求出滑块的信息
self.start_slider_url()
self.gap = self.Image_distance(self.originalImageBase64, self.jigsawImageBase64)
self.js_()
self.start_verification()
# 加密数据
def js_(self):
js_data = '''
// 安装 crypto 加解密包
// npm install crypto-js --save
// 对称加密算法 的结果将会是唯一性,不会变更
const CryptoJS = require('crypto-js')
function AES_encrypted(text_,key_){
// 向量
var iv = CryptoJS.enc.Utf8.parse('')
// 明文内容
var text = CryptoJS.enc.Utf8.parse(text_)
// 密钥 AES的key字节长度必须为8的倍数
var key = CryptoJS.enc.Utf8.parse(key_)
// 加密 DES AES (AES的key字节长度必须为8的倍数)
return CryptoJS.AES.encrypt(text, key,
{
iv: iv,
// mode模式 CBC ECB CFB OFB CTR
mode: CryptoJS.mode.ECB,
// padding模式 Pkcs7....
padding: CryptoJS.pad.Pkcs7
}
).toString();
}
'''
# js
ctx = execjs.compile(js_data)
# 加密滑块信息
self.pointJson = ctx.call('AES_encrypted', '{"x":' + str(self.gap)[:6] + ',"y":5}', self.secretKey)
print(f'加密滑块信息:{self.pointJson}')
# 请求出滑块的信息
def start_slider_url(self):
headers = {
"content-type": "application/json;charset=UTF-8",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
}
url = "https://www.jianshu.com/sfservice/jianshu/captcha/get"
data = {
"captchaType": "blockPuzzle",
"clientUid": "slider-dd2e83ff-99f0-4bd0-9820-d8dd23b857d0",
"ts": int(time.time() * 1000)
}
data = json.dumps(data, separators=(',', ':'))
slider_data = requests.post(url, headers=headers, data=data).json()
# token
self.token = slider_data['repData']['token']
print(f'token:{self.token}')
# AES密钥
self.secretKey = slider_data['repData']['secretKey']
print(f'secretKey:{self.secretKey}')
# 背景图
self.originalImageBase64 = slider_data['repData']['originalImageBase64']
# 小拼图
self.jigsawImageBase64 = slider_data['repData']['jigsawImageBase64']
# OCR识别滑块的距离
def Image_distance(self,beijingtu,xiaopintu):
# base64 解密
def base64_decode(str):
return base64.b64decode(str)
with open('beijingtu.png', 'wb') as f:
f.write(base64_decode(beijingtu))
with open('xiaopintu.png', 'wb') as f:
f.write(base64_decode(xiaopintu))
# 将正常大拼图转换为小拼图, 使用前先去掉多余的透明
def slider_puzzle_qg(
p_puzzle_path: str = '',
save_file: bool = False,
save_file_path: str = ''):
'''
:param p_puzzle_path: 本地长拼图的路径
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: base64图片
'''
# 打开大拼图图片
image = Image.open(p_puzzle_path)
width, height = image.size
pixels = image.load()
# 采用红绿灯模式 存储和记录信息
# _a:第一层-行数 _b:第二层-行数 _c:第三层-行数 k:记录拼图的像素数据
_a = 0 # 红灯-最顶层 - 透明
_b = 0 # 黄灯-中间层 - 数据核心 - 拼图的像素
_c = 0 # 绿灯-最底层 - 透明
k = [] # 将拼图的像素存储, 存储为一维数组
buffered = io.BytesIO()
for x in range(height):
# 临时的行数据像素 - 左右
data_pixel = []
for y in range(width):
# 读取png像素点 RGBA 值 (png是4通道、jpg是3通道) PNG:RGBA JPG:RGB
r, g, b, a = pixels[y, x]
data_pixel += [(r, g, b, a)]
# 转换为NumPy数组
NumPy_data_pixel = np.array(data_pixel)
# 计算一整行像素值是否为透明, 是透明则跳过并记录在红绿灯内, 如果不是透明则将数据进行存储并记录在红绿灯内
# True:透明(无数据) False:不透明(有数据)
if np.all(NumPy_data_pixel == 0):
if _b == 0:
# 记录最顶层的行数 - 小拼图距离顶部的距离
_a += 1
else:
# 当开始底层为0时,则表示第一次执行此命令,则要存储刚刚所保存的一维像素数组数据, 保存为图片
if _c == 0:
# 创建空白图片 - 宽:拼图的宽度 高:通过数据层获取 颜色:透明 格式:png
new_image = Image.new('RGBA', (width, _b), color=(0, 0, 0, 0))
# 采用一维的像素组数据写入图片
new_image.putdata(k)
# 将数据写入buffered里
new_image.save(buffered, format="PNG")
if save_file:
if save_file_path:
new_image.save(save_file_path + '.png')
else:
# 保存最终图片 随机名称
new_image.save(''.join(str(time.time()).split('.') + [".png"]))
# 记录最底层的行数 - 小拼图距离底部的距离
_c += 1
else:
# 记录拼图的有多少行-高度-上下距离-仅拼图的高度
_b += 1
# 拼图的像素数据 一维
k += data_pixel
# 最后返回字典格式数据
return {
'最顶层-顶层距离': _a,
'中间层-拼图的高度': _b,
'最底层-底层距离': _c,
'base64': base64.b64encode(buffered.getvalue()).decode()
}
# 将滑块背景图切割为小滑道
def background_cutting(
b_puzzle_path: str = '',
b_size_h: int = 0,
p_size_h: int = 0,
save_file: bool = False,
save_file_path: str = ''):
'''
:param b_puzzle_path: 本地滑块背景图的路径
:param b_size_h: 小拼图在背景图中的所在高度,上下距离
:param p_size_h: 小拼图的高度大小
:param save_file: 是否保存最终修改后的图片 默认False
:param save_file_path: 保存最终修改后的图片名称,不含后缀 默认:自动生成
:return: base64图片
'''
# 打开图片
image = Image.open(b_puzzle_path)
width, height = image.size
# 宽度,从左到右的距离
left, right = 0, width
# 高度,从上到下的距离
top, bottom = b_size_h, b_size_h + p_size_h
# 切割图片
cropped_image = image.crop((left, top, right, bottom))
if save_file:
if save_file_path:
cropped_image.save(save_file_path + '.png')
else:
# 保存最终图片 随机名称
cropped_image.save(''.join(str(time.time()).split('.') + [".png"]))
buffered = io.BytesIO()
# 将数据写入buffered里
cropped_image.save(buffered, format="PNG")
return {'base64': base64.b64encode(buffered.getvalue()).decode()}
# 将小拼图多余的透明部分删除并返回其图片信息
xiaopintu_data = slider_puzzle_qg(p_puzzle_path='xiaopintu.png', save_file=True, save_file_path='c1')
# 通过小拼图返回的数据 切割背景图的划线通道 方便准确识别
beijingtu_data = background_cutting(b_puzzle_path='beijingtu.png', b_size_h=xiaopintu_data['最顶层-顶层距离'],
p_size_h=xiaopintu_data['中间层-拼图的高度'], save_file=True, save_file_path='c2')
# 将base64数据转为二进制数据导入OCR识别 开始识别滑块距离
res1 = \
det.slide_match(base64_decode(beijingtu_data['base64']), base64_decode(xiaopintu_data['base64']), simple_target=True)[
'target'][0]
print(f'滑块距离:{res1}')
# 直接返回距离
return res1
# 开始验证
def start_verification(self):
headers = {
"content-type": "application/json;charset=UTF-8",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
}
url = "https://www.jianshu.com/sfservice/jianshu/captcha/check"
data = {
"captchaType": "blockPuzzle",
"pointJson": self.pointJson,
"token": self.token,
"clientUid": "slider-dd2e83ff-99f0-4bd0-9820-d8dd23b857d0",
"ts": int(time.time()*1000)
}
data = json.dumps(data, separators=(',', ':'))
response = requests.post(url, headers=headers, data=data)
print(response.text)
print(response)
if response.status_code == 200:
print('成功过滑块!!!')
Book_slider()

更多推荐



所有评论(0)