.:. 草榴社區 » 技術討論區 » [技术文章转载][Web逆向] 保姆式解析某高质量听书网站音频地址全过程(适合小白练手)
--> 本頁主題: [技术文章转载][Web逆向] 保姆式解析某高质量听书网站音频地址全过程(适合小白练手) 字體大小 寬屏顯示 只看樓主 最新點評 熱門評論 時間順序
吾爱干货


級別:新手上路 ( 8 )
發帖:157
威望:28 點
金錢:1366 USD
貢獻:0 點
註冊:2022-01-10


[技术文章转载][Web逆向] 保姆式解析某高质量听书网站音频地址全过程(适合小白练手)



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

如果搜索关键词 .mp3 或 .m4a 没能找到,则试着搜索 var now 或 var next,找到以下这种,一般都是被加密或Base64转码了。
如果是这种加“*”号的,可以使用这个方法解析获得明文地址:
複製代碼
 // 可自行转成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接口请求了。
    有些通过API接口可以直接返回明文真实地址,比如某听书网:
    还有一些API接口返回的是经过加密的地址,需要二次解密,比如这一次的yue听巴网:

    四、开始对目标地址解析全过程
    1、发现有防调试机制 -> 可利用脚本破除控制台检测
      首先,经过关键词搜索尝试之后,发现这个站点的音频真实地址并没有加载在静态的HTML源码中然后通过F12 或手动打开浏览器开发者工具,发现被秒关闭调试网页,说明该站点有防御控制台打开的反调试机制。猜测可能是检测控制台被打开然后触发反调试,这个简单,我们可以通过开启“控制台防检测”脚本或工具,以绕过这种反调试机制。
      比如这个油猴脚本:aHR0cHM6Ly9ncmVhc3lmb3JrLm9yZy96aC1DTi9zY3JpcHRzLzUyMzc5Mi3mtY/op4jlmajmjqfliLblj7DpmLLmo4DmtYs=(需自行将目标站点添加进包含规则@match)


2、打开开发者工具 -> 查找API接口
    对目标站点开启“控制台防检测”脚本后,接下来就是分析API了。因为F12被禁止,我们可以手动打开浏览器开发者工具。切换到“网络”标签选项卡 ->重载刷新(F5)当前网页 -> 随意点播放一个音频章节 -> 找到可疑的api接口地址“ting-with-efi”:


      而且通过替换这个ID,API返回的是不同的efi字段(疑似加密的真实音频地址)。
      3、解密真实地址 -> 查找解密函数逻辑链
      接下来也就是分析api返回的efi字段的值具体是什么了,看看到底是不是音频地址。
      在调试工具窗口试着Ctrl + Shift + F 全局搜索“efi”这个字段,看看它生成逻辑。


发现搜索结果太多干扰项了,换成跟踪ting-with-efi的启动器调用栈,发现都是源自同一个js解析,这说明这个js启动器文件才是关键。

很明显,”initGetData“这个调用栈名称是关于初始化数据包的,我们直接从这一个点进去(以后分析推荐直接从启动器入手)。

太明显了,一眼就看到了相关初始化方法,特别是initAudio(),一看就是关于音频初始化的。

从initAudio()入手,发现【e.playUrl = o,】这一行,右键加断点然后刷新查看,果然是真实的音频地址,看生成逻辑链,很明显,我们下一步就是要找到vs函数:

4、解析函数算法-> 找到关键算法逻辑方法
上一步发现vs函数才是解码地址的关键,完整的明文地址实际上就是服务器地址 'http://36.5.86.202:50010‘ 与 vs.d(e.currentPlayInfo.ef, n, i.padEnd(20, "0")) 返回的结果进行拼接。
js关键代码片段:
複製代碼
  1. const n = e.currentPlayInfo.tingId.replaceAll("-", "")
  2.   , i = e.currentPlayInfo.creationTime.replaceAll("-", "").replaceAll(":", "").replaceAll("T", "").replaceAll(".", "").replaceAll(" ", "")
  3.   , r = vs.d(e.currentPlayInfo.ef, n, i.padEnd(20, "0"))
  4.   , o = e.tingDefaultData.audioServer.url + r;
複製代碼

