乐趣区

关于python:JS-逆向百例医保局-SM2SM4-国产加密算法实战

关注微信公众号:K 哥爬虫,QQ 交换群:808574309,继续分享爬虫进阶、JS/ 安卓逆向等技术干货!

申明

本文章中所有内容仅供学习交换,抓包内容、敏感网址、数据接口均已做脱敏解决,严禁用于商业用途和非法用处,否则由此产生的所有结果均与作者无关,若有侵权,请分割我立刻删除!

逆向指标

  • 指标:医疗保障局公共查问
  • 主页:aHR0cHM6Ly9mdXd1Lm5oc2EuZ292LmNuL25hdGlvbmFsSGFsbFN0LyMvc2VhcmNoL21lZGljYWw=
  • 接口:aHR0cHM6Ly9mdXd1Lm5oc2EuZ292LmNuL2VidXMvZnV3dS9hcGkvbnRobC9hcGkvZml4ZWQvcXVlcnlGaXhlZEhvc3BpdGFs
  • 逆向参数:Request Payload 的 encDatasignData、Request Headers 的 x-tif-noncex-tif-signature

逆向过程

抓包剖析

来到公共查问页面,点击翻页,就能够看到一个 POST 申请,Request Payload 的参数局部是加密的,次要是 appCode、encData 和 signData 参数,同样返回的数据也有这些参数,其加密解密办法是一样的,其中 encType 和 signType 别离为 SM4 和 SM2,所以大概率这是国密算法了,无关国密算法 K 哥后期文章有介绍:《爬虫逆向根底,意识 SM1-SM9、ZUC 国密算法》,此外申请头还有 x-tif-nonce 和 x-tif-signature 参数,如下图所示:

参数逆向

间接全局搜寻 encData 或 signData,搜寻后果仅在 app.1634197175801.js 有,非常明显,下面还有设置 header 的中央,所有参数都在这里,埋下断点,能够看到这里就是加密的中央,如下图所示:

这里的加密函数,次要都传入了一个 e 参数,咱们能够先看一下这个 e,外面的参数含意如下:

  • addr:医疗机构具体地址,默认空;
  • medinsLvCode:医疗机构等级代码,默认空;
  • medinsName:医疗机构名称,默认空;
  • medinsTypeCode:医疗机构类型代码,默认空;
  • pageNum:页数,默认 1;
  • pageSize:每页数据条数,默认 10;
  • regnCode:医疗机构所在地代码,默认 110000(北京市);
  • sprtEcFlag:临时不知其含意,默认空。

等级代码、类型代码、所在地代码,都是通过申请加密接口失去的,他们的加密和解密办法都一样,在最初的残缺代码里有分享,这里不再赘述。其余参数比方 appCode,是在 JS 里写死的。

咱们再察看一下整个 JS 文件,在头部能够看到 .call 语句,并且有 exports 关键字,很显著是一个 webpack 模式的写法。

咱们回到加密的中央,从上往下看,整个函数援用了很多其余模块,如果想整个扣下来,破费工夫必定是无比微小的,如果想间接拿下整个 JS,再将参数导出,这种暴力做法可是能够,然而整个 JS 有七万多行,运行效率必定是有所影响的,所以察看函数,将不必的函数去掉,有用的留下来,是比拟好的做法,察看 function d,第一行 var t = n("6c27").sha256,点进去来到 createOutputMethod 办法,这里整个是一个 SHA256 算法,从这个办法往下整个 copy 下来即可,如下图所示:

这里要留神的是,察看这个函数前面导出的 sha256 实际上是调用了 createMethod 这个办法,那么咱们 copy 下来的办法间接调用 createMethod 即可,即 var t = createMethod(),不须要这些 exports 了。

另外还有一些变量须要定义,整个 copy 下来的构造如下:

接着后面的持续往下看,还有一句 o = Object(i.a)(),同样点进去间接 copy 下来即可,这里没有什么须要留神的中央。

再往下看就来到了 e.data.signData = p(e),点进 function p,将整个函数 copy 下来,这时候你本地调试会发现没有任何谬误,实际上他这里应用了 try-catch 语句,捕捉到了异样之后就没有任何解决,能够本人加一句 console.log(e) 来输入异样,实际上他这里会在 o.doSignature、e.from 两个地位提醒未定义,同样的咱们能够点进去将函数扣进去,然而前面会遇到函数一直援用其余函数,为了不便,咱们能够将其写到 webpack 里,上面的 e.from 也是一样。

