爬虫&逆向--Day24&Day25--补环境专题

一、补环境的原理

浏览器环境和node环境对比:

image-20250113下午70540645-6766341

浏览器下:

image-20250113下午93341880-6775223

node.js

image-20250113下午93621748-6775382

当我们辛苦将浏览器环境的加密或者解密入口找到,把加密或者解密的JS的代码拷贝到本地,由node解释器驱动执行的时候,会因为拷贝的JS代码中包括只能由浏览器调用的API,现在被node执行就会报错,为了解决这个问题,我们需要在拷贝的代码环境中模拟补充需要的前端对象,所以我们就非常有必要掌握浏览器接口对象常用的==八大前端对象==

delete process

window = {
    // 因为下面window.addEventListener("") 带有()进行了调用,所以就会报这个找不到,所以就需要补一下这个方法
    // addEventListener:function (){}  方式一
    addEventListener(){}   // 方式二   类似{}里面的操作基本上都是前端的一些操作,我们后端执行无需关心
}

document = {
    toString(){
        return '[object HTMLDocument]'
    }
}

// console.log(1+1)
// console.log(window)
// console.log(process)

function a(){
    console.log("aaa")
}

function get_sign(){
    // 一、正常的前端操作  因为前端是给用户看的,有一些前端的展示是属于正常的
    window.addEventListener("")

    // 二、环境验证(反爬虫)
    // 判断当前执行环境是否是浏览器环境
    // 思路1: 判断浏览器的某些环境(API)是否存在
    // 比如window,document某些属性某些方法是否可以正常调用,如果能就正常执行,如果不能该报错报错,该给假值给假值
    // 补环境,就是解决思路1的一些问题
    // 思路2: 判断当前环境是否是node环境
    // 判断比如node中的某些api是否存在,比如process,global    global就是node中的顶级变量
    // delete process 这些就是解决思路2的一些问题
    try {
       process   // 1、有这个,在浏览器中必然会报错,一定会走到catch中   进行思路2的一个判断

       return "false1"
    }catch (e){
        // 2、当浏览器走到这里的时候再次进行判断   进行思路1的一个判断
        if (document.toString()==='[object HTMLDocument]'){
            // 3、如果走到这里,那就是确定是浏览器环境,那就继续走核心加密代码
            // 核心加密代码
            return "asdfg"

        }else{
            return "false2"
        }

    }

}

console.log(get_sign())

二、八大对象--补环境

2.1、window:窗口对象,也是最重要的一个对象

image

window是顶级变量,在浏览器中,任何在全局作用域中声明的变量或函数都会成为 window 对象的属性。例如,如果你声明一个全局变量 var yuan = 10;,你可以通过 window.yuan。window 仅在浏览器环境中存在。在 Node.js 等其他环境中,顶级对象是 global

在浏览器中,window == self == top  这三个对象指向一个空间,就是一个内容        self === top === window都是顶级变量​ 指向同一块空间 

self
top

addEventListener:f
setInterval:f
setTimeout:f
name

document
navigator
location
screen
history

toString:f




// window = {} 这样写是全局变量,不是顶级变量 这样写不会把document归属于到window中去
// document = {}
//
// console.log(document.toString())

// node.js中顶级变量:global
// console.log(global)
window = global  // 我们先把这个global空间先交给window
delete global    // 如果代码有检测global怎么办?就先走上面的window = global赋值,然后在delete global

document = {}

console.log(window.document)

2.2、document:文档对象:第二重要对象

body
documentElement
cookie

getElementById:f
getElementByTagName:f
getElementByClassName:f
createElement:f

toString:f

2.3、element:泛指,标签对象

# 常用标签对象
h1-h6标签
p标签
input标签
div和span标签
img标签
table标签
form标签
canvas标签:toDataURL:f
​
# 标签对象常用属性和方法
ele:
    ele.getElementById:f
    ele.getElementByTagName:f
    ele.getElementByClassName:f
ele.style   调整标签的样式
ele.innerHtml  获取标签中的文本
ele.getAttribute()  获取标签中的属性

2.4、navigator:导航对象

userAgent
toString:f

2.5、location:地址栏对象

href
toString:f

2.6、screen:屏幕对象

availHeight
availLeft
availTop
availWidth
toString:f

2.7、history:历史对象

