最近,偶然间看到一个这样的需求,需要在七麦网上逆向拿数据,所以我查看了一下,发现七麦网在请求时,都会在每个url后面新增一个 ?analysis=xxx 这样的键值对,显然这是经过加密的,所以这时候我的好奇心就既然出来了,决定研究研究他的加密方式。。
1、页面分析
打开七麦网,F12打开开发工具,随便查看一个请求,都可以看到确实每个请求url后都有一个经过加密的键值对
这个analysis是怎么来的呢?
第一步,按住Ctrl+F在开发工具栏搜索analysis,发现并没有找到相关的xxx.js文件处理analysis,那我们换个思路,这时候可以考虑跟踪请求调用栈,但是这样一个一个跟踪效率是很低的,所以点击第一个调用栈进入js文件
那怎么定位analysis相关的代码呢?这里我们可以借助XHR断点的方式来试试看,设置了断点后,只要代码运行过程中遇到url中包含有analysis相关的就会像DEBUG调式一样停下来
ok 刷新网页,发现断点打在了这个地方,右边Scope也看不出来什么异常
继续按着F10往下走调试,这个过程中要注意右侧Scope的值,随便放一个调试期间的截图,这时候还没相关的对analysis的处理,继续往下走
下面的图可以发现url上已经没有analysis的,说明可能要到了,继续往下走,这时候用单步调试F9就好
走到这步,右侧的Scope中发现有个字段e的值跟analysis后面跟着的值很像,显然到这里我们已经定位到了这个加密字符串产生的位置,它的主体代码就是左边圈出来的部分
第二步,打开一个可以运行js环境的工具,我这里使用的是pycharm,新建一个js文件,将上面的主体代码拷贝进去,用一个方法封装起来,参数是url
这里将一个知识点,(0, i[jt])这种形式,我们可以把它看作C语言中的逗号操作符,以最后的那位数的结果作为结果,因此可以改写成:args =i[qt](a, d),analysis=ijt
尝试运行看看,发现报错
那我们就去这个i对应的方法在哪里,鼠标移到i[jt]上,进入对应的方法,这里经过混淆,v才是i[jt]的实际方法
将整个v方法拷贝到我们的js文件,将i[jt]替换成v,为了防止后面的操作可能产生和v方法一样的变量名或者方法,我这里将v方法处理一下,然后替换,代码如下
function v(t) {
t = z[V1](t)[T](/%([0-9A-F]{2})/g, function(n, t) {
return o(Y1 + t)
});
try {
return z[Q1](t)
} catch (n) {
return z[W1][K1](t)[U1](Z1)
}
}
const obj = {}
obj.xx = v
function f1(url){
// e = (0,i[jt])((0,
// i[qt])(a, d))
e = obj.xx(i[qt](a,d))
}
url = "/index/banner"
console.log(f1(url))
在运行,还是报错
重复上面的方法,找到i对应的方法,拷贝到js,然后替换相应的i[qt]
运行报错 a is not defined
到主体代码附近找看看a相关的代码,将这些代码拷贝下来到js
分析一下a的组成
a : "Mjg=@#/index/banner@#40001143610@#3"
① Mjg= + ② @# + ③ /index/banner + ④ @# + ⑤ 40001143610 + ⑥ @# + ⑦ 3
① 先不看
②④⑥是相同的字符@#
③是url请求地址,可以知道f1方法需要传入的参数就是跟请求地址有关系了
⑤是一个11位的时间戳
⑦是一个单独的数字3
结合代码来看看哪些是固定的哪些是变化的
a = (a += v + t[Jt][T](t[Mt], _)) + (v + r) + (v + 3)
可以看到变量v固定是@#,结尾固定是数字3,所以我们可以自己定义一个变量v的值是@#
r的值是一个时间戳,在上面的代码中被赋值
那中间部分③对应的就是t[Jt][T](t[Mt], _))这块代码,结合上面改写一下变成这样
现在还剩下① 是怎么来的呢,我们将断点打在这行重新刷新网页一步步调试看看,调试过程中可能会多次进入到这个断点,要注意右侧url是不是对应的请求地址,我们以为上面那个请求地址为例
先对a赋值为[],然后继续走,走到框出来的代码的时候,发现a变成了[28]
可以知道a在经过下面这段代码的时候也经过了处理,将代码拷贝到js
运行报错 z is not defined ,z未定义
到网页代码中,鼠标移动到z看看是什么?可以看到,z是js自带的window对象,既然确实,那就补上,其实在js中window对象就是this
如果出现这样的代码zQ1,Q1的值是固定的字符串,比如"btoa",z为Window对象,可以改写成 zQ1 => btoa(t),z"encodeURIComponent" => encodeURIComponent(t)
运行 W is not defined
鼠标移到W,发现是个字符串,根据上面的说明new z[W] => new Date
继续运行,发现s未定义,重复上面的步骤,这里s的值是-314,不妨先固定写死在代码里,其实这里的s是动态变化的,但是对结果并没有影响
重复上面的步骤,将代码改写一下,变成这样
运行,发现t未定义,结合代码
发现这边t[Zt]取得值是请求参数params,那我们就在f1方法上再加一个参数
这里是return可能会影响代码继续往下执行,调整一下代码,f1方法变成这样
下面的代码看起来比较好理解,就是遍历params里的每个键值对,如果键是analysis就跳过,将其他键的值拼接成字符串
我们根据上面的操作,将 v(t)和h(n, t)的代码调整一下,如下
v(t)中的o的方法
改写一下
总的代码
function o(n) { //o是我们h中要o具体函数的实现
t = "",
['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65']['forEach'](function(n) {
t += unescape("%u00" + n)
});
var t, e = t;
return String[e](n)
}
function v(t) {
t = encodeURIComponent(t)["replace"](/%([0-9A-F]{2})/g, function(n, t) {
return o("0x" + t)
});
try {
return btoa(t)
} catch (n) {
return Buffer["from"](t)["toString"]("base64")
}
}
const obj = {}
obj.xx = v
function h(n, t) {
t = t || u();
for (var e = (n = n["split"](""))["length"], r = t["length"], a = "charCodeAt", i = 0; i < e; i++)
n[i] = o(n[i][a](0) ^ t[(i + 10) % r][a](0));
return n["join"]("")
}
function f1(url,params){
var v = "@#"
var s = -314
var H = 0
var e, r = +new Date - (s || H) - 1661224081041, a = [];
var d = "xyz517cda96efgh"
if(void 0 !== params && (params !== {})){
Object["keys"](params)["forEach"](function(n) {
if (n == "analysis")
return false;
params["hasOwnProperty"](n) && a["push"](params[n])
})
}
//a : "Mjg=@#/index/banner@#40001143610@#3"
a = a["sort"]()["join"]("")
a = obj.xx(a)
a = (a += v + url + (v + r) + (v + 3))
// e = (0,i[jt])((0,
// i[qt])(a, d))
e = obj.xx(h(a,d))
return e
}
url = "/index/banner"
params = {
'cid' : 28
}
console.log(f1(url))
运行,大家可以自行去验证一下
如果运行过程发现ATOB 或者BTOA IS NOT DEFINED的问题,在js前加上以下这段
// =====================================================================
// atob 或者btoa 方法是浏览器实现的而非 js 自带,需要需要使用这两个方法需要自己实现 最前面加上这段js 就可以使用了
// 用来解决ATOB 或者BTOA IS NOT DEFINED的问题
global.Buffer = global.Buffer || require('buffer').Buffer;
if (typeof btoa === 'undefined') {
global.btoa = function (str) {
return new Buffer.from(str, "binary").toString('base64');
};
}
if (typeof atob === 'undefined') {
global.atob = function (b64Encoded) {
return new Buffer.from(b64Encoded, 'base64').toString("binary");
};
}
(function(a) {
var a = atob(a);
var b = [];
for (var i = 0; i < 32; i++) {
b.push(a[i].charCodeAt())
}
var c = [];
for (var i = 0; i < 16; i++) {
c.push(0)
}
var d = (b[0] % 8) * 2 - 1;
if (d < 0) {
d = 5
}
for (var i = 0; i < 16; i++) {
var j = (((i + 1) % 16) * d) % 16;
c[j] = b[i] ^ b[16 + i]
}
var a = '';
for (var i = 0; i < 16; i++) {
a += String.fromCharCode(c[i])
}
console.log(btoa(a))
}
)("SZZ0XDbSPf3OdgwIM2axNScLRZjRvmgmcr7S6J/d9dA=");
// =====================================================================