智慧教育公共服务平台登录分析

查看 137|回复 10
作者:pwp   
1、打开网站https://basic.smartedu.cn/,F12弹出开发者工具:


image.png (244.71 KB, 下载次数: 0)
下载附件
2024-2-3 22:02 上传

2、点击页面上的登录,跳转到登录页面https://auth.smartedu.cn/uias/login ,发现有2个重要请求:


image.png (233.14 KB, 下载次数: 0)
下载附件
2024-2-3 22:03 上传

经过分析,xstudy_web并没有什么实际意义,只需分析sessions:
URL:https://uc-gateway.ykt.eduyun.cn/v1.1/sessions
请求标头有用参数:Sdp-App-Id:e5649925-441d-4a53-b525-51a2f1c4e0a8
请求载荷:device_id: "VwWin10/Chrome114/827420c6-7a60-4b13-a540-58f8260c5c23"
而这两个参数,经过熬夜分析,发现也是固定的,实际上就是浏览器参数,我用了几台不同电脑测试,发现360浏览器的Sdp-App-Id就是上面这个值,一点不变,
如果想取更精准值的同学可以去跟踪,本菜鸡就不跟踪啦
赶紧用python模拟:
[Python] 纯文本查看 复制代码    def get_session(self):
        url = 'https://uc-gateway.ykt.eduyun.cn/v1.1/sessions'
        headers = {
            'Accept': 'application/json',
            'Accept-Encoding': 'gzip, deflate, br',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'Content-Type': 'application/json',
            'Host': 'uc-gateway.ykt.eduyun.cn',
            'Origin': 'https://auth.smartedu.cn',
            'Pragma': 'no-cache',
            'Referer': 'https://auth.smartedu.cn/uias/login',
            'SDP-APP-ID': 'e5649925-441d-4a53-b525-51a2f1c4e0a8',
            'sec-ch-ua': '"Not-A.Brand";v="24", "Chromium";v="14"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Windows"',
            'Sec-Fetch-Dest': 'empty',
            'Sec-Fetch-Mode': 'cors',
            'Sec-Fetch-Site': 'cross-site',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.95 Safari/537.36'
        }
        payload = {
            'device_id': 'VwWin10/Chrome114/827420c6-7a60-4b13-a540-58f8260c5c23'
        }
        response = self.session.post(url, headers=headers, json=payload)
        print('状态代码:', response.status_code)
        print('返回内容:', response.json())
运行,嗯,效果港港的:


image.png (34.7 KB, 下载次数: 0)
下载附件
2024-2-3 22:04 上传

