vd_source参数分析
狗熊岭の熊二
编辑于 2025年04月16日 16:36
  • 省流

  • vd_source

    • 分析

    • 算法

    • 应对

  • 移动端分享链接

    • buvid

    • mid

省流

b站网页端的视频页面会自动在地址栏加上vd_source参数,算法是不太正确的md5调用,点击分享按钮获得的链接也会包含vd_source,算法是正确的md5(mid)。现代CPU每秒可以计算百万次MD5,所以可以轻易从vd_source还原出分享者的mid。使用Bilibili Evolved脚本可以禁止网页自动加上vd_source。

移动端的分享链接是在服务端生成,生成的链接的参数里有AES加密过的mid,无法破解

vd_source

从2022年开始,b站网页端会在视频的链接后加上vd_source参数。前几天我才知道这个参数和mid有关,但是这个参数生成的算法,好像没有太多人去研究。

为什么这么说呢,因为一开始我去网上搜这个算法没搜到,自己逆向完后再一搜,搜到了。。。下面就简单写一下分析的过程吧,如果不想看过程可以直接跳到最后。

分析

播放视频的页面只有一个主要的js文件,直接在里面搜索vd_source关键字,可以看到vd_source是调用wt()函数生成的,参数就是用户id,下面去看看wt()里面是啥

看上去应该是md5,也有可能魔改过了,先把代码全抠出来再说。

代码块
js
自动换行
复制代码
var r = {

}
r.stringToBytes= function(t) {
    for (var e = [], n = 0; n < t.length; n++)
        e.push(255 & t.charCodeAt(n));
    return e
},
r.bytesToWords = function (bytes) {
    for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8)
        words[b >>> 5] |= bytes[i] << (24 - b % 32);

    return words;
}
r.endian= function(t) {
    if (t.constructor == Number)
        return 16711935 & r.rotl(t, 8) | 4278255360 & r.rotl(t, 24);
    for (var e = 0; e < t.length; e++)
        t[e] = r.endian(t[e]);
    return t
}
r.rotl= function(t, e) {
    return t << e | t >>> 32 - e
}
r.bytesToHex= function(t) {
    for (var e = [], n = 0; n < t.length; n++)
        e.push((t[n] >>> 4).toString(16)),
        e.push((15 & t[n]).toString(16));
    return e.join("")
}
r.wordsToBytes= function(t) {
    for (var e = [], n = 0; n < 32 * t.length; n += 8)
        e.push(t[n >>> 5] >>> 24 - n % 32 & 255);
    return e
}
const gg = function(t, e, n, r, i, o, a) {
    var s = t + (e & r | n & ~r) + (i >>> 0) + a;
    return (s << o | s >>> 32 - o) + e
}
const ff = function(t, e, n, r, i, o, a) {
    var s = t + (e & n | ~e & r) + (i >>> 0) + a;
    return (s << o | s >>> 32 - o) + e
}

const hh = function(t, e, n, r, i, o, a) {
    var s = t + (e ^ n ^ r) + (i >>> 0) + a;
    return (s << o | s >>> 32 - o) + e
}

