首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >数美滑块验证码分析

数美滑块验证码分析

作者头像
李玺
发布2021-11-22 16:07:00
发布2021-11-22 16:07:00
2.2K0
举报
文章被收录于专栏:爬虫逆向案例爬虫逆向案例

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

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

文章目录

数美验证码官网:https://www.ishumei.com/trial/captcha.html

1.验证码申请

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

api: ‘https://captcha.fengkongcloud.com/ca/v1/conf?’

代码语言:javascript
复制
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是验证码图片地址。 https://castatic.fengkongcloud.com/crb/+bg

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?

代码语言:javascript
复制
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.完整代码

代码语言:javascript
复制
"""
数美滑块验证码破解验证
"""

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()

原文链接:https://blog.csdn.net/weixin_43582101/article/details/112553479

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/01/14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 1.验证码申请
  • 2.提取js参数
  • 2.验证码注册
  • 3.计算滑块位置
  • 4.验证
  • 5.完整代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档