现在!很多的网站使用Webpack加载和处理JS文件。所以对于使用了Webpack加载的JS代码,一旦它们被打包并在浏览器中执行,通常是难以直接阅读和理解的,因为Webpack会将其压缩、混淆和优化
本文将分享最新的QQ音乐sign参数加密分析。QQ音乐的sign加密目前主要是由webpack生成并通过加载器导出!感兴趣的也可以看看我之前发布的一些关于webpack分析文章
分析目标:
aHR0cHM6Ly95LnFxLmNvbS9uL3J5cXEvc2VhcmNoP3c9JUU3JTlDJTlGJUU3JTlBJTg0JUU3JTg4JUIxJUU0JUJEJUEw
可以看到如下请求提交了两个参数,一个时间戳一个就是加密的sign。先全局搜索一下sign,如下出现两个结果:
点击JS文件进去,找到sign的位置(可以继续在JS内搜索分析)打上断点。断点之后我们重新提交搜索请求,根据断点调试来到下图,可以看到i出来后就是sign。它是经过o函数就变为了加密内容。o()就是加密方法。而这里o又等于n(350).default
o = n(350).default是webpack的调用形式,n是加载器,鼠标移到n点击跳转将webpack的代码先整个扣下来:
上面我们找到了加载器的webpack代码,还有一个就是加载器的具体方法。这个我们也需要扣出来放到加载器中导入:
加载器的webpack代码一共是300多行,而需要加载的具体方法vendor.chunk的文件代码很庞大!至于有多大家可以看下图(2W+行代码),这里我把它扣下来存为qq.js,等待后续加载器的导入使用:
加载器跟加载方法的所有JS代码我们拿到本地编辑器。可以看到这个JS代码直接扣下来是不能用起来的,分析代码可以看到(window、location、navigator)这些都是缺失的!测试的时候自己发现缺什么就补~
上图按照浏览器的环境把location、navigator补上,另外一定自己要看代码分析,f它是加载器,这里也是用全局变量接收的,最终加载器扣下来完善后的最终代码如下所示(require(‘./qq’)这一行代码就是加载方法的所有代码):
// 补环境
window = global;
location = {
"ancestorOrigins": {},
"href": "https://y.qq.com/n/ryqq/search?w=%E7%9C%9F%E7%9A%84%E7%88%B1%E4%BD%A0&t=song&remoteplace=txt.yqq.top",
"origin": "https://y.qq.com",
"protocol": "https:",
"host": "y.qq.com",
"hostname": "y.qq.com",
"port": "",
"pathname": "/n/ryqq/search",
"search": "?w=%E7%9C%9F%E7%9A%84%E7%88%B1%E4%BD%A0&t=song&remoteplace=txt.yqq.top",
"hash": ""
}
navigator = {
appCodeName: "Mozilla",
appName: "Netscape",
appVersion: "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",}
var loader;
require('./qq') // 加载方法代码,需要扣出来
!function(e) {
function t(t) {
for (var a, n, f = t[0], d = t[1], i = t[2], l = 0, u = []; l < f.length; l++)
n = f[l],
Object.prototype.hasOwnProperty.call(o, n) && o[n] && u.push(o[n][0]),
o[n] = 0;
for (a in d)
Object.prototype.hasOwnProperty.call(d, a) && (e[a] = d[a]);
for (b && b(t); u.length; )
u.shift()();
return c.push.apply(c, i || []),
r()
}
function r() {
for (var e, t = 0; t < c.length; t++) {
for (var r = c[t], a = !0, n = 1; n < r.length; n++) {
var d = r[n];
0 !== o[d] && (a = !1)
}
a && (c.splice(t--, 1),
e = f(f.s = r[0]))
}
return e
}
var a = {}
, n = {
20: 0
}
, o = {
20: 0
}
, c = [];
function f(t) {
if (a[t])
return a[t].exports;
var r = a[t] = {
i: t,
l: !1,
exports: {}
};
return e[t].call(r.exports, r, r.exports, f),
r.l = !0,
r.exports
}
f.e = function(e) {
var t = [];
n[e] ? t.push(n[e]) : 0 !== n[e] && {
1: 1,
3: 1,
4: 1,
5: 1,
6: 1,
7: 1,
8: 1,
9: 1,
10: 1,
11: 1,
12: 1,
13: 1,
14: 1,
15: 1,
16: 1,
17: 1,
18: 1,
19: 1,
21: 1,
22: 1,
23: 1,
24: 1,
25: 1
}[e] && t.push(n[e] = new Promise((function(t, r) {
for (var a = "css/" + ({
1: "common",
3: "album",
4: "albumDetail",
5: "album_mall",
6: "category",
7: "cmtpage",
8: "index",
9: "msg_center",
10: "mv",
11: "mvList",
12: "mv_toplist",
13: "notfound",
14: "player",
15: "player_radio",
16: "playlist",
17: "playlist_edit",
18: "profile",
19: "radio",
21: "search",
22: "singer",
23: "singer_list",
24: "songDetail",
25: "toplist"
}[e] || e) + "." + {
1: "2e3d715e72682303d35b",
3: "5cf0d69eaf29bcab23d2",
4: "798353db5b0eb05d5358",
5: "df4c243f917604263e58",
6: "20d532d798099a44bc88",
7: "e3bedf2b5810f8db0684",
8: "ea0adb959fef9011fc25",
9: "020422608fe8bfb1719a",
10: "8bdb1df6c5436b790baa",
11: "47ce9300786df1b70584",
12: "4aee33230ba2d6b81dce",
13: "e6f63b0cf57dd029fbd6",
14: "1d2dbefbea113438324a",
15: "d893492de07ce97d8048",
16: "9484fde660fe93d9f9f0",
17: "67fb85e7f96455763c83",
18: "5e8c651e74b13244f7cf",
19: "3befd83c10b19893ec66",
21: "b2d11f89ea6a512a2302",
22: "c7a38353c5f4ebb47491",
23: "df0961952a2d3f022894",
24: "4c080567e394fd45608b",
25: "8edb142553f97482e00f"
}[e] + ".chunk.css?max_age=2592000", o = f.p + a, c = document.getElementsByTagName("link"), d = 0; d < c.length; d++) {
var i = (b = c[d]).getAttribute("data-href") || b.getAttribute("href");
if ("stylesheet" === b.rel && (i === a || i === o))
return t()
}
var l = document.getElementsByTagName("style");
for (d = 0; d < l.length; d++) {
var b;
if ((i = (b = l[d]).getAttribute("data-href")) === a || i === o)
return t()
}
var u = document.createElement("link");
u.rel = "stylesheet",
u.type = "text/css",
u.onload = t,
u.onerror = function(t) {
var a = t && t.target && t.target.src || o
, c = new Error("Loading CSS chunk " + e + " failed.\n(" + a + ")");
c.code = "CSS_CHUNK_LOAD_FAILED",
c.request = a,
delete n[e],
u.parentNode.removeChild(u),
r(c)
}
,
u.href = o,
0 !== u.href.indexOf(window.location.origin + "/") && (u.crossOrigin = "anonymous"),
document.getElementsByTagName("head")[0].appendChild(u)
}
)).then((function() {
n[e] = 0
}
)));
var r = o[e];
if (0 !== r)
if (r)
t.push(r[2]);
else {
var a = new Promise((function(t, a) {
r = o[e] = [t, a]
}
));
t.push(r[2] = a);
var c, d = document.createElement("script");
d.charset = "utf-8",
d.timeout = 120,
f.nc && d.setAttribute("nonce", f.nc),
d.src = function(e) {
return f.p + "js/" + ({
1: "common",
3: "album",
4: "albumDetail",
5: "album_mall",
6: "category",
7: "cmtpage",
8: "index",
9: "msg_center",
10: "mv",
11: "mvList",
12: "mv_toplist",
13: "notfound",
14: "player",
15: "player_radio",
16: "playlist",
17: "playlist_edit",
18: "profile",
19: "radio",
21: "search",
22: "singer",
23: "singer_list",
24: "songDetail",
25: "toplist"
}[e] || e) + ".chunk." + {
1: "aabe67026a2f3f407781",
3: "57adeab72a3ec5a6940c",
4: "fb9a0df49aac1081fd8b",
5: "ce88bd122dac655490ca",
6: "3c9e9ce78ed1a42f485f",
7: "ca787dcf6db83f6a4d11",
8: "306c7ad24389b8338e1f",
9: "b5cd5a77da26782764c2",
10: "b9b35726ef5f56f87151",
11: "c925c75c1a05b9bd0958",
12: "b2bfa82b42b9bec98cca",
13: "e8b9a6dad95b623cab82",
14: "16f4f61d208558a7c17c",
15: "7264ef7ff4b6052f4c01",
16: "039db3b85d472916677b",
17: "72dfb28846b85bcce963",
18: "7f8ac1ee0d0077c0d960",
19: "89e9600c87d40494d2a0",
21: "157aa8a8c67606f0d243",
22: "07c7b21c4be040114330",
23: "d0f42e091f31e2cd3a85",
24: "4e38573b87120e0a3b0a",
25: "08f75a75f2455ab4c49f"
}[e] + ".js?max_age=2592000"
}(e),
0 !== d.src.indexOf(window.location.origin + "/") && (d.crossOrigin = "anonymous");
var i = new Error;
c = function(t) {
d.onerror = d.onload = null,
clearTimeout(l);
var r = o[e];
if (0 !== r) {
if (r) {
var a = t && ("load" === t.type ? "missing" : t.type)
, n = t && t.target && t.target.src;
i.message = "Loading chunk " + e + " failed.\n(" + a + ": " + n + ")",
i.name = "ChunkLoadError",
i.type = a,
i.request = n,
r[1](i)
}
o[e] = void 0
}
}
;
var l = setTimeout((function() {
c({
type: "timeout",
target: d
})
}
), 12e4);
d.onerror = d.onload = c,
document.head.appendChild(d)
}
return Promise.all(t)
}
,
f.m = e,
f.c = a,
f.d = function(e, t, r) {
f.o(e, t) || Object.defineProperty(e, t, {
enumerable: !0,
get: r
})
}
,
f.r = function(e) {
"undefined" !== typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
value: "Module"
}),
Object.defineProperty(e, "__esModule", {
value: !0
})
}
,
f.t = function(e, t) {
if (1 & t && (e = f(e)),
8 & t)
return e;
if (4 & t && "object" === typeof e && e && e.__esModule)
return e;
var r = Object.create(null);
if (f.r(r),
Object.defineProperty(r, "default", {
enumerable: !0,
value: e
}),
2 & t && "string" != typeof e)
for (var a in e)
f.d(r, a, function(t) {
return e[t]
}
.bind(null, a));
return r
}
,
f.n = function(e) {
var t = e && e.__esModule ? function() {
return e.default
}
: function() {
return e
}
;
return f.d(t, "a", t),
t
}
,
f.o = function(e, t) {
return Object.prototype.hasOwnProperty.call(e, t)
}
,
f.p = "/ryqq/",
f.oe = function(e) {
throw e
}
;
var d = window.webpackJsonp = window.webpackJsonp || []
, i = d.push.bind(d);
d.push = t,
d = d.slice();
for (var l = 0; l < d.length; l++)
t(d[l]);
var b = i;
r()
loader = f;
}([]);
// 加密调用函数
function get_sign(data) {
let o = loader(350).default;
return o(data)
将webpack的加载器JS代码跟加载方法JS代码全部扣下来后,基本上整个加密的流程就出来了,现在我们编写一下脚本来验证一下效果,脚本内容如下:
# -*- coding: utf-8 -*-
# 测试请换自己的cookie
cookies = {
'sd_userid': '2391663752714217',
'pgv_pvid': '5766421817',
'pac_uid': '0_9a2e8dd1c0852',
'RK': 'jiHhTPl9aT',
'ptcz': '1e1eb706edac3e0c76ab95a69f550b99fcdec80366b7f8c358d56f0fc0979946',
'fqm_pvqid': '40c52bcd-87c7-4c72-a26c-27e8707c872c',
'fqm_sessionid': '8dd996ec-c839-42c8-9ca3-d6851c420d58',
'pgv_info': 'ssid=s1216838840',
'ts_uid': '1699674176',
'qm_keyst': 'W_X_5NTcvfg7hswI_XeeT1UL6iqZLxWEjnqblAPOLhMHc19EhzmgRaHfQcyATA3BOkNjn7mBtZxDrgTg',
'wxuin': '1152921504767067454',
'psrf_qqunionid': '',
'psrf_qqrefresh_token': '',
'wxunionid': 'oqFLxsgm67UNDPWQJrsB5zAagvAs',
'psrf_qqopenid': '',
'qqmusic_key': 'W_X_5NTcvfg7hswI_XeeT1UL6iqZLxWEjnqblAPOLhMHc19EhzmgRaHfQcyATA3BOkNjn7mBtZxDrgTg',
'wxopenid': 'opCFJwysEgSAIr0m0m1bcSARkUAg',
'tmeLoginType': '1',
'psrf_qqaccess_token': '',
'wxrefresh_token': '72_LC_VaHu3AQw7rraX_0r5Kw16fCtvHWtO5Qpli1bdCJBtkNnDeAoX6fDYGBDEpalnPYAAs8U9HTsfrleIMkK3ES25lJ1KNYhYIsRpSMK_Fj8',
'euin': 'oK6kowEAoK4z7eSs7ins7ivk7n**',
'login_type': '2',
'ts_last': 'y.qq.com/n/ryqq/search'
}
headers = {
'authority': 'u.y.qq.com',
'accept': 'application/json',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'content-type': 'application/x-www-form-urlencoded',
'origin': 'https://y.qq.com',
'pragma': 'no-cache',
'referer': 'https://y.qq.com/',
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
}
# 直接浏览器拷出来
data = '{"comm":{"cv":4747474,"ct":24,"format":"json","inCharset":"utf-8","outCharset":"utf-8","notice":0,"platform":"yqq.json","needNewCode":1,"uin":"1152921504767067454","g_tk_new_20200303":451075320,"g_tk":451075320},"req_1":{"method":"DoSearchForQQMusicDesktop","module":"music.search.SearchCgiService","param":{"remoteplace":"txt.yqq.top","searchid":"65805105264064724","search_type":0,"query":"海阔天空","page_num":1,"num_per_page":10}}}'
# 获取sign加密参数
with open("./加载器.js") as f: # 上面加载器的JS文件
js_code = f.read()
time_str = round(time.time() * 1000)
sign = execjs.compile(js_code).call("get_sign", data)
print(YELLOW % 'sign: {}'.format(sign))
params = {
'_': time_str,
'sign': sign,
}
response = requests.post('https://u.y.qq.com/cgi-bin/musics.fcg', params=params, cookies=cookies, headers=headers,
data=data.encode()).json()
print(response)
验证效果如下所示:
最后说一句,GPT出来后确实为我们开发者省去了很多曾经繁琐的工作,像上面的Python验证脚本就是让GPT帮我写的,真好~
好了,到这里又到了跟大家说再见的时候了。创作不易,帮忙点个赞再走吧。你的支持是我创作的动力,希望能带给大家更多优质的文章