文章日期: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()

Logo

一站式 AI 云服务平台

更多推荐