探索雨课堂动态签到二维码之记录(未成功)

Denvo 树犹如此,人何以堪

前言

学校中越来越多的课程在签到时都要使用雨课堂的5s刷新动态二维码,于是想了解一下雨课堂的签到动态二维码的生成机制并寻找可能的解决方法。不过最后仍然没有找出来。但就算如此,我也打算在这里浅浅地记录一下发掘的历程,等待下一次机会继续探索。

探寻动态二维码的信息

我事先拍了几个动态二维码的样本。以下展示其中三个:

1
2
3
4
5
https://www.yuketang.cn/api/v3/lesson/check-in/dynamic-qr-code?c=WeDeZUw7tFaF5JH9JFRQ0EHOkJeNuReyhRHrD47lPbo&t=1775182143676&s=DEE9DB0667548734&v=2

https://www.yuketang.cn/api/v3/lesson/check-in/dynamic-qr-code?c=WeDeZUw7tFaF5JH9JFRQ0EHOkJeNuReyhRHrD47lPbo&t=1775182153896&s=9421AA8131E82074&v=2

https://www.yuketang.cn/api/v3/lesson/check-in/dynamic-qr-code?c=WeDeZUw7tFaF5JH9JFRQ0EHOkJeNuReyhRHrD47lPbo&t=1775182164099&s=B0A0511DC0E1B1BE&v=2

根据这些样本,我可以知道以下内容:

  • 主机:www.yuketang.cn
  • 协议:https
  • Path:/api/v3/lesson/check-in/dynamic-qr-code
  • 方法:GET
  • URL参数:
    • c:WeDeZUw7tFaF5JH9JFRQ0EHOkJeNuReyhRHrD47lPbo
    • t:时间戳
    • s:签名
    • v:2

目前来看,主要需要s这个变量才能方便地构造动态二维码。既然如此,我们可以探究一下这个动态二维码是怎么生成的,说不定就能知道这个签名的生成逻辑了。

探索雨课堂程序本身

从雨课堂这个软件本身来分析说不定能找到签名的生成逻辑。我分析了雨课堂的Android端App,发现其未使用代码混淆等,比较容易被逆向,且使用Flutter构建。尝试抓包后发现此App不信任用户创建的CA证书,无法有效解密https的内容。虽然可以通过root后把证书强行放置在/system/etc/security/cacerts/文件夹、改名为[hash值].0并赋予644权限来绕过,但是转念一想动态二维码的内容在教师端生成,研究手机App端应该意义不大,于是我的目光转向了PC端雨课堂。

雨课堂的PC端可以直接从官网直接下载并安装。经分析,雨课堂PC端实际上是一个Electron程序,且易于使用asar命令解包app.asar。经完全解包后发现内部的js代码经过了代码混淆等手段。通过全局搜索后刷新(匹配“动态二维码:X s 后刷新”字符串)字符串,可以得到如下结果。

搜索“后刷新”字符串结果
搜索“后刷新”字符串结果

看上去除了793.af1f97ba45d64dc5.js之外,其他的js基本都是相同的。我们先看下这些包含相同内容的js里面都有些什么。有一个叫teacher.js的文件看上去有些可疑,于是看一下这个js文件。相关部分的代码大概长这样:

teacher.js代码截图
teacher.js代码截图

看样子,这里是多语言支持的占位符。qrcoderefresh应该就是UI上显示动态二维码:X s 后刷新的占位符。去搜索一下哪里使用了这个占位符:

搜索qrcoderefresh结果
搜索qrcoderefresh结果

又是这样。除了793.af1f97ba45d64dc5.js之外,其他的js文件中相关部分都是一样的,且都是像上图teacher.js一样的占位符内容。既然如此,那么793.af1f97ba45d64dc5.js就很值得怀疑了。查看一下相关部分:

793.af1f97ba45d64dc5.js代码截图
793.af1f97ba45d64dc5.js代码截图

嗯,看起来这里确实是老师上课相关的逻辑部分。分析这个js,可以确定这是一个经过Webpack打包后的JavaScript模块,主要负责教师授课端的核心交互界面与逻辑控制。里面包含了开始上课、结束上课、课件操作、开启弹幕、随机点名、发送投稿、答题、签到等多个功能,且使用Vue.js框架。

根据代码逻辑,雨课堂的动态签到二维码通过<img>标签展示。数据来源是一个叫lesson的全局状态中的一个叫qrImage的变量。换句话说,动态二维码不通过本地生成。那么接下来去找找它怎么从服务器获得二维码地址。

