安全矩阵

 找回密码
 立即注册
搜索
查看: 1778|回复: 0

某服务器平台sm系列算法分析

[复制链接]

260

主题

275

帖子

1065

积分

金牌会员

Rank: 6Rank: 6

积分
1065
发表于 2022-7-3 22:59:07 | 显示全部楼层 |阅读模式
某服务器平台sm系列算法分析

原文链接:某服务器平台sm系列算法分析
漁滒 web安全工具库 2022-07-03 00:00 发表于河南

样品网址: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进行简单的复现
  1. def v(e):    t = []
  2.     for n in e:
  3.         if n == 'data':
  4.             data = e[n].copy()
  5.             for each in e[n]:
  6.                 if not data[each]:
  7.                     del data[each]
  8.                 else:
  9.                     data[each] = str(data[each])
  10.             t.append(n + '=' + json.dumps(data, separators=(',', ':')))
  11.         else:
  12.             t.append(n + '=' + str(e[n]))
  13.     t.append('key=NMVFVILMKT13GEMD3BKPKCTBOQBPZR2P')
  14.     return '&'.join(t)
复制代码
获取到这段字符串后继续往下走,就进入到了【doSignature】函数
编辑

这里的第一个参数就是前面的字符串,第二个参数就是私钥
编辑

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

这里可以看到,【y】函数就是sm3算法。继续进入到【y】函数分析
编辑

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

这里又是一个sm3算法,这次更新的参数就比较多了,包括一个【1234567812345678】的一个固定值,以及sm2算法的初始化ecc表,还有传入的私钥。这里已经可以发现,所有的参数其实都是定值(私钥一般不改的情况下),那么这里的返回值也是一个固定的值【fde9a74125ca149ca75f4c2ccdaeed3e7d0b4b8c0f2c9e35530b9fe9a3ba1233】,代码中可以写成固定值,这里只是说明【getZ】函数的算法,用python还原getZ函数
  1. def getZ(crypto):    sign_data = bytes()
  2.     n = '1234567812345678'.encode()
  3.     sign_data += bytes([0, 8 * len(n)])
  4.     sign_data += n
  5.     sign_data += bytes.fromhex(crypto.ecc_table['a'])
  6.     sign_data += bytes.fromhex(crypto.ecc_table['b'])
  7.     sign_data += bytes.fromhex(crypto.ecc_table['g'])
  8.     sign_data += bytes.fromhex(crypto.public_key[2:])
  9.     return bytes.fromhex(sm3.sm3_hash(list(sign_data)))
  10.     # 可以写成固定值    # return bytes.fromhex('fde9a74125ca149ca75f4c2ccdaeed3e7d0b4b8c0f2c9e35530b9fe9a3ba1233')
复制代码
那么将【getZ】的返回值和前面的字符串一起计算sm3,就得到了消息hash
编辑

接着将消息hash计算sm2签名,就得到了【signData】了,最后分析【encData】参数。
编辑

【encData】这里固定传入了sm4,那么必定走sm4的分支,进入函数继续分析
编辑

这个函数比较短,主要是一个b函数,一个w函数,其中b函数是用来计算一个密钥
编辑

用python还原也比较简单
  1. def b(e, t):    crypto = sm4.CryptSM4()
  2.     crypto.set_key(e[:16].encode(), sm4.SM4_ENCRYPT)
  3.     return crypto.crypt_ecb(t.encode()).hex().upper()[:16]
复制代码
拿到密钥后,直接使用sm4算法加密就可以得到【encData】了,现在所有参数都已经能够获取了,就可以发送请求了。
编辑

