逆向爬虫之补环境

一、补环境的原理

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

  •  浏览器下:

  •  node.js下

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

二、前端的8大必备对象

  •  window:窗口对象,也是一个最重要的对象
addEventListener:f
setInterval:f
setTimeout:f
name
self

document
navigator
location
screen
history

toString:f

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

  • document:文档对象:第二重大对象‘
body
documentElement
cookie

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

toString:f
  •  navigator:导航对象
userAgent
toString:f
  •  location:地址栏对象
href
toString:f
  •  screen:屏幕对象
availHeight
availLeft
availTop
availWidth
toString:f
  •  history:历史对象
back
forward
go
toString:f
  •  localStorage和sessionStorage:本地存储对象
getItem:f
setItem:f
removeItem:f
toString:f
  •  element:泛指,标签对象
# 常用标签对象
h1-h6标签
p标签
input标签
div和span标签
img标签
table标签
form标签
canvas标签:toDataURL:f

# 标签对象常用属性和方法
ele.style
ele.innerHtml
ele.getAttribute()

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

 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);
 
     // (2) 基于DOM和BOM进行环境校验
     if (navigator.toString() === '[object Navigator]') {
         let navLength = navigator.userAgent.length
         return "u82d1660a" + navLength // Yuan老师的微信,想深入学逆向爬虫的联系我,结一段善缘!
         
     } else {
         return false
     }
 }
 
 console.log(get_sign())

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

三、Proxy代理

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

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

var zhang = {
    username: "zhang",
    age: 22,
    wx: "u82d1660a"
}
zhang = new Proxy(zhang, {
    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

 

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

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

    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
        }
    }

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

    window = global
    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
        }
    }
    
    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);
    
        // (2) 基于DOM和BOM进行环境校验
        if (navigator.toString() === '[object Navigator]') {
            let navLength = navigator.userAgent.length
            return "u82d1660a" + navLength // Yuan老师的微信,想深入学逆向爬虫的联系我,结一段善缘!
    
        } else {
            return false
        }
    }
    
    console.log(get_sign())
    

     环境验证的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())
    

     

     

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值