吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1832|回复: 12
收起左侧

[求助] 某给网预览音频下载逆向

  [复制链接]
PhiFever 发表于 2025-7-22 17:30

目标网站地址

aHR0cHM6Ly93d3cuYWlnZWkuY29tL3M/cT0lRTglQjclOTElRTYlQUQlQTUmdHlwZT1zb3VuZA==

需求

下载搜索到的所有结果中的预览音频。

分析过程

下载需要登录,但是不登录可以直接播放压缩后的预览音频,因此可以实现捕获。

第一步:获取下载信息

在点击翻页按钮后,可以监听到此请求

s?type=sound&q=搜索关键词&page=2&_pjax=%23tab-mount-content

请求头中的参数在主页的html中可以搜索到

'cccllpptttgt: 08c74eaaab208b45265db2af1f7992fe'

在此请求的返回值中,可以获取到此页内每个item的信息,如

ftype="audio_mp3" fmodel="play" rurl="113619368" extime="1753254000606" token="62affde2b164239242338e4336aaee4a"
 cbk="callBackAudioFilePlay"  rcbk="downloadAudioCallback" 
第二步:获取预览音频文件URL

点击“播放”,系统使用第一步获取的信息,构造加密参数 v/f/d/audio_mp3 发送请求。这个参数 v 明显是经过加密处理的,包含了用户身份验证和文件请求信息。

其响应为:

{"status":"success","message":"aHR0cHM6Ly9zMi5haWdlaS5jb20vc3JjL2F1ZC9tcDMvODgvODg1YWRkYzRjNGRhNDMyMGIxODlmMGMzYjliMWRkNGEubXAzP2U9MTc1MjgxMDEyMCZ0b2tlbj1QN1MyWHB6ZnoxMXZBa0FTTFRrZkhON0Z3LW9PWkJlY3FlSmF4eXBMOmxQeWoybm9TcTBJTVNyb2xueXNDd2VZZWNzbz0=","action":"prev_audio","consumeFundValue":{},"consumeFundRemain":0.0,"freeDownCntRemain":0,"validAction":true,"comsumeFund":true}

可以看到返回值中的message是一个base64,解码即可得到只可被使用一次的预览音频直链

预览音频加密参数构造

查看f12中该请求的“请求发起程序”

send    @    jquery.min.js?v=141:1
ajax    @    jquery.min.js?v=141:1
fget    @    aigei.js:1
fileGet    @    aigei.js:1
audioUrlRequest    @    aigei-a.js:1
playByEl    @    aigei-a.js:1
togglePlay    @    aigei-a.js:1
(匿名)    @    aigei-a.js:1
dispatch    @    jquery.min.js?v=141:1
v.handle    @    jquery.min.js?v=141:1

跟栈可知fget为请求参数生成逻辑的核心,其调用了2个核心加密函数dfucqbj

在最终请求前,通过cupie(U['ud'] + '-' + Q)生成了X-Requested-ETag请求头

通过打断点和单步跟踪,可以知道在加密过程中,需要从目录页的html中获取以下几个用于加密的参数

cccllpptttgt
pIii111lllE
vvvvvvviisssss
llliiii1i0o0o0Oii1
lili1lli0o000oO
lllio0o0OO1i111li1lii1

将其和从aigei.js中剥离出的加密函数一起输入到static/encrypt_func.js中,并编写static/encrypt_func_test.js对函数进行测试。

由于剥离的函数中具有getUUID()这一具有随机性调用的函数,加密函数dfucqbj的结果无法对照验证

使用此处生成的参数进行请求,报错:

{'status': 'forbidden', 'message': '很抱歉,系统出现了错误,文件下载被阻止,我们已经记录下错误,将会尽快解决!错误代码=8287-2-n', 'consumeFundRemain': 0.0, 'comsumeFund': False, 'validAction': False}

逆向过程在此处陷入了僵局,求大佬指点

免费评分

参与人数 1吾爱币 +1 收起 理由
DrCatcher + 1 谢谢@Thanks!

查看全部评分

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

NanRuoSi 发表于 2025-7-23 16:45
PhiFever 发表于 2025-7-23 13:12
需要的,麻烦老哥发一下参考参考,谢谢

