什么是补环境,模拟浏览器环境让浏览器js运行,为什么需要补环境,因为浏览器和本地nodejs环境有差异,网站开发者为了检测用户是否是本地环境运行

主要补的环境Document,Window,Navigator,Location,Element

这是内置原始类型,需要将其实例化并补齐使用的方法,由于本身是函数所以方法只能赋值在原型链,补环境要和扣代码相配合要不然仅凭调试台返回信息,无法补全缺失环境。

一般来说补环境的基本流程是:先补基础环境,添加代理,在加密代码运行报错的地方去源代码处打断点分析具体缺少什么环境,代码整个运行不报错后再用打印环境工具分析或者去代码中进行插桩进行调试。

补环境前准备:

本地代码与浏览器联调

运行此代码即可在浏览器调试台运行本地代码,省去了vscode繁琐的配置,非常方便调试和打印。

node --inspect-brk .\补环境代码文件.js

打印浏览器环境工具

这里用的是大佬无私分享的工具,具体使用方式和作用这里都有,这里就不在重复。爬虫利器SpiderTools谷歌插件教程v1.0.0!!!web端JavaScript环境检测!!!_爬虫谷歌插件-CSDN博客

补环境代理

代理的重要不用多说,这里分享一个代理。


function watch(obj, name) {
    return new Proxy(obj, {
        get: function (target, property) {
            const value = target[property];
            const type = typeof value;

            if (type === "symbol") {
                // 获取 Symbol 的描述,如果没有描述则显示 'no description'
                const symbolDescription = property.description || 'no description';
                console.log(`对象=>${name},读取属性:${symbolDescription},这是一个 Symbol 类型的值`);
            } else if (type === "function") {
                // 对于函数,我们可以打印函数名,如果有的话
                const functionName = value.name || 'anonymous';
                console.log(`对象=>${name},读取属性:${property.toString()},这是一个名为 ${functionName} 的函数`);
            } else {
                // if(name==='window' &&  String(property)==='Symbol(Symbol.toPrimitive)' ){
                //     debugger
                //
                // }
                console.log(`对象=>${name},读取属性:${String(property)},值为:${value},类型为:${type}`);
            }
            return value;
        },
        set: (target, property, newValue, receiver) => {
            const valueType = typeof newValue;
            if (valueType === "symbol") {
                // 获取新设置的 Symbol 的描述
                const symbolDescription = newValue.description || 'no description';
                console.log(`对象=>${name},设置属性:${String(property)},这是一个 Symbol 类型的新值, 描述为: ${symbolDescription}`);
            } else {
                console.log(`对象=>${name},设置属性:${String(property)},值为:${newValue},类型为:${valueType}`);
            }
            return Reflect.set(target, property, newValue, receiver);
        }
    });
}


//第一个参数是要代理的对象,第二个是打印的时候的表示哪个参数
window = watch(window, 'window')
location = watch(new Location(), 'location')

批量赋值对象属性代码

可以把对象中的值都复制到内存中

function copyObj(obj) {
    var newObject = {};
    for (var key in obj) {
        var result = obj[key];
        if (['string', 'boolean', 'number'].indexOf(typeof result) !== -1 || Object.prototype.toString.call(result) === '[object Array]') {
            newObject[key] = result;
        }
    }
    copy(newObject);
};

copyObj(location) 
copyObj(navigator) 

常用补环境方法

设置原型链


//原型链直接设置函数,一般无检测函数直接等于即可
Document.prototype.getElementsByTagName = function getElementsByTagName(tagName) {
    console.log('docuemnt.getElementsByTagName=>', {tagName});
    const tag = {
        i: [], script: [script1,script2],base:[]
    }
    console.log(tag[tagName])
    return tag[tagName]
}


//给对象设置内置属性[Symbol.toStringTag]
Object.defineProperties(window, {
    [Symbol.toStringTag]: {
        value: "Window",
        configurabble: true,

    },
    [Symbol.toPrimitive]: function (a1) {
        console.log("Symbol.toPrimitive==>a1", a1);
    }
})


//设置一个函数的原型链是另一个函数的原型链
HTMLDocument = function HTMLDocument() {}
Object.setPrototypeOf(HTMLDocument.prototype, Document.prototype)


