当前位置: 首页 > 后端技术 > Python

【JS逆向工程100例】医保局SM2+SM4国内加密算法实战

时间:2023-03-26 16:03:26 Python

关注微信公众号:爬虫K哥,QQ交流群:808574309,持续分享爬虫进阶、JS等技术干货/安卓逆向工程!声明本文所有内容仅供学习交流。抓拍内容、敏感网址、数据接口均已脱敏处理,严禁用于商业或非法用途。否则,由此产生的一切后果与作者无关。Infringement,pleasecontactmetodeleteimmediately!逆向目标目标:医疗保障局公共查询主页:aHR0cHM6Ly9mdXd1Lm5oc2EuZ292LmNuL25hdGlvbmFsSGFsbFN0LyMvc2VhcmNoL21lZGljYWw=接口:aHR0cHM6Ly9mdXd1Lm5oc2EuZ292LmNuL2VidXMvZnV3dS9hcGkvbnRobC9hcGkvZml4ZWQvcXVlcnlGaXhlZEhvc3BpdGFs逆向参数:RequestPayload的encData和signData、RequestHeaders的x-tif-nonce和x-tif-signature逆向通过程序抓包分析来到公共查询页面,点击翻页,可以看到一个POST请求,RequestPayload的参数是加密的,主要是appCode、encData和signData参数,返回的数据也有这些参数,加密和解密方法一样,其中encType和signTypes分别是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文件。可以看到header中的.call语句和exports关键字,很明显是用webpack的形式写的。让我们回到加密的地方。从上往下看,整个函数引用了很多其他模块。如果要推导整个函数,将花费大量的时间。如果想直接把整个JS拿下来,然后导出参数,这种Bruteforce是可以的,但是整个JS有7万多行,运行效率肯定会受到影响。因此,最好观察功能,去掉不用的功能,保留有用的。观察函数d,第一行vart=n("6c27").sha256,点进去,来到createOutputMethod方法,这里是一个SHA256算法,直接从这个方法copy下来,如下图:这里需要注意的是,观察这个函数之后导出的sha256其实调用了方法createMethod,所以我们复制的方法可以直接调用createMethod,即vart=createMethod(),而这些导出是不需要的。此外,还有一些变量需要定义。整个拷贝的结构如下:然后继续往下看,有一句o=Object(i.a)(),直接点进去直接拷贝。这里没有什么需要注意的地方。再往下看,会来到e.data.signData=p(e),点进函数p,复制整个函数。这时候你会发现在本地调试没有报错了。其实他这里用的是try-catch语句,捕获到异常后,没有任何处理。您可以添加一个console.log(e)来输出异常。其实在o.doSignature和e.from中会提示undefined。同样的,我们可以点击Deductthefunction,但是后面会遇到不断引用其他函数的函数。为了方便,我们可以写到webpack里面,下面的e.from也是一样的。以webpack的形式编写模块,在自执行方法中调用,然后定义全局变量接收,然后用全局变量替换原来的o,e。这里还有一点要注意,就是o.doSignatureh的输入是固定值,需要定义,否则后面的解密会失败。如下图所示:这里扣除webpack模块的时候也需要注意。不要把原方法中的所有模块都扣掉。其中一些根本没有使用。您可以直接将它们注释掉。这个过程需要耐心。如果你Buckle,会没完没了,不如直接用整个JS文件,所有有用的模块如下(可能会多,但不会少):)这里用到了函数v,v中用到了A、g等函数,都可以推导出来。同时需要注意的是,A函数中也使用了上面提到的e,同样需要换成自己定义的全局变量,如下图所示:至此,加密中用到的所有函数都已经扣完了。至此,我们就可以写一个方法来封装加密过程了。使用时我们只需要传入类似如下的参数:{"addr":"","regnCode":"110000","medinsName":"","sprtEcFlag":"","medinsLvCode":"","medinsTypeCode":"","pageNum":1,"pageSize":10}如下图,getEncryptedData是加密方式:那解密方式呢?显然返回的数据是encData,直接搜索encData只有三个结果,很容易找到函数y。同样的,我们要注意把e.from改成我们自定义的e_.Buffer.from,我们也可以把header参数的生成方法也封装成一个函数,方便调用。完整代码GitHub关注K哥的爬虫,持续分享爬虫相关代码!欢迎加星!https://github.com/kgepachong/下面只是演示了部分关键代码,不能直接运行!完整代码仓库地址:https://github.com/kgepachong...JavaScript加密密钥代码结构varsm2,sm4,e_;!function(e){varn={},i={app:0},r={应用程序:0};函数o(t){}o.e=函数(e){}o.m=eo.c=no.d=函数(e,t,n){}o.r=函数(e){}o.n=函数(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个变量varcreateOutputMethod=function(e,t){},createMethod=function(e){},nodeWrap=function(method,is224){},createHmacOutputMethod=function(e,t){},createHmacMethod=function(e){};functionSha256(e,t){}functionHmacSha256(e,t,n){}//这里省略N个方法functioni(){}functionp(t){}函数m(e){}varc={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((newDate).getTime()/1e3),o=i(),a=r+o+r;functiongetEncryptedData(data){vare={“数据”:数据}返回e.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({//数据:e.data//}),e}functiongetDecryptedData(t){if(!t)返回null;varn=e_.Buffer.from(t.data.data.encData,"hex"),i=function(t,n){vari=sm4.decrypt(n,t),r=i[i.length-1];返回i=i.slice(0,i.length-r),e_.Buffer.from(i).toString("utf-8")}(g(l,u),n);返回JSON.parse(i)}functiongetHeaders(){varheaders={}returnheaders["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#@软件:PyCharm#====================================importexecjsimportrequestsregn_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(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/94.0.4606.81Safari/537.36"withopen('nhsa.js','r',encoding='utf-8')asf:nhsa_js=execjs.compile(f.read())defget_headers():"""获取头部参数,改变每个请求"""headers=nhsa_js.call("getHeaders")headers["User-Agent"]=UAheaders["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)returnheadersdefget_regn_code():"""获取城市代码代码,不加密返回结果"""payload={"data":{"transferFlag":""}}response=requests.post(url=regn_code_url,json=payload,headers=get_headers())print(response.text)defget_medins_lv_or_type_code(key):"""获取医疗机构级别(LV)或类型(TYPE)代码"""ifkey=="LV":payload={"type":"MEDINSLV"}elifkey=="TYPE":payload={"type":"MEDINS_TYPE"}else:print("Incorrectinput!")returnencrypted_pa??yload=nhsa_js.call("getEncryptedData",payload)encrypted_data=requests.post(url=lv_and_type_url,json=encrypted_pa??yload,headers=get_headers()).json()decrypted_data=nhsa_js.call("getDecryptedData",encrypted_data)print(decrypted_data)defget_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("Pleaseenterpagestocrawl(default1):")or1forpageinrange(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+=1encrypted_pa??yload=nhsa_js.call(“getEncryptedData",payload)encrypted_data=requests.post(url=result_url,json=encrypted_pa??yload,headers=get_headers()).json()decrypted_data=nhsa_js.call("getDecryptedData",encrypted_data)print(decrypted_data)defmain():#获取城市代码#get_regn_code()#获取医疗机构级别代码#get_medins_lv_or_type_code("LV")#获取医疗机构类型代码#get_medins_lv_or_type_code("TYPE")#获取搜索结果get_result()if__name__=="__main__":main()