某云音乐爬取:反调试绕过、js调用栈回溯、Web算法逆向
2024-12-16 09:59:0 Author: mp.weixin.qq.com(查看原文) 阅读量:1 收藏


前言

本文以某云音乐爬虫案例为基础,详细探讨了从工具准备到目标数据爬取的全过程,包括爬取目标的需求分析、关键数据的识别以及加密参数的解析和破解。在学习其他案例是发现很多爬虫文章都是莫名其妙的获得某些数据,不知道前因和后果,所以这里就讲逆向和调试的细节分享出来!包括利用源代码搜索、静态堆栈调用回溯、动态调试以及动态堆栈调用等技术手段,最终锁定目标加密函数的调用位置。

文章声明

本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与文章作者无关,本文只做模拟不做任何真实对象的爬取!

爬虫工具准备

在正式开始之前,先确认以下工具和环境已准备就绪:

  1. 浏览器

    • 任意现代浏览器,例如 Chrome、Edge 或 Firefox,用于分析目标网站的网络行为。

  2. Python 环境

    • 配置完整的 Python 开发环境(推荐 3.8+),用于实现爬虫脚本和数据处理。

  3. JavaScript 环境

    • 配置可执行的 JavaScript 环境(如 Node.js),便于运行和测试目标网站中的 JS 代码。

  4. 抓包工具

    • 选择合适的抓包工具(如 Fiddler、Wireshark、Burp Suite),用来监控和分析网络请求。

  5. 基础技能

    • 具备基本的 JavaScript 和 Python 编程能力,了解加密算法的基础知识,尤其是 AES 和 RSA 的常见使用场景。


爬虫入门必遇到的坎,以及绕过方式

爬虫过程中遇到的主要反爬手段和技术难点

新手在接触爬虫时最难也是最痛苦的阶段就是绕过反爬虫和反调试了,目前总结一下爬虫过程种遇到的主要反爬手段和难点技术,这里主要做汇总分析,想了解详细原理的可以移步相关链接!

1.无限 Debugger 技术
这些方法的核心思想是通过动态插入debugger来中断代码执行,增加调试的复杂度。通常采用这些技术的组合与变形,使反调试手段更加隐蔽且有效。

◆简单方法:直接在前端页面中插入debugger语句。

◆使用eval创建debugger

◆通过Function动态创建debugger

重写JavaScript 原生功能:重写constructorevalsetInterval等,在原生功能中添加debugger。

2.阻止开启浏览器开发者工具
通过检测浏览器的行为或使用工具强行禁用开发者工具,这些方法可以有效防止爬虫或调试行为,尤其在页面载入和交互过程中增加了监测和干扰。

简单方式:禁用右键菜单和F12快捷键。

