一、补环境的原理
- 浏览器环境和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())