将模块写成 webpack 模式,在自执行办法里调用,而后定义全局变量来接管,再将原来的 o, e 换成全局变量即可,这里还须要留神的一个中央,那就是 o.doSignature 传入的 h,是一个定值,须要定义一下,不然前面解密是失败的。如下图所示:

这里扣 webpack 模块的时候也须要留神,不要把所有原办法里有的模块都扣进去,有些基本没用到,能够间接正文掉,这个过程是须要有急躁的,你如果全副扣,那将会是无穷无尽的,还不如间接应用整个 JS 文件,所有有用的模块如下(可能会多,但不会少):

接着原来的说,encData: v("SM4", e) 这里用到了 function v,v 外面又用到了 A、g 等函数,全副扣下来即可,同时还须要留神,后面所说的 e 在 A 函数里也用到了,同样须要换成咱们本人定义的全局变量,如下图所示:

到此加密用到的函数都扣完了,此时咱们能够写一个办法,对加密的过程进行封装,应用时只须要传入相似以下参数即可:

{
    "addr": "","regnCode":"110000","medinsName":"", 
    "sprtEcFlag": "","medinsLvCode":"", 
    "medinsTypeCode": "","pageNum": 1,"pageSize": 10
}

如下图所示 getEncryptedData 就是加密办法:

那么解密办法呢?很显著返回的数据是 encData,间接搜寻 encData 就只有三个后果,很容易找到就行 function y,同样的,这里要留神把 e.from 改成咱们自定义的 e_.Buffer.from,另外咱们也能够将 header 参数的生成办法也封装成一个函数,便于调用。

残缺代码

GitHub 关注 K 哥爬虫,继续分享爬虫相干代码!欢送 star!https://github.com/kgepachong/

以下只演示局部要害代码,不能间接运行! 残缺代码仓库地址:https://github.com/kgepachong…

JavaScript 加密要害代码架构

var sm2, sm4, e_;
!function (e) {var n = {},
        i = {app: 0},
        r = {app: 0};

    function o(t) {}

    o.e = function (e) {}
    o.m = e
    o.c = n
    o.d = function (e, t, n) {}
    o.r = function (e) {}
    o.n = function (e) {}
    o.o = function (e, t) {}

    sm2 = o('4d09')
    e_ = o('b639')
    sm4 = o('e04e')

}({"4d09": function (e, t, n) {},
    'f33e': function (e, t, n) {},
    "4d2d": function (e, t, n) {},
    'b381': function (e, t, n) {},
    // 此处省略 N 个模块
})

// 此处省略 N 个变量

var createOutputMethod = function (e, t) {},
    createMethod = function (e) {},
    nodeWrap = function (method, is224) {},
    createHmacOutputMethod = function (e, t) {},
    createHmacMethod = function (e) {};

function Sha256(e, t) {}

function HmacSha256(e, t, n) {}

// 此处省略 N 个办法

function i() {}

function p(t) {}

function m(e) {}

var c = {
    paasId: undefined,
    appCode: "T98HPCGN5ZVVQBS8LZQNOAEXVI9GYHKQ",
    version: "1.0.0",
    appSecret: "NMVFVILMKT13GEMD3BKPKCTBOQBPZR2P",
    publicKey: "BEKaw3Qtc31LG/hTPHFPlriKuAn/nzTWl8LiRxLw4iQiSUIyuglptFxNkdCiNXcXvkqTH79Rh/A2sEFU6hjeK3k=",
    privateKey: "AJxKNdmspMaPGj+onJNoQ0cgWk2E3CYFWKBJhpcJrAtC",
    publicKeyType: "base64",
    privateKeyType: "base64"
    },
    l = c.appCode,
    u = c.appSecret,
    f = c.publicKey,
    h = c.privateKey,
    t = createMethod(),
    // t = n("6c27").sha256,
    r = Math.ceil((new Date).getTime() / 1e3),
    o = i(),
    a = r + o + r;

