网易云音乐加密分析

网易云的加密网上已经写烂了,并且网易云的程序员似乎并没有更新加密的想法,这几乎是唯一一家了,也许人家根本不在乎你能模拟加密。
当初第一次你想网易云的时候,就遗留了一些问题,不过当时技术有限,凑凑别人的代码倒也能实现,现在回来再看,打算完整地分析一遍,解决当时的疑惑。

首先加密位置很容易找到的。现在想来,当时为什么网易云JS分析也觉得这么有难度,我不理解。
asrsea.png

hook的方法

要获取这四个参数具体的值话,有三种办法:

  1. 断点到这里,把代码扣到浏览器控制台,一个个看。参数少的时候可以这样,如果要一直跟踪的话,不推荐使用。
  2. 可以用fiddler拦截这个文件,控制台输出这四个参数。最推荐的方法。
  3. 利用浏览器的改写功能,直接在Source面板改写JS。

想使用一下第三种办法,在浏览Source面板改完如下
source.png
控制台输出效果
console.png

加密函数

跟进加密函数,关键加密逻辑就几行代码
encrypt.png

这里的d,e,f,g就是我们打印的那四个参数

函数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
    }

生成16位随机字符串,这个也是可以写死的,但是伪装就装得像一点。使用Python改写的话也是十分简单。

CHARS = string.ascii_letters + string.digits

def random_str(cnt: int) -> str:
    return ''.join(random.choices(CHARS, k=cnt))

b函数,AES加密,CBC模式,直接调库就好了,不多说。
更多关于AES加密的内容可以查看AES加密

重点看看c函数,RSA加密,这也是我最初学习的时候疑惑的一个点,网上的文章都有一个逆序明文的操作,当时并没有弄明白。要搞清楚,关键在encryptstring方法。改写得好看了一点,函数逻辑没有变化

function encryptedString(a, b) {
    /* 取明文的ascii码到c数组 */
    for (var f, g, h, i, j, k, l, c = [], d = b.length, e = 0; d > e;) {
        c[e] = b.charCodeAt(e);
        e++;
    }
    /* 补0到a.chunkSize,经调试,achunkSize为常量126 */
    for (; 0 !== c.length % a.chunkSize;) {
        c[e++] = 0;
    }
    /* 外层for循环其实只会被执行一次 */
    for (f = c.length, g = "", e = 0; f > e; e += a.chunkSize) {
        /* 大整数 */
        for (j = new BigInt, h = 0, i = e; i < e + a.chunkSize; ++h) {
            j.digits[h] = c[i++];
            j.digits[h] += c[i++] << 8;
        }
        k = a.barrett.powMod(j, a.e);
        l = 16 === a.radix ? biToHex(k) : biToString(k, a.radix);
        g += l + " ";
    }
    return g.substring(0, g.length - 1)
}

举例说明一下这里的BigInt(只针对网易云JS),digits数组元素为16位的int型,相当于每个元素可以表示16位二进制数,逆序拼接(元素为0的不拼入)成一个二进制数即为实际表示的大整数,说逆序只是为了好理解,实际过程用伪代码表示应该是

ret = 0
for i in range(len(digits)):
    ret += digits[i] << (16 * i)

如大整数digits数组为[1, 1, 0, 0]表示实际的二进制数0000_0000_0000_0001_0000_0000_0000_0001

十六进制的字符串"10001"表示成二进制数就是0001_0000_0000_0000_0001,在表示成BigInt就是digits[1, 1],即十进制的65537

同样的,十六进制串"596934344e6d",表示成BigInt为[5969(十六进制), 3434,(十六进制), 4e6d(十六进制)]

接下来看看encryptedstring函数中将随机字符串例如转换成大整数,以"Yi44Nm"为例,步骤如下

  1. 取每一位的ascii码存到数组c,则c=[89, 105, 52, 52, 78, 109](即十六进制串"596934344e6d");
  2. 将c补到126位,c=[89, 105, 52, 52, 78, 109, 0, 0, 0,...];
  3. 将c转化为BigInt,结果为digits[26969, 13364, 27982, 0, 0, 0, ...],对应"695934346d4e",也就是[0110100101011001, 0011010000110100, 0110110101001110, 0, 0, ...],对应的二进制数为0110110101001110_0011010000110100_0110100101011001,转化为十六进制串"6d4e34346959",随机串"mN44iY"

但是在Python中,都是按照正常顺序处理的,这就是为什么我们需要将随机串逆序了,理论上来说,"10001"也是要逆序的,不过这是个回文串,也就无所谓了。

import binascii

s = "Yi44Nm"[::-1]
# 6d4e34346959
hex_str = binascii.hexlify(s.encode()).decode()
int(hex_str, 16)

网上的文章几乎都是一句倒序操作,就没了下文了。

关于登录的二维码

想完成网易云二维码登录功能来着,像平常一样直接抓包,结果怎么找也没找着二维码图片在哪,之前碰到的二维码真的就是一张图片,网易云是JS在Canvas上绘制出来的。这样我们没法通过请求去获取这张二维码。
这里直接记录一下解决办法

  1. selenium找到canvas元素,将图片导出base64
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

COMMON_TIME_OUT = 5

opts = webdriver.ChromeOptions()
opts.add_argument('--window-size=1920,1080')
chrome = webdriver.Chrome(options=opts)
try:
    chrome.get("https://music.163.com/")
    WebDriverWait(chrome, COMMON_TIME_OUT).until(
        EC.element_to_be_clickable((By.XPATH, '//a[@data-action="login"]'))).click()
    WebDriverWait(chrome, COMMON_TIME_OUT).until(
        EC.presence_of_element_located((By.XPATH, '//div[@class="qr"]/div[2]/canvas')))
    base64img = chrome.execute_script(
        "return document.querySelector('#login-qrcode > div > div.main.j-flag > div > div.right > div.qr > div.canvas.f-pen.j-flag > canvas').toDataURL()")
    print(base64img)
finally:
    chrome.quit()

附上chromediver的参数配置列表

  1. 因为只是二维码,经断点调试,发现在二维码中存储的数据只有一个链接,本着试一试的心态。自己画张二维码,把链接数据添加到二维码,结果成了。比起selenium要简单许多,用一下qrcode这个库就好了。

Q.E.D.


一切很好,不缺烦恼。