爬虫&逆向--Day09--Javascript高级
let a = {'name': 'bobo'}; //方法1 function B() { this.name = "bobo" }; let b = new B(); //方法2 console.log(a); // { name: 'bobo' } console.log(b); // B { name: 'bobo' }
2. 原型链
2.1 何为原型链
-
-
概念:原型本身其实是一个function函数(构造函数),可以将其理解成Python中的class类。
-
创建一个原型:
function User(name, pwd) { this.userName = name; this.pwd = pwd; this.regist = function () { console.log(this.name + "在注册") } }
-
注意:
/* 如下代码是使用字面量方式创建对象时,直接使用花括号{}来定义对象的属性和方法。这种方式创建的对象是一个简单的键值对集合,没有原型链和构造函数。 */ let a = {'name':'bobo'}; /* 通过定义一个构造函数B(),然后使用new关键字来创建一个新的对象实例。这种方式创建的对象可以继承构造函数的原型属性和方法,从而实现面向对象编程的特性。 */ function B(){ this.name="bobo"; } let b = new B();
-
-
实例对象
-
概念:通过new关键字创建的对象(调用构造函数)称为实例对象。
-
创建实例对象:
let u1 = new User('jay','123'); let u2 = new User('tom','456');
-
-
原型对象
-
概念:原型对象用于存储所有实例对象共享的属性和方法,以减少每个实例对象重复存储相同属性和方法的开销。
-
原型对象存储所有实例对象共享的属性和方法
//类似于类属性 User.prototype.address = "BJ"; User.prototype.gender = "male"; //类似于类方法 User.prototype.login = function login(username, password){ console.log(`${username}在登录`); } //实例对象共享原型对象存储的内容 u1.login('jay','123'); // jay在登录 u2.login('tom','456'); // tom在登录 console.log(u1.address,u2.address,u1.gender,u2.gender); // BJ BJ male male
-
获取原型对象:
// User.prototype; // u1.__proto__; // User.prototype === u1.__proto__ //true console.log(User.prototype === u1.__proto__) // true //可以通过原型名访问原型对象或者使用实例名访问原型对象
-
-
原型链
-
原型链是JavaScript中对象继承属性和方法的一种方式。具体介绍如下:
原型链是JavaScript中对象继承属性和方法的一种方式。当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,它会通过原型链去它的原型对象中查找,如果原型对象也没有,会继续在其原型对象的原型对象中查找,这样逐级向上,直到找到属性或方法或者达到原型链的末端。
原型对象本身也是一个对象,它也可以使用__proto__访问它(原型对象)的原型对象,类似于:
u1.__proto__.__proto__
-
原型链的成员访问:
-
实例对象可以访问其原型内的成员和其原型链上所有原型对象内的成员
User.toString()
-
-
-
在js中所有的实例对象都可以访问Object这个对象中的成员!
//判断对象类型typeof和Object.prototype.toString.call(Object原型对象) console.log('数字1',typeof 1); // 数字1 number console.log("字符串1",typeof "1"); // 字符串1 string console.log('空对象{}',typeof {}); // 空对象{} object console.log('布尔true',typeof true); // 布尔true boolean console.log('空数组[]',typeof []); // 空数组[] object console.log('null空',typeof null); // null空 object console.log('undefined',typeof undefined); // undefined undefined console.log('函数function (){}',typeof function (){}); //函数function (){} function //发现null和空数组[]类型都是object类型(无法区分具体类型) console.log("=================================") console.log(Object.prototype.toString.call(1)); // [object Number] console.log(Object.prototype.toString.call("1")); // [object String] console.log(Object.prototype.toString.call({})); // [object Object] console.log(Object.prototype.toString.call(true)); // [object Boolean] console.log(Object.prototype.toString.call([])); // [object Array] console.log(Object.prototype.toString.call(null)); // [object Null] console.log(Object.prototype.toString.call(undefined)); // [object Undefined] console.log(Object.prototype.toString.call(function () {})); // [object Function] //object原型对象当中的tostring函数,该函数可以返回具体数据所对应的一个真实的数据类型 //创建新对象,设置其原型对象为window(在node环境下可能需要伪装浏览器环境下的对象) // a = Object.create(window) // 改代码需要在浏览器下面进行 // console.log(a.__proto__ === window) //true //判断对象自身属性中是否具有指定的属性 // 原型 function func(){ this.name="bobo"; this.getAge=function(){} }; // 实例对象 f = new func(); console.log(f.hasOwnProperty('name')) // true console.log(f.hasOwnProperty('getAge')) // true console.log(f.hasOwnProperty('toString')) // false // 因为toString不是实例对象原型内部的实际原有成员,而是原型链上的某一个原型对象的成员 // 获取指定对象上一个自有属性对应的属性描述符 // Object.getOwnPropertyDescriptor(f,'name'); console.log(Object.getOwnPropertyDescriptor(f,'name')) /* writable 变量值是否可以被重写 true可以被重新 enumerable 是否可以被便利 true可以被便利 configurable 是否可以被配置,不用管 { value: 'bobo', writable: true, enumerable: true, configurable: true } */ //获取指定对象上所有属性对应的属性描述符 // Object.getOwnPropertyDescriptors(f); console.log(Object.getOwnPropertyDescriptors(f)) /* { name: { value: 'bobo', writable: true, enumerable: true, configurable: true }, getAge: { value: [Function (anonymous)], writable: true, enumerable: true, configurable: true } } 属性描述符是一组用于精确定义和描述对象属性的特性的集合(属性描述符也是一个对象)。通过属性描述符,开发者可以指定一个属性是否可被修改、删除、枚举或者通过特定的函数来获取和设置其值。 */ //获取实例对象的原型对象 Object.getPrototypeOf(f); // Object.getPrototypeOf(f) === f.__proto__ ; console.log(Object.getPrototypeOf(f) === f.__proto__) //true //设置一个指定的对象的原型(可以对一个已经存在的对象重新设置其原型对象) Object.setPrototypeOf(f,Object.__proto__) //f对象的原型对象设置成了window的原型对象 Object.setPrototypeOf(f,globalThis) //通过setPrototypeOf这个函数明确设定实例对象f,原型对象是globalThis //defineProperty直接在一个对象上定义一个新属性,然后可指定新属性的属性描述的,并返回此对象。 let User = { "name":"小明", }//创建一个User对象 //给对象添加两个成员 User.age = 10; //添加一个新的成员,赋值是10 User["age"] = 20; //通过[]访问新成员,修改值为20 //给对象定义一个新属性且设置其属性描述符(属性描述符可分为:数据描述符和存取描述符)此时使用数据描述符。 //参数1:对象。参数2:属性名。参数3:属性描述的 Object.defineProperty(User, "height", { enumerable:true, //该属性是否可遍历 configurable:true, //该属性是否可配置:决定该属性是否可以被删除或修改其属性描述符。 value:160, //属性的值 writable:false //该属性的值是否可以通过赋值运算符改变 }); //对象属性遍历,如果某个属性的文件描述符中的enumerable:false则无法遍历出该属性 for (const userKey in User) { console.log(userKey); // name age height } //存取描述符 let Stu = { "name":"小红", }//创建一个Stu对象 let temp = null;//临时变量 //给Stu对象定义一个新属性score,且设置其属性描述符 Object.defineProperty(Stu, "score", { enumerable:true, configurable:true, get:function (){// 当获取属性值是调用 console.log("正在获取值"); return temp; }, set:function (value){// 当对属性进行赋值操作时调用 console.log("正在设置值"); temp = value; } }); console.log(User.score); // undefined User.score = 100; console.log(User.score); // 100 /* 属性描述符注意事项:属性描述符分为两类:数据描述符和存取描述符。数据描述符包含value、writable、enumerable和configurable这些属性。存取描述符包含get、set、enumerable和configurable。两者不能混用,即一个描述符如果是数据描述符就不能包含get或set,反之亦然。 */
-
-
减少代码的侵入性:使用hook可以在不改变原始代码的前提下增加新功能,这减少了对原始代码的侵入,使得添加的功能更容易被管理和维护。
-
便于调试和问题定位
-
定义函数
// 1、先定义原函数 function add(a,b){ console.log("add方法正在执行"); return a+b; }
-
保存原函数,目的是为了不修改原函数内部的实现
_add = add; //2、添加一个新的变量保存原始函数
-
对add函数进行hook(进行相关的日志输出)
-
hook的位置必须是加载完需要hook的函数(原函数)后
-
// 3、使用hook对原函数进行hook操作 add = function(a,b){ console.log("原函数调用前, 参数:", a, b); let result = _add(a,b) console.log("原函数调用后, 结果:", result); return result; }
-
调用函数
add(1,2)
3.2 对象属性的hook
//1、创建一个对象 let user = { "name": "波波", }; //2、保存原属性 _name = user.name; //3、对对象属性的hook //defineProperty函数用来重新定义对象的属性。 //参数1:对象。参数2:属性。参数3:属性描述符 Object.defineProperty(user, "name", { get() { // 获取属性值的时候执行 console.log("正在获取属性值"); return _name; }, set(value) { // 设置属性值的时候执行 console.log("正在设置属性值:", value); _name = value; } }); //4、获取属性和设置属性操作 console.log(user.name) user.name = 'Jay' console.log(user.name) /* 正在获取属性值 波波
正在设置属性值: Jay 正在获取属性值 Jay */
如果对象没有/不存在的属性可以被hook吗?
//1、创建一个对象 let user = { "name": "波波", }; //2、保存原属性 _age = 18; //3、对象属性age的hook Object.defineProperty(user, "age",{ get(){ console.log("正在获取属性值"); return _age; }, set(value){ console.log("正在设置属性值:", value); _age = value; } }); //4、获取属性和设置属性操作 console.log(user.age) user.age = 20 console.log(user.age) /* 正在获取属性值 18
正在设置属性值: 20 正在获取属性值 20 */
atob函数是浏览器环境自带的用来对base64数据进行解编码。接下来,使用对atob函数进行hook。
_atob = atob;//保存原函数 atob = function (str){ console.log("正在执行atob方法, 参数:", str); let result = _atob(str); console.log("正在执行atob方法, 返回值:", result); return result; }
-
hook时机:在浏览器页面加载出来之前进行hook
-
1.在一个空白页面打开浏览器开发者工具
-
2.开启js的事件监听器
-
3.访问百度页面,会有断点停留
-
4.在Sources中的Snippets代码段中新增hook代码片段,打上断点,然后运行
-
-
5.查看hook运行,监控atob函数的执行
-
取消事件监听器中的Script,因为此时已经成功对atob函数进行了hook(不可刷新页面)
-
3.4 浏览器环境下cookie的hook
-
操作步骤如步骤:3.3
_cookie = document.cookie; Object.defineProperty(document,'cookie',{ get(){ console.log("正在获取cookie:", _cookie); return _cookie; }, set(value){ console.log("正在设置cookie:", value); _cookie = value; } });
-
toString() 检测法
-
atob原函数的toString() 结果为:
-
atob被hook后的toString() 结果为:
-
结果:两个atob的toString返回的结果是不一样的。
什么是native?
- 在js中,一些内置函数如toString或者atob等函数的函数实现会被显示为[native code],而不是显示实现的具体代码。这样的操作对于提高代码的安全性和封装性有一定的作用。 -
-
toString() 检测法的破解
-
在hook中重写atob函数的toStirng方法:
_atob = atob;//保存原函数 atob = function (str){ console.log("正在执行atob方法, 参数:", str); let result = _atob(str); console.log("正在执行atob方法, 返回值:", result); return result; } //重写atob函数的toString方法 atob.toString = function(){ return 'function atob() { [native code] }' }
-
-
原型链上的toString()检测法
Function.prototype.toString.call(atob) //调用函数原型对象中的toString进行的检测,而不是atob实例对象的toString了。
-
原型链上的toString()检测法的破解
-
在hook中重写原型链上的toString()方法:
_atob = atob;//保存原函数 atob = function (str){ console.log("正在执行atob方法, 参数:", str); let result = _atob(str); console.log("正在执行atob方法, 返回值:", result); return result; } //重写原型链上的toString方法 Function.prototype.toString = function(){ return `function ${this.name}() { [native code] }` }//this.name就是toString的调用者的名字,比如Location.toString,则this.name就是Location,如果将this.name直接换成atob的话,则以后任何调用者调用toString的话,则返回的function后面的名字就都是atob了。也就是如果Location.toString()返回的也是:function atob() { [native code] }
-