爬虫逆向学习(八):Canvas画图滑块验证码解决思路与绕过骚操作

此分享只用于学习用途,不作商业用途,若有冒犯,请联系处理

逆向站点

aHR0cHM6Ly93d3cuYm9odWF5aWNhaS5jbi8/VTU4Iy9jaGVtaWNhbC9sb2dpbj9yZWRpcmVjdD0lMkZjaGVtaWNhbA==

滑块验证码样式

在这里插入图片描述

滑块验证码研究

一般的滑块验证码都是会直接提供滑块和缺口背景图片,而这个站点只能直接拿到背景图,滑块和缺口都是用Canvas绘制出来的
在这里插入图片描述

接下来看看它怎么绘制的,我们点击刷新一次得到图片的调用堆栈,进入最顶层的栈,打上断点再刷新一次
在这里插入图片描述

这个时候不断执行跳出函数,大概7次,就能看到Canvas绘制的具体位置了
在这里插入图片描述

当然如果你对Canvas绘图了解的话,如果站点js没有混淆也是可以直接搜索drawImage定位到绘制的代码位置的
在这里插入图片描述

到这里我们只要破解绘制的距离参数就能得到滑块具体了,不过这里先别急,我们先手动滑成功一次,它验证账号密码的接口是/api/igo-cloud-member/login/loginByCred,但是看它的请求头和请求体都没有跟滑块验证码有关的地方,经过验证后发现这个站点只是在前端做了滑块验证码验证,实际的登录流程可以直接构造接口即可。

登录破解

/api/igo-cloud-member/login/loginByCred接口只是对请求体进行加密,并没有其它强校验
在这里插入图片描述

这里我们添加xhr断点,看看这个接口发包前都做了啥
在这里插入图片描述

看调用堆栈,直接点击loginByUserName栈,就可以看到入口了
在这里插入图片描述

添加断点重新登录一次就能看到加密操作了,也就是c.default.jiami,这里说一下sequenceCode,它是在接口/api/igo-cloud-member/login/getCodeByUserCode?userCode=返回的result值,然后经过与密码一样的加密操作后得到的
在这里插入图片描述

这里提供加密算法参考

def aes_encrypt(plaintext, aes_key):
    cipher = Cipher(algorithms.AES(aes_key.encode()), modes.ECB(), backend=default_backend())
    encryptor = cipher.encryptor()
    padder = padding.PKCS7(algorithms.AES.block_size).padder()
    padded_data = padder.update(plaintext.encode()) + padder.finalize()
    encrypted = encryptor.update(padded_data) + encryptor.finalize()
    return base64.b64encode(encrypted).decode()

附加分析

在不考虑绕过前端直接破解接口的方案下,我们怎么处理滑块呢,当然这里的前提使用模拟登录方案
模拟登录的难点就是如何得到滑动距离,有思考过在打开站点页面前进行代码注入,不过没验证,下面提供的方案是通过cv2包得到滑动距离
cv2讲解可以参考这篇文章:不想用selenium处理滑块验证码?教你用cv2解决

这里我们需要先拿到滑块图和缺口图,在我们点击登录时它是直接都显示的,如果直接保存是无法使用的,这时可以修改标签样式实现隐藏来截图

只要加上style="display: none;"就能隐藏不必要的元素的,截完图后去掉它就恢复显示了
在这里插入图片描述
在这里插入图片描述
以playwright为例,screenshot方法能实现标签元素截图保存,而page.evaluate('document.getElementsByClassName("block")[0].setAttribute("style","display=none;");')能实现标签样式修改

得到后调用下方代码就能拿到滑块距离了。

def identify_gap_cut(bg, tp):
    """
    三通道 RGB 滑块距离识别
    :param bg: 背景图片
    :param tp: 缺口图片
    :return int
    """
    # 读取背景图片和缺口图片
    bg_img = get_cv2_img(bg)  # 背景图片
    tp_img = get_cv2_img(tp)  # 缺口图片
    if type(bg_img) == str or type(tp_img) == str:
        print('图片格式存在问题,无法用cv2读取!')
        return
    # # 裁剪图片
    # tp_height, tp_width, _ = tp_img.shape
    # tp_start_row, tp_start_col = int(tp_height * 0.25), 0
    # tp_end_row, tp_end_col = int(tp_height * 0.63), int(tp_width * 1)
    # tp_img = tp_img[tp_start_row:tp_end_row, tp_start_col:tp_end_col]
    # height, width, _ = bg_img.shape
    # print(tp_height, tp_width)
    # print(height, width)
    # start_row, start_col = 0, int(tp_width)
    # end_row, end_col = int(height), int(width)
    # bg_img = bg_img[start_row:end_row, start_col:end_col]
    # cv_show(bg_img)

    # 识别图片边缘
    bg_edge = cv2.Canny(bg_img, 100, 200)
    tp_edge = cv2.Canny(tp_img, 100, 200)

    # 转换图片格式
    bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
    tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)

    # 缺口匹配
    res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    print(min_val, max_val, min_loc, max_loc)

    # 绘制方框
    th, tw = tp_pic.shape[:2]
    tl = max_loc  # 左上角点的坐标
    br = (tl[0] + tw, tl[1] + th)  # 右下角点的坐标
    cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2)  # 绘制矩形
    cv2.imwrite('check_ground_map.jpeg', bg_img)  # 保存在本地

    # 返回缺口的X坐标
    return max_loc[0]


def get_cv2_img(img_object, cv_type=None):
    if type(img_object) == str:
        cv_img = cv2.imread(img_object, cv_type) if cv_type else cv2.imread(img_object)
    elif type(img_object) == bytes:
        cv_img = np.frombuffer(img_object, np.uint8)
        cv_img = cv2.imdecode(cv_img, cv_type) if cv_type else cv2.imdecode(cv_img, cv2.IMREAD_ANYCOLOR)
    else:
        cv_img = None
    return cv_img
posted @ 2024-10-06 18:19  七夜魔手  阅读(12)  评论(0编辑  收藏  举报  来源