前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【验证码逆向专栏】安某客滑块逆向

【验证码逆向专栏】安某客滑块逆向

原创
作者头像
K哥爬虫
修改2023-01-12 17:26:55
5120
修改2023-01-12 17:26:55
举报
文章被收录于专栏:Python 爬虫Python 爬虫

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

逆向目标

  • 目标:安某客滑动验证码逆向分析
  • 主页:aHR0cHM6Ly93d3cuYW5qdWtlLmNvbS9jYXB0Y2hhLXZlcmlmeS8/Y2FsbGJhY2s9c2hpZWxkJmZyb209YW50aXNwYW0=
01
01

抓包分析

首页请求,有个初始化函数,其中有个 sessionId 后续会用到。

02
02

然后有个 getInfoTp 的请求,Form Data 里有个 dInfo 是加密参数,返回值里 info 也是加密的,包含了图片信息,返回值 responseId 在后续的请求也会用到。

03
03

滑动之后,有个 checkInfoTp 请求,Form Data 里有个 data 是加密参数,包含了轨迹信息,返回值 message 可以看到是否校验成功。

04
04

整体流程就是:请求首页获取 sessionId,请求 getInfoTp 获取图片信息和 responseId,请求 checkInfoTp 校验是否成功,中间涉及到 dInfodata 两个加密参数,以及 getInfoTp 返回得到的 info 的解密。

dInfo 生成

先来看 getInfoTp 请求的 dInfo 参数,直接搜索可定位,刷新断下,大致就可以看出是 AES 加密,传入了 sessionId 和一个 _taN() 函数的返回值:

05
05

_taN() 函数是一些 URL,UA 之类的信息,可以写死:

06
06

往里跟就可以看到 AES 算法了:

07
07

这里简简单单扣一下,JavaScript 代码如下:

代码语言:javascript
复制
/* ==================================
# @Time    : 2021-12-14
# @Author  : 微信公众号:K哥爬虫
# @FileName: ajk.js
# @Software: PyCharm
# ================================== */


var CryptoJS = require('crypto-js')

