zhs在线教育平台刷课 滑块验证码 无限debugger处理

868 阅读10分钟

1.前言

最近刷dy的时候有看到很多接单刷课的,简单的了解了一下现在主流的刷课脚本,比如油猴,ocs等,基本都是要打开浏览器页面,通过操作网页元素来实现刷课

graph TD
刷课脚本 --> 前端页面 --> 前端页面发送请求到后端 --> 后端校验请求

这种方式效率低(需要等待浏览器页面渲染等)不说,还容易被网课平台在前端内置的检测代码给检测到

我们可以换一种思路,直接通过抓包的形式,找到前端给后端发送到关键请求(记录视频状态的请求等),分析关键包的结构,然后我们自己写一个脚本往后端发请求,这样脚本的好处在与可以直接绕过平台的前端校验,且无论视频的长短我们都能实现直接秒杀,且我们脚本在运行的过程中无需渲染浏览器的页面,效率可以直接拉满

graph TD
抓包分析 --> 构造请求发送给后端 --> 后端校验请求

2.代码实现

2.1 登录凭证获取

要做到全自动脚本,当然是要把登录的协议给搞明白的

当我们尝试输入身份信息登陆的时候。。。

image.png

发现zhs平台有内嵌易盾验证码

先找到关键接口发送请求,获取验证码的底图和缺口图片

image.png

经过观察,就是这个接口响应了图片信息

image.png

将图片下载,使用opencv匹配需要滑动的距离

def identify_gap(bg, tp):
    '''
    bg: 背景图片
    tp: 缺口图片
    out:输出图片
    '''
    # 读取背景图片和缺口图片
    bg_img = cv2.imread(bg)  # 背景图片
    tp_img = cv2.imread(tp)  # 缺口图片

    # 识别图片边缘
    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)  # 寻找最优匹配

    # 绘制方框
    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)  # 绘制矩形

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

接下来的寻找校验接口

image.png

这个接口有很长一节信息

对其进行逆向分析,这里面涉及到了滑块轨迹,滑块距离等信息的校验,如果校验通过,则接口返回的validate不为空

image.png 这一步完成之后,紧随其后的下一个请求如下:

image.png

直接全局搜索一下secretStr

image.png 发现Encrypt函数对账号密码等信息做了处理

看看这个函数的内部结构

image.png

可见,文件名字虽然是aes加密,但是细看代码会发现是URL+base64编码

我们直接在控制台调用base64解码,就能验证我们的分析是否正确

image.png

可以看到里面确实包含了账号密码等信息,但是仔细观察,这个validate的格式好像和我们刚刚获取到的不一样,说明中间肯定有处理

跟栈分析找到入口加密函数

image.png

这个Ci就是我们之前获取到的validate,Cx是fp参数(单词fingerprint指纹的缩写),这个参数的生成是用来校验浏览器环境的,这个CF是固定字符串CN31,应该和版本号挂钩,这个函数经过各种冈易易盾自创的哈希,加密算法的处理后,得到了新的validate

这里重点说一下fp参数,这个参数是从cookie中的gdxidpyhxdE获取到的,我们直接使用cookie的hook脚本找到cookie的生成位置

(function () {
  'use strict';
  var cookieTemp = '';
  Object.defineProperty(document, 'cookie', {
    set: function (val) {
      if (val.indexOf('gdxidpyhxdE') != -1) {
        debugger;
      }
      console.log('Hook捕获到cookie设置->', val);
      cookieTemp = val;
      return val;
    },
    get: function () {
      return cookieTemp;
    },
  });
})();

根据堆栈向上分析,将代码全部扣下来,注意这里会涉及到环境检测,把所有环境检测的函数都重写一下,就可以生成出来fp参数了

image.png

fp的长度在200左右

只有生成正确的fp参数(环境校验通过)才能够通过校验,否则后端会响应滑块校验不通过

若校验通过,服务器会返回pwd和uuid,status=1

image.png 至此登陆流程结束

2.2 获取课程信息

在获取到登陆凭证后,就可以拿着登录凭证去请求对应接口获取到专属于我们账号的信息了

因为我们要做的是刷课脚本,当然是要先获取一下我们有哪些课程

image.png

我这里有一门中国西南交通与丝绸之路的课,我直接搜索关键词'中国西南',看能不能快速定位接口

image.png

效果很好,很快就定位到了课程列表接口

image.png

这个包里面又有secretStr,但是我重复刷新了几次,发现这个参数是不会动的,那么我们其实就可以不管它了,直接把这个参数写死

但后面我又分析了一下,这里其实就是两个接口,接口一先对字符串'{module:13}'进行RSA加密,然后发送请求获取到一个固定的字符串,这个字符串再经过一个函数后生成了这个secretStr,然后接口二就是我们刚刚获取课程列表的接口,直接发送secretStr获取到课程列表,这就是一个模块调用而已

点进课程,还是老套路,快速定位视频信息

image.png

观察响应信息,可以发现里面有个学习时长字段,应该就是视频的时长

image.png

2.3 找到标记视频学习完成的包

在开启开发者工具的状态下,点击一个视频进入视频页

image.png

出现了无限debugger的情况,果然刷课平台都有反调试

往上一个堆栈看看

image.png

原来是Function在作怪,这个function的作用就是创建一个函数,这段代码的意思就是创建一个其中含有debugger的函数,我们直接在控制台重写一下Function函数,再点击过掉断点

image.png

发现这个无限debugger就消失了

image.png

播放一个视频,抓包观察,有个叫saveStuStudyRecord的包看起来就是我们要找的目标

image.png

为了防止被后端检测,我们在不拖动状态进度条的状态下,观察浏览器发包的频率,我们在根据这个频率去模拟浏览器发包,这样就可以绕过检测了

image.png

可以观察到,在我们不拖动进度条的状态下,浏览器每隔30秒就会发送一个记录包,直到视频结束,但是最下面有一个字段signature(签名)需要我们处理一下

仔细观察这个签名,长度是32,而且由0-9的数字和a-f的小写字母组成,大概率是md5哈希

直接搜索signature

image.png

发现没有什么有价值的信息,说明代码可能被混淆了

下一个XHR断点,当链接中含有saveStuStudyRecord时,就会被断住

image.png

拖动进度条,触发这个XHR

沿着堆栈向上分析,找到了这个签名函数, image.png

图片中的代码进行了数组混淆,但基本就是对视频播放的信息进行一系列的位运算,然后进行md5哈希,得到的签名 至此所有难点均已解决

接下来构建一下发包的思路

image.png

先记录一下当前的时间,再减去视频播放的总时长,这就是视频开始播放时候的时间戳

接下来写一个for循环,步长为30(模拟浏览器30秒发一次包),从1开始一直到播放时长的结束

在for循环中发包

当然了,如果你的视频时长是30的倍数,那么for循环走完就不用继续发包了,但是如果不是30的倍数,还要在for循环外再发一个包

经过测试,是可以实现视频秒杀的!这证明我们的思路没有问题

3. 知识点总结

滑块验证码,数组混淆,hook脚本编写,无限debugger处理