跳至内容

Jixun's Blog 填坑还是开坑,这是个好问题。

记一次奇怪的 Cloudreve 反盗版检测

复现

如果运气好被反指纹识别改了靠前内容的像素的话,稍等片刻就会自动跳转到 Cloudreve 网盘程序的授权验证错误页面

反盗版检测

反盗版检测分为两部分:

  1. 授权信息获取;
  2. 利用授权信息验证当前域名是否合法。

验证未通过的场合会跳转到上述的授权验证错误页面。

利用图片隐写隐藏授权信息

抵达指定的 URL 后,程序会加载 /api/v3/bg 图片资源,提取每个像素的 RGB 值,然后根据一定规则解密出其隐写的字符串:

function decode_image(e) {
    // e.data = canvas image data

    let height = 272;
    let width = 522;
    let items = [];

    // RGBA
    for (let x = 0; x < width * height * 4; x += 4) {
        // keep RGB only.
        if (x + 4 !== 4 * width) {
            items.push(...e.data.slice(x, x + 3));
        }
    }

    let decode = "";
    let last = null;
    let repeatCount = 0;
    for (let i = 17; i < items.length && repeatCount < 16; i++) {
        const curr = (items[i] + 1) & 1;
        decode += curr;

        if (curr === last) {
            repeatCount++;
        } else {
            last = curr;
            repeatCount = 0;
        }
    }

    // 删除额外的内容
    decode = decode.slice(0, decode.length - (decode.length % 16));

    // 解码二进制 0/1
    return decode.replace(/[01]{16}/g, z => String.fromCharCode(parseInt(z, 2)));
}

(该片段来自 6.2e355853.chunk.js

解码后得到下述内容(加入了换行方便阅读;注意反指纹不一定会改变同一处的内容):

  • 开启 Brave Shield 时得到的值:

    window.faf6fc=["pan.huang1111.cn","|est.huang1111.cn","127.0.0.1","localhost"];
    window.edd3ec="2020112005261691997";
    window.e7afcf=-353960891;
    
  • 关闭 Brave Shield 后得到的值:

    window.faf6fc=["pan.huang1111.cn","test.huang1111.cn","127.0.0.1","localhost"];
    window.edd3ec="2020112005261691997";
    window.e7afcf=-353960891;
    

可以看到其中的一个字符发生了变化。

进行域名验证

网盘程序加载时会每 2 秒检测一次 edd3ec 的值是否设定,然后进行检测:

  • 当前域名是否在 faf6fc 的列表中
  • 这三个值是否被篡改。

检测代码整理后如下:

const keyAllowedDomainList = 'faf6fc';
const keyDomainLicense = 'edd3ec';
const keyExpectedChecksum = 'e7afcf';

function idCheck(text) {
    let checksum = 9541;
    if (text.length == 0)
        return checksum;
    for (let i = 0; i < text.length; i++) {
        checksum = ((checksum << 5) + checksum) - text.charCodeAt(i);
        checksum &= checksum; // 确保数字在 32 位
    }
    return checksum
}

function domainCheck(retryCount) {
    if (retryCount < 0)
        throw 'LEFTBELOW0';

    if (!window[keyDomainLicense]) {
        window.setTimeout((function() {
            try {
                domainCheck(retryCount - 1);
            } catch (code) {
                document.write("<h3>Backend is not running.</h3><p>Please check your Cloudreve status. (Code: " + code + ", ID:" + window[keyDomainLicense] + ")</p>")
            }
        }
        ), 7000);
        return ;
    }

    if (window[keyExpectedChecksum] !== idCheck(JSON.stringify(window[keyAllowedDomainList]) + window[keyDomainLicense]))
        throw "HASH";

    let domainMatch = false;
    for (let i = 0; i < window[keyAllowedDomainList].length; i++) {
        if (window.location.hostname === window[keyAllowedDomainList][i]) {
            domainMatch = true;
        }
    }
    if (!domainMatch)
        throw "MATCH"
}

let checki;
window.onload = function() {
    checki = window.setInterval((function() {
        if (location.pathname.startsWith("/home") || window[keyDomainLicense]) {
            window.clearInterval(checki);
            try {
                domainCheck(2)
            } catch (o) {
                window.location.href = "https://cloudreve.org/FixComplete?code=" + o + '&id=' + window[keyDomainLicense]
            }
        }
    }
    ), 2000)
}

若是验证失败,则会跳转到前文所述的错误页面。

绕过验证

绕过的方法太多了,如重写 /api/v3/bg 回应为空白图片,或是添加 unsafe-eval CSP 规则来禁止使用 eval

重写回应后,解码图像得到的内容为空文字。

本文就不深入这个话题了,读者有兴趣的话就自己尝试吧。

结语

莫名其妙的 bug,因使用 canvas 画图拿像素点触发了浏览器的反指纹识别,导致隐写数据无法正常解析。

如果站长使用的是所谓「开心版」,去除这个检测相对容易。虽然加了混淆,但那几个关键的函数名并没有隐藏。我愿称之为「如混」。

从这个角度来看的话,受伤害的都是「正版用户」了吧。

如果是一名普通的使用者,遇到这类 bug 通常无法自行解决此类问题。莫名其妙的盗版提示也会对网站的声誉带来一定的打击。

修改建议:遇到错误时回报相关信息,授权服务器再对报错的域名进行二次验证是否在合法客户列表中,之后再决定是否展示盗版警告。

以上。

知识共享许可协议 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。

评论区