back
forward
go
toString:f

2.8、localStorage和sessionStorage:本地存储对象

getItem:f
setItem:f
removeItem:f
toString:f

 image-20250114下午90709036-6860029

为了更好的让大家理解我们接下来的实战补环境案例,我先给大家模拟了一套简单的JS逆向代码

window = global

window.addEventListener = function () {

}

kw = {
    getAttribute() {
    }
}

ctx = {
    fillRect() {
    }
}
canvas = {
    getContext() {
        return ctx
    }
}

div = {}
document = {
    getElementById() {
        return kw
    },
    createElement(ele) {
        if (ele === "canvas") {
            return canvas
        }
        if (ele === "div") {
            return div
        }
        return {}
    }
}

navigator  ={
    toString(){
        return '[object Navigator]'
    },
    userAgent:'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
}


function get_sign() {
    // 黑匣子,省略很多代码
    // (1) BOM和DOM正常的前端动作
    window.addEventListener("test")
    // 搜索框
    let kw = document.getElementById("kw")
    let _class = kw.getAttribute("class")
    // 创建画布
    let canvas = document.createElement("canvas")
    let ctx = canvas.getContext("2d");
    ctx.fillRect(10, 10, 100, 100);

    // 创建div
    let div = document.createElement("div")
    console.log(div.innerHTML)

    // (2) 基于DOM和BOM进行环境校验
    if (navigator.toString() === '[object Navigator]') {
        let navLength = navigator.userAgent.length
        return "u82d1660a" + navLength // Yuan老师的微信,想深入学逆向爬虫的联系我,结一段善缘!

    } else {
        return false
    }
}

console.log(get_sign())


/*
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "green";          这个是属性--补环境就补一个对象
ctx.fillRect(10, 10, 100, 100);   这个是函数--补环境就补一个function

 */

在这里,整层的JS加密代码对于我们而言就是一个黑匣子,有千千万万行代码,甚至做了混淆处理,我们不能去一行行读,看看整个过程到底用到了哪些对象以及对应的属性和方法的。所以这里就涉及到了到了补环境最终的一件事情:==代理监控==

三、Proxy代理

JavaScript中的Proxy是一种内置对象,它允许你在访问或操作对象之前拦截和自定义底层操作的行为。通过使用Proxy,你可以修改对象的默认行为,添加额外的逻辑或进行验证,以实现更高级的操作和控制。

Proxy对象包装了另一个对象(目标对象),并允许你定义一个处理程序(handler)来拦截对目标对象的操作。处理程序是一个带有特定方法的对象,这些方法被称为"捕获器"(traps),它们会在执行相应的操作时被调用。

var yuan = {
    username: "yuan",
    age: 22,
    wx: "u82d1660a"
}
yuan = new Proxy(yuan, {
    get(target, p, receiver) {
        console.log(target, "查询了属性", p)
        // return window['username'];/// 这里如果这样写. 有递归风险的...
        // return Reflect.get(...arguments);
        return Reflect.get(target, p);
    },
    set(target, p, value, receiver) {
        console.log(target, "设置了属性", p, "值为:", value)
        Reflect.set(target, p, value);
    }
});
​
yuan.username;
yuan.age;
yuan.username = "rain"
yuan.age = 18

 

image-20250113下午94526816-6775927
var yuan = {
    username: "yuan",
    age: 22,
    wx: "u82d1660a",
    teach(){
        console.log("教学")
        return "asdf"
    }
}

var rain = {
    username: "rain",
    age: 23,
    wx: "u82d1660a",
    teach(){
        console.log("教学2")
        return "fdsa"
    }
}

yuan = new Proxy(yuan, {
    get(target, p, receiver) {
        console.log(target, "查询了属性", p)
        // return window['username'];/// 这里如果这样写. 有递归风险的...
        // return Reflect.get(...arguments);
        return Reflect.get(target, p);   // 该句本身就是get方法内部固有的,所以不能少
    },
    set(target, p, value, receiver) {
        console.log(target, "设置了属性", p, "值为:", value)
        Reflect.set(target, p, value);
    }
});
rain = new Proxy(rain, {
    get(target, p, receiver) {
        console.log(target, "查询了属性", p)
        // return window['username'];/// 这里如果这样写. 有递归风险的...
        // return Reflect.get(...arguments);
        return Reflect.get(target, p);
    },
    set(target, p, value, receiver) {
        console.log(target, "设置了属性", p, "值为:", value)
        Reflect.set(target, p, value);
    }
});
console.log(yuan.username + rain.age + rain.wx + yuan.teach())

 