const ii = function(t, e, n, r, i, o, a) {
    var s = t + (n ^ (e | ~r)) + (i >>> 0) + a;
    return (s << o | s >>> 32 - o) + e
}
const o = function(t) {
    /*
    return null != t && (n(t) || function(t) {
        return "function" == typeof t.readFloatLE && "function" == typeof t.slice && n(t.slice(0, 0))
    }(t) || !!t._isBuffer)
    */
   return false
}
function t(e, n) {
    e.constructor == String ? e = n && "binary" === n.encoding ? r.stringToBytes(e) : r.stringToBytes(e) : o(e) ? e = Array.prototype.slice.call(e, 0) : Array.isArray(e) || e.constructor === Uint8Array || (e = e.toString());
    for (var s = r.bytesToWords(e), c = 8 * e.length, u = 1732584193, l = -271733879, f = -1732584194, d = 271733878, p = 0; p < s.length; p++)
        s[p] = 16711935 & (s[p] << 8 | s[p] >>> 24) | 4278255360 & (s[p] << 24 | s[p] >>> 8);
    s[c >>> 5] |= 128 << c % 32,
    s[14 + (c + 64 >>> 9 << 4)] = c;
    var h = ff
      , v = gg
      , m = hh
      , y = ii;
    for (p = 0; p < s.length; p += 16) {
        var g = u
          , b = l
          , w = f
          , A = d;
        u = h(u, l, f, d, s[p + 0], 7, -680876936),
        d = h(d, u, l, f, s[p + 1], 12, -389564586),
        f = h(f, d, u, l, s[p + 2], 17, 606105819),
        l = h(l, f, d, u, s[p + 3], 22, -1044525330),
        u = h(u, l, f, d, s[p + 4], 7, -176418897),
        d = h(d, u, l, f, s[p + 5], 12, 1200080426),
        f = h(f, d, u, l, s[p + 6], 17, -1473231341),
        l = h(l, f, d, u, s[p + 7], 22, -45705983),
        u = h(u, l, f, d, s[p + 8], 7, 1770035416),
        d = h(d, u, l, f, s[p + 9], 12, -1958414417),
        f = h(f, d, u, l, s[p + 10], 17, -42063),
        l = h(l, f, d, u, s[p + 11], 22, -1990404162),
        u = h(u, l, f, d, s[p + 12], 7, 1804603682),
        d = h(d, u, l, f, s[p + 13], 12, -40341101),
        f = h(f, d, u, l, s[p + 14], 17, -1502002290),
        u = v(u, l = h(l, f, d, u, s[p + 15], 22, 1236535329), f, d, s[p + 1], 5, -165796510),
        d = v(d, u, l, f, s[p + 6], 9, -1069501632),
        f = v(f, d, u, l, s[p + 11], 14, 643717713),
        l = v(l, f, d, u, s[p + 0], 20, -373897302),
        u = v(u, l, f, d, s[p + 5], 5, -701558691),
        d = v(d, u, l, f, s[p + 10], 9, 38016083),
        f = v(f, d, u, l, s[p + 15], 14, -660478335),
        l = v(l, f, d, u, s[p + 4], 20, -405537848),
        u = v(u, l, f, d, s[p + 9], 5, 568446438),
        d = v(d, u, l, f, s[p + 14], 9, -1019803690),
        f = v(f, d, u, l, s[p + 3], 14, -187363961),
        l = v(l, f, d, u, s[p + 8], 20, 1163531501),
        u = v(u, l, f, d, s[p + 13], 5, -1444681467),
        d = v(d, u, l, f, s[p + 2], 9, -51403784),
        f = v(f, d, u, l, s[p + 7], 14, 1735328473),
        u = m(u, l = v(l, f, d, u, s[p + 12], 20, -1926607734), f, d, s[p + 5], 4, -378558),
        d = m(d, u, l, f, s[p + 8], 11, -2022574463),
        f = m(f, d, u, l, s[p + 11], 16, 1839030562),
        l = m(l, f, d, u, s[p + 14], 23, -35309556),
        u = m(u, l, f, d, s[p + 1], 4, -1530992060),
        d = m(d, u, l, f, s[p + 4], 11, 1272893353),
        f = m(f, d, u, l, s[p + 7], 16, -155497632),
        l = m(l, f, d, u, s[p + 10], 23, -1094730640),
        u = m(u, l, f, d, s[p + 13], 4, 681279174),
        d = m(d, u, l, f, s[p + 0], 11, -358537222),
        f = m(f, d, u, l, s[p + 3], 16, -722521979),
        l = m(l, f, d, u, s[p + 6], 23, 76029189),
        u = m(u, l, f, d, s[p + 9], 4, -640364487),
        d = m(d, u, l, f, s[p + 12], 11, -421815835),
        f = m(f, d, u, l, s[p + 15], 16, 530742520),
        u = y(u, l = m(l, f, d, u, s[p + 2], 23, -995338651), f, d, s[p + 0], 6, -198630844),
        d = y(d, u, l, f, s[p + 7], 10, 1126891415),
        f = y(f, d, u, l, s[p + 14], 15, -1416354905),
        l = y(l, f, d, u, s[p + 5], 21, -57434055),
        u = y(u, l, f, d, s[p + 12], 6, 1700485571),
        d = y(d, u, l, f, s[p + 3], 10, -1894986606),
        f = y(f, d, u, l, s[p + 10], 15, -1051523),
        l = y(l, f, d, u, s[p + 1], 21, -2054922799),
        u = y(u, l, f, d, s[p + 8], 6, 1873313359),
        d = y(d, u, l, f, s[p + 15], 10, -30611744),
        f = y(f, d, u, l, s[p + 6], 15, -1560198380),
        l = y(l, f, d, u, s[p + 13], 21, 1309151649),
        u = y(u, l, f, d, s[p + 4], 6, -145523070),
        d = y(d, u, l, f, s[p + 11], 10, -1120210379),
        f = y(f, d, u, l, s[p + 2], 15, 718787259),
        l = y(l, f, d, u, s[p + 9], 21, -343485551),
        u = u + g >>> 0,
        l = l + b >>> 0,
        f = f + w >>> 0,
        d = d + A >>> 0
    }
    return r.endian([u, l, f, d])
}
function calc(num) {
    return r.bytesToHex(r.wordsToBytes(t(num)))
}
复制成功