3、输入测试账号密码,拖动滑块,勾选同意协议,点击登录,发现一个query链接:
(嗯,为啥不分析滑块验证,滑块验证竟然是假的,程序猿也够懒,或故意给我们降低学习逆向难度2333!


image.png (190.2 KB, 下载次数: 0)
下载附件
2024-2-3 22:34 上传

分析得知,请求头没啥好玩的:


image.png (139.36 KB, 下载次数: 0)
下载附件
2024-2-3 22:35 上传

关键是请求参数加密了:


image.png (141 KB, 下载次数: 0)
下载附件
2024-2-3 22:36 上传

尝试搜索链接,点进去发现是queryAccountPublicInfo函数在请求,参数加密了:


image.png (172.46 KB, 下载次数: 0)
下载附件
2024-2-3 22:39 上传

那就下断点看看参数在哪里加密的,跟踪了很久很久,无意间在一次偶然运行中,出现一个奇怪的链接:


image.png (164.94 KB, 下载次数: 0)
下载附件
2024-2-3 22:41 上传

一顿操作猛如虎,仔细一看原地杵。
天菩萨,白白浪费时间了,都怪自己学艺不精啊,原来真正的登录地址是 https://sso.basic.smartedu.cn/v1.1/tokens?$proxy=proxyhttp&bodys=%7B......,开始不注意看全部请求,大意了
先去吃个炒饭,稍后完成。
吃饭回来,苏不知,困难才刚刚开始。。。
搜索
v1.1/tokens
发现有14个地方,哪个才是登录需要的呢?


image.png (95.24 KB, 下载次数: 0)
下载附件
2024-2-3 22:44 上传

最笨的办法,给全部打上断点吧,也还好只有14个,要是有10000个,这可怎么办啊
不行,得换个思路,既然这个网址这么长,肯定还有别的东西可搜索,先解码看看这长串URL是神马:


image.png (92.44 KB, 下载次数: 0)
下载附件
2024-2-3 22:45 上传

解码出来的URL:


image.png (190.16 KB, 下载次数: 0)
下载附件
2024-2-3 22:46 上传

https://sso.basic.smartedu.cn/v1.1/tokens?$proxy=proxyhttp&bodys={"$headers":{"Accept":"application/json","Content-Type":"application/json","SDP-APP-ID":"e5649925-441d-4a53-b525-51a2f1c4e0a8","UC-COLL":"e5649925-441d-4a53-b525-51a2f1c4e0a8/1.0(Win10;Chrome114;1536x864;FwWin10/Chrome114/a12bff20-b7d5-4eb8-aad8-6dd787fa6bf7;)","Host":"sso.basic.smartedu.cn"},"$body":{"session_id":"55d72157-d104-491e-9aa7-0658389f9d96","login_name":"iCDmD5S7K8rXp/ey6VMMTw==","password":"57tzlTCBcM0uUQxfnwg5n+OQMeBj7zDIDqV9VzPfKZEe3PEiRA/gsQ==","zhitong_password":"9IssSbCut+7PDHTz3MGLjaHupay6OVkaKcfhpzds5v0e3PEiRA/gsQ=="},"$method":"post"}&callback=nd_uc_sdk_17069708211320
找到一个特别的参数:zhitong_password,搜索进去,尽然所有加密的参数都在这里,
下断点运行到此处,众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。所有参数得来全部费工夫,包括函数名都是login:


image.png (216.67 KB, 下载次数: 0)
下载附件
2024-2-3 22:58 上传

经过观察,加密函数也就两个:
encryptByDes$1和ndMd5s,扒开她神秘的罩衣,圆形毕露:
[JavaScript] 纯文本查看 复制代码   function encryptByDes$1(e, t) {
        return e += "",
        cryptoJS.DES.encrypt(e, cryptoJS.enc.Utf8.parse(t), {
            mode: cryptoJS.mode.ECB,
            padding: cryptoJS.pad.Pkcs7
        }).toString()
    }
[JavaScript] 纯文本查看 复制代码 var ndMd5s = function(e, t) {
        return MD5(e + (t || ""))
    }
这个ndmd5,好像尽然是1年多前我学习的加盐md5:
这个superMd5三十二位加密如何用python实现
https://www.52pojie.cn/thread-1735850-1-1.html
(出处: 吾爱破解论坛)
这就简单多了,纸上得来终觉浅,绝知此事要躬行,都用python摸你出来:
[Python] 纯文本查看 复制代码    def AesEcbEncrypt(self,data, key):
        key = key  # 加密key,加密方式ECB秘钥必须是八位字节
        mode = pyDes.ECB  # 加密方式 默认是ECB,也可以不填写
        IV = "00000000"  # 偏移量,加密方式不是ECB的时候加密key字段必须是16位字节,秘钥不够用0补充
        k = pyDes.des(key, mode, IV=IV, pad=None, padmode=pyDes.PAD_PKCS5)  # 传入秘钥,加密方式
        d = k.encrypt(data)  # 加密数据
        base = str(base64.b64encode(d), encoding="utf-8")  # 指定输出格式为base64
        # print(base)
        return base
[Python] 纯文本查看 复制代码class MD5(object):
    # 初始化密文
    def __init__(self, message):
        self.message = message
        self.ciphertext = ""
        self.A = 0x67452301
        self.B = 0xEFCDAB89
        self.C = 0x98BADCFE
        self.D = 0x10325476
        self.init_A = 0x67452301
        self.init_B = 0xEFCDAB89
        self.init_C = 0x98BADCFE
        self.init_D = 0x10325476
        '''
        self.A = 0x01234567
        self.B = 0x89ABCDEF
        self.C = 0xFEDCBA98
        self.D = 0x76543210
         '''
        #设置常数表T
        self.T = [0xD76AA478,0xE8C7B756,0x242070DB,0xC1BDCEEE,0xF57C0FAF,0x4787C62A,0xA8304613,0xFD469501,
                    0x698098D8,0x8B44F7AF,0xFFFF5BB1,0x895CD7BE,0x6B901122,0xFD987193,0xA679438E,0x49B40821,
                    0xF61E2562,0xC040B340,0x265E5A51,0xE9B6C7AA,0xD62F105D,0x02441453,0xD8A1E681,0xE7D3FBC8,
                    0x21E1CDE6,0xC33707D6,0xF4D50D87,0x455A14ED,0xA9E3E905,0xFCEFA3F8,0x676F02D9,0x8D2A4C8A,
                    0xFFFA3942,0x8771F681,0x6D9D6122,0xFDE5380C,0xA4BEEA44,0x4BDECFA9,0xF6BB4B60,0xBEBFBC70,
                    0x289B7EC6,0xEAA127FA,0xD4EF3085,0x04881D05,0xD9D4D039,0xE6DB99E5,0x1FA27CF8,0xC4AC5665,
                    0xF4292244,0x432AFF97,0xAB9423A7,0xFC93A039,0x655B59C3,0x8F0CCC92,0xFFEFF47D,0x85845DD1,
                    0x6FA87E4F,0xFE2CE6E0,0xA3014314,0x4E0811A1,0xF7537E82,0xBD3AF235,0x2AD7D2BB,0xEB86D391]
        #循环左移位数
        self.s = [7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,
                    5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,
                    4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,
                    6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21]
        self.m = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
                    1,6,11,0,5,10,15,4,9,14,3,8,13,2,7,12,
                    5,8,11,14,1,4,7,10,13,0,3,6,9,12,15,2,
                    0,7,14,5,12,3,10,1,8,15,6,13,4,11,2,9]
    # 附加填充位
    def fill_text(self):
        for i in range(len(self.message)):
            c = int2bin(ord(self.message), 8)
            self.ciphertext += c
        if (len(self.ciphertext)%512 != 448):
            if ((len(self.ciphertext)+1)%512 != 448):
                self.ciphertext += '1'
            while (len(self.ciphertext)%512 != 448):
                self.ciphertext += '0'
        length = len(self.message)*8
        if (length > (32 - amount))) & 0xFFFFFFFF
    def change_pos(self):
        a = self.A
        b = self.B
        c = self.C
        d = self.D
        self.A = d
        self.B = a
        self.C = b
        self.D = c
    def FF(self, mj, s, ti):
        mj = int(mj, 2)
        temp = self.F(self.B, self.C, self.D) + self.A + mj + ti
        temp = self.circuit_shift(temp, s)
        self.A = (self.B + temp)%pow(2, 32)
        self.change_pos()
    def GG(self, mj, s, ti):
        mj = int(mj, 2)
        temp = self.G(self.B, self.C, self.D) + self.A + mj + ti
        temp = self.circuit_shift(temp, s)
        self.A = (self.B + temp)%pow(2, 32)
        self.change_pos()
    def HH(self, mj, s, ti):
        mj = int(mj, 2)
        temp = self.H(self.B, self.C, self.D) + self.A + mj + ti
        temp = self.circuit_shift(temp, s)
        self.A = (self.B + temp)%pow(2, 32)
        self.change_pos()
    def II(self, mj, s, ti):
        mj = int(mj, 2)
        temp = self.I(self.B, self.C, self.D) + self.A + mj + ti
        temp = self.circuit_shift(temp, s)
        self.A = (self.B + temp)%pow(2, 32)
        self.change_pos()
    def F(self, X, Y, Z):
        return (X & Y) | ((~X) & Z)
    def G(self, X, Y, Z):
        return (X & Z) | (Y & (~Z))
    def H(self, X, Y, Z):
        return X ^ Y ^ Z
    def I(self, X, Y, Z):
        return Y ^ (X | (~Z))
    def group_processing(self):
        M = []
        for i in range(0, 512, 32):
            num = ""
            # 获取每一段的标准十六进制形式
            for j in range(0, len(self.ciphertext[i:i+32]), 4):
                temp = self.ciphertext[i:i+32][j:j + 4]
                temp = hex(int(temp, 2))
                num += temp[2]
            # 对十六进制进行小端排序
            num_tmp = ""
            for j in range(8, 0, -2):
                temp = num[j-2:j]
                num_tmp += temp
            num = ""
            for i in range(len(num_tmp)):
                num += int2bin(int(num_tmp, 16), 4)
            M.append(num)
        #print(M)
        for j in range(0, 16, 4):
            self.FF(M[self.m[j]], self.s[j], self.T[j])
            self.FF(M[self.m[j+1]], self.s[j+1], self.T[j+1])
            self.FF(M[self.m[j+2]], self.s[j+2], self.T[j+2])
            self.FF(M[self.m[j+3]], self.s[j+3], self.T[j+3])
        for j in range(0, 16, 4):
            self.GG(M[self.m[16+j]], self.s[16+j], self.T[16+j])
            self.GG(M[self.m[16+j+1]], self.s[16+j+1], self.T[16+j+1])
            self.GG(M[self.m[16+j+2]], self.s[16+j+2], self.T[16+j+2])
            self.GG(M[self.m[16+j+3]], self.s[16+j+3], self.T[16+j+3])
        for j in range(0, 16, 4):
            self.HH(M[self.m[32+j]], self.s[32+j], self.T[32+j])
            self.HH(M[self.m[32+j+1]], self.s[32+j+1], self.T[32+j+1])
            self.HH(M[self.m[32+j+2]], self.s[32+j+2], self.T[32+j+2])
            self.HH(M[self.m[32+j+3]], self.s[32+j+3], self.T[32+j+3])
        for j in range(0, 16, 4):
            self.II(M[self.m[48+j]], self.s[48+j], self.T[48+j])
            self.II(M[self.m[48+j+1]], self.s[48+j+1], self.T[48+j+1])
            self.II(M[self.m[48+j+2]], self.s[48+j+2], self.T[48+j+2])
            self.II(M[self.m[48+j+3]], self.s[48+j+3], self.T[48+j+3])
        self.A = (self.A+self.init_A)%pow(2, 32)
        self.B = (self.B+self.init_B)%pow(2, 32)
        self.C = (self.C+self.init_C)%pow(2, 32)
        self.D = (self.D+self.init_D)%pow(2, 32)
        """
        print("A:{}".format(hex(self.A)))
        print("B:{}".format(hex(self.B)))
        print("C:{}".format(hex(self.C)))
        print("D:{}".format(hex(self.D)))
        """
        answer = ""
        for register in [self.A, self.B, self.C, self.D]:
            if len(hex(register))!=10:
                str1 = list(hex(register))
                str1.insert(2,'0')
                str2 = ''.join(str1)
                register = str2[2:]
            else:
                register = hex(register)[2:]
            for i in range(8, 0, -2):
                answer += str(register[i-2:i])
        return answer