基于Proxy的特性,衍生了基本补环境思路:在本地,用Proxy代理监控浏览器所有的BOM、DOM对象,相当于在node.js中,对整个浏览器环境对象进行了代理,拷贝的加密JS代码使用任何==浏览器 api==都能被我们所拦截。然后我们针对拦截到的环境检测点去补对应属性和方法。

因为接下来的补环境中,涉及到需要监控的前端对象还是比较多的 ,如果每一个都按着上面对yuan的Proxy代理,就会导致监控代码大量重复不简洁。所以,在这里,爬虫工程师都需要一个简单的功能函数,对需要监控的数组对象循环监控:

yuan = {
    username: "yuan",
    age: 22,
    wx: "u82d1660a",
    teach() {
        console.log("教学")
        return "asdf"
    }
}

rain = {
    username: "rain",
    age: 23,
    wx: "u82d1660a",
    teach() {
        console.log("教学2")
        return "fdsa"
    }
}

function setProxyArr(proxyObjArr) {
    for (let i = 0; i < proxyObjArr.length; i++) {
        const objName = proxyObjArr[i];
        const handler = {
            get(target, property, receiver) {
                console.log("方法:", "get", "对象:", objName, "属性:", property, "属性类型:", typeof property, "属性值:", target[property], "属性值类型:", typeof target[property]);

                return target[property];
            },
            set(target, property, value, receiver) {
                console.log("方法:", "set", "对象:", objName, "属性:", property, "属性类型:", typeof property, "属性值:", value, "属性值类型:", typeof target[property]);
                return Reflect.set(target, property, value, receiver);
            }
        };
        // 检查并初始化对象
        let targetObject = global[objName] || {};  // 在 Node.js 环境中使用 global  找到"yuan"这个字符串对应的yuan变量
        global[objName] = new Proxy(targetObject, handler);  // 在 Node.js 中使用 global
    }
}
// 设置一个代理数组
setProxyArr(["yuan", "rain"])

console.log(yuan.username + rain.age + rain.wx + yuan.teach())

// console.log(yuan.username) 

接下来,我们就将这段Proxy监控函数注入到刚才我们的案例中:

window = global
window.addEventListener = function () {

}

kw = {
    getAttribute() {
        // 如果补完这个,报别的错误,就证明这里已经过了,补需要在管这里了
    }
}
ctx = {
    fillRect: function () {

    }
}
canvas = {
    getContext() {
        return ctx
    }
}
div = {
    innerHTML: {}
}
document = {
    getElementById(ele) {  // 补完getElementById 还是卡在这里,证明有返回值,并且需要返回值做别的事情
        console.log("document getElementById", ele)   // 我们就需要打印一下这个标签,并且进行返回

        if (ele === "kw") {
            return kw
        }

    },

    createElement(ele) {
        console.log("document createElement", ele)
        if (ele === "canvas") {

            return canvas

        }
        if (ele === "div") {

            return div

        }

    }
}
location = {}
navigator = {
    toString() {
        return '[object Navigator]'
    },
    userAgent: ""
}

function setProxyArr(proxyObjArr) {
    for (let i = 0; i < proxyObjArr.length; i++) {
        const objName = proxyObjArr[i];
        const handler = {
            get(target, property, receiver) {
                console.log("方法:", "get", "对象:", objName, "属性:", property, "属性类型:", typeof property, "属性值:", target[property], "属性值类型:", typeof target[property]);

                return target[property];
            },
            set(target, property, value, receiver) {
                console.log("方法:", "set", "对象:", objName, "属性:", property, "属性类型:", typeof property, "属性值:", value, "属性值类型:", typeof target[property]);
                return Reflect.set(target, property, value, receiver);
            }
        };
        // 检查并初始化对象
        let targetObject = global[objName] || {};  // 在 Node.js 环境中使用 global
        global[objName] = new Proxy(targetObject, handler);  // 在 Node.js 中使用 global
    }
}

