吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 666|回复: 7
上一主题 下一主题
收起左侧

[Android 原创] 某U+平台软件,签到全流程抓包分析

  [复制链接]
跳转到指定楼层
楼主
RetiredGuitar64 发表于 2026-5-5 21:32 回帖奖励
本帖最后由 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

免费评分

参与人数 1吾爱币 +1 收起 理由
aigc + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
stash 发表于 2026-5-7 14:05
这是什么?
推荐
aigc 发表于 2026-5-8 00:12
推荐
kingc138 发表于 2026-5-8 10:33
推荐
y5295276 发表于 2026-5-7 22:49
谢谢分享!!!
3#
 楼主| RetiredGuitar64 发表于 2026-5-7 14:55 |楼主

一个软件叫U+平台,类似于学习通,也是有各种其他功能,也有签到功能,
然后我把这个软件的签到功能全流程,抓包了一下,然后复刻了一下
7#
 楼主| RetiredGuitar64 发表于 2026-5-8 18:11 |楼主

aaa,这个软件就叫U+平台, 可能比较小众吧,我们学校在用这个,
你说的umu我查了一下,是别的公司的软件?
8#
 楼主| RetiredGuitar64 发表于 2026-5-8 18:11 |楼主
aigc 发表于 2026-5-8 00:12
佩服网页抓包分析需要很多耐心去尝试。

谢谢夸奖!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-9 12:01

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表