爬虫&逆向--Day24&Day25--补环境专题
一、补环境的原理
浏览器环境和node环境对比:
浏览器下:
node.js
下
当我们辛苦将浏览器环境的加密或者解密入口找到,把加密或者解密的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:窗口对象,也是最重要的一个对象
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
为了更好的让大家理解我们接下来的实战补环境案例,我先给大家模拟了一套简单的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

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())