1. 确定需要的参数
通过抓包分析,可以发现存在三个参数,这三个毫无疑问是需要的
在进行翻页时,会出现弹窗,说明cookie失效,所以肯定是与cookie有关系的,接下来验证到底是哪一个cookie来判断有效的:
- 通过删除可疑cookie的方法进行测试,到底哪一个才是有用的,有三种情况
- 只有m
- 在删除m之后,在cookie失效前是可以正常进行请求的,所以可疑暂时进行排除,但也不能肯定
- 只有rm4
- 在删除rm4后,再次进行翻页直接就出现了弹窗表示cookie失效,和刚刚删除m的情况完全不同,所以有效cookie肯定与rm4是有关系的
- m和rm4都必须同时有
- 删除m都可以正常进行请求,这个也就不用进行测试了
2. 寻找参数的加密逻辑
在确定好需要的参数之后,接下来就是寻找参数的加密逻辑了,我们首先来找这个cookie的加密逻辑:
- 一般来讲,确定cookie的加密位置,通常使用hook的方式
//hook cookie的代码如下
(function(){
var cookieTmp = '';
//给对象document添加一个属性
Object.defineProperty(document,'cookie',{
set:function(val){
//这个里面的代码要根据具体情况去设定,找不到RM4hZBv0dDon443M返回-1,找到返回其他的值
if(val.indexOf('RM4hZBv0dDon443M') != -1){ //里面传入的是要找的cookie的名称
console.log('hook 成功!');
debugger;
}
console.log("hook到cookie设置->",val);
cookieTmp = val;
return val;
},
get:function(){
return cookieTmp;
},
});
})()
我是进行手工hook的,第一次断住如下:
到这里不要停,明显这时cookie的值还没有生成,所以还要点下一步一直到有值为止
到这里就可以向上进行跟栈了,就可以直接找到cookie的加密逻辑
-
向上进行跟栈,找到加密的位置
//这是网站中的写法 _0x3d0f3f[_$Fe] = 'R' + 'M' + '4' + 'h' + 'Z' + 'B' + 'v' + '0' + 'd' + 'D' + 'o' + 'n' + '4' + '4' + '3' + 'M=' + _0x4e96b4['_$ss'] + ';\x20path=/'; //手动进行解混淆 document.cookie = 'RM4hZBv0dDon443M='+window._$ss+';\x20path=/'
手动解混淆之后可疑发现,关键就在于window._$ss身上
-
对全局变量进行hook
(function () { 'use strict' Object.defineProperty(window, '_$ss', { set: function (val) { console.log('Hook捕获到_$ss的值->', val); debugger; }, }); })();
hook的方法和刚刚一样,hook成功之后直接向上进行跟栈即可
向上跟栈找到加密逻辑
//加密位置
_0x4e96b4['_$' + _$UH[0x348][0x1] + _$UH[0x353][0x1]] = _0x29dd83[_$UH[0x1f]]();
//手动解混淆
window['_$ss'] = _0x29dd83['toString']() //意思就是将_0x29dd83这个变量转成字符串
//_0x29dd83,向上找可疑找到这个变量的位置如下:
_0x29dd83 = _$Tk['A' + _$UH[0x32d]][_$UH[0x337] + _$UH[0x336]](_$Ww, _0x4e96b4[_0xc77418('0x6', 'OCbs')], {
'mode': _$Tk[_$UH[0x339] + _$UH[0x33a]][_$UH[0x2e5]],
'padding': _$Tk[_$UH[0x33b]][_$UH[0x33c] + _$UH[0x33d]]
})
//手动解混淆
_0x29dd83 = _$Tk['AES']['encrypt'](_$Ww, _0x4e96b4['_$qF'], {
'mode': _$Tk['mode']['ECB'],
'padding': _$Tk['pad']['Pkcs7']
})
/*很明显的AES加密(对称加密算法),传入的第一个参数是经过处理后的明文,第二个参数是密钥,第三个参数是一个配置对象,里面包含了对称解密的算法以及数据的填充模式*/
/*另外注意,对称加密算法,对明文的加解密使用同一个密钥,也就是说服务器端也要知道这个密钥才能进行解密,而密钥是在前端进行生成的(可能是固定的,也可能是动态生成的),如果是动态生成的那么前端肯定要给后端发送密钥或者是能够生成密钥的参数,经过相同的算法得到密钥才能进行解密
*/
_$Tk = CryptoJS; //很可能使用的是标准库
-
验证是否使用的是第三方加密的标准库,将处理后的明文以及密钥写成固定值(与调试时的浏览器中的值保持一致),观察得到的rm4是否一致即可
-
我们先来找密钥生成的逻辑
//密钥也就是
_0x4e96b4['_$qF']
//在页面中搜索_$qF就可以找到生成位置
_0x4e96b4['_$qF'] = CryptoJS['enc']['Utf8'][_$UH[0xff]](_0x4e96b4['btoa'](_0x4e96b4['_$is'])['slice'](0x0, 0x10))
//手动解混淆
_0x4e96b4['_$qF'] = CryptoJS['enc']['Utf8']['parse'](_0x4e96b4['btoa'](_0x4e96b4['_$is'])['slice'](0,16)) //window.btoa表示将传入的字符串_0x4e96b4['_$is']进行base64编码,编码之后取前16位
//接下来就是寻找_0x4e96b4['_$is']了,还是在页面中进行搜索
_$yw = _0x2d5f5b()['toString']();
_0x4e96b4['_$is'] = _$yw; //接下来就是扣代码环节了,不再赘述
其中会出现:
因为在node中并不具有window这种环境,所以要换成new Buffer.from(_0x4e96b4['_$is']).toString('base64')
-
接下来寻找明文值的生成
//页面中 _$Ww = _$Tk[_$UH[0x2db]][_$UH[0x2dc]][_$UH[0xff]](_0x4e96b4['_$pr'][_$UH[0x1f]]()) //手动解混淆 _$Ww = _$Tk['enc']['Utf8']['parse'](window['_$pr']['toString']()) // CryptoJs.enc.utf8.parse() 是为了将字符串转成utf8编码,也就是说知道window._$pr是怎么生成的 //通过搜索可以找到有7个位置存在_$pr,将7个位置全部打上断点进行调试,看它能够断到那个位置,其中第一个是创建一个空数组_$pr
经过第一次断点,断到了这边,此时数组中元素个数位0
再向下进行可以发现,在这个位置断了4次,传入了四个值,直到第五个值的时候才换了地方
再下一步就到了生成密文的位置了
从图中也可以知道,生成_$Ww
需要的$pr
也就是一个拥有五个值的数组
6.1 先来看前面四个值的生成
function _0x12eaf3() {
return Date.parse(new Date())
}
_$Wa = _0x12eaf3() //本质上就是一个时间戳
window['_$pr']['push'](_0x474032(_$Wa))
//接下来就是寻找_0x474032()的生成,就是扣代码了
其中会出现如下这种情况①,_$UH实际上就是一个大数组,里面有一些固定的字符串,直接将里面表示的值给替换即可
还会出现这种情况②:
history未定义,history是在浏览其中才有的,在node环境中是没法进行补的,我们就到出现错误的代码中看一看
发现是一个try-catch语句,打上断点看一看
在浏览器中看一看:
可以发现是有值的,而且固定是25,所以可以直接将op的值写成25
接下来就是一致补函数即可,补好后就出来了结果如下:
接下来进行调试:
我们将加密时间戳的这个函数在浏览器中hook一下:
(function(){
var _0x11 = _0x37614a
_0x37614a = function(t){
var res = _0x11(t)
console.log('加密之前的值:',t, '_$6_:',_0x4e96b4['_$6_'])
console.log('_$tT:',_0x4e96b4['_$tT'], '_$Jy:',_0x4e96b4['_$Jy'])
console.log('加密后的值:',res)
return res
}
})()
第一次:
第二次:
通过两次分析可以发现,第一次加密与最后一次加密时,三个window变量是一一对应相等的
我们再想一下,cookie rm4的生成流程,明文经过密钥加密之后即可生成cookie,那么当后端拿到cookie之后需要经过相同的密钥进行解密之后得到明文,也就是上一步还原出来的装有5个MD5的数组,我们知道md5理论上来讲并不是可解密的,只有通过撞库才可能进行匹配。所以要想验证这个明文数组是正确的,很大可能性只是验证了其中的一个元素,于是猜想可能是第一个或者是最后一个,因为window的参数是固定的,后端验证的流程可能是前端将最后一组加密前的时间戳传给了后端,后端按照与前端加密一致的方法拿到经过md5加密后的值,与传过来的rm4解密后明文值的最后一个元素进行比对,匹配上才能成功返回数据。
所以,假设明文数组的最后一个有效,前面四个反正也没用就写成明文,最后一个将那三个window参数写成最后一个加密的固定值即可
注意:
-
接着是来分析参数m与f,直接跟栈即可,不再赘述
-
测试如下: 是成功的
3. 总结
到此,关于请求已经完成,我们可以回过头想一想,为什么要传入m与f两个参数,f可以理解,用于记录请求时的时间,那么m是干什么的呢?
- 通过上面生成cookie时的分析可知,使用了
aes
加密,aes
加密使用到了明文以及密钥,且经过测试,明文以及密钥都是动态变化的 - 密钥是由时间戳
_$is
生成的,后端想要解密就必须要知道密钥,所以将_$is
以m的形式传到了后端 - 明文数组的最后一个参数同样也用到了
_$is
还要注意,当自己模拟生成且程序不报错之后并不代表就是正确的,要传入一个固定的参数用于测试自己的程序是否正确才行。
over!!