setProxyArr(["window", "document", "kw", "canvas", 'ctx', "div", "location", "navigator"])


function get_sign() {
    // 黑匣子,省略很多代码
    // (1) BOM和DOM正常的前端动作
    window.addEventListener("test")
    // 搜索框
    let kw = document.getElementById("kw")
    let _class = kw.getAttribute("class")
    // 创建画布
    let canvas = document.createElement("canvas")
    let ctx = canvas.getContext("2d");
    ctx.fillRect(10, 10, 100, 100);

    // 创建div
    let div = document.createElement("div")
    console.log(div.innerHTML)

    // (2) 基于DOM和BOM进行环境校验
    if (navigator.toString() === '[object Navigator]') {
        let navLength = navigator.userAgent.length
        return "u82d1660a" + navLength // Yuan老师的微信,想深入学逆向爬虫的联系我,结一段善缘!

    } else {
        return false
    }
}

console.log(get_sign())


// 找到报错位置,断点调试
// 也要会Proxy补环境的方式  在补环境中如果不知道是方法还是属性,可以去MDN中去查找一下看看是方法还是属性了   对象直接{}  方法直接function(){}
// 也要会注释代码
// 

环境验证的JS代码其实一共有两个思路,思路一是基于当前环境是不是浏览器环境,是的话正常执行。思路二是当前环境是不是node环境,如果不是,则正常执行,扩展案例:

window = global
​
delete global
delete process
delete Buffer
delete __dirname
delete __filename
​
window.addEventListener = function () {
}
kw = {
    getAttribute() {
    }
}
ctx = {
    fillRect: function () {
    }
}
canvas = {
    getContext() {
        return ctx
    }
}
document = {
    getElementById(id) {
        console.log("document getElementById by:", id)
        if (id === "kw") {
            return kw
        }
    },
    createElement(ele) {
        console.log("document createElement ", ele)
        return canvas
    }
}
navigator = {
    userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
    toString: function () {
        return '[object Navigator]'
    }
}
​
function setProxyArr(proxyObjArr) {
    for (let i = 0; i < proxyObjArr.length; i++) {
        const objName = proxyObjArr[i];
        const handler = {
            get(target, property, receiver) {
                console.log("方法:", "get", "对象:", objName, "属性:", property, "属性类型:", typeof property, "属性值:", target[property], "属性值类型:", typeof target[property]);
​
                return target[property];
            },
            set(target, property, value, receiver) {
                console.log("方法:", "set", "对象:", objName, "属性:", property, "属性类型:", typeof property, "属性值:", value, "属性值类型:", typeof target[property]);
                return Reflect.set(target, property, value, receiver);
            }
        };
        // 检查并初始化对象
        // let targetObject = global[objName] || {};  // 在 Node.js 环境中使用 global
        // global[objName] = new Proxy(targetObject, handler);  // 在 Node.js 中使用 global
​
        let targetObject = window[objName] || {};  // 在 Node.js 环境中使用 global
        window[objName] = new Proxy(targetObject, handler);  // 在 Node.js 中使用 global
    }
}
​
setProxyArr(["window", "document", "navigator"])
​
function get_sign() {
    // 黑匣子,省略很多代码
    // (1) BOM和DOM正常的前端动作
    window.addEventListener("test")
    // 搜索框
    let kw = document.getElementById("kw")
    let _class = kw.getAttribute("class")
    // 创建画布
    let canvas = document.createElement("canvas")
    let ctx = canvas.getContext("2d");
    ctx.fillRect(10, 10, 100, 100);
    let navLength = navigator.userAgent.length
    // (2) 基于DOM和BOM进行环境校验
    if (navigator.toString() === '[object Navigator]') {
​
        // (3) 基于node关键字判断是不是浏览器环境
        const isNode = typeof process !== 'undefined' && typeof global !== 'undefined';
        if (isNode) {
            console.log("当前环境是 Node.js");
            return false
        } else {
            console.log("当前环境不是 Node.js");
            return "u82d1660a".toUpperCase() + navLength // Yuan老师的微信,想深入学逆向爬虫的联系我,结一段善缘!
        }
​
    } else {
        return false
    }
}
​
console.log(get_sign())
​

 

 

 

 

posted @ 2025-09-19 16:20  L遇上J  阅读(90)  评论(0)    收藏  举报