//修改一个对象中值的不可修改属性
const obj = {
  _name: 'John'
};

Object.defineProperty(obj, 'name', {
  get() {
    return this._name;
  },
  set(value) {
    this._name = value;
  },
  enumerable: true,
  configurable: true
});
//查询对象属性不可修改值
console.log(Object.getOwnPropertyDescriptor(obj,'name'))

补环境示例

补上window、location、document、navigator,并删除__filename、__dirname、global、(浏览器中没有这两个环境),navigator也要删除,因为nodejs会内置一个navigator属性,在自己补充环境时可能会造成一些错误,同时对一些常用检测环境针对性还原。

delete __filename
delete __dirname
delete navigator

const locationInfo = {};
const navigatorInfo = {};

//使用箭头函数把locationInfo的属性添加到Location函数的this中
function Location() {
    Object.keys(locationInfo).forEach(key => {
        this[key] = locationInfo[key];
    })
}

function Navigator() {
}

Object.keys(navigatorInfo).forEach(key => {
    Navigator.prototype[key] = navigatorInfo[key];
})

function Document() {
}

function Window() {
}

location = new Location()
navigator = new Navigator()
document = new Document()

window = global
//删除global检测
delete global
window.__proto__  = new Window()
window.top = window;
window.self = window

重点属性检测

以下是一些经常用到的环境检测,也是补环境中的重点难点。

✅ document.all 的 IE 兼容检测

✅ navigator.webdriver 反自动化检测

✅ window.chrome 存在性检测

✅ MutationObserver DOM 变化检测

✅ WebSocket 连接检测

✅ getBattery 设备信息检测

✅ localStorage / sessionStorage 访问检测

✅ indexedDB 可用性检测

✅ CanvasRenderingContext2D 指纹检测

✅ setTimeout 和 setInterval 时间检测

✅ fetch 和 XMLHttpRequest API 访问检测

✅ visibilityState 页面可见性检测

✅ window.top / window.self 反 iframe 检测

通过补环境实例解决这些环境检测

delete __dirname;
delete __filename;

window = global;

delete global;

Object.defineProperties(window, {
    [Symbol.toStringTag]: {
        value: 'Window',
        configurable: true
    }
});

l_obj = {

};

l_input = {

};

l2_input = {

};

l3_input = {

};

var form = {
};

form_action = '';

Object.defineProperty(form, 'action',{
    get() {
        console.log('form->action.get--------->', l_input)
        return l_input;
    },
    set(v) {
        console.log('form->action.set--------->', v)
        form_action = v;
    }
});

form_textContent = {};

Object.defineProperty(form, 'textContent',{
    get() {
        console.log('form->textContent.get--------->', l2_input)
        return l2_input;
    },
    set(v) {
        console.log('form->textContent.set--------->', v)
        form_action = v;
    }
});

form_id = '';

Object.defineProperty(form, 'id',{
    get() {
        console.log('form->id.get--------->', l3_input)
        return l3_input;
    },
    set(v) {
        console.log('form->id.set--------->', v)
        form_id = v;
    }
});

form_innerText = '';

Object.defineProperty(form, 'innerText',{
    get() {
        console.log('form->innerText.get--------->', l3_input)
        return l3_input;
    },
    set(v) {
        console.log('form->innerText.set--------->', v)
        form_innerText = v;
    }
});


a_labl = {
    //去浏览器里拿
    href: 'xxxxxxxxxxxxxx',
    protocol: 'https:',
    port: '',
    //去浏览器里拿
    hostname: 'xxxxxxxxxxxxxxx',
    //去浏览器里拿
    pathname: 'xxxxxxxxxxxxxxx'
}

window.HTMLAnchorElement = function (){};

