Chrome-JS前端代码调试2
Chrome-JS前端代码调试2
Takake 前言:在以前的Chrome代码调试中我通常喜欢使用鼠标点击按钮的点击事件来从头触发事件监听,但是在之后的使用中发现这种办法并不是很奏效,会需要很长一段调试,才能最追踪到我想要的数据当中,这种效率相当的低,且有时候甚至直接迷失了方向,完全不知道具体跟到哪个代码逻辑下了,也无法判断是否还需要继续往下跟。
吸取了之前的教训,我尝试使用关键词断点的办法来直接定位我要的信息。
1.定位关键词
关键词:encrypt,decrypt,login,password,username,secret,privateKey,publicKey,以及所有加密算法名词(AES,SM3,SM4,SM2,RSA)。
全局搜索关键词,并逐个判断,可能是具体加密逻辑位置,不需要一定判断准确,只要觉得可能是具体加密逻辑的位置就可以打上断点。
找到两个关键位置打上断点。
2.运行程序
2.1.请求加密逻辑分析
等程序运行到断点处,查看变量的值,(可能有些代码看不明白,但是变量的值一定能够看明白,因为这些都是我们输入的数据)
在这里就能清楚的看到变量e是我们输入的参数,也就是POST请求的请求体,而 ah.sm4则是具体要进行的加密算法,因此我们能够大致判断,下面程序进行sm4加密算法了。
而阅读return后的代码我们能判断出,程序在判断请求方法,如果为get请求则执行 r = n.encrypt(TC(e), ns()),
而如果为POST请求者执行n.encrypt(JSON.stringify(e), ns()),都是sm4算法,我们也能猜测出n.encrypt(),的第一个参数是需要加密的字符串,而ns()这为密钥,为了证明这个逻辑是否正确我们可以在控制台验证。
通过以上验证,我们判断完全没有问题。
这个时候按正常逻辑可能想要判断这个密钥的位置,那么可能往ns()函数代码里跟踪,看这个密钥是哪来的。
然而不用这么麻烦,密钥的来源无非就三种情况,前端硬编码,前端生成(发送给后端),后端生成(发送给前端)。
我们要判断是否为前端硬编码,只需要在前端源代码中全局搜索即可,发现没有找到匹配项,那么我们可以初步判断密钥是生成的了。
我们看我们之前发送的所有请求包,并未发现任何密钥相关的请求,因此我们大概能判断密钥是在前端生成的了。
那么后端想要解密前端使用sm4加密的数据那么后端就必须要拿到这个密钥。
那么这时我们就停止断点,直接发包看,请求包中是否包含密钥。
请求体中不包含密钥那么一定在这个请求头中了,可惜的是这个也加密了。
我们再重新回到调试中来,定位Cvit-Secret-Key位置。
让代码运行到 t.headers[“Cvit-secret-key”] = o上,o=ZF()的位置,进入zF()查看O是怎么来的。
进入zF()函数发现使用了sh加密算法,现在我们不清楚sh具体是什么加密算法,但是我们看到e.setPublicKey()因此我们可以判断用到了非对称加密算法,sm2/rsa。
在控制台查看t.serverPublicKey的值。
同样我们全局搜索这个密钥,也未找到该密钥的硬编码,因此我们大概可以判断这个公钥是后端发送过来的,不可能前端生成发送到后端的。因为发送私钥那样没有意义。
我们刷新页面可看到
因此可以明白RSA公钥对是由后端生成,同时将公钥发送到前端,让前端将sm4密钥加密写入请求头中发送给后端。
结论:我们初步可以判断出请求加密的sm4密钥是由前端生成并且通过后端发送来的 rsa公钥加密放入请求头的Cvit-Secret-Key字段中。
CyberChef加解密具体逻辑截图
2.2.响应包加密逻辑分析
通过阅读代码+断点定位到拦截响应包位置。
可以从代码看出系统从响应包中的获取到cvit-secret-key和响应包的加密数据,放入到GF(),跟入GF()。
t为响应包中的cvit-secret-key,通过n.setPrivateKey(VF());算法我们可以明确的看出,VF()是一种非对称加密算法的私钥,通过进入VF调试我发现,这个逻辑和之前的请求包中的cvit-secret-key的逻辑一样,是由前端生成RSA密钥对,将公钥通过请求中的public-key字段发送给后端,后端将生成的sm4密钥,使用公钥加密通过cvit-secret-key发送给前端。
因此可以从这部分代码判断出VF()为前端生成的RSA私钥,用于解密响应包中的cvit-secret-key,用解密出的sm4密钥来对响应包中的数据进行解密。
使用CyberChef具体实现逻辑如下:
2.3.签名校验具体逻辑分析
通过分析请求包我们大致就能判断出唯一能够进行签名校验的Cvit-Digest。
因此我们定位到请求包中请求头生成位置,可以直接看出UF()方法则为签名的生成所使用的函数,我们跟入UF()函数内部。
通过UF()函数我们能够确定t为请求方法,e为请求参数对象。
通过定义的ah.sm3我们可以判断出使用了sm3签名算法。
以及阅读return后的语句,可以判断出具体逻辑加签逻辑如下
则对sm3(query=<当为get请求时,该值为get请求参数,否则为空>&body=<当为post请求时,该值为post请求体,否则为空>&secretKey=<密钥|盐>)
即该密钥为该签名的salt值。
通过观察发现该密钥和之前前端生成的密钥一致。
因此我们可以伪造此签名逻辑。
sm3 get签名
sm3 post签名
3.总结
该项目的加解密逻辑是我目前遇到最复杂的一个,完全通过前端调试出加解密逻辑还是挺复杂的,还是需要借助项目提供的资料和代码,才更能轻松的还原出数据的加解密逻辑。
而该加解密的主要难点还是在数据的定位上,如何才能刚好的定位到具体的加解密逻辑,这是需要仔细且细心才能完成的。
对于前端未使用到的接口,或者说对外的接口该如何测试?
其实很简单,由于没有CA,所以后端并不能确定自己接收到的密钥是否是前端生产的,所以我们只需要自己生成一个sm4密钥和一对RSA密钥对,将公钥发给后端,sm4加密后发给后端,这样同样能够对其他接口进行测试,而不是一定需要依赖前端生成的密钥。