爬虫&逆向--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:窗口对象,也是最重要的一个对象

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

为了更好的让大家理解我们接下来的实战补环境案例,我先给大家模拟了一套简单的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())
更多推荐
所有评论(0)