Greasy Fork

Greasy Fork is available in English.

Canvas & WebGL 指纹防护

防止Canvas和WebGL指纹识别追踪

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Canvas & WebGL 指纹防护
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  防止Canvas和WebGL指纹识别追踪
// @author       By 9527
// @namespace    https://github.com/0xD88C/UserScript/Canvas&WebGL指纹防护-By9527_v1.0.0.js
// @match        *://*/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // 生成随机噪声
    function getRandomNoise() {
        return Math.random() * 0.0001 - 0.00005;
    }

    // ==================== Canvas 指纹防护 ====================
    
    const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
    const originalToBlob = HTMLCanvasElement.prototype.toBlob;
    const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;

    // 添加噪声到像素数据
    function addNoiseToImageData(imageData) {
        const data = imageData.data;
        for (let i = 0; i < data.length; i += 4) {
            // 对RGB通道添加微小随机噪声
            data[i] += Math.floor(getRandomNoise() * 10);     // R
            data[i + 1] += Math.floor(getRandomNoise() * 10); // G
            data[i + 2] += Math.floor(getRandomNoise() * 10); // B
            // Alpha通道保持不变
        }
        return imageData;
    }

    // 重写 toDataURL
    HTMLCanvasElement.prototype.toDataURL = function() {
        const context = this.getContext('2d');
        if (context) {
            const imageData = context.getImageData(0, 0, this.width, this.height);
            addNoiseToImageData(imageData);
            context.putImageData(imageData, 0, 0);
        }
        return originalToDataURL.apply(this, arguments);
    };

    // 重写 toBlob
    HTMLCanvasElement.prototype.toBlob = function(callback) {
        const context = this.getContext('2d');
        if (context) {
            const imageData = context.getImageData(0, 0, this.width, this.height);
            addNoiseToImageData(imageData);
            context.putImageData(imageData, 0, 0);
        }
        return originalToBlob.apply(this, arguments);
    };

    // 重写 getImageData
    CanvasRenderingContext2D.prototype.getImageData = function() {
        const imageData = originalGetImageData.apply(this, arguments);
        return addNoiseToImageData(imageData);
    };

    // ==================== WebGL 指纹防护 ====================

    const getParameterProto = WebGLRenderingContext.prototype.getParameter;
    const getParameterProto2 = WebGL2RenderingContext.prototype.getParameter;

    // 需要伪造的WebGL参数
    const webglParams = {
        // 渲染器信息
        37445: 'Intel Inc.',  // UNMASKED_VENDOR_WEBGL
        37446: 'Intel Iris OpenGL Engine',  // UNMASKED_RENDERER_WEBGL
        
        // 其他关键参数
        7936: 'WebKit',  // VENDOR
        7937: 'WebKit WebGL',  // RENDERER
        7938: 'WebGL 1.0',  // VERSION
        35724: 'WebGL GLSL ES 1.0',  // SHADING_LANGUAGE_VERSION
        
        // 数值参数添加噪声
        3379: 16384 + Math.floor(Math.random() * 100),  // MAX_TEXTURE_SIZE
        34076: 16384 + Math.floor(Math.random() * 100), // MAX_RENDERBUFFER_SIZE
        34024: 16 + Math.floor(Math.random() * 4),      // MAX_VIEWPORT_DIMS
        3386: 8 + Math.floor(Math.random() * 8),        // MAX_VERTEX_ATTRIBS
    };

    // 重写 getParameter (WebGL 1.0)
    WebGLRenderingContext.prototype.getParameter = function(parameter) {
        if (webglParams.hasOwnProperty(parameter)) {
            return webglParams[parameter];
        }
        return getParameterProto.call(this, parameter);
    };

    // 重写 getParameter (WebGL 2.0)
    if (typeof WebGL2RenderingContext !== 'undefined') {
        WebGL2RenderingContext.prototype.getParameter = function(parameter) {
            if (webglParams.hasOwnProperty(parameter)) {
                return webglParams[parameter];
            }
            return getParameterProto2.call(this, parameter);
        };
    }

    // 重写 getExtension
    const originalGetExtension = WebGLRenderingContext.prototype.getExtension;
    WebGLRenderingContext.prototype.getExtension = function(name) {
        // 阻止某些敏感扩展
        const blockedExtensions = [
            'WEBGL_debug_renderer_info',
            'WEBKIT_WEBGL_debug_renderer_info'
        ];
        
        if (blockedExtensions.includes(name)) {
            return null;
        }
        
        return originalGetExtension.call(this, name);
    };

    // 重写 getSupportedExtensions
    const originalGetSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions;
    WebGLRenderingContext.prototype.getSupportedExtensions = function() {
        const extensions = originalGetSupportedExtensions.call(this);
        if (extensions) {
            return extensions.filter(ext => 
                !ext.includes('debug_renderer_info')
            );
        }
        return extensions;
    };

    // ==================== AudioContext 指纹防护 (额外) ====================
    
    if (typeof AudioContext !== 'undefined') {
        const OriginalAudioContext = AudioContext;
        const OriginalOfflineAudioContext = OfflineAudioContext;

        // 添加音频噪声
        const addAudioNoise = function(buffer) {
            for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
                const channelData = buffer.getChannelData(channel);
                for (let i = 0; i < channelData.length; i++) {
                    channelData[i] += getRandomNoise();
                }
            }
        };

        // 重写 OfflineAudioContext
        window.OfflineAudioContext = function() {
            const context = new OriginalOfflineAudioContext(...arguments);
            const originalStartRendering = context.startRendering;
            
            context.startRendering = function() {
                return originalStartRendering.call(this).then(buffer => {
                    addAudioNoise(buffer);
                    return buffer;
                });
            };
            
            return context;
        };
    }

    console.log('🛡️ Canvas & WebGL 指纹防护已启用');

})();