免责声明:
本贴中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关。
一、前言
要获取网络上免费站的音视频真实地址的方法其实有很多,比如直接用IDman插件嗅探,或者抓包缓存等。
但是!!!这只是单个音频地址的获取方法,而且不一定有效,比如音频地址使用异步动态加载,甚至是加密包装过的,使用嗅探工具不一定能嗅探到。
而且!!!如果想要批量获取,就必须了解这个音频真实地址的产生过程,这个过程就是解析(逆向)其算法过程。
本次解析目标就是一个解析算法的过程。
我也是小白,对JS一窍不通,有错漏之处望海涵。
大佬见笑了。
二、目标地址
aHR0cDovL3d3dy55dWV0aW5nYmEuY24vYm9vay9kZXRhaWwvM2ExODAxNzQtN2I4Yi05ZWVhLWUwNmYtMzk0NmQ5Y2E5NmYwLzA=
三、解析思路
1、通过HTML源码 -> 直接搜索关键词
如果是这种加“*”号的,可以使用这个方法解析获得明文地址:
// 可自行转成Python的方法;
function FonHen_JieMa(u) {
var a = u.split("*");
var b = '';
for (var i = 1, n = a.length; i < n; i++) {
b += String.fromCharCode(a[i])
}
return b
}
2、找到对应API接口分析
如果在HTML源码并没有找到疑似加密或明文的音频真实地址,大概率就是通过API接口请求了。
四、开始对目标地址解析全过程
1、发现有防调试机制 -> 可利用脚本破除控制台检测
- 首先,经过关键词搜索尝试之后,发现这个站点的音频真实地址并没有加载在静态的HTML源码中
- 然后通过F12 或手动打开浏览器开发者工具,发现被秒关闭调试网页,说明该站点有防御控制台打开的反调试机制。
- 猜测可能是检测控制台被打开然后触发反调试,这个简单,我们可以通过开启“控制台防检测”脚本或工具,以绕过这种反调试机制。
比如这个油猴脚本:aHR0cHM6Ly9ncmVhc3lmb3JrLm9yZy96aC1DTi9zY3JpcHRzLzUyMzc5Mi3mtY/op4jlmajmjqfliLblj7DpmLLmo4DmtYs=([i]需自行将目标站点添加进包含规则@match)
2、打开开发者工具 -> 查找API接口
4、解析函数算法-> 找到关键算法逻辑方法
上一步发现vs函数才是解码地址的关键,完整的明文地址实际上就是服务器地址 'http://36.5.86.202:50010‘ 与 vs.d(e.currentPlayInfo.ef, n, i.padEnd(20, "0")) 返回的结果进行拼接。
js关键代码片段:
const n = e.currentPlayInfo.tingId.replaceAll("-", "")
, i = e.currentPlayInfo.creationTime.replaceAll("-", "").replaceAll(":", "").replaceAll("T", "").replaceAll(".", "").replaceAll(" ", "")
, r = vs.d(e.currentPlayInfo.ef, n, i.padEnd(20, "0"))
, o = e.tingDefaultData.audioServer.url + r;
api返回的响应数据:
{
"id": "3a1801e1-74ec-871b-6d8b-68bce50caf01",
"bookId": "3a180174-7b8b-9eea-e06f-3946d9ca96f0",
"tingNo": 2,
"title": "0002_避难所",
"efi": "30Bf6+rGEZSFx+73P01z67iEDCBtOZzjkC4E2ENX2g7VwllOb/MS4GO5SMb7ug8rdcpKiGpARuRzHYPuqp+mHROrNemNftGuVT7QW6G3Nyjjc2MRRg+NLyRACPORkhoCbTl3WiHAWrVCyYIev2UZ1g==",
"creationTime": "2025-02-10T19:52:58.873983"
}
先别急,结合代码片段,需要分析vs.d(e.currentPlayInfo.ef, n, i.padEnd(20, "0"))中各传递参数:
- 参数e.currentPlayInfo.ef ,很容易就猜到是当前API请求响应中efi字段加密的Base64字符串值。
- 参数n:根据n = e.currentPlayInfo.tingId.replaceAll("-", ""),就是API响应的章节id字段的值经过去除”-“号的结果(不是bookId)。
- 参数:i.padEnd(20, "0"),就是i = e.currentPlayInfo.creationTime.replaceAll("-", "").replaceAll(":", "").replaceAll("T", "").replaceAll(".", "").replaceAll(" ", ""),翻译过来就是根据API响应的creationTime,去掉”-“连接号,去掉”:"号,去掉"T",去掉”.“号,去掉空格 -> 即"20250210195258873983",然后经过padEnd(20, "0"),我没学过js,但可以问AI助手啊:
str.padEnd(targetLength [, padString]) 即根据"20250210195258873983"填充后的长度为20,不够则右侧补0,最终结果还是i.padEnd(20, "0") -> "20250210195258873983"
所以,vs.d(e.currentPlayInfo.ef, n, i.padEnd(20, "0"))实际上就是 -> vs.d(efi, id, creationTime)) // id、creationTime都需要按照上面提到的逻辑处理
5、定位并重现关键函数方法 -> 最终得到明文解密
5.1理清传递参数之后,全局搜索找到vs这个函数或声明方法
发现是vs = new class{}声明,而且在下方还看到了定义的Base64解密字符集、AES,以及d的方法。
我是js小白,看不懂怎么办?
没关系,使用复制-粘贴大法,写好自然语言指令,把涉及到的相关代码一股脑丢给AI助手:
已知API返回响应数据:
{
"id": "3a1801e1-74ec-871b-6d8b-68bce50caf01",
"bookId": "3a180174-7b8b-9eea-e06f-3946d9ca96f0",
"tingNo": 2,
"title": "0002_避难所",
"efi": "30Bf6+rGEZSFx+73P01z67iEDCBtOZzjkC4E2ENX2g7VwllOb/MS4GO5SMb7ug8rdcpKiGpARuRzHYPuqp+mHROrNemNftGuVT7QW6G3Nyjjc2MRRg+NLyRACPORkhoCbTl3WiHAWrVCyYIev2UZ1g==",
"creationTime": "2025-02-10T19:52:58.873983"
}
efi的解密方法为vs.d(efi, id.tingId.replaceAll("-", ""), creationTime.padEnd(20, "0")) // 注意其中"id": "3a1801e1-74ec-871b-6d8b-68bce50caf01",非bookid
其中vs.d相关代码如下,请写出整个解密efi值的过程(步骤),最后转为python的方法:
, vs = new class {
constructor() {
this.k = pi.enc.Base64.parse("le95G3hnFDJsBE+1/v9eYw=="),
this.i = pi.enc.Base64.parse("IvswQFEUdKYf+d1wKpYLTg=="),
this.as = 43,
this.ae = 116
}
e(e, t, n) {
const i = this.gk(t, n)
, r = this.gi(t, n);
this.k = pi.enc.Base64.parse(i),
this.i = pi.enc.Base64.parse(r);
const o = pi.enc.Utf8.parse(e);
return pi.AES.encrypt(o, this.k, {
iv: this.i,
mode: pi.mode.CBC,
padding: pi.pad.Pkcs7
}).ciphertext.toString(pi.enc.Base64)
}
d(e, t, n) {
const i = this.gk(t, n)
, r = this.gi(t, n);
this.k = pi.enc.Base64.parse(btoa(i)),
this.i = pi.enc.Base64.parse(btoa(r)),
e = (e + "").replace(/\n*$/g, "").replace(/\n/g, "");
const o = pi.enc.Base64.parse(e)
, a = pi.enc.Base64.stringify(o);
return pi.AES.decrypt(a, this.k, {
iv: this.i,
mode: pi.mode.CBC,
padding: pi.pad.Pkcs7
}).toString(pi.enc.Utf8).toString()
}
gk(e, t) {
let n = "";
for (let i = 0; i < 20; i++) {
const r = e[i].charCodeAt(0) + Number(t[i]);
n += String.fromCharCode(r)
}
for (let i = 20; i < e.length; i++) {
const r = e[i].charCodeAt(0) + Number(t[i - 20]);
n += String.fromCharCode(r)
}
return n
}
gi(e, t) {
let n = "";
for (let i = 20; i > 4; i--) {
const r = e[i].charCodeAt(0) + Number(t[i - 1]);
n += String.fromCharCode(r)
}
return n
}
}
某AI助手分析过程:
5.2最终AI助手转成Python的等价方法:
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
class VS:
def __init__(self):
self.k = base64.b64decode("le95G3hnFDJsBE+1/v9eYw==")
self.i = base64.b64decode("IvswQFEUdKYf+d1wKpYLTg==")
def gk(self, e: str, t: str) -> str:
n = ''
for i in range(20):
r = ord(e[i]) + int(t[i])
n += chr(r)
for i in range(20, len(e)):
r = ord(e[i]) + int(t[i - 20])
n += chr(r)
return n
def gi(self, e: str, t: str) -> str:
n = ''
for i in range(20, 4, -1):
r = ord(e[i]) + int(t[i - 1])
n += chr(r)
return n
def d(self, efi: str, ting_id: str, creation_time: str) -> str:
e = ting_id.replace('-', '')
t = creation_time.replace("-", "").replace(":", "").replace("T", "").replace(".", "").replace(" ", "")
t = t.ljust(20, '0')
key_str = self.gk(e, t)
iv_str = self.gi(e, t)
key = base64.b64encode(key_str.encode())
iv = base64.b64encode(iv_str.encode())
key_bytes = base64.b64decode(key)
iv_bytes = base64.b64decode(iv)
# clean and decode efi
efi_clean = efi.replace('\n', '').rstrip()
encrypted_data = base64.b64decode(efi_clean)
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
decrypted = cipher.decrypt(encrypted_data)
try:
return unpad(decrypted, AES.block_size).decode('utf-8')
except ValueError:
return "[解密失败] Padding error 或 key/iv 错误"
# 示例用法
vs = VS()
efi = "30Bf6+rGEZSFx+73P01z67iEDCBtOZzjkC4E2ENX2g7VwllOb/MS4GO5SMb7ug8rdcpKiGpARuRzHYPuqp+mHROrNemNftGuVT7QW6G3Nyjjc2MRRg+NLyRACPORkhoCbTl3WiHAWrVCyYIev2UZ1g=="
ting_id = "3a1801e1-74ec-871b-6d8b-68bce50caf01"
creation_time = "2025-02-10T19:52:58.873983"
result = vs.d(efi, ting_id, creation_time)
print("解码结果:", result)
5.3验证
打印结果:
解码结果: /myfiles/host/listen/听书目录/黄金召唤师~醉虎~紫襟剧社/e804fa07a1bb47d8835da153c2643c2c.m4a
与上述提到的服务器地址 'http://36.5.86.202:50010‘ 拼接,就是http://36.5.86.202:50010/myfiles/host/listen/听书目录/黄金召唤师~醉虎~紫襟剧社/e804fa07a1bb47d8835da153c2643c2c.m4a
验证地址真实有效