数美滑块验证码分析

简介: 本文以官网的滑块验证码为例,分析验证过程,完成模拟验证。

小红书、蘑菇街、脉脉、抖鱼等很多都用了数美的验证码。

本文以官网的滑块验证码为例,分析验证过程,完成模拟验证。


数美验证码官网:https://www.ishumei.com/trial/captcha.html
在这里插入图片描述


1.验证码申请

打开控制台多看几遍请求过程,就大抵明白请求步骤了,这里就不再细说。

api: 'https://captcha.fengkongcloud.com/ca/v1/conf?'
在这里插入图片描述

params= {
        'organization': 'RlokQwRlVjUrTUlkIqOg',
        'model': 'slide',
        'sdkver': '1.1.3',
        'rversion': '1.0.3',
        'appId': 'default',
        'lang': 'zh-cn',
        'channel': 'YingYongBao',
        'callback': 'sm_{}'.format(int(time.time() * 1000))
    }

该接口返回的js参数,是下一步需要请求的目标。
在这里插入图片描述

2.提取js参数

js地址:https://castatic.fengkongcloud.com/pr/auto-build/v1.0.3-70/captcha-sdk.min.js
需要提取该js中的参数名,会在最后验证的时候使用。
不过一般情况下参数名不会变,也可以省略此处,在后面写死。
在这里插入图片描述
在该js文件中的参数是倒序的
在这里插入图片描述


2.验证码注册

api: https://captcha.fengkongcloud.com/ca/v1/register?
在这里插入图片描述
bg和fg是验证码图片地址。
在这里插入图片描述

3.计算滑块位置

根据上一步可以得到验证图片的地址。
验证码图片:https://castatic.fengkongcloud.com/crb/set-000006/v2/a13e3325e9f864fa42a94a6e07cd95fc_bg.jpg
滑块图片:https://castatic.fengkongcloud.com/crb/set-000006/v2/a13e3325e9f864fa42a94a6e07cd95fc_fg.png
在这里插入图片描述

使用opencv查找并匹配图像模板中的滑块。
需要注意的是,这里是以原图计算的,而页面上的图片大小只有(300,150),(应用不同的产品可能大小也不同)
所以需要按比例进行缩小。

4.验证

api:https://captcha.fengkongcloud.com/ca/v2/fverify?

params = {
    'protocol': '70',
    'organization': 'RlokQwRlVjUrTUlkIqOg',
    'rversion': '1.0.3',
    'oe': 'V/QxFC7ISm1=',
    'nj': '1ISY9IKNM+OGjwl0F7LP...省略...7VOki5p4sm+h/qMX2QAhN/4w',
    'zl': '8LwMmaImogs=',
    'sq': '14RHMSbfhJU=',
    'kh': 'qCcI31wL/Fs=',
    'mn': '4AAyKWNy6K0=',
    'rid': '20210113105643e313f68a420c9d240d',
    'ch': 'uiJ+hjbCOka=',
    'yv': 'b9NFDsBKGcg=',
    'ga': 'WssDiJ1wOQI=',
    'ko': '14bDqW72JnI=',
    'callback': 'sm_1610506618948',
    'act.os': 'web_pc',
    'vj': 'IFXKu8Pjb3k=',
    'ostype': 'web',
    'sdkver': '1.1.3'
}

params参数里的 oe,mn,kh等等,都经过了DES加密。

验证后会返回,
在这里插入图片描述
message = success,riskLevel=PASS 说明验证通过


5.完整代码

"""
数美滑块验证码破解验证
"""

import base64
import json
import random
import re
import time
from io import BytesIO
import cv2
import numpy as np
import requests
from pyDes import des, ECB

CAPTCHA_DISPLAY_WIDTH = 310
CAPTCHA_DISPLAY_HEIGHT = 155

p = {}


def pad(b):
    """
    块填充
    """
    block_size = 8
    while len(b) % block_size:
        b += b'\0'
    return b


def split_args(s):
    """
    分割js参数
    """
    r = []
    a = ''
    i = 0
    while i < len(s):
        c = s[i]
        if c == ',' and (a[0] != '\'' or len(a) >= 2 and a[-1] == '\''):
            r.append(a)
            a = ''
        elif c:
            a += c
        i += 1
    r.append(a)
    return r


