Cubic Zone

超星尔雅网课平台反作弊机制的探究 (1) - 初探

声明: 本文章只供技术学习交流使用, 请同学们诚信学习, 不要擅自尝试本文中的任何方法

网页版的尔雅课程播放器有几个烦人的特点: 第一, 播放时鼠标必须悬停在播放器上方, 鼠标移出播放器外视频就会自动停止播放; 第二, 没看过的部分不可以跳转, 只有看过一次之后才可以任意跳转进度; 第三, 课程播放时会插入课程测试题, 只有完成测试题之后才能继续观看.

这三点都阻止了我们一边学习网课一边打电动. 关于第一点, 在点击播放按键后视频刚开始加载的时迅速将浏览器窗口最小化可破, 因为此时网页脚本还来不及读取鼠标位置, 自然也不会触发鼠标移开事件. 但尔雅的播放器采用 Adobe Flash, 会导致浏览器变得非常卡, 尝试切换浏览器 UA 到 iOS 或者 Android 都会跳转到 App 下载页面. 有必要找到一些方法干掉这些烦人的限制, 让刷网课变得更轻松愉快.
首先打开尔雅播放页面, 右键点击 Flash 播放器, 发现有个 “开发者工具” 选项. 点开:

photo_2017-09-13_23-02-22

看起来是 Flash 播放器监听了鼠标移出播放器事件, 在鼠标移出时自动暂停了视频的播放.

因此, 第一点问题的解决方法有两个: 想办法干掉事件监听函数, 这样鼠标移出的时候就不会暂停视频; 或者想办法模拟出鼠标悬停, 视频也可以一直播放.

上面的图片里还出现了一个 logFunc 函数. 开始视频播放并用 Fiddler 抓包, 得到了一个有意思的请求:

GET https://mooc1-1.chaoxing.com/multimedia/log/xxxxxx HTTP/1.1

(防止被查水表, xxxxx 是隐藏的数据)

其中 QueryString 长这样子:

photo_2017-09-13_23-27-35

返回 JSON 中的 isPassed 字段标志检查点是否已经完成.

抓多了几个包观察一下, 发现在同一课内会发生变化的只有 playingTimeenc 两个字段. 简单验证了一下, playingTime 的含义是当前播放位置距离开始的秒数, 而 enc 看起来是一个其余参数的一个哈希值, 用作校验. 尝试修改了 playingTime 后重发请求, 不出意外地返回了 HTTP 403. 那么, 第二个问题的解决方法就很明确了: 找出生成 enc 的函数, 模拟播放器向服务器发送 GET 请求即可.

但是, 播放器是如何获取到已经观看过视频的进度的呢? 刷新页面后, 又发现了一个有趣的请求(在点击视频播放按钮之后发出):

POST https://mooc1-1.chaoxing.com/multimedia/getvideo HTTP/1.1

返回的 JSON 数据如下:

photo_2017-09-13_23-37-36

那么 startPoint 是否就是已播放过的视频秒数呢? 尝试使用 Fiddler 的断点功能, 在收到服务器响应后拦截并修改此参数为 600. 再打开视频后, 播放器果然跳转到了 10 分钟处. 播放了几秒后, 在数据包中发现新的 playingTime 也被更新为十分钟过后的时间. 这样, 我们就可以将播放器当作一个黑盒, 只需要喂给它假的起始时间, 它自然会向服务器发送修改后的数据, 达到我们的欺骗目的.

警告: 这种做法有时并不是十分稳妥. 在谷歌搜索 “尔雅 刷课” 关键词, 发现有很多人利用变速精灵等软件使播放进度加快, 从而达到快速刷课的目的. 然而, 使用此方法后会有很大概率被尔雅标记 “不良行为”. 推测尔雅服务器端校验了两次请求的时间差和两次发送 playingTime 的时间差, 如果两次请求时间差相对于 playingTime 时间差过短, 则会触发尔雅的刷课检测系统. 因此, 更加稳妥的做法是等待经过视频播放的实际时间后, 再发送请求. 经过观察, 播放器请求此接口的频率为每 120 秒一次.