function AESEncrypt(_cRV, _2undefinedp) {
    _2undefinedp = _2undefinedp.split("").reduce(function(_PUi, _JrX, _JP9) {
        return _JP9 % 2 == 0 ? _PUi + "" : _PUi + _JrX;
    }, "");
    _2undefinedp = CryptoJS.enc.Utf8.parse(_2undefinedp);
    _cRV = "string" == typeof _cRV ? _cRV : JSON.stringify(_cRV);
    _cRV = CryptoJS.AES.encrypt(_cRV, _2undefinedp, {
        iv: _2undefinedp,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return encodeURIComponent(_cRV.toString())
}

function u() {
    return {
        "sdkv": "3.0.1",
        "busurl": "https://www.脱敏处理.com/captcha-verify/?callback=shield&from=antispam",
        "useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
        "clienttype": "1"
    }
}

function getDInfo(sessionId){
    return AESEncrypt(u(), sessionId)
}

// 测试样例
var sessionId = "a8b339ec0c26459598786fee1cce8dc2"
console.log(getDInfo(sessionId))

这段逻辑也可以用 Python 来实现,关键代码如下(脱敏处理,不能直接运行):

代码语言:python
复制
# ==================================
# --*-- coding: utf-8 --*--
# @Time    : 2021-12-14
# @Author  : 微信公众号:K哥爬虫
# @FileName: ajk.py
# @Software: PyCharm
# ==================================


import json
import base64
import requests
from lxml import etree
from loguru import logger
from urllib.parse import quote_plus
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad


class AESAlgorithm:
    @staticmethod
    def encrypt(aes_key_iv, text):
        """ 对明文进行加密 """
        cipher = AES.new(key=bytes(aes_key_iv, encoding='utf-8'), mode=AES.MODE_CBC, iv=bytes(aes_key_iv, encoding='utf-8'))
        result = base64.b64encode(cipher.encrypt(pad(text.encode('utf-8'), 16))).decode('utf-8')
        result = quote_plus(result)
        return result

    @staticmethod
    def decrypt(aes_key_iv, text):
        """ 对密文进行解密 """
        cipher = AES.new(key=bytes(aes_key_iv, encoding='utf-8'), mode=AES.MODE_CBC, iv=bytes(aes_key_iv, encoding='utf-8'))
        result = unpad(cipher.decrypt(base64.b64decode(text)), 16).decode('utf-8')
        return result


class AJKSlide:
    def __init__(self, index_url, user_agent):
        self.aes = AESAlgorithm()
        self.index_url = index_url
        self.user_agent = user_agent
        self.headers = {"user-agent": self.user_agent}

    def get_session_id(self):
        """ 获取 sessionId """
        response = requests.get(url=self.index_url, headers=self.headers).text
        session_id = etree.HTML(response).xpath("//input[@name='sessionId']/@value")[0]
        logger.info(f"sessionId ==> {session_id}")
        return session_id

    @staticmethod
    def get_aes_key_iv(session_id):
        """设置 AES key 和 iv"""
        aes_key_iv = ''
        for index, value in enumerate(session_id):
            if index % 2 != 0:
                aes_key_iv += value
        logger.info(f"处理 sessionId 获取 aes key iv ==> {aes_key_iv}")
        return aes_key_iv

    def get_d_info(self, aes_key_iv):
        """获取 dInfo"""
        sdk_info = {
            "sdkv": "3.0.1",
            "busurl": self.index_url,
            "useragent": self.user_agent,
            "clienttype": 1
        }
        d_info = self.aes.encrypt(aes_key_iv, json.dumps(sdk_info))
        logger.info(f'dInfo ==> {d_info}')
        return d_info

    def run(self, session_id=None):
        if not session_id:
            session_id = self.get_session_id()
        aes_key_iv = self.get_aes_key_iv(session_id)
        self.get_d_info(aes_key_iv)


if __name__ == '__main__':
    UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
    index_url_ = "https://www.脱敏处理.com/captcha-verify/?callback=shield&from=antispam"
    ajk_slide = AJKSlide(index_url_, UA)
    ajk_slide.run()

getInfoTp 解密

getInfoTp 这个接口返回的 info 的值是加密的,前面我们已经知道用到了 AES 加密算法,这里可以直接猜测也是用的的 AES 来解密的,找到 AESDecrypt 这个方法,下个断点,刷新发现断下之后传入了两个参数,第一个正是 info 的内容,第二个则是 sessionId

08
08
09
09

解密结果可以看到滑块的图片地址等信息:

10
10

data 生成

接下来就是 checkInfoTp 提交验证了,要搞清楚提交的 data 是什么东西,同样搜索打断点,如下图所示 _5DD 就是 data 值,传过来的。

11
11

往上跟栈,可以看到 _Ug0 里面有个 track 参数,这明显就是轨迹了,同样最后的结果经过了 AES 加密。

12
12

再往上跟,可以看到 _Ug0 由三个参数组成,x 是水平滑动的距离,track 是轨迹,p 是定值。

13
13

轨迹处理

轨迹生成前,得先识别缺口得到要滑动的距离,方式有很多,比如 OpenCV、开源的 ddddocr,或者直接打码平台都行,这里唯一要注意的一点就是图片是有缩放的,原始尺寸 480 × 270 px 渲染后的尺寸 280 × 158 px,比例大概是 1:0.5833333333333333,可以先将图片进行缩放后再识别,也可以先识别距离后再将距离进行缩放。

14
14

轨迹的处理,该站点校验并不太严格,所以可以自己写一下,关于滑块的轨迹处理,主要有缩放法、本地轨迹库、根据一些函数来生成轨迹,如缓动函数、贝塞尔曲线等,K哥以后再单独写一篇文章来介绍,本例中可以使用缩放法,先采集一条正常的,手动滑出来的轨迹,然后根据识别出的实际距离和样本轨迹中的距离相比,得到一个比值,然后将样本中的 x 值和时间值都做一个对应的缩放,生成新的轨迹,主要代码如下:

代码语言:python
复制
def generate_track(distance):
    """生成轨迹,样本距离为 126"""
    ratio = distance / 126
    new_track = ""
    base_track = "29,11,0|29,11,11|29,11,26|33,11,56|34,11,66|36,11,67|39,11,76|41,11,83|43,11,86|46,11,92|49,11,98|50,11,102|52,11,106|53,11,111|55,11,116|57,11,118|59,11,123|60,11,126|62,11,132|64,12,134|65,12,138|66,12,142|68,12,148|69,12,151|70,13,155|71,13,158|72,13,164|74,13,166|75,13,170|76,14,174|77,14,180|79,14,182|81,14,186|82,14,196|84,14,198|86,14,207|87,15,212|89,15,219|90,15,223|92,15,230|93,15,234|94,15,239|95,15,243|98,15,246|100,15,250|102,15,260|105,15,262|106,15,266|108,15,270|109,16,276|111,16,278|113,16,283|115,16,286|117,16,291|118,16,294|119,16,298|121,16,302|123,16,309|124,16,311|125,16,315|126,16,319|129,16,324|130,16,327|131,16,331|132,16,334|132,16,388|132,16,522|133,16,566|134,16,574|135,16,575|136,16,594|137,16,620|138,16,625|139,16,652|140,16,657|141,17,676|141,18,680|142,18,684|143,18,688|144,18,716|145,18,724|146,18,796|147,19,828|148,19,860|149,19,888|149,19,890|150,19,916|151,20,932|152,20,936|152,20,1021|153,20,1150|154,20,1152|155,20,1236|155,20,1388|155,20,1522|155,20,1717|"
    base_track = base_track.split("|")[:-1]
    for track in base_track:
        t = track.split(",")
        new_track += str(int(int(t[0]) * ratio)) + "," + str(t[1]) + "," + str(int(int(t[2]) * ratio)) + "|"
        logger.info(f"new_track ==> {new_track}")
        return new_track

结果验证

整个过程比较简单,验证成功。

15
15

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 声明
  • 逆向目标
  • 抓包分析
  • dInfo 生成
  • getInfoTp 解密
  • data 生成
  • 轨迹处理
  • 结果验证
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档