qq音乐sign参数纯算逆向
- 前期准备
- 定位入口,出口
- 打桩,拿日志
- 打桩
- 非全日志做法
- 合并日志
- 简化日志
- 分析日志
- 最终实现
- 总结
免责声明
本文技术内容仅供学习研究之用,转载请说明出处,如有侵权联系删除。作者不保证信息完全准确,不对因使用本文导致的任何损失负责。读者应自行承担使用风险,并遵守相关法律法规。
前期准备
文末进群。
vscode,pycharm,浏览器
首先告诉你,这个纯算有个大坑,如果你打算要全日志的话你就话,在日志中你会发现这不就是SHA1
的算法吗?然后你苦苦地翻日志,找到了那个长度为40的字符串,然后你将参数拿过去与标准的SHA1
加密结果一对比,一模一样 😐。然而这加密部分却占了日志的一大部分。
如果你想学学SHA1
算法,你可以要全日志。
但是还有一个坑在等着你,就是内存栈d有一个大数组,长度为7206,贯穿整个日志,但是不幸的是却用不到 😐。
全日志的日志量还是比较大的,如果直接将数据打印到控制台,会导致浏览器崩溃,因此可以把日志保存到本地,接下来就需要用到:
window.get_obj_str = function get_obj_str(one_obj) {
try {
return JSON.stringify(one_obj, function (k, v) {
if (v == window) {
return 'window';
} else if (typeof v == 'function') {
return `function ${v.name}`; // 如果想更详细,可以用v.toString(),但是建议本次不用,不然,后期很难处理。
} else if (v && v[Symbol.toStringTag]) {
return `Symbol ${v[Symbol.toStringTag]}`;
} else {
return v;
}
})
} catch (e) { };
}
window.add_json_str = function (param_name, param) {
var add_log = function (log_str) {
var save_log = function (textContent, file_name) {
const blob = new Blob([textContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = file_name;
document.body.appendChild(a);
a.click();
setTimeout(() => { document.body.removeChild(a); window.URL.revokeObjectURL(url); }, 0);
}
window.my_code += log_str;
if (window.my_code.length >= 1024 * 1024 * 5) {
save_log(window.my_code, `logs${window.my_offset}.txt`)
window.my_offset += 1;
window.my_code = "";
}
}
add_log(param_name + " = " + window.get_obj_str(param) + "\n\n");
};
window.my_offset = 0;
window.my_code = "";
这些代码是需要注入到控制台的,如果不想每次都注入,可以用一个插件叫篡改猴,怎么安装这里就不提了。安装完成后,在篡改猴管理面板添加新脚本,把上述代码粘贴进去,别忘了开头的// @match *://*/*
,还有脚本的开关打开。
如果安装正确,那么,你可以在任意的网页打开devtools的时候,在控制台输入get_obj_str
,并回车,如果显示的不是undefined,那么就代表可用。
定位入口,出口
打开qq音乐,抓包,发现有几个数据包请求携带的参数中带有sign,长度约40多位,不固定,且都以
zcc
开头。请求方式为POST,携带一个数据表单。
直接参数搜索"sign:"
,发现在vendor.chunk
js文件有两处地方,但大体一样:
// 第一个
-1 !== t.url.indexOf("cgi-bin/musics.fcg")) {
var i, o = n(350).default;
i = "GET" === t.type.toUpperCase() ? o(t.data.data) : o(t.data), // 入口
t.url = B({ // 出口
sign: i
}, t.url)
}
// 第二个
-1 !== t.url.indexOf("cgi-bin/musics.fcg")) {
var i, o = n(360).default;
i = "GET" === t.type.toUpperCase() ? o(t.data.data) : o(t.data),
t.url = T({
sign: i
}, t.url)
}
只是B
和T
函数的区别,观察程序,sign: i
,而i = "GET" === t.type.toUpperCase() ? o(t.data.data) : o(t.data),
,由于携带sign
参数的数据包的请求方式都是POST,所以i=o(t.data)
,所以在o(t.data)
前打上断点,B
和T
两处都要打,刷新网页,发现程序断在了B
处,在控制台输入o(t.data)
,发现正是sign
参数,入口出口都找到了。
打桩,拿日志
打桩
接下来就是打桩。如果你没有篡改猴插件的话,现在需要注入前面的下载日志的代码。
刚才我们的程序已经断在了o(t,data)
前,接下来只需要单步执行,会发现:
已经到程序的内部了,然后就是找内存栈,这里的是d,但是很恶心的一点是类似d[n[++g]]
,不过调试我发现他是有规律的,例如d[n[++g]] = d[n[++g]] + d[n[++g]]
,可以这样:
原式 | d[n[++g]] | = | d[n[++g]] | + | d[n[++g]] |
---|---|---|---|---|---|
断点等效 | d[n[g+1]] | = | d[n[g+2]] | + | d[n[g+3]] |
插桩,断点类型为条件断点,注释文字为断点内容,在日志中为了区分每个断点,特意加了断点标记,例如d23,意思是
case 23:
内的代码
function l() {
for (var f, p, d = [a, s, t, this, arguments, l, n, 0], h = void 0, g = e, v = [];;) try {
for (;;)
switch (n[++g]) { // add_json_str(`d总循环`,d),false
case 0:
d[n[++g]] = new d[n[++g]](d[n[++g]]);
break;
case 1:
return d[n[++g]];
case 2:
for (f = [], p = n[++g]; p > 0; p--) f.push(d[n[++g]]);
d[n[++g]] = u(g + n[++g], f, a, s, c);
try {
Object.defineProperty(d[n[g - 1]], "length", {
value: n[++g],
configurable: !0,
writable: !1,
enumerable: !1
})
} catch (m) {}
break;
case 3:
d[n[++g]] = d[n[++g]] < d[n[++g]]; // add_json_str(`d3 ${d[n[g+2]]} < ${d[n[g+3]]}`,d[n[g+2]] < d[n[g+3]]),false
break;
case 4:
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] = d[n[++g]][d[n[++g]]];
break;
case 5:
d[n[++g]] = d[n[++g]] >= n[++g]; // add_json_str(`d5 ${d[n[g+2]]} >= ${n[g+3]}`,d[n[g+2]] >= n[g+3]),false
break;
case 6:
d[n[++g]] = d[n[++g]] >> n[++g], // add_json_str(`d6 ${d[n[g+2]]} >> ${n[g+3]}`,d[n[g+2]] >> n[g+3]),false
d[n[++g]] = d[n[++g]][d[n[++g]]];
break;
case 7:
d[n[++g]] = d[n[++g]] < n[++g]; // add_json_str(`d7 ${d[n[g+2]]} < ${n[g+3]}`,d[n[g+2]] < n[g+3]),false
break;
case 8:
d[n[++g]] = d[n[++g]].call(h);
break;
case 9:
d[n[++g]] = "",
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] = n[++g];
break;
case 10:
d[n[++g]] = d[n[++g]] | n[++g]; // add_json_str(`d10 ${d[n[g+2]]} | ${n[g+3]}`,d[n[g+2]] | n[g+3]),false
break;
case 11:
d[n[++g]] = d[n[++g]] & n[++g], // add_json_str(`d11 ${d[n[g+2]]} & ${n[g+3]}`,d[n[g+2]] & n[g+3]),false
d[n[++g]] = d[n[++g]][d[n[++g]]];
break;
case 12:
d[n[++g]] = {};
break;
case 13:
d[n[++g]] = d[n[++g]] | d[n[++g]], // add_json_str(`d13 ${d[n[g+2]]} | ${d[n[g+3]]}`,d[n[g+2]] | d[n[g+3]]),false
d[n[++g]][d[n[++g]]] = d[n[++g]],
g += d[n[++g]] ? n[++g] : n[(++g,
++g)];
break;
case 14:
d[n[++g]] = h;
break;
case 15:
d[n[++g]] = n[++g],
d[n[++g]] = d[n[++g]][n[++g]],
d[n[++g]] = n[++g];
break;
case 16:
d[n[++g]] = !0;
break;
case 17:
d[n[++g]] = d[n[++g]] === d[n[++g]];
break;
case 18:
d[n[++g]] = d[n[++g]] / d[n[++g]]; // add_json_str(`d18 ${d[n[g+2]]} / ${d[n[g+3]]}`,d[n[g+2]] / d[n[g+3]]),false
break;
case 19:
d[n[++g]][d[n[++g]]] = d[n[++g]],
d[n[++g]] = "",
d[n[++g]] += String.fromCharCode(n[++g]);
break;
case 20:
d[n[++g]][n[++g]] = d[n[++g]],
d[n[++g]][n[++g]] = d[n[++g]],
d[n[++g]][n[++g]] = d[n[++g]];
break;
case 21:
d[n[++g]] = d[n[++g]] * d[n[++g]]; // add_json_str(`d21 ${d[n[g+2]]} * ${d[n[g+3]]}`,d[n[g+2]] * d[n[g+3]]),false
break;
case 22:
d[n[++g]] = ++d[n[++g]],
d[n[++g]] = d[n[++g]];
break;
case 23:
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] = d[n[++g]][d[n[++g]]],
d[n[++g]] = d[n[++g]];
break;
case 24:
d[n[++g]] = d[n[++g]] << n[++g]; // add_json_str(`d24 ${d[n[g+2]]} << ${n[g+3]}`,d[n[g+2]] << n[g+3]),false
break;
case 25:
d[n[++g]] = r(d[n[++g]]);
break;
case 26:
d[n[++g]] = d[n[++g]] | d[n[++g]]; // add_json_str(`d26 ${d[n[g+2]]} | ${d[n[g+3]]}`,d[n[g+2]] | d[n[g+3]]),false
break;
case 27:
d[n[++g]] = n[++g];
break;
case 28:
d[n[++g]] = d[n[++g]][n[++g]];
break;
case 29:
d[n[++g]] = n[++g],
d[n[++g]][n[++g]] = d[n[++g]],
d[n[++g]] = n[++g];
break;
case 30:
d[n[++g]] = d[n[++g]].call(h, d[n[++g]], d[n[++g]]);
break;
case 31:
d[n[++g]] = n[++g],
d[n[++g]] = n[++g],
d[n[++g]] = n[++g];
break;
case 32:
d[n[++g]] = n[++g],
d[n[++g]][d[n[++g]]] = d[n[++g]];
break;
case 33:
d[n[++g]] = d[n[++g]] === n[++g];
break;
case 34:
d[n[++g]] = d[n[++g]] + n[++g]; // add_json_str(`d34 ${d[n[g+2]]} + ${n[g+3]}`,d[n[g+2]] + n[g+3]),false
break;
case 35:
d[n[++g]] += String.fromCharCode(n[++g]);
break;
case 36:
d[n[++g]] = "",
d[n[++g]] += String.fromCharCode(n[++g]);
break;
case 37:
d[n[++g]] = d[n[++g]][n[++g]],
d[n[++g]] = d[n[++g]][n[++g]],
d[n[++g]] = d[n[++g]][n[++g]];
break;
case 38:
d[n[++g]] = "",
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] += String.fromCharCode(n[++g]);
break;
case 39:
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] = d[n[++g]] === d[n[++g]],
g += d[n[++g]] ? n[++g] : n[(++g,
++g)];
break;
case 40:
d[n[++g]] = d[n[++g]] > d[n[++g]]; // add_json_str(`d40 ${d[n[g+2]]} > ${d[n[g+3]]}`,d[n[g+2]] > d[n[g+3]]),false
break;
case 41:
d[n[++g]] = d[n[++g]] - d[n[++g]]; // add_json_str(`d41 ${d[n[g+2]]} - ${d[n[g+3]]}`,d[n[g+2]] - d[n[g+3]]),false
break;
case 42:
d[n[++g]] = d[n[++g]] << d[n[++g]]; // add_json_str(`d42 ${d[n[g+2]]} << ${d[n[g+3]]}`,d[n[g+2]] << d[n[g+3]]),false
break;
case 43:
d[n[++g]] = d[n[++g]] & d[n[++g]]; // add_json_str(`d43 ${d[n[g+2]]} & ${d[n[g+3]]}`,d[n[g+2]] & d[n[g+3]]),false
break;
case 44:
d[n[++g]] = d[n[++g]] & n[++g]; // add_json_str(`d44 ${d[n[g+2]]} & ${n[g+3]}`,d[n[g+2]] & n[g+3]),false
break;
case 45:
d[n[++g]] = -d[n[++g]];
break;
case 46:
for (f = [], p = n[++g]; p > 0; p--) f.push(d[n[++g]]);
d[n[++g]] = o(g + n[++g], f, a, s, c);
try {
Object.defineProperty(d[n[g - 1]], "length", {
value: n[++g],
configurable: !0,
writable: !1,
enumerable: !1
})
} catch (y) {}
break;
case 47:
g += d[n[++g]] ? n[++g] : n[(++g,
++g)];
break;
case 48:
d[n[++g]][n[++g]] = d[n[++g]];
break;
case 49:
d[n[++g]] = ~d[n[++g]]; // add_json_str(`d49 ~${d[n[g+2]]}`,~d[n[g+2]]),false
break;
case 50:
d[n[++g]] = d[n[++g]].call(d[n[++g]]);
break;
case 51:
d[n[++g]] = d[n[++g]] ^ d[n[++g]]; // add_json_str(`d51 ${d[n[g+2]]} ^ ${d[n[g+3]]}`,d[n[g+2]] ^ d[n[g+3]]),false
break;
case 52:
d[n[++g]] = ++d[n[++g]];
break;
case 53:
d[n[++g]] = !1;
break;
case 54:
d[n[++g]] = d[n[++g]] >>> n[++g]; // add_json_str(`d54 ${d[n[g+2]]} >>> ${n[g+3]}`,d[n[g+2]] >>> n[g+3]),false
break;
case 55:
d[n[++g]][n[++g]] = d[n[++g]],
d[n[++g]] = n[++g],
d[n[++g]][n[++g]] = d[n[++g]];
break;
case 56:
d[n[++g]] = Array(n[++g]);
break;
case 57:
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]][n[++g]] = d[n[++g]];
break;
case 58:
d[n[++g]] = d[n[++g]] % d[n[++g]]; // add_json_str(`d58 ${d[n[g+2]]} % ${d[n[g+3]]}`,d[n[g+2]] % d[n[g+3]]),false
break;
case 59:
d[n[++g]] = d[n[++g]][d[n[++g]]],
d[n[++g]] = d[n[++g]][n[++g]];
break;
case 60:
d[n[++g]] = d[n[++g]][n[++g]],
d[n[++g]] = n[++g];
break;
case 61:
d[n[++g]] = d[n[++g]] - n[++g]; // add_json_str(`d61 ${d[n[g+2]]} - ${n[g+3]}`,d[n[g+2]] - n[g+3]),false
break;
case 62:
d[n[++g]] = d[n[++g]] + d[n[++g]]; // add_json_str(`d62 ${d[n[g+2]]} + ${d[n[g+3]]}`,d[n[g+2]] + d[n[g+3]]),false
break;
case 63:
d[n[++g]] = !d[n[++g]];
break;
case 64:
d[n[++g]][d[n[++g]]] = d[n[++g]];
break;
case 65:
for (d[n[++g]] += String.fromCharCode(n[++g]), f = [], p = n[++g]; p > 0; p--) f.push(d[n[++g]]);
d[n[++g]] = o(g + n[++g], f, a, s, c);
try {
Object.defineProperty(d[n[g - 1]], "length", {
value: n[++g],
configurable: !0,
writable: !1,
enumerable: !1
})
} catch (A) {}
d[n[++g]][d[n[++g]]] = d[n[++g]];
break;
case 66:
d[n[++g]] = d[n[++g]] - 0;
break;
case 67:
d[n[++g]] = d[n[++g]].call(d[n[++g]], d[n[++g]]);
break;
case 68:
d[n[++g]] = d[n[++g]][n[++g]],
d[n[++g]] = d[n[++g]],
d[n[++g]] = d[n[++g]] - 0;
break;
case 69:
d[n[++g]] = d[n[++g]][d[n[++g]]],
d[n[++g]] = d[n[++g]] + d[n[++g]]; // add_json_str(`d69 ${d[n[g+2]]} + ${d[n[g+3]]}`,d[n[g+2]] + d[n[g+3]]),false
break;
case 70:
d[n[++g]] = n[++g] + d[n[++g]]; // add_json_str(`d70 ${n[g+2]} + ${d[n[g+3]]}`,n[g+2] + d[n[g+3]]),false
break;
case 71:
d[n[++g]] = d[n[++g]] << d[n[++g]], // add_json_str(`d71 ${d[n[g+2]]} << ${d[n[g+3]]}`,d[n[g+2]] << d[n[g+3]]),false
d[n[++g]] = d[n[++g]] | d[n[++g]], // add_json_str(`d71 ${d[n[g+2]]} | ${d[n[g+3]]}`,d[n[g+2]] | d[n[g+3]]),false
d[n[++g]][d[n[++g]]] = d[n[++g]];
break;
case 72:
d[n[++g]] = d[n[++g]].call(d[n[++g]], d[n[++g]], d[n[++g]]);
break;
case 73:
d[n[++g]] = d[n[++g]] >> n[++g]; // add_json_str(`d73 ${d[n[g+2]]} >> ${n[g+3]}`,d[n[g+2]] >> n[g+3]),false
break;
case 74:
d[n[++g]][d[n[++g]]] = d[n[++g]],
d[n[++g]][d[n[++g]]] = d[n[++g]],
d[n[++g]][d[n[++g]]] = d[n[++g]];
break;
case 75:
d[n[++g]] = n[++g],
d[n[++g]][n[++g]] = d[n[++g]],
g += d[n[++g]] ? n[++g] : n[(++g,
++g)];
break;
case 76:
d[n[++g]] = d[n[++g]].call(h, d[n[++g]]);
break;
case 77:
d[n[++g]] = d[n[++g]];
break;
case 78:
d[n[++g]] = d[n[++g]][d[n[++g]]];
break;
case 79:
d[n[++g]] = d[n[++g]][n[++g]],
d[n[++g]] = d[n[++g]] >> n[++g], // add_json_str(`d79 ${d[n[g+2]]} >> ${n[g+3]}`,d[n[g+2]] >> n[g+3]),false
d[n[++g]] = d[n[++g]] & n[++g]; // add_json_str(`d79 ${d[n[g+2]]} & ${n[g+3]}`,d[n[g+2]] & n[g+3]),false
break;
case 80:
d[n[++g]] = "";
break;
case 81:
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] += String.fromCharCode(n[++g]);
break;
case 82:
d[n[++g]] += String.fromCharCode(n[++g]),
d[n[++g]] = d[n[++g]][d[n[++g]]],
g += d[n[++g]] ? n[++g] : n[(++g,
++g)]
}
} catch (b) {
if (v.length > 0 && (i = []), i.push(g), 0 === v.length) throw c ? c(b, d, i) : b;
g = v.pop(),
i.pop()
}
}
如果你要全日志的话就可以让程序继续执行了,别忘了注入代码,出口断点。
非全日志做法
如果不要,那么你先把除了入口和出口的所有断点都禁用。
然后我会告诉你怎么做,为什么这么做:
首先,找到
l
函数中的case 62:
,然后打上条件断点:
case 62:
d[n[++g]] = d[n[++g]] + d[n[++g]]; // d[n[g+2]].length==39
break;
case 63:
然后让程序继续执行,直到程序断在case 62
。
为什么是这样
先给你个参数:
m='{"comm":{"cv":4747474,"ct":24,"format":"json","inCharset":"utf-8","outCharset":"utf-8","notice":0,"platform":"yqq.json","needNewCode":1,"uin":0,"g_tk_new_20200303":5381,"g_tk":5381},"req_1":{"module":"music.musicsearch.HotkeyService","method":"GetHotkeyForQQMusicMobile","param":{"searchid":"27021597846147553","remoteplace":"txt.yqq.top","from":"yqqweb"}},"req_2":{"module":"music.paycenterapi.LoginStateVerificationApi","method":"GetChargeAccount","param":{"appid":"mlive"}},"req_3":{"module":"MvService.MvInfoProServer","method":"GetAllocTag","param":{}},"req_4":{"module":"MvService.MvInfoProServer","method":"GetAllocMvInfo","param":{"start":0,"size":20,"version_id":7,"area_id":15,"order":1}}}';
// SHA1(m)= 'DD69685AD37B71DB3E870E35FB14EFE3CC465405' 大写的,这个你们应该都能算出来吧
这是某次我拿的全日志里面的:
看断点标记是d62
,说明在case 62
内最终生成了加密结果,因此要断在case 62
处。
前面我们说程序已经断在了case 62
处,接下来,我们将case 62
的断点恢复为
add_json_str(`d62 ${d[n[g+2]]} + ${d[n[g+3]]}`,d[n[g+2]] + d[n[g+3]]),false
然后启用所有的断点,就可以让程序继续执行了。
无论是全日志还是非全日志:当程序断在出口处时,执行下面代码,将最后一点日志下载:
!function (textContent, file_name) {
const blob = new Blob([textContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = file_name;
document.body.appendChild(a);
a.click();
setTimeout(() => { document.body.removeChild(a); window.URL.revokeObjectURL(url); }, 0);
}(window.my_code, `logs${window.my_offset}.txt`);
这样你就拿到了日志。
合并日志
接下来,你要合并日志(pycharm):
import os
for i in range(0, 1000):
n_txt = f'path/logs{i}.txt' # 这个path需要替换
try:
with open(n_txt, 'r', encoding=ut) as r:
data = r.read()
with open('path/log.txt', 'a', encoding=ut) as l: # 这个path同样需要替换
l.write(data)
os.remove(n_txt)
except FileNotFoundError:
break
简化日志
然后简化日志(pycharm):
import os
import json
def readline_txt(file_path) -> list:
with open(file_path, 'r', encoding='utf-8', errors='replace') as file:
d = [line.strip() for line in file.readlines()]
return d
def write_list_txt(file_path_n: str, content_list: list):
with open(file_path_n, 'a', encoding='utf-8') as file:
for content in content_list:
file.write(f'{content}\n')
return
si_log_path = 'path/log.txt' # 替换path
date_lines = readline_txt(si_log_path)
for i in range(len(date_lines)):
if date_lines[i].startswith('d总循环 = '):
t = json.loads(date_lines[i].replace('d总循环 = ', ''))
t_l = []
for ele in t:
if ele:
if hasattr(ele, '__len__'):
if len(ele) < 3000:
t_l.append(ele)
else: # 过滤掉那个巨大的数组
t_l.append('sz7000')
else:
t_l.append(ele)
else:
t_l.append(ele)
date_lines[i] = 'd总循环 = ' + json.dumps(t_l)
write_list_txt('path/log_simplify.txt', date_lines) # 替换path
分析日志
这里就不在在研究SHA1
的算法了感兴趣的小伙伴可以自行研究。
vscode打开日志
无论你拿的是全日志还是非全日志,首先要找到SHA1
的加密结果位置,由于我拿的全日志,我这里是206209行。
将加密的参数命名为param_str
,将加密结果命名为hash_value
,我的param_str
,hash_value
上面已经给出了。
然后另开一个js文件做笔记
翻日志,发现先是检测了环境:
不管他,继续。
好像没用到。继续。
然后好像是取hash_value
的23位:
验证一下:
为什么是23呢?
又取了14位:
14?
找到了:[23, 14, 6, 36, 16, 40, 7, 19]
原来就在眼前
所以这个结果不就是:"5D553A7"
嘛。
接下来是同样的操作,不过是另一个数组:[16, 1, 32, 12, 19, 27, 8, 5]
结果就是"3DC774D8"
。
然后好像在添加数组:
怎么来的呢?不过我调试发现,他是固定的。命名为xorlist
吧:
var xorlist = [89, 39, 179, 150, 218, 82, 58, 252, 177, 52, 186, 123, 120, 64, 242, 133, 143, 161, 121, 179];
然后开始了计算:
13?,16?
继续:
0 * 2 = 0
0 + 1 = 1
208 + 13 = 221
221 ^ 89 = 132 // 89 是xorlist[0]
1 < 20 = true
1 * 2 = 2
6 * 16 = 96 // 6? 又是16
1 * 2 = 2
2 + 1 = 3
96 + 9 = 105
105 ^ 39 = 78 // 39=xorlist[1]
先研究一下:
'DD69685AD37B71DB3E870E35FB14EFE3CC465405'[0]
=D
,parseInt('D',16)=13
13*16=208
16是固定的
'DD69685AD37B71DB3E870E35FB14EFE3CC465405'[1]
=D
,parseInt('D',16)=13
'DD69685AD37B71DB3E870E35FB14EFE3CC465405'[2]
=6
,parseInt('6',16)=6
'DD69685AD37B71DB3E870E35FB14EFE3CC465405'[3]
=9
,parseInt('D',16)=9
明白了吧,并且我也经过调试程序验证了。
接下来就是写程序:
var hexmap = { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15 };
var bytes = [];
for (var i = 0; i < 20; i++) bytes.push((hexmap[hex_hash[2 * i]] * 16 + hexmap[hex_hash[2 * i + 1]]) ^ xorlist[i]); // parseInt()也行,hex_hash就是上面的hash_value
// 得到 bytes=[132, 78, 219, 204, 9, 41, 75, 39, 143, 179, 180, 78, 131, 84, 29, 102, 67, 231, 45, 182]
然后找到了结束的位置:
接下来继续,然后是一堆计算。
0 < 6 = true
0 * 3 = 0
0 * 3 = 0
0 + 1 = 1
0 * 3 = 0
0 + 2 = 2 // 这上面的用不到
132 >> 2 = 33 // bytes[0]=132
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[33]=h
132 & 3 = 0
0 << 4 = 0
78 >> 4 = 4 // bytes[1]=78
0 | 4 = 4
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[4]=E
78 & 15 = 14
14 << 2 = 56
219 >> 6 = 3
56 | 3 = 59
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[59]=7
219 & 63 = 27 // bytes[2]=219
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[27]=b
‘’+ h = “h”
h + E = “hE”
hE + 7 = “hE7”
hE7 + b = “hE7b”
这规律已经很清晰了,但是每次只要bytes的3位,但是bytes一共20位。所以直接看最后的末位处理
6 < 6 = false
45 >> 2 = 11 // bytes[18]=45
45 & 3 = 1
1 << 4 = 16
182 >> 4 = 11 // bytes[19]=182
16 | 11 = 27
182 & 15 = 6
6 << 2 = 24
hE7bzAkpSyePs7ROg1QdZkPn + L = “hE7bzAkpSyePs7ROg1QdZkPnL”
hE7bzAkpSyePs7ROg1QdZkPnL + b = “hE7bzAkpSyePs7ROg1QdZkPnLb”
hE7bzAkpSyePs7ROg1QdZkPnLb + Y = “hE7bzAkpSyePs7ROg1QdZkPnLbY”
上代码:
var base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var sign_mid_ = "";
for (var i = 0; i < 20; i += 3) {
var a = base64chars[bytes[i] >> 2];
var b = base64chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
var c = base64chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
var d = base64chars[bytes[i + 2] & 63];
if (i + 2 == 20) {
var e = base64chars[(bytes[i + 1] & 15) << 2];
sign_mid_ += (a ? a : "") + (b ? b : "") + (e ? e : ""); // 处理最后一位
} else sign_mid_ += (a ? a : "") + (b ? b : "") + (c ? c : "") + (d ? d : ""); // 有可能越界,导致undefined,需要判断一下
}
zzc + 5D553A7 = "zzc5D553A7" zzc5D553A7 + hE7bzAkpSyePs7ROg1QdZkPnLbY = "zzc5D553A7hE7bzAkpSyePs7ROg1QdZkPnLbY" zzc5D553A7hE7bzAkpSyePs7ROg1QdZkPnLbY + 3DC774D8 = "zzc5D553A7hE7bzAkpSyePs7ROg1QdZkPnLbY3DC774D8"
zzc
是固定的,5D553A7
和3DC774D8
前面获取过,hE7bzAkpSyePs7ROg1QdZkPnLbY
是刚才生成的。
最终实现
ok,分析完毕,写代码:
SHA1
算法加密应该很容易获得,因此最终代码将省略SHA1
加密实现过程
function get_qq_sign(param_str_n) {
var xorlist = [89, 39, 179, 150, 218, 82, 58, 252, 177, 52, 186, 123, 120, 64, 242, 133, 143, 161, 121, 179];
var base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var hexmap = { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15 };
function get_hex_hash() {
var hex_hash = SHA1(param_str_n).toString().toUpperCase();
return hex_hash;
}
function get_sign_head(hex_hash) {
var sign_head_ = "";
for (var i = 0; i < 8; i++) {
var temp = hex_hash[[23, 14, 6, 36, 16, 40, 7, 19][i]];
if (temp) sign_head_ += temp;
}
return sign_head_;
}
function get_sign_mid(hex_hash) {
var bytes = [], sign_mid_ = "";
for (var i = 0; i < 20; i++) bytes.push((hexmap[hex_hash[2 * i]] * 16 + hexmap[hex_hash[2 * i + 1]]) ^ xorlist[i]);
for (var i = 0; i < 20; i += 3) {
var a = base64chars[bytes[i] >> 2];
var b = base64chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
var c = base64chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
var d = base64chars[bytes[i + 2] & 63];
if (i + 2 == 20) {
var e = base64chars[(bytes[i + 1] & 15) << 2];
sign_mid_ += (a ? a : "") + (b ? b : "") + (e ? e : "");
} else sign_mid_ += (a ? a : "") + (b ? b : "") + (c ? c : "") + (d ? d : "");
}
return sign_mid_;
}
function get_sign_tail(hex_hash) {
var sign_tail_ = "";
for (var i = 0; i < 8; i++) sign_tail_ += hex_hash[[16, 1, 32, 12, 19, 27, 8, 5][i]];
return sign_tail_;
}
hash_value = get_hex_hash();
sign_ = `zzc${get_sign_head(hash_value)}${get_sign_mid(hash_value)}${get_sign_tail(hash_value)}`.toLowerCase();
return sign_;
}
总结
这个纯算需要你对常用的加密算法熟悉,但总体来说不是太难。
如果有更好的方案,欢迎评论区留言。
本文仅用于学习交流,勿他用。
祝大家学习愉快 ☺️
python&爬虫逆向交流群:dzEzMDg3MDk4NTU5