qq音乐sign参数纯算逆向

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.chunkjs文件有两处地方,但大体一样:

// 第一个
-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)
}

只是BT函数的区别,观察程序,sign: i,而i = "GET" === t.type.toUpperCase() ? o(t.data.data) : o(t.data),,由于携带sign参数的数据包的请求方式都是POST,所以i=o(t.data),所以在o(t.data)前打上断点,BT两处都要打,刷新网页,发现程序断在了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_strhash_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]=DparseInt('D',16)=13
13*16=208 16是固定的
'DD69685AD37B71DB3E870E35FB14EFE3CC465405'[1]=DparseInt('D',16)=13
'DD69685AD37B71DB3E870E35FB14EFE3CC465405'[2]=6parseInt('6',16)=6
'DD69685AD37B71DB3E870E35FB14EFE3CC465405'[3]=9parseInt('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是固定的,5D553A73DC774D8前面获取过,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

### 抓取QQ音乐数据的方法 由于QQ音乐官方并未提供开放的API接口用于直接获取其数据[^1],因此如果需要抓取QQ音乐的相关数据(如排行榜、歌曲信息等),通常会依赖于网络爬虫技术来完成。以下是关于如何通过爬虫技术抓取QQ音乐数据的具体方法。 #### 数据源分析 目前QQ音乐的数据请求已由早期的JSONP方式改为基于XHR的Ajax请求形式[^2]。这意味着所有的数据交互都发生在客户端与服务器之间,而这些请求可以通过浏览器开发者工具中的Network面板捕获并解析。 #### Python爬虫实现过程 为了模拟真实的HTTP请求行为,可以利用`requests`库发起GET/POST请求,并结合`json`模块处理返回的结果。以下是一个简单的Python脚本示例: ```python import requests import json import time headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } def fetch_song_url(song_mid): url1 = "https://u.y.qq.com/cgi-bin/musicu.fcg" params1 = { '_': int(time.time() * 1000), 'sign': 'zzakgej75pk8w36d82032784bdb9204d99bf6351acb7d', "data": '{“req”:{“module”:“vkey.GetVkeyServer”,“method”:“CgiGetVkey”,“param”:{“guid”:“7469768631”,“songmid":["' + song_mid + '"],“songtype”:[0],“uin”:“1164153961”,“loginflag”:1,“platform”:“20”}}}' } response = requests.get(url1, params=params1, headers=headers) data = response.json() purl = data['req']['data']['midurlinfo'][0]['purl'] return f"https://isure.stream.qqmusic.qq.com/{purl}" if __name__ == "__main__": song_mid = input("请输入歌曲的SongMid: ") download_link = fetch_song_url(song_mid) print(f"下载链接为: {download_link}") ``` 上述代码实现了根据指定的`songsong_mid`值生成对应的歌曲播放地址的功能[^3]。需要注意的是,在实际应用过程中可能还需要动态调整某些固定参数(如时间戳、签名字符串等)以适应目标网站的变化。 #### 注意事项 尽管存在多种途径能够帮助我们成功抓取到所需的信息,但在操作前务必确认该行为是否违反了服务提供商的服务条款以及当地法律法规的要求。此外,频繁的大规模访问可能会触发反爬机制导致IP被封禁等问题发生[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值