function getEncryptedData(data) {var e = {"data": data}
    return e.data = {data: e.data || {}
        },
        e.data.appCode = c.appCode,
        e.data.version = c.version,
        e.data.encType = "SM4",
        e.data.signType = "SM2",
        e.data.timestamp = r,
        e.data.signData = p(e),
        e.data.data = {encData: v("SM4", e)
        },
        // e.data = JSON.stringify({
        //     data: e.data
        // }),
        e
}

function getDecryptedData(t) {if (!t)
        return null;
    var n = e_.Buffer.from(t.data.data.encData, "hex")
      , i = function(t, n) {var i = sm4.decrypt(n, t)
          , r = i[i.length - 1];
        return i = i.slice(0, i.length - r),
        e_.Buffer.from(i).toString("utf-8")
    }(g(l, u), n);
    return JSON.parse(i)
}

function getHeaders(){var headers = {}
    return headers["x-tif-paasid"] = c.paasId,
        headers["x-tif-signature"] = t(a),
        headers["x-tif-timestamp"] = r.toString(),
        headers["x-tif-nonce"] = o,
        headers["Accept"] = "application/json",
        headers["contentType"] = "application/x-www-form-urlencoded",
        headers
}

Python 获取数据要害代码

# ==================================
# --*-- coding: utf-8 --*--
# @Time    : 2021-11-03
# @Author  : 微信公众号:K 哥爬虫
# @FileName: nhsa.py
# @Software: PyCharm
# ==================================


import execjs
import requests


regn_code_url = "脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler"
lv_and_type_url = "脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler"
result_url = "脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler"
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"

with open('nhsa.js', 'r', encoding='utf-8') as f:
    nhsa_js = execjs.compile(f.read())


def get_headers():
    """获取 header 参数,每次申请扭转"""
    headers = nhsa_js.call("getHeaders")
    headers["User-Agent"] = UA
    headers["Content-Type"] = "application/json"
    headers["Host"] = "脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler"
    headers["Origin"] = "脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler"
    headers["Referer"] = "脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler"
    # print(headers)
    return headers


def get_regn_code():
    """获取城市代码,返回后果无加密"""
    payload = {"data": {"transferFlag": ""}}
    response = requests.post(url=regn_code_url, json=payload, headers=get_headers())
    print(response.text)


def get_medins_lv_or_type_code(key):
    """获取医疗机构等级 (LV) or 类型 (TYPE) 代码"""
    if key == "LV":
        payload = {"type": "MEDINSLV"}
    elif key == "TYPE":
        payload = {"type": "MEDINS_TYPE"}
    else:
        print("输出有误!")
        return
    encrypted_payload = nhsa_js.call("getEncryptedData", payload)
    encrypted_data = requests.post(url=lv_and_type_url, json=encrypted_payload, headers=get_headers()).json()
    decrypted_data = nhsa_js.call("getDecryptedData", encrypted_data)
    print(decrypted_data)


def get_result():
    addr = input("请输出医疗机构具体地址 ( 默认无):") or ""medins_lv_code = input(" 请输出医疗机构等级代码 ( 默认无): ") or""
    medins_name = input("请输出医疗机构名称 ( 默认无):") or ""medins_type_code = input(" 请输出医疗机构类型代码 ( 默认无): ") or""
    regn_code = input("请输出医疗机构所在地代码 ( 默认北京市):") or "110000"
    page_num = input("请输出要爬取的页数 ( 默认 1):") or 1

    for page in range(1, int(page_num)+1):
        payload = {
            "addr": addr,
            "medinsLvCode": medins_lv_code,
            "medinsName": medins_name,
            "medinsTypeCode": medins_type_code,
            "pageNum": page,
            "pageSize": 10,
            "regnCode": regn_code,
            "sprtEcFlag": ""
        }
        page += 1
        encrypted_payload = nhsa_js.call("getEncryptedData", payload)
        encrypted_data = requests.post(url=result_url, json=encrypted_payload, headers=get_headers()).json()
        decrypted_data = nhsa_js.call("getDecryptedData", encrypted_data)
        print(decrypted_data)


def main():
    # 获取城市代码
    # get_regn_code()
    # 获取医疗机构等级代码
    # get_medins_lv_or_type_code("LV")
    # 获取医疗机构类型代码
    # get_medins_lv_or_type_code("TYPE")
    # 获取搜寻后果
    get_result()


if __name__ == "__main__":
    main()

退出移动版