检测开发者工具是否开启:

  • 通过浏览器上下文加载的时间间隔来判断,若开启调试,则跳转到空白页。

  • 根据浏览器宽高和实际网页宽高判断是否在调试状态,若是则动态替换页面内容。

    ◆使用开源项目如disable-devtool插件来强制禁用开发者工具。

    3.前端代码混淆技术
    前端代码混淆技术通过减少代码可读性、隐藏关键数据、以及分发资源的方式,阻碍逆向工程和爬虫的成功率,提高了安全性和防护力度。

    压缩代码:通过压缩 JavaScript 代码,删除注释和格式化空格,降低代码的可读性。

    混淆代码:使用混淆技术将变量、函数名等改为无意义的字符,增加破解难度。

    加密关键数据:将敏感数据加密后传输,增加数据解密的难度。

    使用 CDN:通过 CDN 分发前端资源,隐藏实际的服务器请求地址,避免直接访问源代码。

    浏览器反爬虫手段汇总:

    1.记录--别想调试我的前端页面代码_disable-devtool-auto 绕过-CSDN博客

    2.js逆向-无限debugger的原理及绕过_js debugger-CSDN博客

    3.如何禁止别人调试自己的前端代码 - 向治洪的全栈技术 - SegmentFault 思否

    4.如何防止别人调试你的前端代码?-duidaima 堆代码

    目前反调试技术的通解手段和实现原理

    我们为什么要绕过反调试?因为原生浏览器才是最好的js调试环境和算法逆向工具!不然为什么这么多的网页都来进行反爬虫反调试?而且市面上的最主流的调试工具都是浏览器?而不像Linux,Windows逆向那样市面上存在五花八门各种各样的调试器?下面就来讲解绕过反调试技术的核心原理!

    原理讲解

    目前,反调试手段主要通过检测浏览器的行为来进行防护。然而,这些反调试代码通常是从服务器端传输到浏览器的,这意味着这些代码必须先到达浏览器才能执行。关键问题在于,在从服务器传输到浏览器的过程中,反调试代码无法被执行,但相关的流量依然可以被第三方的抓包工具截获并转发。下面是流量的流程交互图:

    正常的浏览器和服务器交互

    爬虫工作者一般的浏览器交互图

    因此,尽管开发者工具被禁用,但是由于反调试代码通常嵌入在 JavaScript 或 HTML 中,仍然可以通过抓包工具获取这些前端页面代码进行分析。我们可以使用中间的代理工具Charles,Fiddler,等抓包工具,替换相关的 JavaScript 和 HTML 代码,从而逐步一次次的绕过目标页面中的各种反调试手段。这样,就能利用浏览器自带的调试工具和抓包工具进行逆向分析和爬虫操作。这种方法有效地避开了反调试措施,助力爬虫技术的实施。

    反爬虫案例实际分享

    下面是相关案例,都可以通过替换原有js代码来进行绕过反调试:

    在Web安全领域,反调试技术常被用于防止攻击者通过开发者工具对网页进行调试,从而保护网页中的敏感逻辑和代码不被轻易篡改或分析。然而,随着攻击者技术的不断提升,绕过这些反调试措施的方法也层出不穷。以下是对几种常见绕过反调试技术的策略进行理论化总结与拓展。

    1. 禁用开发者工具的反制措施

    案例一展示了使用disable-devtool插件阻止用户打开开发者工具的情况。针对此类反调试技术,攻击者和防御者可以采取以下策略:

    修改浏览器设置:部分浏览器允许用户通过修改设置或使用命令行参数来禁用或绕过某些插件的限制。

    使用外部工具:利用诸如Browser Exploitation Framework(BeEF)等外部工具,可以在不打开开发者工具的情况下执行JavaScript代码。

    2. 应对evalFunction构造函数的反调试

    案例二展示了使用evalFunction构造函数执行无限debugger语句的反调试技术。这些技术通常会导致代码难以阅读和调试。攻击者可以通过以下方式绕过:

    代码混淆与去混淆:对混淆后的代码进行静态分析,识别并去除混淆逻辑,从而找到原始的eval调用点。

    动态调试:利用浏览器的调试工具或外部调试器,在运行时动态分析代码的执行流程,找到evalFunction调用的上下文。

    替换evalFunction:通过修改网页代码,将evalFunction替换为其他不触发反调试机制的函数调用方式。

    可以寻找页面中的eval放调试函数的混淆后调用,当然有些eval函数也是正常的只是用来迷惑人的。

    3. 嵌入开源框架中的反调试代码

    案例三展示了将反调试代码嵌入到开源框架(如jQuery)中的情况。这种策略增加了识别和绕过反调试措施的难度。攻击者可以采取以下策略:

    源码分析:下载并分析开源框架的源码,找到被嵌入的反调试代码段。

    版本对比:对比不同版本的开源框架,识别出被篡改的代码部分。

    替换框架文件:在不影响网页功能的前提下,替换被篡改的框架文件,以去除反调试代码。

    将反调试代码加进开源框架jquery.js里面,让人摸不着头脑。

    通过劫持页面源代码实现绕过无限debugger

    实验网站:https://spiderbuf.cn/playground/h04
    在实际操作中,攻击者需要综合考虑目标网页的复杂性、安全性以及自身的技术能力。

    以下是一些实用的替换策略:

    1.备份原始代码:在进行任何修改之前,务必备份原始网页代码,以防万一。

    2.逐步替换:不要一次性替换大量代码,而是逐步进行,每次替换后都进行测试,以确保网页功能不受影响。

    3.选中魔改后的代码,通过fiddle等抓包工具进行代码替换。

    4.测试与验证:在替换完成后,进行充分的测试,确保网页在各种浏览器和环境下都能正常运行,并且反调试措施已被成功绕过。


    开始分析某云音乐的正确爬取目标需求分析

    抓取数据包获得目标资源来源URL

    在Web爬虫和数据抓取领域,通过抓取数据包来获取目标资源的URL是一种常见且有效的策略。以下是对这一策略的理论化总结与拓展,包括目标确定、数据包分析、关键数据识别以及自动化实现等方面。

    一、目标确定

    首先,需要明确要抓取的目标网页和具体的资源(如音乐、视频、图片等)。在本案例中,目标是抓取某云音乐平台上的音乐资源URL。明确目标后,可以开始分析资源的加载方式,如通过Ajax请求加载资源等。首先找到要爬取的目标网页:

    找到目标先确定单首音乐的爬取方式才可以确定完整的方法。

    首先分析一下资源的加载方式:当我点击播放按钮后音乐开始播放,由此可以得出某云的资源加载方式是ajax!分析接下来的需求,先开启抓包然后筛选出需要的url资源目标链接。开始抓包,本次并无任何反调试主要是算法逆向。

    二、数据包分析

    使用浏览器开发者工具或第三方抓包工具(如 Wireshark、Fiddler)捕获网络数据包后,可通过分析请求 URL、方法和请求头筛选出与目标资源相关的 HTTP 请求,重点关注 XHR 数据包,其中通常包含以 JSON 格式传递的关键数据(如资源 URL)。

    开启F12后在点击音乐播放就可以发现很多的数据包,由于这种音乐格式的数据包大部分都是通过json格式的数据中藏有mp4链接来传递数据,所以首先就是选中XHR这里的数据包进行分析。

    1.筛选数据包:在捕获的数据包中,筛选出与目标资源相关的HTTP请求。这通常可以通过分析请求URL、请求方法(如GET、POST等)以及请求头中的信息来实现。

    2.分析XHR数据包:由于Ajax请求通常通过XHR(XMLHttpRequest)对象发送,因此应重点关注XHR数据包。这些数据包中可能包含以JSON格式传递的关键数据,如资源URL。

    三、关键数据识别

    分析 XHR 数据包的响应内容时,可通过预览响应快速定位关键字段,解析 JSON 格式数据提取目标资源 URL,并通过直接访问或工具测试验证 URL 的有效性。

    一个个的手工看,可以发现一个我们需要的资源链接:

    可以看见update,weblog,get,v1等等的HTTP的请求数据包,这些数据包中都有可能藏有关键数据,但是更多还是靠经验,没有经验的话就直接一个个看:

    ◆update:可能是更新页面的数据包

    ◆get:可能是获取资源的数据包

    ◆weblog:可能是记录用户行为的数据包

    ◆v1:其他相关的数据包

    主要是看数据预览部分,来查看数据!我们就可以得到一个音频链接:http://m804.music.126.net/20241113011636/c53d40244287ed16507ad686b6a83e86/jdyyaac/obj/w5rDlsOJwrLDjj7CmsOj/28481676823/4af4/3b82/de3c/082fc537ce73819afdeb6694703f398a.m4a

    或者直接在媒体中也可以找到,但是我们是需要自动化,这样就算是拿到了也没用:

    所以抓包到这里也就可以确定要分析的数据包了!目标url:https://music.163.com/weapi/song/enhance/player/url/v1


    在XHR数据包中,需要仔细分析响应内容以识别关键数据。这通常涉及以下几个步骤:

    1.查看数据预览:在开发者工具中,可以查看XHR数据包的响应内容预览。这有助于快速定位可能包含资源URL的字段。

    2.解析JSON数据:如果响应内容是JSON格式,可以使用JSON解析工具(如在线JSON解析器或编程语言中的JSON库)来解析数据,并提取出目标资源的URL。

    3.验证URL:提取出的URL可能需要进行验证,以确保其有效性。这可以通过在浏览器中直接访问该URL或使用其他工具(如curl、wget等)来测试。

    正式开启目标URL的参数和负载分析

    在现代的 Web 应用程序中,为了防止爬虫和恶意攻击,开发者采用了各种加密与混淆技术来保护传输中的敏感数据。与传统的明文传输相比,加密的请求参数和复杂的加密逻辑能够有效增加爬虫抓取的难度,提高服务器安全性。本文将通过具体案例分析一个加密的 Web 请求,并探讨如何对其中的加密参数进行逆向分析。

    1. 识别请求中的关键参数

    识别关键参数

    ◆在目标URL中,首先需要识别出哪些参数是关键的。这些参数可能包含敏感信息,如用户ID、会话令牌(session token)、CSRF令牌(csrf_token)等。

    ◆在本案例中,csrf_tokenparamsencSecKey是三个关键的请求参数。

    参数作用与加密方式

    csrf_token:通常用于防止跨站请求伪造(CSRF)攻击。它可能是一个随机生成的字符串,每次请求都会变化。

    paramsencSecKey:这两个参数很可能包含加密后的数据。加密的目的可能是为了保护数据的机密性,防止被未经授权的用户或爬虫轻易获取。

    2. 关键参数的作用和加密方式

    根据数据包分析出其中的关键点:


    请求 URL示例:

    `https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=a440a8d7bdb5da6fa2c9..`.

    从请求的结构可以看出,参数并不是直接明文传输,而是经过加密处理。这种做法有效地减少了爬虫直接访问数据的可能性,因为即便获取到了请求 URL 和参数,参数的值仍然需要解密才能获得真实信息。


    csrf_token

    作用:用于防止跨站请求伪造(CSRF)攻击,通常是一个随机生成的字符串,每次请求都会变化。它是 Web 安全中的一种常见防护机制,用来确保请求来自合法用户。

    特点:csrf_token 一般由前端 JavaScript 动态生成并附加到请求中。服务器会验证该 token 是否与用户当前会话的 CSRF 值匹配,从而防止恶意站点伪造请求。

    请求方式:POST


    params 和 encSecKey
    这两个参数显然是经过加密的,其中params应该是包含请求数据的加密字符串,而encSecKey很可能是加密过程中使用的密钥或中间结果。这种加密方式的目的是增强安全性,防止爬虫工具直接通过抓包获取有效数据。

    3.猜测加密逻辑与逆向分析目标

    在该请求中,你希望获取一条指定的音乐资源,但是并未能直接在 URL 中找到对应的唯一音乐 ID。传统上,网络请求通常是明文传输所需的数据(如音乐的唯一 ID),但是随着批量爬虫的增加,网站通常会采用加密措施来提高请求的复杂性,从而增加爬虫抓取的难度。


    通过观察请求中的加密参数,我们可以推测,某些参数(如paramsencSecKey)可能包含了关键的加密信息,其中可能包括音乐资源的唯一 ID。


    通过获取该URL的三个请求参数,我们可以肯定这三个参数中就是由音乐的唯一id和一些参数加密而来的,所以我们要做的就是一步步逆向算法了!

    csrf_token: a440a8d7bdb5da6fa2c9d17ba16a3b91...
    params: G2O0AhGQTQOP2fgkeXwkPg+F/zGvL...
    encSecKey: 2dae29e53a8fd629e09d7fa6497f...

    确定逆向目标:我们需要寻找这三个参数了解他们的加密方式和产生原理!


    正式开启参数加密算法的分析

    加密位置的寻找方法

    寻找参数加密位置是逆向工程的关键步骤,通常需结合多种方法。源代码分析虽繁琐,但在缺乏线索时仍有效。静态堆栈回溯可忽略无关调用,逐步定位加密函数。动态调试则通过运行时断点观察程序行为,筛选目标请求。一旦捕获目标,动态堆栈回溯能进一步确定加密算法位置。这些方法需根据实际情况灵活运用,以提高分析效率。寻找着写参数的加密位置有几种方法。

    1.根据源代码搜索目标位置

    可以通过源代码搜索目标位置,这种方法虽然繁琐,但适用于没有明确思路的情况。具体步骤如下:

    1.下载前端JS文件:使用浏览器开发者工具下载页面加载的所有JS文件。

    2.搜索关键字:在JS文件中搜索与加密相关的关键词,如 encryptaesrsa 等。

    3.下断点调试:在找到的函数处设置断点,运行请求并观察断点是否被触发。

    发现这些方法后可以一个个的下断点看我们请求的api到底停止在哪一个代码位置,这样比较麻烦,等于大海捞针,如果没有思路可以用这个方法,推荐第二个方法!

    2.静态的堆栈调用回溯查找

    调用栈回溯,根据调用栈来寻找目标位置,这里俗语静态的堆栈调用:

    这些函数的调用过程中就藏有我们所需要的加密算法:

    查看调用栈:在开发者工具的“Sources”面板中查看调用栈,找到与目标API相关的函数调用。

    过滤无关调用:忽略如 XMLHttpRequest.send 等原生框架调用,专注于自定义函数调用。

    通过调用栈回溯,可以快速定位到可能包含加密算法的函数。例如:

    1.调用栈函数1,调用apply,无用:

    2.调用栈函数2,调用apply,无用:

    调用栈函数3,调用send,可能有用:

    从一个个的调用栈进行查看可以很快发现很多都是apply()方法js apply()用法详解-CSDN博客在这些调用栈中,通常不是发送数据的请求。继续向上查找,可以找到send()函数XMLHttpRequest.send() - Web API | MDN,即发送数据包的位置。在此处设置断点,可以进一步追踪目标参数的加密位置。

    3.动态调试寻找参数加密位置

    Js前端动态调试锁定指定请求包发送时候的数据:

    设置断点:在 XMLHttpRequest.send 函数处设置断点。

    触发请求:刷新页面并触发目标API请求。

    筛选无关请求:放行非目标API请求,直到找到目标请求。

    操作开始:

    下一个断点在send函数我们可以发现,此时的url并不是我们要爬取的目标,而是其他api请求,所以我们需要一个个的放行这些我们不需要的api请求,找到我们的目标请求!刷新页面重新点击音乐播放按钮就可以一个个的筛选断点获取我们的目标api的js处理请求!

    成功找到发送目标请求的数据包的上下文,可以开始下面的步骤了。

    4.动态的堆栈调用回溯

    找到并分析目标API请求中的参数加密位置。静态的堆栈调用回溯和动态调试相结合,能够快速定位加密算法,提高逆向分析的效率。

    找到之后就可以开始动态调试的栈回溯了,动态的堆栈调用回溯。

    观察参数:在断点处观察参数,确认目标参数已被加密。

    回溯调用栈:查看调用栈,找到上层调用中可能包含加密算法的函数。

    观察一下此时的参数,可以发现:

    我们需要你逆向的参数已经被加密了,也就意味着这个函数调用的上一层调用之中可能存在加密算法继续观看动态函数调用栈。

    分别看每次调用时候的变量的值来判断是否存在加密算法:

    发现这里的函数存在参数组装:

        b6f.bzu9l = function() {
    this.Az2x();
    t6n.be6Y("/api/song/enhance/player/url/v1", {
    type: "json",
    query: {
    ids: JSON.stringify([this.cx7q.id]),
    level: DEFAULT_LEVEL,
    encodeType: DEFAULT_ENCODETYPE
    },
    onload: this.bOl0x.f6b(this),
    onerror: this.bOl0x.f6b(this)
    })
    }

    在上面其他的几次调用中都存在,这两个参数:
    params: G2O0AhGQTQOP2fgkeXwkPg+F/zGvL...
    encSecKey: 2dae29e53a8fd629e09d7fa6497f...

    由此可以判断,t6n.be6Y函数负责发送请求,并且参数paramsencSecKey在此函数调用中生成。因此,可以推断t6n.be6Y函数中包含了实际的加密算法。

    锁定加密算法调用位置

    锁定算法位置并且动态调试暂停之后就可以开始分析相关参数了!

    ...
    e6c.data = j6d.cr7k({
    params: bVj1x.encText, //目标参数的数值来源
    encSecKey: bVj1x.encSecKey
    })
    ...

    可以发现着就是我吗之前要寻找的加密参数,从上面的动态调试加上堆栈回溯可以成功找到这里。

    在这段代码中,e6c.data被赋值为j6d.cr7k函数的返回值,该函数接收两个参数:paramsencSecKey。这两个参数正是我们在请求中看到的加密参数。

    加密函数的传入参数分析和加密函数的锁定

    发现加密算法的位置,然后就可以开始动态调试了,不要被这些变量名吓到,直接开动态调试分析每一个变量的值的变化:

        t6n.be6Y = function(X6R, e6c) {
    var i6c = {} //定义一个变量
    , e6c = NEJ.X({}, e6c)//更新e6c的值,但是通过动态调试发现他并没有修改任何值,逆向的时候直接跳过,其实作用就是格式化字符串当其实并无影响
    , mv9m = X6R.indexOf("?");//动态调试发现他是一个用来定位url链接?号的功能,作用不大
    //发现并无任何复制操作,只是单纯的if判断所以直接跳过,也可以去控制台看下他们的值
    if (window.GEnc && /(^|\.com)\/api/.test(X6R) && !(e6c.headers && e6c.headers[eu7n.BQ2x] == eu7n.Iv5A) && !e6c.noEnc) {
    if (mv9m != -1) {//发现这个mv9a永远都是不成立的而且跟url相关,但是我吗的url是固定的所以这个也没什么用
    i6c = j6d.gT8L(X6R.substring(mv9m + 1));
    X6R = X6R.substring(0, mv9m)
    }
    if (e6c.query) {//动态调试可知,这里也比较简单只是将e6c的值赋值给i6c,
    i6c = NEJ.X(i6c, j6d.fT8L(e6c.query) ? j6d.gT8L(e6c.query) : e6c.query)
    }
    if (e6c.data) {//同理
    i6c = NEJ.X(i6c, j6d.fT8L(e6c.data) ? j6d.gT8L(e6c.data) : e6c.data)
    }
    i6c["csrf_token"] = t6n.gV8N("__csrf");//这部分解析在后面,这段是获取cookie中的值
    X6R = X6R.replace("api", "weapi");//url替换,也和我们没关系,我们的url是固定的
    e6c.method = "post";//赋值api请求的方法
    delete e6c.query;//后面会有解释
    //第三部分算法逆向,再继续动态调试可以发现bVj1x就是我们需要的东西encText,encSecKey两个参数的来源!!!!
    var bVj1x = window.asrsea(JSON.stringify(i6c), bse8W(["流泪", "强"]), bse8W(RR7K.md), bse8W(["爱心", "女孩", "惊恐", "大笑"]));
    e6c.data = j6d.cr7k({
    params: bVj1x.encText, //目标参数的数值来源
    encSecKey: bVj1x.encSecKey
    })
    }
    //上面就已经把参数加密出来了,也就没必要分析后面了,直接分析关键加密就可以了!
    var cdnHost = "y.music.163.com";
    var apiHost = "interface.music.163.com";
    if (location.host === cdnHost) {
    X6R = X6R.replace(cdnHost, apiHost);
    if (X6R.match(/^\/(we)?api/)) {
    X6R = "//" + apiHost + X6R
    }
    e6c.cookie = true
    }
    cxW4a(X6R, e6c)
    }

    第一部分代码解析

    查看函数源码位置:

    发现这个函数是从cookie中取出一个csrf的值

    第二部分代码解析

    这里毫无疑问是最后一步了将请求头和参数进行组装,这里将e6c的query字段删除,但是前面已经把它的值赋给了i6c。可以在看看参数可以发现e6c越看越像一个缺少参数的http请求!

    所以毫无疑问后面的代码就是将参数传入进行加密了,我们就可以得到最原始的参数了。

    这些就是原始参数了

    1. i6c:
    1. csrf_token: "a440a8d7bdb5da6fa2c9d17ba16a3b91"
    2. encodeType: "aac"
    3. ids: "[1325905146]"
    4. level: "standard"

    经过算法加密后就成了,我吗需要的参数encText,encSecKey

    目前得到的数据有:
    "/weapi/song/enhance/player/url/v1"

    data ={
    "csrf_token": "a440a8d7bdb5da6fa2c9d17ba16a3b91",
    "encodeType": "aac",
    "ids": "[1325905146]",
    "level": "standard"
    }

    最后就差算法加密了。

    第三部分代码解析

    最后分析关键加密算法了:

     var bVj1x = window.asrsea(JSON.stringify(i6c), bse8W(["流泪", "强"]), bse8W(RR7K.md), bse8W(["爱心", "女孩", "惊恐", "大笑"]));

    可以分析一下者该函数的参数了,这个参数可以很清晰的发现一共传入了4个很奇怪的参数其实很简单,直接拿去js控制台跑一下就可以了。

    成功得到所有参数:


    JSON.stringify(i6c)
    '{"ids":"[1325905146]","level":"standard","encodeType":"aac","csrf_token":"a440a8d7bdb5da6fa2c9d17ba16a3b91"}'
    bse8W(["流泪", "强"])
    '010001'
    bse8W(RR7K.md)
    '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
    bse8W(["爱心", "女孩", "惊恐", "大笑"])
    '0CoJUm6Qyw8W8jud'

    建议遇到这种东西的时候多敲几遍在控制台,用来确定是否是常数。

    得到最终加密函数的输入:

     var bVj1x = window.asrsea(
    '{"ids":"[1325905146]","level":"standard","encodeType":"aac","csrf_token":"a440a8d7bdb5da6fa2c9d17ba16a3b91"}', //参数1
    '010001', //参数2
    '00e0b509f6259df...2741d546b8e289dc6935b3ece0462db0a22b8e7', //参数3
    '0CoJUm6Qyw8W8jud'//参数
    );

    那么剩下的就是找asrsea函数了,东盎太调试下个断点,然后鼠标移动到函数上就可以进行代码跳转了。

    锁定加密函数的算法实现位置

    可以很清晰的发现我们找到目标了,把这部分的函数都提取出来:

    !function() {
    function a(a) {
    var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
    for (d = 0; a > d; d += 1)
    e = Math.random() * b.length, //这里允许随机数,也就意味着这个加密算法中允许随机数的存在,也就是任意一个数都可以,那么直接自己固定一个数就可以了
    e = Math.floor(e),
    c += b.charAt(e);
    return c
    }
    function b(a, b) { //这里将数据进行了AES加密!
    var c = CryptoJS.enc.Utf8.parse(b)
    , d = CryptoJS.enc.Utf8.parse("0102030405060708")
    , e = CryptoJS.enc.Utf8.parse(a)
    , f = CryptoJS.AES.encrypt(e, c, { //密钥偏移数据模式都有了,那么我就可以进行算法逆向了,CBC模式的AES加密
    iv: d,
    mode: CryptoJS.mode.CBC
    });
    return f.toString()
    }
    function c(a, b, c) {//进行RSA加密,但是是大厂自己实现的一套加密,逆向有些难度,但是前面的随机字符也传了进来,前面可以随机,把随机固定下来,那么这个密码自然也就固定下来了!
    var d, e;
    return setMaxDigits(131),
    d = new RSAKeyPair(b,"",c),
    e = encryptedString(d, a)
    }
    function d(d, e, f, g) {
    var h = {}
    , i = a(16); //直接就可以将i的值定死了!
    return h.encText = b(d, g),
    h.encText = b(h.encText, i),
    h.encSecKey = c(i, e, f),
    h
    }
    function e(a, b, d, e) {
    var f = {};
    return f.encText = c(a + e, b, d),
    f
    }
    window.asrsea = d,
    window.ecnonasr = e
    }();

    上面就是我们获取到的加密函数了,简单的给函数注释一下在来分析,开始逐个函数分析:

    function a(a) 存在随机数的函数

        function a(a) {
    var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
    for (d = 0; a > d; d += 1)
    e = Math.random() * b.length, //这里允许随机数,也就意味着这个加密算法中允许随机数的存在,也就是任意一个数都可以,那么直接自己固定一个数就可以了
    e = Math.floor(e),
    c += b.charAt(e);
    return c
    }

    类似这种函数可以发现它传入一个A然后和一个随机数进行操作在返回一个C,首先验证一下这个e = Math.random() * b.length是不是真随机,在控制台多试几次。

    试完之后可以发现着其实就是一个产生随机数的盒子,直接抽象函数为:

        function a(a) {
    seed(a)
    return randnum()
    }

    手动测试,在浏览器的控制台试试发现a(16)可以等于很字符串,随机字符串!


    function b(a, b) 特征明显的AES加密

        function b(a, b) {                    //这里将数据进行了AES加密!
    var c = CryptoJS.enc.Utf8.parse(b)
    , d = CryptoJS.enc.Utf8.parse("0102030405060708")
    , e = CryptoJS.enc.Utf8.parse(a)
    , f = CryptoJS.AES.encrypt(e, c, { //密钥偏移数据模式都有了,那么我就可以进行算法逆向了,CBC模式的AES加密
    iv: d,
    mode: CryptoJS.mode.CBC
    });
    return f.toString()
    }

    直接使用了标准的AES加密直接上python脚本就行了!

    动态调试可以发现这个AES算法在将数据输出的时候编码成了base64加密。

    function c(a, b, c) 同样特征明显的使用了RSA加密

        function c(a, b, c) {//进行RSA加密,但是是大厂自己实现的一套加密,逆向有些难度,但是前面的随机字符也传了进来,前面可以随机,把随机固定下来,那么这个密码自然也就固定下来了!
    var d, e;
    return setMaxDigits(131),
    d = new RSAKeyPair(b,"",c),
    e = encryptedString(d, a)
    }

    function d(d, e, f, g) 最终赋值函数

        function d(d, e, f, g) {
    var h = {}
    , i = a(16); //直接就可以将i的值定死了!
    return h.encText = b(d, g),
    h.encText = b(h.encText, i),
    h.encSecKey = c(i, e, f),
    h
    }

    直接进行等效转换:

        function d(d, e, f, g) {
    var h = {}
    var i = a(16); //产生一个随机数i
    h.encText = b(d, g) //调用b函数进行AES加密
    h.encText = b(h.encText, i)//继续调用AES加密,得到encText
    h.encSecKey = c(i, e, f)//将数据在进行RSA加密,得到encSecKey
    return h //将最终数据返回
    }

    成功梳理完成整个加密流程,有前面的分析我们可以很清楚的知道d函数传入的参数。

    window.asrsea函数的参数其实大部分都是常数只是被代码混淆了,直接在控制台运行一下就可以解密了。

    ....
    window.asrsea = d
    ....
    var bVj1x = window.asrsea(
    '{"ids":"[1325905146]","level":"standard","encodeType":"aac","csrf_token":"a440a8d7bdb5da6fa2c9d17ba16a3b91"}', //参数1
    '010001', //参数2
    '00e0b509f6259df...2741d546b8e289dc6935b3ece0462db0a22b8e7', //参数3
    '0CoJUm6Qyw8W8jud'//参数
    );

    就是:

    d(
    d, //'{"ids":"[132590514.....8d7bdb5da6fa2c9d17ba16a3b91"}'
    e, //'010001'
    f, // '00e0b509f6259df...2741d546b8e289dc6935b3ece0462db0a22b8e7'
    g //'0CoJUm6Qyw8W8jud'
    )

    成功得到加密函数的输入参数,和加密函数所使用的算法,那么最后一步写python爬虫脚本就轻而易举了。


    手搓加密算法的python实现

    完整的代码这里就不放了下面的连接里面都有这里只做代码讲解:

    function d(d, e, f, g) 对应的python

        def getData(self):
    d={"ids":"[%s]"%self.id,"level":"standard","encodeType":"aac","csrf_token":""}
    d=json.dumps(d)
    e = '010001'
    f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
    g = '0CoJUm6Qyw8W8jud'
    i = self.a()
    params = self.b(d, g)
    params = self.b(params, i)
    encSecKey = self.c(i, e, f)
    return {'params': params, 'encSecKey': encSecKey}

    这个函数主要是讲得到的参数进行加密得到params,encSecKey用来发包!

    function b(a, b) 对应的python

        def b(self,data,key):
    """
    AES 加密
    """
    iv = b'0102030405060708'
    pad = 16 - len(data) % 16
    data = data + chr(2) * pad

    aes = AES.new(key.encode(), AES.MODE_CBC, iv)
    tmp = aes.encrypt(data.encode())
    result = base64.b64encode(tmp).decode()
    return result

    正常的AES

    function c(a, b, c) 对应的python

        def c(self,a,b,c):
    """
    RSA 加密
    """
    a = a[::-1]
    result = pow(int(hexlify(a.encode()), 16), int(b, 16), int(c, 16))
    return format(result, 'x').zfill(131)

    正常的RSA

    function a(a) 对应的python

        def a(self,a=16):
    """
    获取16位随机字符串
    """
    words="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    result=''
    for i in range(a):
    result+=words[random.randint(0,len(words)-1)]
    return result

    生成指定长度的随机字符串

    完成加密流程后,生成的paramsencSecKey将作为请求参数,提交至目标服务器,最终成功获取到目标音乐资源。以下截图展示了实际抓包分析的结果:


    注:某云特别懒三四年了都没更新加密算法,而且很多地方的加密算法都是同一个,所以可以去试试。


    参考资料

    ◆****云音乐JS逆向&Python爬虫&****云逆向_****云js逆向-CSDN博客

    https://blog.csdn.net/Not__Cry/article/details/137360972

    ◆Python爬虫——js逆向****云音乐 - Manlu - 博客园

    https://www.cnblogs.com/manlu-/p/15700515.html

    ◆【爬虫逆向系列】****云JS逆向实战—曲单首音乐抓取、js逆向综合、加解密分析_哔哩哔哩_bilibili

    https://www.bilibili.com/video/BV13w411A7C5?spm_id_from=333.788.videopod.episodes&bvid=BV13w411A7C5&vd_source=50bf1de1542abaf8301b5b650d0f2292

    ◆【Python爬虫---js逆向】Js逆向之****云音乐_python ****云 逆向爬取-CSDN博客

    https://blog.csdn.net/m0_67844671/article/details/132755576

    ◆【爬虫实战】使用Python和JS两种方式逆向****云音乐接口并下载歌曲_音乐源js下载-CSDN博客

    https://blog.csdn.net/u013046615/article/details/134447621

    ◆(二)JS逆向——爬取****云音乐 - Mrterrific - 博客园

    https://www.cnblogs.com/sxy-blog/p/18271548

    看雪ID:Brinmon

    https://bbs.kanxue.com/user-home-950404.htm

    *本文为看雪论坛精华文章,由 Brinmon 原创,转载请注明来自看雪社区

    # 往期推荐

    1、PWN入门-SROP拜师

    2、一种apc注入型的Gamarue病毒的变种

    3、野蛮fuzz:提升性能

    4、关于安卓注入几种方式的讨论,开源注入模块实现

    5、2024年KCTF水泊梁山-反混淆

    球分享

    球点赞

    球在看

    点击阅读原文查看更多


    文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458587045&idx=1&sn=451d4e39ee6982e4ac225a270737091b&chksm=b18c3f2f86fbb639ad78c51ca7b83b5ee91c3ec4e0bea7a28f337a0344bd1f1ace99cd8bb4bc&scene=58&subscene=0#rd
    如有侵权请联系:admin#unsafe.sh