下载:https://wwmv.lanzouu.com/ibGj931p44cf 密码:52poj
js代码,记得配置本地的node环境
FitContent 发表于 2025-7-22 22:36

dfu、cupie 代码没有扣错,这个我在浏览器和本地验证了,从而发现在 encrypt_func.js 中,变量 vvvvvvviisssss 的值是固定为 0bd8c0306ea5,在我的测试中,这个值为 56d0cb916c2a
我没有运行 Python 代码测试整个流程,楼主现在查看原网页看看,这个值是否变动了。

 楼主| PhiFever 发表于 2025-7-26 17:02
感谢各位老哥的指点,问题解决了,总结问题如下:

1. 我的js代码抠的没问题,问题出在从dfu到cqbj之间添加的几个参数上,我错误的把它们的类型设置成了str而不是bool(还是对js不熟)
2. 请求出现need_login的问题直接清除cookie+使用指纹请求(如curl_cffi)可以解决
3. 每次刷新页面后cccllpptttgt和pIii111lllE都会更新,验证时需要重新抠字段

放一下核心代码:
[Python] 纯文本查看 复制代码
from typing import Optional, Dict, Any, Tuple

import execjs
import requests
from loguru import logger
from pydantic import BaseModel, Field, field_validator


class AudioData(BaseModel):
    itemid: str
    fuid: Optional[str] = None
    ftype: str = Field(default="audio_mp3")
    extime: str
    token: str

    # 限制token的长度为32个字符
    @field_validator("token", mode="before")
    def validate_token_length(cls, v):
        length = len(v)
        if length != 32:
            raise ValueError(
                f"Token must be exactly 64 characters long, got {length} characters."
            )
        return v


class GlobalVar(BaseModel):
    cccllpptttgt: str
    vvvvvvviisssss: str
    p_iii111lll_e: str


def generate_sign(
    global_var: GlobalVar, audio_data: AudioData
) -> Tuple[Dict[str, Any], str]:
    with open("static/encrypt_func.js", "r", encoding="utf-8") as f:
        js_encrypt_raw = f.read()
        # TODO:是否需要加上剩下几个固定的参数?
        js_encrypt_ctx = execjs.compile(
            f'{js_encrypt_raw}\nvar vvvvvvviisssss = "{global_var.vvvvvvviisssss}"'
        )
    encrypted_data: Dict[str, Any] = js_encrypt_ctx.call(
        "dfu",
        audio_data.ftype,
        audio_data.itemid,
        audio_data.extime,
        audio_data.token,
    )
    logger.info(f"Encrypted data(dfu) for item {audio_data.itemid}: {encrypted_data}")
    encrypted_data["rescUrl"] = audio_data.itemid
    encrypted_data["isc"] = False
    encrypted_data["ilg"] = False
    encrypted_data["confirm"] = False
    encrypted_data["downUuid"] = audio_data.fuid
    # 移除SaveAs参数
    encrypted_data.pop("SaveAs", None)
    logger.info(f"Final encrypted data for item {audio_data.itemid}: {encrypted_data}")
    # 构建请求数据
    request_data = js_encrypt_ctx.call("cqbj", encrypted_data)
    logger.info(
        f"Final request data(cpbj) for item {audio_data.itemid}: {request_data}"
    )
    etag = js_encrypt_ctx.call(
        "cupie",
        f'{encrypted_data["ud"]}-{global_var.p_iii111lll_e}',
    )
    logger.info(f"Generated ETag: {etag}")
    return request_data, etag

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
NanRuoSi + 1 + 1 用心讨论,共获提升!

查看全部评分

FitContent 发表于 2025-7-23 19:12
PhiFever 发表于 2025-7-23 10:49
感谢!参数vvvvvvviisssss的值确实是按某种规律变化的,只是在一段时间内的固定设备上一致而已。

我还 ...

当然,可以 hook 对应函数让它返回固定的值,这样才能验证浏览器和本地是否相同。