最后,模拟登录:
[Python] 纯文本查看 复制代码    def login(self,session_id,session_key):
        '''
        return this.getSession().then(function(t) {
                var l = t.session_id
                  , p = t.session_key
                  , h = Object(K.d)(n, p)
                  , m = Object(K.d)(j()(r, q.h), p)
                  , g = {
                    session_id: l,
                    login_name_type: o,
                    login_name: h,
                    account_type: i,
                    country_code: s,
                    org_code: u,
                    password: m,
                    identify_code: a
                };
        :return:
        '''
        login_name = self.AesEcbEncrypt(self.username,session_key)
        q_h = "£¬¡£fdjf,jkgfkl"
        password = self.password+q_h
        md532 = MD5(password)
        md532.fill_text()
        result = md532.group_processing()
        print("32位小写MD5加密:{}".format(result))
        password = self.AesEcbEncrypt(result, session_key)
        url = 'https://uc-gateway.ykt.eduyun.cn/v1.1/tokens'
        data = {
            "session_id":session_id,
            "login_name":login_name,
            "password":password
        }
        headers = {
            "Accept": "application/json",
            "Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": "zh-CN,zh;q=0.9",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Content-Length": "163",
            "Content-Type": "application/json",
            "Host": "uc-gateway.ykt.eduyun.cn",
            "Pragma": "no-cache",
            "SDP-APP-ID": "e5649925-441d-4a53-b525-51a2f1c4e0a8",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "cross-site",
            "UC-COLL": "e5649925-441d-4a53-b525-51a2f1c4e0a8/1.0(Win10;Chrome86;1920x1080;YwWin10/Chrome86/5707e80d-dc3f-4d10-b7e6-6af039482f8c;)",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
            "x-sdp-fp": "180e4fa6022cfb5f31a2e9de45f81c83"
        }
        print(data)
        r = self.session.post(url,json=data,headers=headers,verify=False)
        print(r.json())
        return r.json()
