本贴中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关。
一、前言
要获取网络上免费站的音视频真实地址的方法其实有很多,比如直接用IDman插件嗅探,或者抓包缓存等。
但是!!!这只是单个音频地址的获取方法,而且不一定有效,比如音频地址使用异步动态加载,甚至是加密包装过的,使用嗅探工具不一定能嗅探到。
而且!!!如果想要批量获取,就必须了解这个音频真实地址的产生过程,这个过程就是解析(逆向)其算法过程。
本次解析目标就是一个解析算法的过程。
我也是小白,对JS一窍不通,有错漏之处望海涵。
大佬见笑了。
二、目标地址
aHR0cDovL3d3dy55dWV0aW5nYmEuY24vYm9vay9kZXRhaWwvM2ExODAxNzQtN2I4Yi05ZWVhLWUwNmYtMzk0NmQ5Y2E5NmYwLzA=
三、解析思路
1、通过HTML源码 -> 直接搜索关键词
通过Ctrl + U查看HTML源码 Ctrl + F搜索关键词 .mp3 或.m4a 找到明文地址,如以下这种:

m4a明文.jpg (49.57 KB, 下载次数: 0)
下载附件
m4a明文
2025-5-29 15:54 上传
如果搜索关键词
.mp3
或
.m4a
没能找到,则试着搜索
var now
或
var next
,找到以下这种,一般都是被加密或Base64转码了。

星号.jpg (42.82 KB, 下载次数: 0)
下载附件
星号
2025-5-29 15:55 上传
如果是这种加“
*
”号的,可以使用这个方法解析获得明文地址:
// 可自行转成Python的方法;
function FonHen_JieMa(u) {
var a = u.split("*");
var b = '';
for (var i = 1, n = a.length; i
2、找到对应API接口分析
如果在HTML源码并没有找到疑似加密或明文的音频真实地址,大概率就是通过API接口请求了。
有些通过API接口可以直接返回明文真实地址,比如某听书网:

明文api.jpg (36.25 KB, 下载次数: 0)
下载附件
明文api
2025-5-29 15:54 上传
还有一些API接口返回的是经过加密的地址,需要二次解密,比如这一次的yue听巴网:

yuetingba api.jpg (45.77 KB, 下载次数: 0)
下载附件
yuetingba api
2025-5-29 16:01 上传
四、开始对目标地址解析全过程
1、发现有防调试机制 -> 可利用脚本破除控制台检测
比如这个油猴脚本:aHR0cHM6Ly9ncmVhc3lmb3JrLm9yZy96aC1DTi9zY3JpcHRzLzUyMzc5Mi3mtY/op4jlmajmjqfliLblj7DpmLLmo4DmtYs=(需自行将目标站点添加进包含规则@match)

控制台.jpg (63.43 KB, 下载次数: 0)
下载附件
控制台
2025-5-29 16:39 上传
2、打开开发者工具 -> 查找API接口
切换到“网络”标签选项卡 ->重载刷新(F5)当前网页 -> 随意点播放一个音频章节 -> 找到可疑的api接口地址“
ting-with-efi
”:

找到API.jpg (171.63 KB, 下载次数: 0)
下载附件
ting-with-efi
2025-5-29 17:00 上传
这个API接口地址:3a1801e1-74ec-871b-6d8b-68bce50caf01/ting-with-efi" target="_blank" rel="noopener noreferrer nofollow">http://www.yue听巴.cn/api/app/docs-listen/
3a1801e1-74ec-871b-6d8b-68bce50caf01
/ting-with-efi 并没有负载,而且请求头也没有签名Token。
不需要分析,可以肯定这是根据请求地址上
3a1801e1-74ec-871b-6d8b-68bce50caf01
这个ID来确定区分章节的,直接在HTML源代码搜索验证了猜想:

id章节来源.jpg (26.9 KB, 下载次数: 0)
下载附件
id章节来源
2025-5-29 17:09 上传
而且通过替换这个ID,API返回的是不同的
efi
字段(疑似加密的真实音频地址)。
3、解密真实地址 -> 查找解密函数逻辑链
接下来也就是分析api返回的efi字段的值具体是什么了,看看到底是不是音频地址。
efi
”这个字段,看看它生成逻辑。

大海捞针.jpg (222.76 KB, 下载次数: 0)
下载附件
大海捞针
2025-5-29 17:37 上传

启动器.jpg (46.28 KB, 下载次数: 0)
下载附件
启动器
2025-5-29 17:32 上传
initGetData
“这个调用栈名称是关于初始化数据包的,我们直接从这一个点进去(以后分析推荐直接从启动器入手)。

data.jpg (51.02 KB, 下载次数: 0)
下载附件
data
2025-5-29 17:33 上传
initAudio()
,一看就是关于音频初始化的。

get方法.jpg (96.6 KB, 下载次数: 0)
下载附件
get
2025-5-29 17:54 上传

需要找到vs方法.jpg (143.86 KB, 下载次数: 0)
下载附件
需要找到vs
2025-5-29 18:06 上传
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"))中各传递参数:
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 4; i--) {
const r = e.charCodeAt(0) + Number(t[i - 1]);
n += String.fromCharCode(r)
}
return n
}
}
某AI助手分析过程:

Snipaste_2025-05-29_20-18-55.jpg (80.6 KB, 下载次数: 0)
下载附件
2025-5-29 20:20 上传

Snipaste_2025-05-29_20-27-30.jpg (57.84 KB, 下载次数: 0)
下载附件
2025-5-29 20:31 上传
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) + int(t)
n += chr(r)
for i in range(20, len(e)):
r = ord(e) + 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) + 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
验证地址真实有效

2025-05-29_204456.jpg (227.93 KB, 下载次数: 0)
下载附件
2025-5-29 20:49 上传