我不好配置环境。楼主可以通过以下方式测试。先将处理参数的代码单独分离出去,不要使用框架来调试:

  1. 从网站上复制、保存 HTML 文件,然后修改程序中的【请求】代码,不发出请求,而是读取本地文件,测试【获取到的参数是否和网站相同】
  2. 如果参数生成没有错误,使用第三方库比如 requests 来发送请求进行测试。如果测试时正常,但使用 Scrapy 时请求失败了,可能是请求头中缺少了什么,此时抓包 Scrapy 发出去的请求包,对比请求头和浏览器中的请求头。
  3. 似乎这个网站的音频请求有时效,所以整个测试的时候,拦截网站的请求,不要让它实际去请求 mp3 音频文件(浏览器开发者工具自带的功能),但它的 JS 代码会运行,楼主可以看到请求生成的参数 —— 此时浏览器显示该请求被阻止,部分信息显示不全,此时复制出来到一些 conver curl 之类的网站上查看。等一切校验之后,再让 Python 去发送请求,看看能否成功
  4. 上述完成之后,再将代码移入到 Scrapy 中。爬虫开发变动大,将核心的处理代码整合成函数分离出去方便测试,不要和框架合到一起了,后期网站变动调试不方便

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
PhiFever + 1 + 1 谢谢@Thanks!

查看全部评分

NanRuoSi 发表于 2025-7-23 11:53
刚刚尝试了一波爬虫,有时成功,但是之后后端接口返回了need_login。
我的ip地址被标记了应该,多次访问后   一直返回需要登录了。
看看需要不,需要的话  我发压缩包。
 楼主| PhiFever 发表于 2025-7-22 17:40
补充附件:https://wwpl.lanzouw.com/ivi9Q31mb45c 密码:52pojie.cn
wh1983118 发表于 2025-7-22 18:57
谢谢,支持发帖
 楼主| PhiFever 发表于 2025-7-23 10:49
FitContent 发表于 2025-7-22 22:36
[md]
`dfu、cupie` 代码没有扣错,这个我在浏览器和本地验证了,从而发现在 `encrypt_func.js` 中,变量 ` ...

感谢!参数vvvvvvviisssss的值确实是按某种规律变化的,只是在一段时间内的固定设备上一致而已。

我还有以下两个问题请老哥解答:

1. 如何对抠出的js代码进行验证:由于`getUUID()`的存在每次运行js得到的结果都不一致,莫非需要hook这个函数吗

2. 动态获取参数后请求接口依然失败:我重新修改了python代码,将此值改为动态获取,请求接口的结果依然与上文相同。能麻烦老哥帮忙看看是什么原因吗?我重新打包了一份完整的scrapy代码,在unix环境下使用docker按照文件里readme的说明能非常方便的跑起来

   https://wwpl.lanzouw.com/iMfHX31obyxc 密码:52pojie.cn
NanRuoSi 发表于 2025-7-23 11:56
NanRuoSi 发表于 2025-7-23 11:53
刚刚尝试了一波爬虫,有时成功,但是之后后端接口返回了need_login。
我的ip地址被标记了应该,多次访问后 ...

未加代理池的方式,因为没有那么多代理ip,如果有的话    可以再看看

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
PhiFever + 1 + 1 谢谢@Thanks!

查看全部评分

 楼主| PhiFever 发表于 2025-7-23 13:12
NanRuoSi 发表于 2025-7-23 11:53
刚刚尝试了一波爬虫,有时成功,但是之后后端接口返回了need_login。
我的ip地址被标记了应该,多次访问后 ...

需要的,麻烦老哥发一下参考参考,谢谢
 楼主| PhiFever 发表于 2025-7-23 17:46
NanRuoSi 发表于 2025-7-23 16:45
下载:https://wwmv.lanzouu.com/ibGj931p44cf 密码:52poj
js代码,记得配置本地的node环境

感谢老哥,我刚刚看了一下你是用puppeteer捕获响应实现的。

这种思路我之前用playwright-python试过,大概每访问8个目录页会吃到一次反爬。吃到反爬后就必须更换代理重新创建浏览器进程了。由于我使用的代理有时间限制需要动态获取,这种方法对我来说非常折腾,因此我还是更希望把接口参数逆向出来
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-6-19 09:12

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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