api返回的响应数据:
複製代碼
  1. {
  2.     "id": "3a1801e1-74ec-871b-6d8b-68bce50caf01",
  3.     "bookId": "3a180174-7b8b-9eea-e06f-3946d9ca96f0",
  4.     "tingNo": 2,
  5.     "title": "0002_避难所",
  6.     "efi": "30Bf6+rGEZSFx+73P01z67iEDCBtOZzjkC4E2ENX2g7VwllOb/MS4GO5SMb7ug8rdcpKiGpARuRzHYPuqp+mHROrNemNftGuVT7QW6G3Nyjjc2MRRg+NLyRACPORkhoCbTl3WiHAWrVCyYIev2UZ1g==",
  7.     "creationTime": "2025-02-10T19:52:58.873983"
  8. }
複製代碼

先别急,结合代码片段,需要分析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助手:
複製代碼
  1. 已知API返回响应数据:
  2. {
  3.     "id": "3a1801e1-74ec-871b-6d8b-68bce50caf01",
  4.     "bookId": "3a180174-7b8b-9eea-e06f-3946d9ca96f0",
  5.     "tingNo": 2,
  6.     "title": "0002_避难所",
  7.     "efi": "30Bf6+rGEZSFx+73P01z67iEDCBtOZzjkC4E2ENX2g7VwllOb/MS4GO5SMb7ug8rdcpKiGpARuRzHYPuqp+mHROrNemNftGuVT7QW6G3Nyjjc2MRRg+NLyRACPORkhoCbTl3WiHAWrVCyYIev2UZ1g==",
  8.     "creationTime": "2025-02-10T19:52:58.873983"
  9. }
  10. efi的解密方法为vs.d(efi, id.tingId.replaceAll("-", ""), creationTime.padEnd(20, "0")) // 注意其中"id": "3a1801e1-74ec-871b-6d8b-68bce50caf01",非bookid
  11. 其中vs.d相关代码如下,请写出整个解密efi值的过程(步骤),最后转为python的方法:
  12. , vs = new class {
  13.     constructor() {
  14.         this.k = pi.enc.Base64.parse("le95G3hnFDJsBE+1/v9eYw=="),
  15.         this.i = pi.enc.Base64.parse("IvswQFEUdKYf+d1wKpYLTg=="),
  16.         this.as = 43,
  17.         this.ae = 116
  18.     }
  19.     e(e, t, n) {
  20.         const i = this.gk(t, n)
  21.           , r = this.gi(t, n);
  22.         this.k = pi.enc.Base64.parse(i),
  23.         this.i = pi.enc.Base64.parse(r);
  24.         const o = pi.enc.Utf8.parse(e);
  25.         return pi.AES.encrypt(o, this.k, {
  26.             iv: this.i,
  27.             mode: pi.mode.CBC,
  28.             padding: pi.pad.Pkcs7
  29.         }).ciphertext.toString(pi.enc.Base64)
  30.     }
  31.     d(e, t, n) {
  32.         const i = this.gk(t, n)
  33.           , r = this.gi(t, n);
  34.         this.k = pi.enc.Base64.parse(btoa(i)),
  35.         this.i = pi.enc.Base64.parse(btoa(r)),
  36.         e = (e + "").replace(/\n*$/g, "").replace(/\n/g, "");
  37.         const o = pi.enc.Base64.parse(e)
  38.           , a = pi.enc.Base64.stringify(o);
  39.         return pi.AES.decrypt(a, this.k, {
  40.             iv: this.i,
  41.             mode: pi.mode.CBC,
  42.             padding: pi.pad.Pkcs7
  43.         }).toString(pi.enc.Utf8).toString()
  44.     }
  45.     gk(e, t) {
  46.         let n = "";
  47.         for (let i = 0; i < 20; i++) {
  48.             const r = e[i].charCodeAt(0) + Number(t[i]);
  49.             n += String.fromCharCode(r)
  50.         }
  51.         for (let i = 20; i < e.length; i++) {
  52.             const r = e[i].charCodeAt(0) + Number(t[i - 20]);
  53.             n += String.fromCharCode(r)
  54.         }
  55.         return n
  56.     }
  57.     gi(e, t) {
  58.         let n = "";
  59.         for (let i = 20; i > 4; i--) {
  60.             const r = e[i].charCodeAt(0) + Number(t[i - 1]);
  61.             n += String.fromCharCode(r)
  62.         }
  63.         return n
  64.     }
  65. }
複製代碼

某AI助手分析过程:


