• 【JS逆向系列】某服务器平台sm系列算法分析


    【JS逆向系列】某服务器平台sm系列算法分析

    样品网址:aHR0cHM6Ly9mdXd1Lm5oc2EuZ292LmNuL25hdGlvbmFsSGFsbFN0LyMvc2VhcmNoL21lZGljYWw/Y29kZT05MDAwMCZmbGFnPWZhbHNlJmdiRmxhZz10cnVl

    打开网站后,需要的就是中间显示的数据

    在这里插入图片描述
    分析数据来自于哪个接口就跳过了,因为不重要,这里直接说结果

    在这里插入图片描述
    其中数据来自于【queryFixedHospital】这个接口,本次要分析的就是请求头中所有【x-tif】开头的参数,以及请求体中的【encData】和【signData】的生成算法

    然后随便搜索请求体中的【encData】或者【signData】,都可以直接定位到【app.1654997618917.js】这个js文件

    在这里插入图片描述
    这里可以直接找到所有参数的生成的地方,好像比较顺利,接着从上往下开始分析

    【paasid】是定值,【timestamp】是当前时间戳,【nonce】是8位随机值,这三个都非常容易看出来,而【signature】就是【s(g)】的结果,g就是时间戳拼接随机值再拼接时间戳,s就是sha256函数。请求头的参数非常容易,接下来看看请求体的参数。

    在signData函数内部下一个断点

    在这里插入图片描述
    其中比较重要的是【v(i)】的函数,这里生成了一段字符串来计算签名

    在这里插入图片描述
    这个函数和查询参数编码的功能类似,除了data参数,并且在最后拼接了一个定值字符串,这里用python进行简单的复现

    def v(e):
        t = []
        for n in e:
            if n == 'data':
                data = e[n].copy()
                for each in e[n]:
                    if not data[each]:
                        del data[each]
                    else:
                        data[each] = str(data[each])
                t.append(n + '=' + json.dumps(data, separators=(',', ':')))
            else:
                t.append(n + '=' + str(e[n]))
        t.append('key=NMVFVILMKT13GEMD3BKPKCTBOQBPZR2P')
        return '&'.join(t)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    获取到这段字符串后继续往下走,就进入到了【doSignature】函数

    在这里插入图片描述
    这里的第一个参数就是前面的字符串,第二个参数就是私钥

    在这里插入图片描述
    来到这里有一个判断,因为前面传入的hash参数恒为真,所以要先对签名的内容计算hash,计算hash用到的是【y】函数,网上查看来源

    在这里插入图片描述
    这里可以看到,【y】函数就是sm3算法。继续进入到【y】函数分析

    在这里插入图片描述
    可以看到这里更新了两次数据,相当于是计算这两个数据的hash,首先是r参数,这个是【getZ】函数的返回值。另一个是a参数,就是前面传入的字符串,这个前面已经分析了,那么继续进入到【getZ】函数查看

    在这里插入图片描述
    这里又是一个sm3算法,这次更新的参数就比较多了,包括一个【1234567812345678】的一个固定值,以及sm2算法的初始化ecc表,还有传入的私钥。这里已经可以发现,所有的参数其实都是定值(私钥一般不改的情况下),那么这里的返回值也是一个固定的值【fde9a74125ca149ca75f4c2ccdaeed3e7d0b4b8c0f2c9e35530b9fe9a3ba1233】,代码中可以写成固定值,这里只是说明【getZ】函数的算法,用python还原getZ函数

    def getZ(crypto):
        sign_data = bytes()
        n = '1234567812345678'.encode()
        sign_data += bytes([0, 8 * len(n)])
        sign_data += n
        sign_data += bytes.fromhex(crypto.ecc_table['a'])
        sign_data += bytes.fromhex(crypto.ecc_table['b'])
        sign_data += bytes.fromhex(crypto.ecc_table['g'])
        sign_data += bytes.fromhex(crypto.public_key[2:])
        return bytes.fromhex(sm3.sm3_hash(list(sign_data)))
        # 可以写成固定值
        # return bytes.fromhex('fde9a74125ca149ca75f4c2ccdaeed3e7d0b4b8c0f2c9e35530b9fe9a3ba1233')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    那么将【getZ】的返回值和前面的字符串一起计算sm3,就得到了消息hash

    在这里插入图片描述
    接着将消息hash计算sm2签名,就得到了【signData】了,最后分析【encData】参数。

    在这里插入图片描述
    【encData】这里固定传入了sm4,那么必定走sm4的分支,进入函数继续分析

    在这里插入图片描述
    这个函数比较短,主要是一个b函数,一个w函数,其中b函数是用来计算一个密钥

    在这里插入图片描述
    用python还原也比较简单

    def b(e, t):
        crypto = sm4.CryptSM4()
        crypto.set_key(e[:16].encode(), sm4.SM4_ENCRYPT)
        return crypto.crypt_ecb(t.encode()).hex().upper()[:16]
    
    • 1
    • 2
    • 3
    • 4

    拿到密钥后,直接使用sm4算法加密就可以得到【encData】了,现在所有参数都已经能够获取了,就可以发送请求了。

    在这里插入图片描述
    不过请求的响应也是加密的,幸好的是解密就一个sm4算法,key和前面的是一样的,那么直接解密就可以了,完整代码

    
    import requests_html
    import random
    import time
    import json
    import base64
    from Crypto.Hash import SHA256
    from gmssl import sm2, sm3, sm4, func
    
    publicKey = base64.b64decode("BEKaw3Qtc31LG/hTPHFPlriKuAn/nzTWl8LiRxLw4iQiSUIyuglptFxNkdCiNXcXvkqTH79Rh/A2sEFU6hjeK3k=".encode()).hex()
    privateKey = base64.b64decode("AJxKNdmspMaPGj+onJNoQ0cgWk2E3CYFWKBJhpcJrAtC".encode()).hex()
    appSecret = 'NMVFVILMKT13GEMD3BKPKCTBOQBPZR2P'
    appCode = 'T98HPCGN5ZVVQBS8LZQNOAEXVI9GYHKQ'
    
    def main():
        requests = requests_html.HTMLSession()
        key = b(appCode, appSecret).encode()
        s = str(int(time.time()))
        c = ''.join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZzbcdefghijklmnopqrstuvwxyz0123456789", k=8))
        headers = {
            'x-tif-paasid': 'undefined',
            '"x-tif-timestamp': s,
            'x-tif-nonce': c,
            '"x-tif-signature': SHA256.new((s + c + s).encode()).hexdigest(),
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
        }
        data = {
            'appCode': appCode,
            'data': {
                'addr': '',
                'medinsLvCode': '',
                'medinsName': '',
                'medinsTypeCode': '',
                'openElec': '',
                'pageNum': 1,
                'pageSize': 10,
                'regnCode': '110000'
            },
            'encType': 'SM4',
            'signType': 'SM2',
            'timestamp': int(s),
            'version': '1.0.0'
        }
        crypto = sm2.CryptSM2(privateKey, publicKey)
        data['signData'] = base64.b64encode(bytes.fromhex(crypto.sign(bytes.fromhex(sm3.sm3_hash(list(getZ(crypto) + v(data).encode()))), func.random_hex(crypto.para_len)))).decode()
        crypto = sm4.CryptSM4()
        crypto.set_key(key, sm4.SM4_ENCRYPT)
        data['data'] = {
            'encData': crypto.crypt_ecb(json.dumps(data['data']).encode()).hex().upper()
        }
        data = {
            'data': data
        }
        response = requests.post('https://fuwu.nhsa.gov.cn/ebus/fuwu/api/nthl/api/CommQuery/queryFixedHospital', json=data, headers=headers).json()
        crypto = sm4.CryptSM4()
        crypto.set_key(key, sm4.SM4_DECRYPT)
        data = json.loads(crypto.crypt_ecb(bytes.fromhex(response['data']['data']['encData'])).decode())
        print(data)
    
    
    def getZ(crypto):
        sign_data = bytes()
        n = '1234567812345678'.encode()
        sign_data += bytes([0, 8 * len(n)])
        sign_data += n
        sign_data += bytes.fromhex(crypto.ecc_table['a'])
        sign_data += bytes.fromhex(crypto.ecc_table['b'])
        sign_data += bytes.fromhex(crypto.ecc_table['g'])
        sign_data += bytes.fromhex(crypto.public_key[2:])
        return bytes.fromhex(sm3.sm3_hash(list(sign_data)))
        # 可以写成固定值
        # return bytes.fromhex('fde9a74125ca149ca75f4c2ccdaeed3e7d0b4b8c0f2c9e35530b9fe9a3ba1233')
    
    
    def v(e):
        t = []
        for n in e:
            if n == 'data':
                data = e[n].copy()
                for each in e[n]:
                    if not data[each]:
                        del data[each]
                    else:
                        data[each] = str(data[each])
                t.append(n + '=' + json.dumps(data, separators=(',', ':')))
            else:
                t.append(n + '=' + str(e[n]))
        t.append('key=' + appSecret)
        return '&'.join(t)
    
    
    def b(e, t):
        crypto = sm4.CryptSM4()
        crypto.set_key(e[:16].encode(), sm4.SM4_ENCRYPT)
        return crypto.crypt_ecb(t.encode()).hex().upper()[:16]
    
    
    if __name__ == '__main__':
        main()
    
    
    • 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
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    在这里插入图片描述
    成功获取到结果,完成!!!

    更多内容可以加入我的星球学习

    在这里插入图片描述

  • 相关阅读:
    淘宝API接口,交易,退款退货,物流数据获取,erp系统对接交易订单
    CATT的应用
    稀疏-平坦矩阵信号的重构条件
    从苏宁电器到卡巴斯基(第二部)第33篇:我当高校教师的这几年 IX
    快鲸智慧楼宇:提供物业管理综合方案,实现物业收益最大化
    Java NIO Selector选择器源码分析
    PIMPL技巧
    R语言求取大量遥感影像的平均值、标准差:raster库
    kafka connector
    【mysql】ssl_choose_client_version:unsupported protocol
  • 原文地址:https://blog.csdn.net/zjq592767809/article/details/125469200