深入理解JS逆向代理与环境监测

博客文章:深入理解JS逆向代理与环境监测

1. 引言

首先要明确JavaScript(JS)在真实网页浏览器环境和Node.js环境中有很多使用特性的区别。尤其是在环境监测和对象原型链的检测方面。本文将探讨如何使用JS的代理(Proxy)模式来手动补充环境,Node环境和浏览器this环境,以及如何通过原型链检测来增强代码的安全性以及在。

2. 代理补环境

代理是ES6引入的一种新特性,它允许你定义对象的行为,例如属性的读取和设置。以下是一个简单的代理补环境的实现:

// 代理补环境(缺啥补啥)
function vmProxy(object, objName) {// 创建代理对象,捕获对对象的访问和赋值操作return new Proxy(object, {get: function(target, property, receiver) {// 打印属性访问信息console.log(objName, "get: ", property, target[property]);// 返回目标对象的属性值return target[property];},set: function(target, property, value) {// 打印属性赋值信息console.log(objName, "set: ", property, value);// 使用Reflect.set实现属性赋值return Reflect.set(...arguments);}});
}// 模拟浏览器环境
navigator = {userAgent: 'qh'
};
document = {};// 使用代理来增强navigator和document对象
navigator = vmProxy(navigator, 'navigator');
document = vmProxy(document, 'document');// 测试代理效果
console.log(navigator.userAgent); // 应输出代理访问信息
console.log(navigator.platform); // 将触发代理的get方法
console.log(document.cookie); // 将触发代理的get方法
console.log(document.createElement); // 将触发代理的get方法

3. Node环境与浏览器this环境监测

在Node.js中,global对象是全局对象,而在浏览器中,window是全局对象。检测这些环境可以通过以下方式实现:

// 确保window指向全局对象
window = globalThis;
// 补充window对象的方法
window['addEventListener'] = function() {};
// 初始化window的navigator和document属性
window['navigator'] = {};
window['document'] = {};// 使用代理来增强window对象
// 错误写法1:
// window = vmProxy(window, 'window'); // 代理器会影响对象本身的指向,代理window对象本身并不是错误,但关键在于不应该覆盖原有的window对象。代理可以用来增强或监测window对象的行为,但不应该改变其身份。
// 错误写法2:
// window ={'addEventListener': function() {},'navigator':{},'document' {}}; // 直接赋值window为一个新对象会覆盖原有的window对象,这会丢失所有原有的全局属性和方法,包括继承来的属性。
// 错误写法3 重新赋值会影响指向  将window赋值为一个空对象同样会覆盖原有的window对象,导致丢失所有属性和方法。
// window={}// 测试eval.call方法是否正确指向window
!function (){function test(){// 测试eval.call的this指向console.log(eval["call"](undefined, this) === window); // 应输出true}test.apply(null);
}();

这段代码的功能是测试eval.call方法的this指向是否正确。在JavaScript中,eval函数可以计算一个字符串表达式的值。当使用call方法调用eval时,可以指定this的值。在这个例子中,想要测试eval.call是否能够正确地将this指向window对象得到true的结果证明现在node环境和浏览器环境是一致的。

下面是对上面代码的详细拆分和解读,如果已经理解就跳过。

错误写法1解释:

// 错误写法1:
// window = vmProxy(window, 'window'); // 代理器会影响对象本身的指向

这行代码是错误的,因为window对象是全局对象,其原型链上有很多内置属性和方法。将window重新赋值为vmProxy的返回值会切断window与原有原型链的联系,导致丢失原有的全局属性和方法。此外,这行代码试图将window对象自身作为代理的目标,这在逻辑上是有问题的,因为window对象本身不应该被代理。如下图高亮紫色是原有全局属性是继承来的,
在浏览器中打印window对象时,你可能会注意到属性颜色的差异,这通常是由于浏览器的开发者工具中的颜色编码。在Chrome的开发者工具中,全局对象window的属性通常分为两类:

原有全局属性:这些属性是window对象直接定义的,通常是浅色显示,表示它们是window对象的自有属性,而不是通过原型链继承的。

继承来的属性:这些属性是window对象通过原型链从其原型Window.prototype继承的,通常会以高亮紫色显示,以区分自有属性。

例如,navigator对象是window对象的一个自有属性,而navigator对象本身继承自Navigator的属性。在浏览器的控制台中打印window对象时,你会看到类似下面的结构:

Window {window: Window { ... },self: Window { ... },document: document,name: "name",location: Location { ... },history: History { ... },navigator: Navigator { ... }, // 继承自 Navigator 的属性将显示为高亮紫色// ... 其他自有属性和继承属性
}

在这个例子中,document、location、history等是window对象的自有属性,而navigator对象的属性,如userAgent,是继承自Navigator的属性。

代码示例:
以下是如何在控制台中查看这些属性的示例代码:

console.dir(window); // 打印window对象及其属性

使用console.dir可以打印出对象的详细信息,包括原型链上的属性。
在这里插入图片描述

在这里插入图片描述

错误写法2解释:

// 错误写法2:
// window ={'addEventListener': function() {},'navigator':{},'document' {}}; // 直接赋值window为一个新对象会覆盖原有的window对象,这会丢失所有原有的全局属性和方法,包括继承来的属性。

这行代码同样是错误的,因为它试图将window重新定义为一个具有单个属性addEventListener的对象,这个属性的值是一个空字符串。正确的做法是为window添加方法,而不是重新定义window对象。

错误写法3解释:

// 错误写法3 重新赋值会影响指向  将window赋值为一个空对象同样会覆盖原有的window对象,导致丢失所有属性和方法。
// window = {};

这行代码是错误的,因为它将window重新赋值为一个空对象,这会覆盖原有的全局window对象,导致所有原有的全局属性和方法丢失。

衡量错误的标准:

在JS逆向中,衡量错误的标准是在Node环境中是否成功模拟了浏览器环境。正确的做法应该是在不破坏原有window对象的基础上,补充或修改其属性和方法,以模拟浏览器环境。

加代理正确的做法1:

// 确保window指向全局对象
window = globalThis;// 补充window对象的方法
if (typeof window.addEventListener === 'undefined') {window['addEventListener'] = function() {};
}// 初始化window的navigator和document属性,但不要覆盖原有的属性
if (!window.navigator) {window['navigator'] = {};
}
if (!window.document) {window['document'] = {};
}// 使用代理来增强window对象,但不要重新赋值window
window = vmProxy(window, 'window');

在这个修正的代码中,我们首先检查window对象上是否已经有addEventListener方法,如果没有,我们才添加它。同样,我们检查navigatordocument是否存在,如果不存在,我们才初始化它们。最后,我们使用vmProxy来增强window对象,而不是重新赋值它。

加代理正确的做法2:

// 创建window的代理,而不是重新赋值window
const originalWindow = window;
const proxiedWindow = new Proxy(originalWindow, {get(target, property, receiver) {if (property === 'navigator') {console.log('Accessing navigator');}return Reflect.get(target, property, receiver);},set(target, property, value, receiver) {console.log(`Setting ${property} to ${value}`);return Reflect.set(target, property, value, receiver);}
});// 使用代理对象进行操作,而不是直接操作window
proxiedWindow.navigator.userAgent; // 这将触发get陷阱,并打印日志

创建了一个window的代理,而不是直接修改window对象。这样,我们可以在不改变window对象本身的情况下,监测和增强其行为。

测试eval.call方法:

!function (){function test(){// 测试eval.call的this指向console.log(eval["call"](undefined, this) === window); // 应输出true}test.apply(null);
}();

这个测试函数test使用apply方法将this指向null,然后通过eval.callthis指向window。如果eval.call正确地将this指向了window,那么console.log应该输出true

4. 原型链检测

原型链是JS中实现继承的关键机制。## 4. 原型链检测的深入分析

原型链是JavaScript中实现继承的核心机制。每个JavaScript对象都有一个原型对象,这个原型对象可以是另一个对象或者null。当访问一个对象的属性时,如果该属性在对象上不存在,JavaScript引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末端(null)。

代码拆分

定义构造函数和原型属性
// 定义构造函数Navigator
Navigator = function Navigator(){};
// 在Navigator.prototype上定义userAgent属性
Object.defineProperty(Navigator.prototype,'userAgent',{set: undefined, // 不允许赋值enumerable: true, // 可枚举configurable: true, // 可配置get: function() {return "Custom UserAgent"; // 自定义getter函数} // 自定义getter}
);
// 创建navigator对象,其原型指向Navigator.prototype
navigator = {};
navigator.__proto__ = Navigator.prototype;// 检测navigator的属性描述符
console.log(Object.getOwnPropertyDescriptors(navigator)); // 应输出undefined
console.log(Object.getOwnPropertyDescriptors(Navigator.prototype)); // 显示实际的属性描述符

截图示例:在浏览器的控制台中,可以看到Navigator构造函数和其prototype上的userAgent属性定义。

创建并链接自定义navigator对象
// 创建navigator对象,其原型指向Navigator.prototype
var navigator = {};
Object.setPrototypeOf(navigator, new Navigator()); // 更现代的方法来设置原型

截图示例:在控制台中,通过Object.getPrototypeOf(navigator)可以看到navigator的原型现在指向Navigator.prototype

属性描述符检测
// 检测navigator的属性描述符
console.log(Object.getOwnPropertyDescriptors(navigator)); // 应输出undefined,因为navigator上没有直接定义userAgent属性
console.log(Object.getOwnPropertyDescriptors(Navigator.prototype)); // 显示实际的属性描述符,包括userAgent的定义

截图示例:在控制台中,Object.getOwnPropertyDescriptors的调用结果展示了Navigator.prototype上的userAgent属性描述符。

JS逆向原型链相关知识

在JavaScript逆向工程中,了解原型链对于分析和修改代码行为至关重要。以下是一些与原型链检测相关的逆向知识点:

  1. 原型链遍历:逆向工程师可以通过遍历对象的原型链来查找对象的来源和构造方式。
  2. 原型链污染:通过修改对象的原型链,可以引入新的行为或属性,这在某些情况下可以用于绕过安全限制。
  3. 构造函数欺骗:通过修改构造函数的prototype,可以改变通过该构造函数创建的所有新对象的行为。
  4. 属性拦截:使用Proxy对象,可以在访问或设置属性时进行拦截和自定义行为,这可以用来模拟或监测对象的行为。
  5. 环境检测:通过检测对象的原型链,可以判断代码运行在何种环境中(浏览器或Node.js),并据此调整代码行为。

通过这些技术,逆向工程师可以深入了解和操纵JavaScript代码的运行时行为,实现代码审计、安全测试或功能增强。然而,这些技术也应谨慎使用,以避免潜在的安全风险和代码维护问题。

5. 环境监测点案例

以下是一些Node和浏览器环境监测点的案例:

  • 监测全局对象类型typeof global !== 'undefined' ? 'node' : 'browser'
  • 监测Node.js特有的模块require.main === module
  • 监测浏览器特有的对象typeof window !== 'undefined' && !!window.document
  • 监测浏览器的BOM和DOMtypeof document !== 'undefined' && !!document.createElement
  • 监测环境支持的ES6特性'startsWith' in String.prototype

7. 高级监测点:使用evalProxy进行环境监测

在JavaScript中,eval函数允许你执行字符串中的代码。然而,使用eval通常被认为是不安全的,因为它可以执行任意代码。但是,在某些情况下,我们可以通过修改eval的行为来增强环境监测。以下是一个示例代码,展示了如何重写eval.call方法,以监测和区分代码执行环境:

// 重写eval.call方法以监测执行环境
eval['call'] = function (){debugger; // 启动调试模式,便于开发者调试if (arguments[1].toString() === '[object Window]'){debugger; // 再次启动调试模式,如果this指向Window对象// 如果调用环境是浏览器的window对象,则直接返回windowreturn window;} else {// 否则,执行原始的eval函数return eval(arguments);}
};

代码解释

  • eval['call']: 我们通过eval['call']访问eval函数的call方法,并对其进行重写。
  • debugger: 这是一个调试语句,当代码执行到这里时,如果正在调试模式下,执行会暂停,允许开发者检查当前的执行环境和变量状态。
  • arguments[1].toString(): arguments对象包含了调用函数时传入的所有参数。在这里,我们检查arguments[1](即this的值),并使用toString()方法获取它的类型描述。
  • '[object Window]': 这是一个特定的字符串,用来检测this是否指向浏览器的window对象。
  • return window: 如果检测到thiswindow对象,我们直接返回window,这样eval.call调用的结果将总是指向全局的window对象,而不是局部作用域中的this

使用场景

这种技术可以用于确保在执行动态代码时,this总是指向预期的全局对象,从而避免潜在的作用域问题。此外,它还可以用于调试和测试,帮助开发者更好地理解代码在不同环境下的行为。

7.与局部加密方法对象导出到全局对象的区别

使用代理补环境和原型链检测是一种在运行时动态地修改对象的行为和结构的方法。与之相比,将局部加密方法对象导出到全局对象是一种静态的修改,通常在代码的编写阶段就已经确定。代理补环境提供了一种灵活的方式来监测和修改对象的行为,而局部加密方法对象的导出则是一种更静态、更难以在运行时改变的方法。

结语

通过本文的探讨,我们了解到了JS代理的强大功能以及如何使用它来监测和增强JS运行环境。同时,我们也学习了如何通过原型链检测来提高代码的安全性。这些技术不仅能够帮助开发者更好地理解和控制JS代码的行为,还能够在开发过程中提供更多的灵活性和安全性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/41194.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32-USART

本内容基于江协科技STM32视频学习之后整理而得。 文章目录 1. 串口通信协议1.1 通信接口1.2 串口通信1.3 硬件电路1.4 电平标准1.5 串口参数及时序1.6 串口时序 2. USART串口通信2.1 USART简介2.2 USART框图2.3 USART基本结构2.4 数据帧2.5 数据帧-配置停止位2.6 起始位侦测2.…

【Python】一文向您详细介绍 argparse中 action=‘store_true’ 的作用

【Python】一文向您详细介绍 argparse中 action‘store_true’ 的作用 下滑即可查看博客内容 🌈 欢迎莅临我的个人主页 👈这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地!🎇 🎓 博主简介:98…

pdf怎么转换成图片格式文件,pdf文档怎么转换成图片格式

在数字化时代,pdf文件转换成图片格式是一种常见的操作,无论是在工作还是日常生活中,我们总会遇到需要将pdf文件转换为图片的需求。这可能是因为图片格式更易于分享、展示或编辑。那么,如何高效地将pdf转换成图片呢?本文…

图神经网络实战(16)——经典图生成算法

图神经网络实战(16)——经典图生成算法 0. 前言1. 图生成技术2. Erdős–Rnyi模型3. 小世界模型小结系列链接 0. 前言 图生成算法是指用于创建模拟图或网络结构的算法,这些算法可以根据特定的规则和概率分布生成具有特定属性的图&#xff0c…

将大型语言模型模块化打造协作智能体

B UILDING C OOPERATIVE E MBODIED A GENTS MODULARLY WITH L ARGE L ANGUAGE M ODELS 论文链接: https://arxiv.org/abs/2307.02485https://arxiv.org/abs/2307.02485 1.概述 在去中心化控制及多任务环境中,多智能体合作问题因原始感官观察、高昂…

【机器学习】机器学习重塑广告营销:精准触达,高效转化的未来之路

📝个人主页🌹:Eternity._ 🌹🌹期待您的关注 🌹🌹 ❀目录 📒1. 引言📙2. 机器学习基础与广告营销的结合🧩机器学习在广告营销中的核心应用领域🌹用…

【React】React18 Hooks 之 useReducer

目录 useReducer案例1:useReducer不带初始化函数案例2:useReducer带初始化函数注意事项1:dispatch函数不会改变正在运行的代码的状态注意事项2:获取dispatch函数触发后 JavaScript 变量的值注意事项3:触发了reducer&am…

Spring Boot集成olingo快速入门demo

1.什么是olingo? Apache Olingo 是个 Java 库,用来实现 Open Data Protocol (OData)。 Apache Olingo 包括服务客户端和 OData 服务器方面。 Open Data Protocol (开放数据协议,OData) 是用来查询和更新数据的一种W…

【吊打面试官系列-MyBatis面试题】MyBatis 实现一对多有几种方式,怎么操作的?

大家好,我是锋哥。今天分享关于 【MyBatis 实现一对多有几种方式,怎么操作的?】面试题,希望对大家有帮助; MyBatis 实现一对多有几种方式,怎么操作的? 有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过…

观察矩阵(View Matrix)、投影矩阵(Projection Matrix)、视口矩阵(Window Matrix)及VPM矩阵及它们之间的关系

V表示摄像机的观察矩阵(View Matrix),它的作用是把对象从世界坐标系变换到摄像机坐标系。因此,对于世界坐标系下的坐标值worldCoord(x0, y0, z0),如果希望使用观察矩阵VM将其变换为摄像机坐标系下的坐标值localCoord(x…

Node.js-path 模块

path 模块 path 模块提供了 操作路径 的功能,如下是几个较为常用的几个 API: 代码实例: const path require(path);//获取路径分隔符 console.log(path.sep);//拼接绝对路径 console.log(path.resolve(__dirname, test));//解析路径 let pa…

vulhub-activemq(CVE-2016-3088)

在 Apache ActiveMQ 5.12.x~5.13.x 版本中,默认关闭了 fileserver 这个应用(不过,可以在conf/jetty.xml 中开启);在 5.14.0 版本后,彻底删除了 fileserver 应用。【所以在渗透测试过程中要确定好 ActiveMQ …

数据结构1:C++实现变长数组

数组作为线性表的一种,具有内存连续这一特点,可以通过下标访问元素,并且下标访问的时间复杂的是O(1),在数组的末尾插入和删除元素的时间复杂度同样是O(1),我们使用C实现一个简单的边长数组。 数据结构定义 class Arr…

华为OD机试 - 来自异国的客人(Java 2024 D卷 100分)

华为OD机试 2024D卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(D卷C卷A卷B卷)》。 刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测…

新手教学系列——前后端分离API优化版

在之前的文章《Vue 前后端分离开发:懒人必备的API SDK》中,我介绍了通过Object对象自动生成API的方法。然而,之前的代码存在一些冗余之处。今天,我将分享一个改进版本,帮助你更高效地管理API。 改进版API SDK 首先,让我们来看一下改进后的代码: import request from …

【反悔贪心 反悔堆】1642. 可以到达的最远建筑

本文涉及知识点 反悔贪心 反悔堆 LeetCode1642. 可以到达的最远建筑 给你一个整数数组 heights ,表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。 你从建筑物 0 开始旅程,不断向后面的建筑物移动,期间可能会用到砖块或梯子。 当…

MATLAB 2024b 更新了些什么?

MATLAB 2024b版本已经推出了预览版,本期介绍一些MATLAB部分的主要的更新内容。 帮助浏览器被移除 在此前的版本,当我们从MATLAB中访问帮助文档时,默认会通过MATLAB的帮助浏览器(Help browser)。 2024b版本开始&…

【Unity数据交互】如何Unity中读取Ecxel中的数据

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 专栏交流🧧&…

医院挂号系统小程序的设计

管理员账户功能包括:系统首页,个人中心,患者管理,医生管理,专家信息管理,科室管理,预约信息管理,系统管理 微信端账号功能包括:系统首页,专家信息&#xff0…

数据结构算法-排序(一)-冒泡排序

什么是冒泡排序 冒泡排序:在原数组中通过相邻两项元素的比较,交换而完成的排序算法。 算法核心 数组中相邻两项比较、交换。 算法复杂度 时间复杂度 实现一次排序找到最大值需要遍历 n-1次(n为数组长度) 需要这样的排序 n-1次。 需要 (n-1) * (n-1) —…