5.2最终AI助手转成Python的等价方法:
複製代碼
  1. import base64
  2. from Crypto.Cipher import AES
  3. from Crypto.Util.Padding import unpad
  4. class VS:
  5.     def __init__(self):
  6.         self.k = base64.b64decode("le95G3hnFDJsBE+1/v9eYw==")
  7.         self.i = base64.b64decode("IvswQFEUdKYf+d1wKpYLTg==")
  8.     def gk(self, e: str, t: str) -> str:
  9.         n = ''
  10.         for i in range(20):
  11.             r = ord(e[i]) + int(t[i])
  12.             n += chr(r)
  13.         for i in range(20, len(e)):
  14.             r = ord(e[i]) + int(t[i - 20])
  15.             n += chr(r)
  16.         return n
  17.     def gi(self, e: str, t: str) -> str:
  18.         n = ''
  19.         for i in range(20, 4, -1):
  20.             r = ord(e[i]) + int(t[i - 1])
  21.             n += chr(r)
  22.         return n
  23.     def d(self, efi: str, ting_id: str, creation_time: str) -> str:
  24.         e = ting_id.replace('-', '')
  25.         t = creation_time.replace("-", "").replace(":", "").replace("T", "").replace(".", "").replace(" ", "")
  26.         t = t.ljust(20, '0')
  27.         key_str = self.gk(e, t)
  28.         iv_str = self.gi(e, t)
  29.         key = base64.b64encode(key_str.encode())
  30.         iv = base64.b64encode(iv_str.encode())
  31.         key_bytes = base64.b64decode(key)
  32.         iv_bytes = base64.b64decode(iv)
  33.         # clean and decode efi
  34.         efi_clean = efi.replace('\n', '').rstrip()
  35.         encrypted_data = base64.b64decode(efi_clean)
  36.         cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
  37.         decrypted = cipher.decrypt(encrypted_data)
  38.         try:
  39.             return unpad(decrypted, AES.block_size).decode('utf-8')
  40.         except ValueError:
  41.             return "[解密失败] Padding error 或 key/iv 错误"
  42. # 示例用法
  43. vs = VS()
  44. efi = "30Bf6+rGEZSFx+73P01z67iEDCBtOZzjkC4E2ENX2g7VwllOb/MS4GO5SMb7ug8rdcpKiGpARuRzHYPuqp+mHROrNemNftGuVT7QW6G3Nyjjc2MRRg+NLyRACPORkhoCbTl3WiHAWrVCyYIev2UZ1g=="
  45. ting_id = "3a1801e1-74ec-871b-6d8b-68bce50caf01"
  46. creation_time = "2025-02-10T19:52:58.873983"
  47. result = vs.d(efi, ting_id, creation_time)
  48. 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
验证地址真实有效


赞(25)
DMCA / ABUSE REPORT | TOP Posted: 02-27 20:28 樓主 引用 | 發表評論
归来的奥特曼


級別:騎士 ( 10 )
發帖:6394
威望:364 點
金錢:21817 USD
貢獻:999 點
註冊:2025-02-20


感谢分享
TOP Posted: 02-27 20:34 #1樓 引用 | 點評
清风皓月


級別:騎士 ( 10 )
發帖:1075
威望:350 點
金錢:11258 USD
貢獻:333 點
註冊:2025-11-11

支持发帖
TOP Posted: 02-27 20:40 #2樓 引用 | 點評
边荒传说


級別:俠客 ( 9 )
發帖:508
威望:110 點
金錢:4960 USD
貢獻:0 點
註冊:2026-01-01

感谢分享
TOP Posted: 02-27 20:51 #3樓 引用 | 點評
蓝莓山药


級別:新手上路 ( 8 )
發帖:21
威望:3 點
金錢:107 USD
貢獻:0 點
註冊:2026-02-24

感谢分享,地址还是经过base64编码的(不知本站是否也能通过这种方式发地址),试了下这个站已经无法ctrl+u了,也无法通过油猴脚本调起F12,可以学下思路还是很好的~
TOP Posted: 02-27 21:28 #4樓 引用 | 點評
美人醉卧膝


級別:俠客 ( 9 )
發帖:1647
威望:192 點
金錢:4486 USD
貢獻:0 點
註冊:2017-09-25

厉害👍🏻能不能出一期上ChatGPT的保姆级教程?
TOP Posted: 02-27 21:30 #5樓 引用 | 點評
特洛夫斯基


級別:風雲使者 ( 13 )
發帖:19513
威望:1873 點
金錢:50825092 USD
貢獻:166666 點
註冊:2015-01-27

看起来有点复杂
TOP Posted: 02-27 22:06 #6樓 引用 | 點評
www.xxx