此外, 在视频播放时会被定期请求的接口还有另外一个:

GET https://passport2.chaoxing.com/api/monitor HTTP/1.1

QueryString 如下:

photo_2017-09-14_00-37-28

各个参数的生成方法可以在 https://passport2.chaoxing.com/api/passport2-onlineinfo.js 中找到. versionreferer 包含在请求参数中, t 为当前毫秒时间戳, jsoncallback 为一随机数. 即使是在视频暂停时, 网页脚本也会每 90 秒调用此接口. 一般情况下, 返回的 json 中 status 字段为 true. 修改此字段值为 false 后, 页面跳转并弹出提示: 多终端登录可能被判定为不良行为. 推测此接口是为防止多终端登录所用.

第二个问题有了解决的思路之后, 接下来就该解决视频观看过程中弹出的课程任务点. 一般来说, 任务点完成并解答完毕章节测验后, 就可以进入下一课的学习 (但视频观看时间过短可能也会被判定为不良行为). 在 Fiddler 中继续查找, 发现了这个:

GET https://mooc1-1.chaoxing.com/richvideo/initdatawithviewer?&start=undefined&mid=xxxxxx HTTP/1.1

photo_2017-09-14_00-17-06

嗯……? 这是…? 看起来, 答案是在本地进行验证的. startTime 则标志了任务点的弹出时间. 不知道是因为偷懒呢还是心太大… 总之, 任务点的解答问题也可以解决了. 回答任务点的题目后看捕捉到的请求:

GET https://mooc1-1.chaoxing.com/richvideo/qv HTTP/1.1

请求参数为 resourceid=684423&answer=%27A%27

QueryString 有 resourceidanswer 两个字段, 返回值是一个 JSON, 格式如下:

{
    "resourceId": 684423,
    "isRight": true,
    "answer": "A"
}

可以看出服务器端实际也做了判断, 返回的答案数据大概是历史遗留问题.

至于章节测验的解答, 网上已经有很多尔雅课程的答案, 想办法自动匹配即可.

经过上述分析, 尔雅并没有采用特别复杂的反作弊技术, 对于反作弊的检测主要集中在校验两次接口数据提交时间和视频真实时间差值的大小, 以及是否使用多终端登录. 当然, 也不能排除尔雅采用各种资源是否加载判断纯命令行刷课软件的可能性. 但网上刷课软件的通用做法 (时间加速, 直接提交 playingTime, 甚至多开窗口刷课) 均可能触发上述检测, 导致被标记为不良行为. 最终可行的解决方法有两个:

  1. 直接丢掉 GUI 界面, 模拟播放器向服务器接口发送数据 (包括播放进度和检查点), 并自动回答章节测验题. 没必要管第一个问题, 因为实际上我们根本不用放视频. (实现较难 -> 需要找出生成代码, 风险较大, 但占资源少, 可以在服务器上挂机)
  2. 使用 python, selenium + chrome webdriver, 模拟鼠标悬停事件, 自动点击并提交任务点问题, 自动点击并提交章节测验. 或者直接使用 userscript 实现. (实现简单, 风险小, 但占资源较大. 但或许我们可以把视频直接干掉?)

但其中的重点都是: 不要图省时间!!! 该花的时间还是要花, 只不过之前电脑被课程视频独占, 之后可以在听课的同时在电脑上做别的事情. 如果你只剩下 24 小时但是网课却一点没刷, 那恭喜你, 你挂定了.

另外, 有反馈提到在手机端使用二倍速播放也被判定为不良行为, 因此未观看完成的视频使用官方手机客户端二倍速播放也存在风险.

对尔雅页面的简单分析就是这样, 进一步的分析和具体实现请期待后继文章.