好友
阅读权限10
听众
最后登录1970-1-1
|
本帖最后由 RetiredGuitar64 于 2026-5-5 21:46 编辑
楼主本来想在网上,找找看有没有这个平台的分析,结果没找到,可能因为软件太小众了,不像某通那么大众化,于是直接自己分析
先是用root手机,对U加平台这个软件,进行了完整的抓包,并tls解密,
然后开始人肉分析数据包
抓包工具:Fiddler
下面为关键数据包分析
1.账号登录
获取公钥 post:
POST https://uc.eduplus.net/spi/login/checkup
可以看到请求体是密码登录, 我们也已密码登录为例,暂不讨论验证码登录
响应
可以看到,响应中下发了用于加密的公钥,公钥每次都不同,用于加密我们输入的账号密码,然后平台的服务端用私钥解密
提交密文 post:
POST https://uc.eduplus.net/spi/login/submit
软件把我们输入的账号密码,通过某种加密算法,加密成密文cryptogram,连同公钥一同post到了服务端,
响应
服务端登录认证通过,下发token,
然后就会登录成功,登录也都是通过这个token,
有一些加载配置,加载个人页面的数据包,忽略不讲
2.在课程页面,等待时的数据包
当停留在软件的主页面,也就是课程列表页面时,软件会每两秒进行一次get包,来获取课程的状态
get心跳包:
GET https://www.eduplus.net/api/course/courses/v1/study?types=Theory,Train
而且我注意到,这个get包中的x-access-token和cookie,居然是一样的,都是上面登录成功后,下发的token,而且token过期机制很松,获取新的token不会使老token失效
响应
响应返回的也是json, 格式如下
{
"code": 2000000,
"data": [
{
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "xxxxxxxxxxx",
"type": "xxxxxx",
"typeTitle": "xx",
"startTime": 1775059200000,
"endTime": 1806681599000,
"creator": "xxxxxxxxxx",
"orgId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"icon": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"term": "xxxxxxxxxxxxx",
"videoDrag": true,
"publicStatus": "xxxxxxx",
"publicStatusTitle": "xxx",
"focus": false,
"teachClasses": [
{
"createAt": 1775229931033,
"updateAt": 1775229931033,
"createBy": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"updateBy": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "xxxxxxxxxxxxx",
"courseId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"code": "xxxxxx",
"homeworkWeightChange": "xx",
"homeworkWeightChangeTitle": "x",
"experimentWeightChange": "xx",
"experimentWeightChangeTitle": "x",
"teacherTeachClassModels": []
}
],
"leader": false,
"courseSignInOpen": false
},
{
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "xxxxxxxxxx",
"type": "xxxxxx",
"typeTitle": "xx",
"startTime": 1774972800000,
"endTime": 1806595199000,
"creator": "xxxxxxxxxx",
"orgId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"icon": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"term": "xxxxxxxxxxxxx",
"videoDrag": true,
"publicStatus": "xxxxxxx",
"publicStatusTitle": "xxx",
"focus": false,
"teachClasses": [
{
"createAt": 1775210370885,
"updateAt": 1775210370885,
"createBy": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"updateBy": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "xxxx",
"courseId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"code": "xxxxxx",
"homeworkWeightChange": "xx",
"homeworkWeightChangeTitle": "x",
"experimentWeightChange": "xx",
"experimentWeightChangeTitle": "x",
"teacherTeachClassModels": []
}
],
"leader": false,
"courseSignInOpen": false
}
],
"success": true,
"tracer": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"message": "xx",
"status": 200
}
这个data数组中,存着每一个课程,每个课程又都有自己的名称,courseId, 创建时间等信息,
但,最关键的信息是:
"courseSignInOpen": false
这一条,course sign in open ,也就是课程签到是否开启,如果无签到的话,是false, 有签到就是true,
每一个课程都有一个自己的courseSignInOpen, 用来记录课程当前是否有签到
软件会每两秒进行一次上述的get, 拉取课程状态,相当于软件的待机状态
3.发布签到,数据包的变化
假如这时候,有一门课程发布了签到,get的响应包会变成如下:
{
"code": 2000000,
"data": [
{
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "xxxxxxxxxxx",
"type": "xxxxxx",
"typeTitle": "xx",
"startTime": 1775059200000,
"endTime": 1806681599000,
"creator": "xxxxxxxxxx",
"orgId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"icon": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"term": "xxxxxxxxxxxxx",
"videoDrag": true,
"publicStatus": "xxxxxxx",
"publicStatusTitle": "xxx",
"focus": false,
"teachClasses": [
{
"createAt": 1775229931033,
"updateAt": 1775229931033,
"createBy": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"updateBy": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "xxxxxxxxxxxxx",
"courseId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"code": "xxxxxx",
"homeworkWeightChange": "xx",
"homeworkWeightChangeTitle": "x",
"experimentWeightChange": "xx",
"experimentWeightChangeTitle": "x",
"teacherTeachClassModels": []
}
],
"leader": false,
"courseSignInOpen": false
},
{
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "xxxxxxxxxx",
"type": "xxxxxx",
"typeTitle": "xx",
"startTime": 1774972800000,
"endTime": 1806595199000,
"creator": "xxxxxxxxxx",
"orgId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"icon": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"term": "xxxxxxxxxxxxx",
"videoDrag": true,
"publicStatus": "xxxxxxx",
"publicStatusTitle": "xxx",
"focus": false,
"teachClasses": [
{
"createAt": 1775210370885,
"updateAt": 1775210370885,
"createBy": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"updateBy": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "xxxx",
"courseId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"code": "xxxxxx",
"homeworkWeightChange": "xx",
"homeworkWeightChangeTitle": "x",
"experimentWeightChange": "xx",
"experimentWeightChangeTitle": "x",
"teacherTeachClassModels": []
}
],
"leader": false,
"courseSignInOpen": true,
"courseSignInId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
],
"success": true,
"tracer": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"message": "xx",
"status": 200
}
可以看到,第二个课程的courseSignInOpen变为了true,
而且多了一个字段,"courseSignInId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
多的这个字段,就是现在正在进行的签到id
此时,软件主页面上,有签到的课程,就会在那个课程的右下角,弹出来一个“立即签到”按钮
4.点击立即签到按钮,进入签到页面,数据包的变化
我们点击立即签到按钮,会跳转到签到页面,get包会变为
GET https://www.eduplus.net/api/course/clock_in/此处为刚才获取到的签到id/student
响应
{
"code": 2000000,
"data": {
"createAt": 1775444232982,
"createBy": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"id": "此处为刚刚的签到id",
"courseId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"type": 1,
"codeDistance": "8469",
"startTime": 1775444233000,
"endTime": 1775444293000,
"timeLimit": 1,
"end": 0,
"startYYYYMMDD": "2026.04.06",
"startHHmm": "10:57",
"endYYYYMMDD": "2026.04.06",
"endHHmm": "10:58",
"remainingTime": 13,
"courseName": "testcourse"
},
"success": true,
"tracer": "1511699208d7fd90930e7c5394c4fa51",
"message": "OK",
"status": 200
}
如上可见,到了签到页面后,get包的响应里面包含了刚才的签到id,还包含了签到码!!!,也就是codeDistance字段, 还有remainingTime字段,也就是签到剩余秒数
也就是说,签到码是直接在前端进行校验的(我试了一下,后端也有校验),但签到码会直接显示在响应包中,
注意,此时如果发起的是普通签到,签到码codeDistance字段就是 200
5.进行签到
此时我们在软件中输入签到码,继续看数据包,软件会发送签到的post包
POST https://www.eduplus.net/api/course/clock_in/study?signInId=此处会替换为刚才的签到id&codeDistance=8469
注意看这个post包的请求头,就是签到id和,签到码字段拼接起来的,提交到服务端校验
如果为普通签到,则没有&codeDistance=8469这个字段,即
POST https://www.eduplus.net/api/course/clock_in/study?signInId=此处为普通签到的签到id
响应
{
"code": 2000000,
"data": true,
"success": true,
"tracer": "abde8daa7a2d63a585a4c54f8c05667b",
"message": "OK",
"status": 200
}
即为签到成功
至此已经把U+平台的签到全流程,都分析完了,其实也略有草台班子
然后我也用我正在学的crystal语言,当作练手把这个流程给完整复刻了一下,开源了:
https://github.com/RetiredGuitar64/ujia-signer-by-crystal
|
免费评分
-
查看全部评分
|