scripts = [
                {
                    type: "text/javascript",
                    r: 'm',
                    parentElement: {
                        getAttribute: function(args) {
                            console.log('head1->parentElement->getAttribute: ', args)
                            console.log(arguments)
                            debugger;
                            if (args == 'r')
                            {
                                return 'm';
                            }
                        },
                        getElementsByTagName: function(args) {
                            console.log('head1->getElementsByTagName: ', args)
                            console.log(arguments)
                            debugger
                        },
                         removeChild: function (args) {
                            console.log('head1->parentElement->removeChild', args);
                            console.log(arguments);
                            debugger;
                        },
                    },
                    getAttribute: function(args) {
                        console.log('script1->getAttribute: ', args)
                        console.log(arguments)
                        debugger;
                        if (args == 'r')
                        {
                            return 'm';
                        }
                    }
                },
                {
                    type: "text/javascript",
                    r: 'm',
                    parentElement: {
                         getAttribute: function(args) {
                            console.log('head2->parentElement->getAttribute: ', args);
                            console.log(arguments);
                            debugger;
                        },
                        getElementsByTagName: function(args) {
                            console.log('head2->getElementsByTagName: ', args);
                            console.log(arguments);
                            debugger
                        },
                         removeChild: function (args) {
                            console.log('head2->parentElement->removeChild', args);
                            console.log(arguments);
                            debugger;
                        },
                    },
                    getAttribute: function(args) {
                        console.log('script2->getAttribute: ', args);
                        console.log(arguments);
                        debugger;
                        if (args == 'r')
                        {
                            return 'm';
                        }
                    },
                    //去浏览器里拿
                    src: "xxxxxxxxxx",
                }
            ]

var input_count = 0;

var l_meta = {
        id: 'FbkwzLN5XOx0',
        content: 'tyGGg5AdQlANmSX9z3xbpGEoEKVuG9rmj_VCz71ozkpQ9tph9oDZE2RjIwQz8iL5oWgiCSPtU67jWlcPgf7DyTWP8X_.29Z5B0y9OtqwW4e6THU9dqdapsjx4a81rlUo',
        r: 'm',
        getAttribute: function(args)
        {
             console.log('meta->getAttribute: ', args);
             console.log(arguments);
             debugger;
             if (args == 'r')
             {
                return 'm';
             }
        },
        parentNode: {
             removeChild: function (args) {
                console.log('meta->parentNode->removeChild', args)
                debugger;
                return {};
            },
        }
}

div_i = [];

div = {
    getElementsByTagName:function (args)
    {
        console.log('document->div->getElementsByTagName', args)
        console.log(arguments)
        debugger;
        if(args === "i"){
            return div_i;
        }
    }
}

doc_base = [

]

Document = function Document(){}

Object.defineProperty(Document.prototype,'createElement',{
    configurable: true,
    enumerable: true,
    value: function createElement(args) {
        console.log('document->createElement', args)
        console.log(arguments);
        debugger;
        if (args == 'div')
        {
            return div;
        }
        else if (args == 'form')
        {
            return form;
        }
        else if (args == 'input')
        {
            if (input_count == 0)
            {
                input_count++;
                return l_input;
            }
            else if (input_count == 1)
            {
                input_count++;
                return l2_input;
            }
            else if (input_count == 2)
            {
                return l3_input;
            }
        }
        else if (args == 'a')
        {
            return a_labl;
        }
        else
        {
            return l_obj;
        }
    },
    writable: true,
})

const v8 =require('v8');
const vm= require('vm');
v8.setFlagsFromString('--allow-natives-syntax');
let undetectable = vm.runInThisContext("%GetUndetectable()");
v8.setFlagsFromString('--no-allow-natives-syntax');

Object.defineProperty(Document.prototype,'all',{
    configurable: true,
    enumerable: true,
    value: undetectable,
    writable: true,
})

Object.defineProperty(Document.prototype,'body',{
    configurable: true,
    enumerable: true,
    value: null,
    writable: true,
})

Object.defineProperty(Document.prototype,'visibilityState',{
    configurable: true,
    enumerable: true,
    value: 'hidden',
    writable: true,
})

Object.defineProperty(Document.prototype,'toString',{
    configurable: true,
    enumerable: true,
    value: function toString() {return '[object HTMLDocument]';},
    writable: true,
})

Object.defineProperty(Document.prototype,'addEventListener',{
    configurable: true,
    enumerable: true,
    value: function addEventListener(args) {
        console.log('document->addEventListener', args)
        console.log(arguments);
        debugger;
        return {};
    },
    writable: true,
})