級別:精靈王 ( 12 )
發帖:15245
威望:3447 點
金錢:6728 USD
貢獻:18147 點
註冊:2007-12-06

意义不太大
TOP Posted: 02-27 22:16 #7樓 引用 | 點評
吃喝玩乐


級別:聖騎士 ( 11 )
發帖:1133
威望:347 點
金錢:1844270 USD
貢獻:20000 點
註冊:2025-04-26

感谢分享
TOP Posted: 02-27 22:47 #8樓 引用 | 點評
夏季的季风


級別:騎士 ( 10 )
發帖:1107
威望:282 點
金錢:50 USD
貢獻:3438 點
註冊:2025-11-05

这是技术贴值得学习
TOP Posted: 02-27 23:08 #9樓 引用 | 點評
染血的圣骑士


級別:精靈王 ( 12 )
發帖:4231
威望:1277 點
金錢:33305 USD
貢獻:800 點
註冊:2021-01-11

感谢分享技术贴
TOP Posted: 02-28 00:02 #10樓 引用 | 點評
docn


級別:聖騎士 ( 11 )
發帖:8062
威望:777 點
金錢:24971 USD
貢獻:0 點
註冊:2019-04-29


感谢分享
TOP Posted: 02-28 04:04 #11樓 引用 | 點評
会飞的小熊


級別:聖騎士 ( 11 )
發帖:6318
威望:939 點
金錢:10385 USD
貢獻:800 點
註冊:2018-11-16

最佩服有技术还爱破解的人
TOP Posted: 02-28 04:29 #12樓 引用 | 點評
不再潜水


級別:精靈王 ( 12 )
發帖:13852
威望:1611 點
金錢:3541 USD
貢獻:1576 點
註冊:2018-11-21

感谢分享!
TOP Posted: 02-28 04:38 #13樓 引用 | 點評
贫道法号一本


級別:騎士 ( 10 )
發帖:2524
威望:503 點
金錢:241294 USD
貢獻:3 點
註冊:2022-01-01

感谢分享
TOP Posted: 02-28 07:22 #14樓 引用 | 點評
ylxnrGR


級別:光明使者 ( 14 )
發帖:50476
威望:5032 點
金錢:50 USD
貢獻:1269030 點
註冊:2016-09-08

这是技术贴,但是看不懂
TOP Posted: 02-28 08:21 #15樓 引用 | 點評
小念


級別:精靈王 ( 12 )
發帖:9842
威望:985 點
金錢:3402 USD
貢獻:50000 點
註冊:2021-10-01

感谢分享
TOP Posted: 02-28 08:24 #16樓 引用 | 點評
操做系捅


級別:俠客 ( 9 )
發帖:1351
威望:136 點
金錢:2183 USD
貢獻:51 點
註冊:2011-07-26


感谢分享
TOP Posted: 02-28 08:31 #17樓 引用 | 點評
这是个问题啊


級別:風雲使者 ( 13 )
發帖:66634
威望:6719 點
金錢:441 USD
貢獻:106889 點
註冊:2021-01-01

发帖辛苦
TOP Posted: 02-28 19:17 #18樓 引用 | 點評
沉睡的木玛


級別:風雲使者 ( 13 )
發帖:66634
威望:6819 點
金錢:442 USD
貢獻:106889 點
註冊:2018-11-16

支持发帖
TOP Posted: 02-28 19:17 #19樓 引用 | 點評
鸭打鹅


級別:風雲使者 ( 13 )
發帖:57553
威望:5729 點
金錢:89 USD
貢獻:29310 點
註冊:2022-02-02

感谢分享
TOP Posted: 02-28 19:17 #20樓 引用 | 點評
苏筱熙


級別:天使 ( 14 )
發帖:133251
威望:56932 點
金錢:108733 USD
貢獻:69 點
註冊:2012-09-01

谢谢分享
------------------------
7
TOP Posted: 02-28 19:18 #21樓 引用 | 點評
钐青


級別:騎士 ( 10 )
發帖:2499
威望:415 點
金錢:8330 USD
貢獻:169 點
註冊:2024-07-14

终于来点干货了
------------------------
H
TOP Posted: 02-28 23:50 #22樓 引用 | 點評

.:. 草榴社區 -> 技術討論區

快速回帖 頂端
內容
HTML 代碼不可用

使用簽名
Wind Code自動轉換

按 Ctrl+Enter 直接提交