不过请求的响应也是加密的,幸好的是解密就一个sm4算法,key和前面的是一样的,那么直接解密就可以了,完整代码
  1. import requests_htmlimport randomimport timeimport jsonimport base64from Crypto.Hash import SHA256from gmssl import sm2, sm3, sm4, func

  2. publicKey = base64.b64decode("BEKaw3Qtc31LG/hTPHFPlriKuAn/nzTWl8LiRxLw4iQiSUIyuglptFxNkdCiNXcXvkqTH79Rh/A2sEFU6hjeK3k=".encode()).hex()
  3. privateKey = base64.b64decode("AJxKNdmspMaPGj+onJNoQ0cgWk2E3CYFWKBJhpcJrAtC".encode()).hex()
  4. appSecret = 'NMVFVILMKT13GEMD3BKPKCTBOQBPZR2P'appCode = 'T98HPCGN5ZVVQBS8LZQNOAEXVI9GYHKQ'def main():    requests = requests_html.HTMLSession()
  5.     key = b(appCode, appSecret).encode()
  6.     s = str(int(time.time()))
  7.     c = ''.join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZzbcdefghijklmnopqrstuvwxyz0123456789", k=8))
  8.     headers = {
  9.         'x-tif-paasid': 'undefined',
  10.         '"x-tif-timestamp': s,
  11.         'x-tif-nonce': c,
  12.         '"x-tif-signature': SHA256.new((s + c + s).encode()).hexdigest(),
  13.         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'    }
  14.     data = {
  15.         'appCode': appCode,
  16.         'data': {
  17.             'addr': '',
  18.             'medinsLvCode': '',
  19.             'medinsName': '',
  20.             'medinsTypeCode': '',
  21.             'openElec': '',
  22.             'pageNum': 1,
  23.             'pageSize': 10,
  24.             'regnCode': '110000'        },
  25.         'encType': 'SM4',
  26.         'signType': 'SM2',
  27.         'timestamp': int(s),
  28.         'version': '1.0.0'    }
  29.     crypto = sm2.CryptSM2(privateKey, publicKey)
  30.     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()
  31.     crypto = sm4.CryptSM4()
  32.     crypto.set_key(key, sm4.SM4_ENCRYPT)
  33.     data['data'] = {
  34.         'encData': crypto.crypt_ecb(json.dumps(data['data']).encode()).hex().upper()
  35.     }
  36.     data = {
  37.         'data': data
  38.     }
  39.     response = requests.post('https://fuwu.nhsa.gov.cn/ebus/fuwu/api/nthl/api/CommQuery/queryFixedHospital', json=data, headers=headers).json()
  40.     crypto = sm4.CryptSM4()
  41.     crypto.set_key(key, sm4.SM4_DECRYPT)
  42.     data = json.loads(crypto.crypt_ecb(bytes.fromhex(response['data']['data']['encData'])).decode())
  43.     print(data)def getZ(crypto):    sign_data = bytes()
  44.     n = '1234567812345678'.encode()
  45.     sign_data += bytes([0, 8 * len(n)])
  46.     sign_data += n
  47.     sign_data += bytes.fromhex(crypto.ecc_table['a'])
  48.     sign_data += bytes.fromhex(crypto.ecc_table['b'])
  49.     sign_data += bytes.fromhex(crypto.ecc_table['g'])
  50.     sign_data += bytes.fromhex(crypto.public_key[2:])
  51.     return bytes.fromhex(sm3.sm3_hash(list(sign_data)))
  52.     # 可以写成固定值    # return bytes.fromhex('fde9a74125ca149ca75f4c2ccdaeed3e7d0b4b8c0f2c9e35530b9fe9a3ba1233')def v(e):    t = []
  53.     for n in e:
  54.         if n == 'data':
  55.             data = e[n].copy()
  56.             for each in e[n]:
  57.                 if not data[each]:
  58.                     del data[each]
  59.                 else:
  60.                     data[each] = str(data[each])
  61.             t.append(n + '=' + json.dumps(data, separators=(',', ':')))
  62.         else:
  63.             t.append(n + '=' + str(e[n]))
  64.     t.append('key=' + appSecret)
  65.     return '&'.join(t)def b(e, t):    crypto = sm4.CryptSM4()
  66.     crypto.set_key(e[:16].encode(), sm4.SM4_ENCRYPT)
  67.     return crypto.crypt_ecb(t.encode()).hex().upper()[:16]if __name__ == '__main__':
  68.     main()
复制代码


成功获取到结果,完成!!!

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-20 14:11 , Processed in 0.017890 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

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