吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4494|回复: 55
上一主题 下一主题
收起左侧

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

  [复制链接]
跳转到指定楼层
楼主
lenvy1 发表于 2025-5-29 20:56 回帖奖励
本帖最后由 lenvy1 于 2025-5-29 21:02 编辑

免责声明:

本贴中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关。

一、前言

要获取网络上免费站的音视频真实地址的方法其实有很多,比如直接用IDman插件嗅探,或者抓包缓存等。
但是!!!这只是单个音频地址的获取方法,而且不一定有效,比如音频地址使用异步动态加载,甚至是加密包装过的,使用嗅探工具不一定能嗅探到。
而且!!!如果想要批量获取,就必须了解这个音频真实地址的产生过程,这个过程就是解析(逆向)其算法过程。
本次解析目标就是一个解析算法的过程。
我也是小白,对JS一窍不通,有错漏之处望海涵。
大佬见笑了。

二、目标地址

aHR0cDovL3d3dy55dWV0aW5nYmEuY24vYm9vay9kZXRhaWwvM2ExODAxNzQtN2I4Yi05ZWVhLWUwNmYtMzk0NmQ5Y2E5NmYwLzA=

三、解析思路

1、通过HTML源码 -> 直接搜索关键词

  • 通过Ctrl + U查看HTML源码 Ctrl + F搜索关键词 .mp3 或.m4a 找到明文地址,如以下这种:

  • 如果搜索关键词 .mp3.m4a 没能找到,则试着搜索 var nowvar 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=([i]需自行将目标站点添加进包含规则@match)

2、打开开发者工具 -> 查找API接口

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

    这个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,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关键代码片段:

 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
验证地址真实有效

免费评分

参与人数 19威望 +2 吾爱币 +123 热心值 +17 收起 理由
starcrafter + 1 + 1 用心讨论,共获提升!
——— + 1 谢谢@Thanks!
pelephone + 1 + 1 热心回复!
Mark2022 + 1 谢谢@Thanks!
杨辣子 + 1 + 1 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!
wolfstudio + 1 + 1 我很赞同!
daxz + 1 + 1 热心回复!
qqycra + 2 + 1 我很赞同!
IcePlume + 1 + 1 我很赞同!
涛之雨 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
laozhang4201 + 1 + 1 我很赞同!
hj170520 + 2 + 1 热心回复!
theStyx + 2 + 1 谢谢@Thanks!
jh95wxg + 2 + 1 谢谢@Thanks!
0jiao0 + 2 + 1 用心讨论,共获提升!
liuxuming3303 + 1 + 1 谢谢@Thanks!
killjd + 1 + 1 谢谢@Thanks!
FitContent + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
eastmarquis 发表于 2025-5-29 21:04
这个嗅探到地址能直接下载缓存不能
推荐
cick 发表于 2025-5-30 13:54
话说你这个网站地址是怎么装md5的我找了个加密 网址的然后在md5解析不了。  猫抓是可以下载这个站的数据是mp4格式可以听
3#
FYL11162022 发表于 2025-5-29 21:16
4#
 楼主| lenvy1 发表于 2025-5-29 21:19 |楼主
eastmarquis 发表于 2025-5-29 21:04
这个嗅探到地址能直接下载缓存不能

IDman等工具能直接嗅探到播放地址下载
5#
zsr849408332 发表于 2025-5-29 21:46
学习学习,感谢大佬的精彩讲解
6#
amwquhwqas128 发表于 2025-5-29 22:23
多谢大佬的讲解文章
7#
killjd 发表于 2025-5-29 22:30
收藏学习进步
8#
crazyxsl 发表于 2025-5-29 23:55
学习了 感谢
9#
0jiao0 发表于 2025-5-30 00:10
思路很清晰,收藏了
10#
zqfbz 发表于 2025-5-30 05:24
一般小白路过!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-6-15 13:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表