运行,成功:


image.png (67.57 KB, 下载次数: 0)
下载附件
2024-2-3 23:03 上传

下载次数, 下载附件

rcg1997   

这个登录有什么作用吗?单纯好奇
pkingson   

系统升级了,Message:Mac签名错误,如何处理
{"host_id":"x-study-record-api.ykt.eduyun.cn","request_id":"x-study-record-api-172.22.72.30^1725017935601^6236560","server_time":"2024-09-01T19:47:16.380+0800","code":"UC/INVALID_MAC","message":"您的登录状态已失效,请重新登录。如有疑问,请联系相关管理员。","force_update":null,"detail":"Message:Mac签名错误 \r\n SourceAppName:x-study-record-api \r\n ","cause":null}
Pwaerm   

这站最近有点火呀
pwp
OP
  


Pwaerm 发表于 2024-2-3 15:08
这站最近有点火呀

全国中小学教师在上面寒假研修啊。大佬也是牛逼,大佬的代码直接在浏览器运行,无需造轮子
aaron505   

谢谢楼主分享经验教学
sxpdwzs   

直接做一个成品吧
fireshark   

放假也要学习,不进步就是退步
pwp
OP
  


sxpdwzs 发表于 2024-2-3 15:29
直接做一个成品吧

仅供学习交流,禁止非法用途
pwp
OP
  


rcg1997 发表于 2024-2-3 16:15
这个登录有什么作用吗?单纯好奇

自动化办公
您需要登录后才可以回帖 登录 | 立即注册