def find_arg_names(script):
    """
    通过js解析出参数名
    """
    names = {}
    a = []
    for r in re.findall(r'function\((.*?)\)', script):
        if len(r.split(',')) > 100:
            a = split_args(r)
            break

    r = re.search(r';\)(.*?)\(}', script[::-1]).group(1)
    v = split_args(r[::-1])

    d = r'{%s}' % ''.join([((',' if i else '') + '\'k{}\':([_x0-9a-z]*)'.format(i + 1)) for i in range(15)])

    k = []
    r = re.search(d, script)
    for i in range(15):
        k.append(r.group(i + 1))

    n = int(v[a.index(re.search(r'arguments;.*?,(.*?)\);', script).group(1))], base=16)

    for i in range(n // 2):
        v[i], v[n - 1 - i] = v[n - 1 - i], v[i]

    for i, b in enumerate(k):
        t = v[a.index(b)].strip('\'')
        names['k{}'.format(i + 1)] = t if len(t) > 2 else t[::-1]

    return names


def get_encrypt_content(message, key, flag):
    """
    接口参数的加密、解密
    """
    des_obj = des(key.encode(), mode=ECB)
    if flag:
        content = pad(str(message).replace(' ', '').encode())
        return base64.b64encode(des_obj.encrypt(content)).decode('utf-8')
    else:
        return des_obj.decrypt(base64.b64decode(message)).decode('utf-8')


def get_random_ge(distance):
    """
    生成随机的轨迹
    """
    ge = []

    y = 0
    v = 0
    t = 1
    current = 0
    mid = distance * 3 / 4
    exceed = 20
    z = t

    ge.append([0, 0, 1])

    while current < (distance + exceed):
        if current < mid / 2:
            a = 15
        elif current < mid:
            a = 20
        else:
            a = -30
        a /= 2
        v0 = v
        s = v0 * t + 0.5 * a * (t * t)
        current += int(s)
        v = v0 + a * t

        y += random.randint(-5, 5)
        z += 100 + random.randint(0, 10)

        ge.append([min(current, (distance + exceed)), y, z])

    while exceed > 0:
        exceed -= random.randint(0, 5)
        y += random.randint(-5, 5)
        z += 100 + random.randint(0, 10)
        ge.append([min(current, (distance + exceed)), y, z])

    return ge


def make_mouse_action_args(distance):
    """
    生成鼠标行为相关的参数
    """
    ge = get_random_ge(distance)
    args = {
        p['k']['k5']: round(distance / CAPTCHA_DISPLAY_WIDTH, 2),
        p['k']['k6']: get_random_ge(distance),
        p['k']['k7']: ge[-1][-1] + random.randint(0, 100),
        p['k']['k8']: CAPTCHA_DISPLAY_WIDTH,
        p['k']['k9']: CAPTCHA_DISPLAY_HEIGHT,
        p['k']['k11']: 1,
        p['k']['k12']: 0,
        p['k']['k13']: -1,
        'act.os': 'android'
    }
    return args


def get_distance(fg, bg):
    """
    计算滑动距离
    """
    target = cv2.imdecode(np.asarray(bytearray(fg.read()), dtype=np.uint8), 0)
    template = cv2.imdecode(np.asarray(bytearray(bg.read()), dtype=np.uint8), 0)
    result = cv2.matchTemplate(target, template, cv2.TM_CCORR_NORMED)
    _, distance = np.unravel_index(result.argmax(), result.shape)
    return distance


def update_protocol(protocol_num, js_uri):
    """
    更新协议
    """
    global p
    r = requests.get(js_uri, verify=False)
    names = find_arg_names(r.text)
    p = {
        'i': protocol_num,
        'k': names
    }


def conf_captcha(organization):
    """
    获取验证码设置
    """
    url = 'https://captcha.fengkongcloud.com/ca/v1/conf'

    args = {
        'organization': organization,
        'model': 'slide',
        'sdkver': '1.1.3',
        'rversion': '1.0.3',
        'appId': 'default',
        'lang': 'zh-cn',
        'channel': 'YingYongBao',
        'callback': 'sm_{}'.format(int(time.time() * 1000))
    }

    r = requests.get(url, params=args, verify=False)
    resp = json.loads(re.search(r'{}\((.*)\)'.format(args['callback']), r.text).group(1))
    return resp


def register_captcha(organization):
    """
    注册验证码
    """
    url = 'https://captcha.fengkongcloud.com/ca/v1/register'

    args = {
        'organization': organization,
        'channel': 'YingYongBao',
        'lang': 'zh-cn',
        'model': 'slide',
        'appId': 'default',
        'sdkver': '1.1.3',
        'data': '{}',
        'rversion': '1.0.3',
        'callback': 'sm_{}'.format(int(time.time() * 1000))
    }

    r = requests.get(url, params=args, verify=False)
    resp = json.loads(re.search(r'{}\((.*)\)'.format(args['callback']), r.text).group(1))

    return resp


def verify_captcha(organization, rid, key, distance):
    """
    提交验证
    """
    url = 'https://captcha.fengkongcloud.com/ca/v2/fverify'
    args = {
        'organization': organization,
        p['k']['k1']: 'default',
        p['k']['k2']: 'YingYongBao',
        p['k']['k3']: 'zh-cn',
        'rid': rid,
        'rversion': '1.0.3',
        'sdkver': '1.1.3',
        'protocol': p['i'],
        'ostype': 'web',
        'callback': 'sm_{}'.format(int(time.time() * 1000))
    }

    args.update(make_mouse_action_args(distance))

    key = get_encrypt_content(key, 'sshummei', 0)

    for k, v in args.items():
        if len(k) == 2:
            args[k] = get_encrypt_content(v, key, 1)
    print(args)
    r = requests.get(url, params=args, verify=False)
    resp = json.loads(re.search(r'{}\((.*)\)'.format(args['callback']), r.text).group(1))

    return resp


def get_verify(organization):
    """
    进行验证
    """
    resp = conf_captcha(organization)
    protocol_num = re.search(r'build/v1.0.3-(.*?)/captcha-sdk.min.js', resp['detail']['js']).group(1)

    if not p.get('id') or protocol_num != p['i']:
        update_protocol(protocol_num, ''.join(['https://', resp['detail']['domains'][0], resp['detail']['js']]))

    resp = register_captcha(organization)

    rid = resp['detail']['rid']
    key = resp['detail']['k']

    domain = resp['detail']['domains'][0]
    fg_uri = resp['detail']['fg']
    bg_uri = resp['detail']['bg']

    fg_url = ''.join(['http://', domain, fg_uri])
    bg_url = ''.join(['http://', domain, bg_uri])

    r = requests.get(fg_url, verify=False)
    fg = BytesIO(r.content)

    r = requests.get(bg_url, verify=False)
    bg = BytesIO(r.content)

    distance = get_distance(fg, bg)
    print(distance)
    r = verify_captcha(organization, rid, key, int(distance / 600 * 310))

    return rid, r


def test():
    # 表示小红书
    organization = 'eR46sBuqF0fdw7KWFLYa'

    # rid是验证过程中响应的标示,r是最后提交验证返回的响应
    rid, r = get_verify(organization)
    print(rid, r)

    # riskLevel为PASS说明验证通过
    if r['riskLevel'] == 'PASS':
        # 这里需要向小红书提交rid
        # 具体可抓包查看,接口:/api/sns/v1/system_service/slide_captcha_check
        pass


if __name__ == '__main__':
    test()
目录
相关文章
Pyside6-第四篇-QCheckBox复选框
Pyside6-第四篇-QCheckBox复选框
1172 0
Pyside6-第四篇-QCheckBox复选框
|
编解码 小程序
微信小程序11177版本开启控制台方法
微信小程序11177版本开启控制台方法
|
Windows
【Windows 逆向】OD 调试器工具 ( 推荐汉化版的 OD 调试工具 | 吾爱破解专用版Ollydbg | 备选工具 )
【Windows 逆向】OD 调试器工具 ( 推荐汉化版的 OD 调试工具 | 吾爱破解专用版Ollydbg | 备选工具 )
11102 0
【Windows 逆向】OD 调试器工具 ( 推荐汉化版的 OD 调试工具 | 吾爱破解专用版Ollydbg | 备选工具 )
|
10月前
|
存储 数据采集 算法
数据中台最全详解,一文彻底搞懂!
中台是大型数据架构模式,也是大型架构的必备技能,本篇全面详解数据中台,建议收藏备用。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
数据中台最全详解,一文彻底搞懂!
Linux中将文件解压到指定目录
Linux中将文件解压到指定目录
|
JavaScript 前端开发 开发者
太爽了!这10个 Vite 插件,帮我在开发中节省了大量时间!!
太爽了!这10个 Vite 插件,帮我在开发中节省了大量时间!!
|
Linux 网络安全 开发者
【Python】已解决:WARNING: pip is configured with locations that require TLS/SSL, however the ssl module i
【Python】已解决:WARNING: pip is configured with locations that require TLS/SSL, however the ssl module i
4182 3
|
存储 监控 前端开发
Sentry 监控部署与使用(详细流程)
Sentry 监控部署与使用(详细流程)
12820 1
|
算法 JavaScript 前端开发
什么是模块化开发
【8月更文挑战第26天】什么是模块化开发
455 1
|
缓存 JavaScript 前端开发