某红书旋转滑块验证码分析与协议算法实现(高通过率)

文章目录

  • [1. 写在前面](#1. 写在前面)
  • [2. 接口分析](#2. 接口分析)
  • [3. 验证轨迹](#3. 验证轨迹)
  • [4. 算法还原](#4. 算法还原)

【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章

作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!

1. 写在前面

作者在使用红薯的时候,经常会选择通过手机验证的方式去登录。但是,天公不作美!我甚至有时候第一次登录,就给我弹出一个验证码...有风控固然是好的,但是略微的影响到了用户的交互体验,一般用户只能选择是手动拖动滑块进行验证...

但是!作为一名科技行业的程序员。肯定是不会屈服的,于是带着对技术的好奇心在想过这个旋转的验证码,是否可以自动化呢?当然自动化没有挑战!作者选择通过协议+算法的方式去通过这个验证码~

2. 接口分析

这里我们浅拉一下旋转的滑块,然后监测register这个接口的发包,如下所示:

这几个参数中verifyUuid是重要的,它在每一次生成弹出验证码的时候,给出的一个新且唯一的验证ID,在后续的验证接口同样需要携带!获取方式如下所示:

在输入手机号点击发送短信验证码的时候,假设失败被风控了。短信接口会正常给到你成功的响应,如下所示:

bash 复制代码
{"code":0,"success":true,"msg":"成功","data":{}}

但是如果你光通过接口去检验,是不知道出验证的。得看请求状态,失败出现验证码则是471,这个时候再去头部分析,拿到验证码的UID

通过register的接口,获取到验证码提交所需要的ridcaptchaInfo字段信息

请求同样是需要头部带X-s、X-s-common参数的

第二个我们需要分析的接口则是check,这个是提交验证检测的。如下所示:

可以看到提交的参数中captchaInfo是最重要的,通过字段信息能够看到提交的鼠标轨迹相关的一些东西,而且看这样子应该还是加密的!另外的rid、uid在前面环节都能够获取到,checkCount参数则是验证次数,通不过就会自增

3. 验证轨迹

接下来如果没有解决头部X系列的两个参数加密,建议先去研究这个两个参数,再来研究滑块!当然如果你不知道怎么还原可以去看作者以前的文章!现在开始还原captchaInfo这个参数

JS调试发现轨迹验证参数的值采用了DES加密,可以拿浏览器内的加密轨迹来解密看看,如下所示:

轨迹分析后开始实现算法,如下所示:

javascript 复制代码
function generate_Track(slideDistance) {
    var trackList = [];
    var x = 0;
    while (x < slideDistance) {
        x += 2;
        var y = -(Math.floor(x / 10));
        var z = 2 * (x - 1) + Math.floor(Math.random() * 7) + 1;
        trackList.push([x, y, z]);
    }
    return JSON.stringify(trackList);
}
javascript 复制代码
function get_mousetrack(distance,time){
    var mousetrack1=get_trace(distance,time);
    var mousetrack2=generate_Track(distance);
    return DES_Encrypt(mousetrack2,"PYrm8rMk")
}
javascript 复制代码
function get_trace(distance,time) {
    distance = Math.floor(distance);
    var trace = [];
    var sy = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0];
    var st = [15, 16, 17, 18, 15, 16, 17, 18, 15, 16, 17, 18, 15, 16, 17, 18, 15, 16, 17, 18, 15, 16, 17, 18, 15, 16, 17,
        18, 15, 16, 17, 18, 15, 16, 17, 18, 15, 16, 17, 18, 14, 16, 17, 18, 16, 17, 18, 19, 20, 17];

    if (distance < 95) {
        var sx = [1, 2, 1, 2, 1, 2, 1, 1, 2, 1];
    }else{
        var sx = [1, 2, 1, 2, 1, 2, 2, 2, 3, 4];
    }
    var zt = RandomNum(10, 100);
    var zx = 0,
        zy = 0;
    var random_x = RandomNum(9, 14);
    var n = 0, x = 0, y = 0, t = 0;
    while (true){
        n += 1;
        if (n < 5){
            x = 1;
        }else{
            x = RandomChoice(sx)
        }
        if (distance > 125 && random_x === n){
            x = RandomNum(14, 18)
        }
        y = RandomChoice(sy);
        t = RandomChoice(st);
        zx += x;
        zy += y;
        zt += t;
        trace.push([zx, zy, zt]);
        if (distance - zx < 6){
            break;
        }
    }
    var value = distance - zx;
    for (var i = 0; i < value; i++){
        t = RandomChoice(st);

        if (value === i + 1){
            t = RandomNum(42, 56)
        }
        if (value === i + 2){
            t = RandomNum(32, 38)
        }
        if (value === i + 3){
            t = RandomNum(30, 36)
        }
        x = 1;
        zx += x;
        zt += t;
        trace.push([zx, zy, zt]);
    }
    let csz=RandomNum(1, 10)
    let elementToInsert0 = [0, 0, csz];
    let elementToInsert1 = [0, 0, csz+2];
    trace.unshift(elementToInsert1);
    trace.unshift(elementToInsert0);
	return JSON.stringify(trace);
}

4. 算法还原

首先需要封装register接口的协议请求,核心请求提交封参如下所示:

python 复制代码
json_data = {
  "secretId": "000",
  "verifyType": "102",
  "verifyUuid": v_id,
  "verifyBiz": "471",
  "sourceSite": "",
  "captchaVersion": "1.1.0"
}
jmurl='url=/api/redcaptcha/v2/captcha/register'+json.dumps(json_data).replace(" ","")

a1=ck['a1']
xts = self.ctx.call('get_x_s',jmurl,a1)
xtscommon = self.ctx.call('get_x_s_common',xts,a1)
self.headers['x-s'] = xts['X-s']
self.headers['x-s-common']=xtscommon
self.headers['x-t']=str(xts['X-t'])

data=json.dumps(json_data,separators=(',',':'))
response = requests.post(url, headers=self.headers, cookies=ck, data=data)

check验证接口的提交核心封参请求如下所示:

python 复制代码
chainfo={
		"mouseEnd":self.ctx.call('DES_Encrypt',mouseend,"WquqhEkd"),
		"time":self.ctx.call('DES_Encrypt',time,"vPMvCY4K"),
		"track":self.ctx.call('get_mousetrack',mouseend,time),
		"width":self.ctx.call('DES_Encrypt',width,"WquqhEkd")
	}
	json_data={
		"rid":rid,
		"verifyType":"102",
		"verifyBiz":"471",
		"verifyUuid":v_id,
		"sourceSite":"",
		"captchaVersion":"1.1.0",
		"checkCount":str(check_count),
		"captchaInfo":json.dumps(chainfo)
	}

当然,请求完成后的过程中我们还需要对旋转图片角度的分析与图像处理(包括裁剪)!主要就是通过对两个图像进行分析处理来找出最佳的角度。使得合并后的图像在梯度上的差异最小,这里我们采用了CV2,通过核心Py。源码如下所示:

python 复制代码
def perform_angle_analysis(self,query_image_path, background_image_path):
    def calculate_gradient_difference(image, cx, cy, circle_radius):
        circle_inner_mask = np.zeros_like(image, dtype=np.uint8)
        cv2.circle(circle_inner_mask, (cx, cy), circle_radius, 255, -1)
        circle_outer_mask = np.zeros_like(image, dtype=np.uint8)
        cv2.circle(circle_outer_mask, (cx, cy), circle_radius + 30, 255, -1)
        inner_pixels = cv2.bitwise_and(image, circle_inner_mask)
        outer_pixels = cv2.bitwise_and(image, circle_outer_mask)
        inner_sobel_x = cv2.Sobel(inner_pixels, cv2.CV_64F, 1, 0)
        inner_sobel_y = cv2.Sobel(inner_pixels, cv2.CV_64F, 0, 1)
        inner_gradient_magnitude = cv2.magnitude(inner_sobel_x, inner_sobel_y)
        outer_sobel_x = cv2.Sobel(outer_pixels, cv2.CV_64F, 1, 0)
        outer_sobel_y = cv2.Sobel(outer_pixels, cv2.CV_64F, 0, 1)
        outer_gradient_magnitude = cv2.magnitude(outer_sobel_x, outer_sobel_y)
        gradient_diff = np.sum(outer_gradient_magnitude) - np.sum(inner_gradient_magnitude)
        return gradient_diff

    def merge_images(query_result, bg_image, radius, angle):
        query_height, query_width = query_result.shape[:2]
        rotation_matrix = cv2.getRotationMatrix2D((query_width / 2, query_height / 2), angle, 1)
        rotated_result = cv2.warpAffine(query_result, rotation_matrix, (query_width, query_height))
        center = (bg_image.shape[1] // 2, bg_image.shape[0] // 2)
        cv2.circle(bg_image, center, radius, (0, 0, 0), -1)
        bg_height, bg_width = bg_image.shape[:2]
        circle_height, circle_width = rotated_result.shape[:2]
        x = (bg_width - circle_width) // 2
        y = (bg_height - circle_height) // 2
        overlay = np.zeros_like(bg_image)
        overlay[y:y + circle_height, x:x + circle_width] = rotated_result
        result = cv2.bitwise_or(bg_image, overlay)
        return result

    def split_circular_region(image, radius):
        height, width, _ = image.shape
        center = (width // 2, height // 2)
        mask = np.zeros_like(image)
        cv2.circle(mask, center, radius, (255, 255, 255), -1)
        result = cv2.bitwise_and(image, mask)
        return result

    def enlarge_image(image, scale_factor):
        circle_height, circle_width = image.shape[:2]
        new_height = int(circle_height * scale_factor)
        new_width = int(circle_width * scale_factor)
        result = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_LINEAR)
        return result

    query_image = cv2.imread(query_image_path)
    bg_image = cv2.imread(background_image_path)

    query_result = split_circular_region(query_image, 89)
    query_result = enlarge_image(query_result, 1.1)
    min_difference = float('inf')
    min_angle = 0
    for angle in range(0, 360, 5):
        result = merge_images(query_result, bg_image, 89, angle)
        gradient_difference = calculate_gradient_difference(result, bg_image.shape[1] // 2, bg_image.shape[0] // 2, 70)
        if gradient_difference < min_difference:
            min_difference = gradient_difference
            min_angle = angle
    result = merge_images(query_result, bg_image, 89, min_angle)
    cv2.imwrite('./jpg/result.png', result)
    return min_angle

最后完成所有的编码后,把滑块验证部署成一个API服务,这样的话更加方便调用,如下:

本地直接通过检测471验证码,拿到uid、cookie调用纯协议滑块验证服务,效果如下:

相关推荐
LZXCyrus3 分钟前
【杂记】vLLM如何指定GPU单卡/多卡离线推理
人工智能·经验分享·python·深度学习·语言模型·llm·vllm
Enougme6 分钟前
Appium常用的使用方法(一)
python·appium
一直学习永不止步11 分钟前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表
懷淰メ11 分钟前
PyQt飞机大战游戏(附下载地址)
开发语言·python·qt·游戏·pyqt·游戏开发·pyqt5
hummhumm25 分钟前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
hummhumm42 分钟前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
Rstln1 小时前
【DP】个人练习-Leetcode-2019. The Score of Students Solving Math Expression
算法·leetcode·职场和发展
芜湖_1 小时前
【山大909算法题】2014-T1
算法·c·单链表
珹洺1 小时前
C语言数据结构——详细讲解 双链表
c语言·开发语言·网络·数据结构·c++·算法·leetcode
每天吃饭的羊1 小时前
python里的数据结构
开发语言·python