算法

仔细分析一下可以发现

  • 参数是字符串类型时,这个函数就是标准的md5

  • 参数是数字时,会把每个数字的内容作为int放入数组,比如”12“ -> [1,2],再进行md5,地址栏的vd_source就是这种情况。

你可以使用其他语言来写,以获得更快的速度。我这里用Java试了下,遍历6亿个id耗时92秒,比node大概快15倍。

下面给一个Java的实现

代码块
Java
自动换行
复制代码
    public static String calc(int n)  {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        digest.reset();
        String s = String.valueOf(n);
        int index = 0;
        byte[] hash = new byte[s.length()];
        for (byte b : s.getBytes()) {
            hash[index++] = (byte) (b-48);
        }
        StringBuilder hexString = new StringBuilder();
        for (byte b : digest.digest(hash)) {
            hexString.append(String.format("%02x", b & 0xFF));
        }
        return hexString.toString();
    }
    public static void main(String[] args) {
        System.out.println(calc(451537183));
    }
复制成功

上面的程序输出3f134867a7ce5d2911ebe492f213efcb,和上图一致。

另外要注意只有在浏览器地址栏自动加上的vd_source是不标准的md5算法,如果是点击分享按钮获得的链接,这个vd_source就是正常的md5(mid)

应对

那么如何去掉这个vd_source呢?虽然对于大多数人来说,就算别人知道了你的mid也没啥影响,但是看着就是不舒服。。。

这里可以使用Bilibili-Evolved: https://github.com/the1812/Bilibili-Evolved​

安装后会在网页左侧显示配置按钮,添加这个组件:https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/utils/url-params-clean.js

再次刷新页面就会发现不地址栏没有那些参数了。

移动端分享链接

移动端的分享链接是在服务端生成的。

生成的短链接访问后会重定向到完整的链接

看上去比较有用的参数是buvid和mid。

buvid

反编译apk,绕来绕去,找到了buvid的生成逻辑:

大致是一个前缀+md5(x)的第2,12,22个字符+md5(x),和实际看到的buvid是对的上的,但是没找到x是啥。换了两个账号,buvid是一样的,估计这个参数是设备指纹吧。

mid

试了两个账号的mid值

代码块
bash
自动换行
复制代码
/Todf1sb+JzK3DxeX4w/xX8FTQ/SZMtL1rElX6M3iMo=
so/5QmJU9FyVlzrwUtH9FQ==
复制成功

不同的账号id长度,一个是短的id451537183,一个长的3546848329468598,结果的长度也不一样了,应该是AES加密了,这个就不可能破解了。