documentElement = {};
Object.defineProperty(Document.prototype,'documentElement',{
    configurable: true,
    enumerable: true,
    // value: function documentElement(args) {
    //     console.log('document->documentElement', args)
    //     console.log(arguments);
    //     debugger;
    //     return {};
    // },
    value:documentElement,
    writable: true,
})

Object.defineProperty(Document.prototype,'appendChild',{
    configurable: true,
    enumerable: true,
    value: function appendChild(args) {
        console.log('document->appendChild', args)
        console.log(this)
        console.log(arguments);
        debugger;
        return {};
    },
    writable: true,
})


Object.defineProperty(Document.prototype,'removeChild',{
    configurable: true,
    enumerable: true,
    value: function removeChild(args) {
        console.log('document->removeChild', args)
        console.log(arguments);
        debugger;
        return {};
    },
    writable: true,
})

frist_get_script = 1;
Object.defineProperty(Document.prototype,'getElementsByTagName',{
    configurable: true,
    enumerable: true,
    value: function getElementsByTagName(args) {
        console.log('document->getElementsByTagName: ', args);
        console.log(arguments)
        debugger
        if (args == 'script')
        {
            if (frist_get_script == 1)
            {
                frist_get_script = 0;
                return scripts;
            }
            return [];
        }
        if (args === 'base') {
            debugger;
            return doc_base;
        }
        return [];
    },
    writable: true,
})

Object.defineProperty(Document.prototype,'getElementById',{
    configurable: true,
    enumerable: true,
    value: function getElementById(args) {
        console.log('document->getElementById', args)
        console.log(arguments);
        debugger;
        return l_meta;
    },
    writable: true,
})


HTMLDocument = function HTMLDocument(){}

Object.setPrototypeOf(HTMLDocument.prototype,Document.prototype)
document = new HTMLDocument()
// console.log(document.createElement('script'));

Object.defineProperty(document.all,'length',{
    get : function (){
        console.log('document.all.length ------------------------------------->')
        return Object.keys(document.all).length
    }
})

document.all[0] = null;
document.all[1] = null;
document.all[2] = null;
document.all[3] = null;
document.all[4] = null;
document.all[5] = null;

// document.all = [{},{},{},{},{},{}];

function Window(){};

window.Window = Window;

window.__proto__ = Window.prototype;

_null = function (){
    debugger;
    console.log(arguments)
    return {};
}

_mutationObserver = {
    observe:function(args)
    {
        console.log('_mutationObserver->observe', args)
        console.log(arguments);
        return {};
    }
};

window.innerHeight = 945;
window.innerWidth = 1920;
window.outerHeight = 1022;
window.outerWidth = 1910;
window.TEMPORARY = 0;

window.MutationObserver = function(args)
{
    console.log('window->mutationObserver', args)
    console.log(arguments);
    return _mutationObserver;
}

CanvasRenderingContext2D = function () {

};

getImageData = {
    toString() {
        console.log('getImageData');
        return 'function getImageData() { [native code] }'
    }
}

Object.defineProperty(CanvasRenderingContext2D.prototype,'getImageData',{
    get : function (){
        return getImageData;
    }
})

HTMLCanvasElement = function () {

};

toBlob = {
    toString() {
        console.log('toBlob');
        return 'function toBlob() { [native code] }'
    }
}

toDataURL = {
    toString() {
        console.log('toDataURL');
        return 'function toDataURL() { [native code] }'
    }
}

Object.defineProperty(HTMLCanvasElement.prototype,'toBlob',{
    get : function (){
        return toBlob;
    }
})

Object.defineProperty(HTMLCanvasElement.prototype,'toDataURL',{
    get : function (){
        return toDataURL;
    }
})

window.CanvasRenderingContext2D = CanvasRenderingContext2D;
window.HTMLCanvasElement = HTMLCanvasElement;

WebSocket = function(args)
{
    console.log('WebSocket ----------------------->', args);
    return {};
}

window.WebSocket = WebSocket;

webkitRequestFileSystem = _null;

window.webkitRequestFileSystem = webkitRequestFileSystem;

chrome = {};
window.chrome = chrome;

//去浏览器里拿
location = {
    "ancestorOrigins": {},
    "href": "xxxxxxxx",
    "origin": "xxxxxxxx",
    "protocol": "https:",
    "host": "xxxxxxxx",
    "hostname": "xxxxxxxx",
    "port": "",
    "pathname": "xxxxxxxx",
    "search": "xxxxxxxx",
    "hash": ""
}