经分析,大部分数据交换通过WebSocket进行,URL地址是wss://{domain}/wsapp/。其中domain这个变量是服务器主机。建立连接后,sendXinTiao方法会每隔1分钟发送一次心跳包(内容是{op: "xintiao"}),且在断开连接时,前三次断开会在1s后重连,第四次断开会在3s后重连,后续失败则会进入循环重连模式,界面显示10s倒计时。

根据55.18ea63c160d1e63a.js显示,雨课堂一共分四个服务器(默认服务器是www.yuketang.cn):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// ...
37094: function(e, t, a) {
// ...
var r = [{
domain: "www.yuketang.cn",
logo: "iconyu",
name: "雨课堂",
enName: "Rain",
index: 0,
wechat: "雨课堂",
img: a(35255),
envId: 1
}, {
domain: "pro.yuketang.cn",
logo: "iconhetang",
name: "荷塘·雨课堂",
enName: "Hetang",
index: 4,
wechat: "荷塘雨课堂",
img: a(45510),
envId: 2
}, {
domain: "changjiang.yuketang.cn",
logo: "iconchangjiang",
name: "长江·雨课堂",
enName: "Changjiang",
index: 5,
wechat: "长江雨课堂",
img: a(33663),
envId: 3
}, {
domain: "huanghe.yuketang.cn",
logo: "iconhuanghe",
name: "黄河·雨课堂",
enName: "Huanghe",
index: 6,
wechat: "黄河雨课堂",
img: a(75563),
envId: 4
}];
// ...
}

经分析,Logo等文件存放在七牛云存储中,而课件上传等文件存放在阿里云OSS(北京节点)中。

与上述的WebSocket地址建立连接,并不靠HTTP header(如Authorization: Bearer ...)鉴权,而是在建立连接后,向服务器发送一个特定的数据包(json)完成身份验证。这个数据包的结构如下:

1
2
3
4
5
6
7
8
9
{
"op": "hello",
"userid": "用户的 identityId 或 id",
"role": "addin",
"auth": "lessonToken (课堂令牌)",
"version": 5.5,
"call": -1,
"lessonid": "当前课堂的 lessonId"
}

其中,op字段、role字段、version字段、call字段均被固定。那么,鉴权所需要的变量只有useridauthlessonid

当身份验证通过后,服务器才会发送包含动态二维码在内的课堂信息。userid一般每个用户是不可变的,获取难度应该较低;lessonid可以通过获取当前课堂列表之类的API获取,应该也比较容易获取;问题就出在auth上了。经过分析,这个变量的值是在老师点击“开始上课”按钮时才能从服务器获取到的。

雨课堂的系统在设计时就考虑到了“多端同时操作”或“中途潜入”。雨课堂的授课系统包含了一套完整的“课堂控制权”的管理逻辑,这个逻辑默认只允许一堂课只有一个合法的教师端连接并控制。换句话说,一旦老师点击了“开始上课”按钮,我只要模拟请求“开始上课”的API,原有的老师端就会受到影响。通过正常的模拟API来实现获取动态二维码的信息目前是比较困难的。

在现有的代码逻辑下,auth几乎是一个无法被静默获取的变量,而强行通过API获取一定会影响原有的老师端。如果想要绕过雨课堂的授课系统的单人控制限制,一般只有下面的两种方法:

  • 在教室的电脑上运行监控程序,通过内存扫描或网络劫持来获得这个变量的值。

  • 通过网络中间人攻击来获取发向教室电脑的这个变量的值。

但无论是哪一个,实现起来都比较困难。

尝试自己创建课堂来获取

雨课堂学生端也能创建自己的课堂。我本想打算通过这个功能,并使用DevTools来尝试获取一些信息,但很不巧的是,虽然学生也可以创建课堂,但不允许使用动态二维码签到功能。动态二维码签到功能仅开放给具有组织授权的用户使用。故此想法也被pass掉。

结束

通过上面简单的探索过程,如果需要继续探索这个动态二维码的生成逻辑,或许有下面几种方法:

  • 通过强行计算各种hash的值来突破(几乎不可能)

  • 通过获取auth的值来获取动态二维码(也很困难)

    • 在教室的电脑上运行监控程序

    • 网络中间人攻击

个人的探索目前仅能进行到这里。如果有人有更多的想法,欢迎在评论区交流!

  • 标题: 探索雨课堂动态签到二维码之记录(未成功)
  • 作者: Denvo
  • 创建于 : 2026-05-01 11:40:55
  • 更新于 : 2026-05-01 11:50:47
  • 链接: https://www.denvoshome.xyz/posts/yuketang-try-failed/
  • 版权声明: 本文章采用 CC BY-SA 4.0 进行许可。
评论