window.top = window;
window.self = window;

navigator = {
    appCodeName: "Mozilla",
    appName: "Netscape",
    appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
    connection: {
       downlink: 2.4,
       effectiveType: "4g",
       onchange: null,
        rtt: 50,
        saveData: false
    },
    cookieEnabled: true,
    deprecatedRunAdAuctionEnforcesKAnonymity: true,
    deviceMemory: 8,
    doNotTrack: null,
    hardwareConcurrency: 22,
    languages: ["zh-CN", "en", "zh"],
    language: "zh-CN",
    onLine: true,
    platform: "Win32",
    product: "Gecko",
    productSub: '20030107',
    userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
    vendor: "Google Inc.",
    vendorSub: "",
    // webdriver: false,
    webkitPersistentStorage: {},
    getBattery: function() {return {then(){}}}
};

function Naviator(){};

Object.defineProperties(Naviator.prototype,{})

function webdriver()
{
    console.log("webdriver--------------------->");
    return false;
}

webdriver.toString = function () {return 'false'}

Object.defineProperty(Naviator.prototype, 'webdriver',{
    [Symbol.toStringTag]: {
        value: 'webdriver',
        configurable: true
    },
    configurable:true,
    enumerable: true,
    get: webdriver
});

navigator.__proto__ = Naviator.prototype;


Object.defineProperties(navigator, {
    [Symbol.toStringTag]: {
        value: 'webdriver',
        configurable: true
    }
})


window.navigator = navigator;

window["clientInformation"] = navigator;

window.location = location;

window.history = {
    length: 2,
    state: null,
    scrollRestoration: "auto",
    replaceState: _null,
}

screen = {
    availHeight: 1392,
    availLeft: 1536,
    availTop: 0,
    availWidth: 2560,
    colorDepth: 24,
    height: 1440,
    isExtended: true,
    onchange: null,
    orientation: {angle: 0, type: 'landscape-primary', onchange: null},
    pixelDepth: 24,
    width: 2560
}

window.screen = screen;

window.DOMParser = function ()
{
    debugger;
    return {};
}

window.XMLHttpRequest = function () {
    debugger;
    return {}
}

localStorage = {
    length: 0,
    removeItem: function () {
        console.log('localStorage->removeItem')
        console.log(arguments);
    },
    setItem: function () {
        console.log('localStorage->setItem');
        console.log(arguments);
        this[arguments[0]] = arguments[1];
        console.log(this);
    },
    getItem: function (args) {
        console.log('localStorage->getItem')
        console.log(arguments);
        return this[args];
    },
}
sessionStorage = {
    length: 0,
    removeItem: function () {
        console.log('localStorage->removeItem')
        console.log(arguments);
    },
    setItem: function () {
        console.log('localStorage->setItem');
        console.log(arguments);
        this[arguments[0]] = arguments[1];
        console.log(this);
    },
    getItem: function (args) {
        console.log('localStorage->getItem')
        console.log(arguments);
        console.log(this[args]);
        return this[args];
    },
}

window.localStorage = localStorage;
window.sessionStorage = sessionStorage;
window.name = '$_YWTU=7nXC8M_ZRylQDpM8YlUdxPdHlh7M_t8lWHF71cWe4Q7&$_YVTX=Js3&vdFm='

indexedDB = {
    open: function (args) {
        console.log('indexedDB->open---------------->');
        // return {};
        return indexedDB;
    }
}

window.indexedDB = indexedDB;


window.addEventListener = function (args)
{
    console.log('window->addEventListener: ', args)
    debugger;
    return {};
}

window.attachEvent = undefined;


window.Request = function (args)
{
    console.log('window->Request: ', args)
    debugger;
    return {};
}

window.fetch  = function (args)
{
    console.log('window->fetch: ', args)
    debugger;
    return {};
}


window.setInterval = _null;
window.setTimeout = _null;

window.document = document;

//$_ts=window['$_ts']内容
require('./ts')

//外链js内容
require('./link')

function get_cookie()
{
    return document.cookie;
}

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