js逆向学习笔记【一篇就够】

js逆向学习笔记【一篇就够】

  • 不够再来一篇

文章目录

  • js逆向学习笔记【一篇就够】
    • 算法还原
      • 白盒还原
      • 黑盒还原
      • RPC 调用
      • 浏览器自动化
    • 基本数据类型
      • 原始类型
      • 合成类型
      • 查看类型
      • null undefined 和布尔值
      • 数值
      • 进制
      • NaN
      • Infinity
      • 全局 api
      • 字符串
      • 字符集
      • base64 转码
      • 对象
      • 对象引用
      • 属性查看
      • 属性删除
      • 属性存在判断
      • 属性遍历
      • 函数
      • 函数声明
      • 函数是一等公民
      • 函数变量名提升
      • 函数的属性和方法
      • 函数作用域
      • 函数参数省略
      • 参数传递方式
      • arguments 对象
      • 闭包
      • 立即调用函数表达式
      • eval 命令
      • 数组
      • 数组的属性
      • 数组循环
      • 数组空值
      • 类数组对象
    • 数据类型转换
      • 自动数据类型转换
      • 其他类型转字符串
      • 其他类型转布尔值
      • 其他类型转为数值
      • 强制类型转换
    • 异常处理
      • Error 对象
      • try catch finally
    • 对象详解和 hook
      • Object 静态方法和实例方法
      • Object 的静态方法
      • Object 的实例方法
      • 构造函数
      • 创建对象的方式
      • 原型对象 Object.prototype
      • 原型链
      • constructor
      • hook cookie
      • object copy
      • Array 对象
      • 包装对象
      • Math
      • Date
      • 控制台 API
      • this 关键字
      • 绑定 this 的方法
    • es6 部分新特性
      • 块级作用域
      • 属性简洁表达
      • 对象方法简洁表达
      • 字符串模板
      • 解构赋值
      • 箭头函数
      • 模块的引入
      • 链式判断运算符
      • null 判断运算符
      • 对象的新增方法
      • 类的表示
    • Proxy 和 Reflect
    • window 和 navigator Proxy 实战
    • 异步操作与 Ajax
      • 单线程模型
      • 同步任务和异步任务
      • 任务队列和事件循环
      • 异步操作模式
      • 定时器
      • Promise 对象
      • Ajax 请求
    • Dom 和浏览器模型
      • Dom 简介
      • 节点
      • 事件
      • window
      • Document
      • navigator
      • screen
      • cookie
      • storage
      • history
      • location

算法还原

白盒还原

  • 直接扣算法,或者是标准算法
  • 理解对方 js 的意思,能够翻译成其他语言,可能会占用较长的分析时间

黑盒还原

  • 直接整体调用(加密复杂,更新频繁,整体关联度高)
  • 不需要关注算法逻辑,需要模拟浏览器环境,需要对抗环境检测

RPC 调用

  • 算法复杂度高,浏览器环境难以模拟
  • 找到算法位置,暴露出来,直接 rpc 调用,需要保证浏览器状态(内存泄漏,保活)

浏览器自动化

  • 无法逆向
  • 接近真人,但是有大量的自动化痕迹;

基本数据类型

  • 数值 Number:整数和小数
  • 字符串 String:文本
  • 布尔值 Boolean:布尔值,true 表示真,false 表示假
  • undefined:未定义,或者不存在
  • null:表示空值
  • 对象 Object:各种值组成的集合

原始类型

  1. 数值
  2. 字符串
  3. 布尔值
  • 原始类型就是最基本的数据类型,不能再进行细分;
  • undefined 和 null 一般看成是两个特殊值;

合成类型

  • 对象
    • 狭义的对象 Object
    • 数组 Array
    • 函数 Function
  • 一个对象往往是由多个类型的值组成,可以看成是一个存放各种值的容器

查看类型

  • typeof:返回一个值的数据类型

  • instanceof:表示对象是否是某个构造函数的实例

  • Object.prototype.toString

  • typeof 可以用来检查一个未声明的变量,而不报错;

    // 基本数据类型
    var tmp1 = "字符串";
    var tmp2 = 1;
    var tmp3 = 1.1;
    var tmp4 = true;
    
    // 特殊类型
    var tmp5=  undefined;
    var tmp6 = null; // null 是一个 object
    
    // 对象
    var tmp7 = {}; // 对象
    var tmp8 = []; // 数组
    var tmp9 = function(){}; // 函数
    
    console.log("typeof(tmp1)", typeof (tmp1));
    console.log("typeof(tmp2)", typeof (tmp2));
    console.log("typeof(tmp3)", typeof (tmp3));
    console.log("typeof(tmp4)", typeof (tmp4));
    console.log("typeof(tmp5)", typeof (tmp5));
    console.log("typeof(tmp6)", typeof (tmp6));
    console.log("typeof(tmp7)", typeof (tmp7));
    console.log("typeof(tmp8)", typeof (tmp8));
    console.log("typeof(tmp9)", typeof (tmp9));
    
    // function 因为有类object 的操作, 所以也属于 object
    console.log("tmp9.name", tmp9.name); // 获取函数名
    
    //typeof(tmp1) string
    //typeof(tmp2) number
    //typeof(tmp3) number
    //typeof(tmp4) boolean
    //typeof(tmp5) undefined
    //typeof(tmp6) object
    //typeof(tmp7) object
    //typeof(tmp8) object
    //typeof(tmp9) function
    //tmp9.name tmp9
    

null undefined 和布尔值

null 和 undefined 的区别

  • null 表示一个空对象,undefined 表示未定义;
  • null 转为数值的时候为 0, undefined 转为数值的实收为 NaN;

boolean

布尔值表示真和假,true 表示真,false 表示假;

下列运算符会返回布尔值:

  1. ! (not)
  2. 相等运算符: =, , !, !=
  3. 比较运算符: >=, <=, <,>

表示 false 的值

在自动数据转换中,下列值会表示 false:

  1. undefined
  2. null
  3. false
  4. 0
  5. NaN
  6. “” 或者 ‘’ 空字符串

其他的值都会被当成 true;

空数组 [] 和空对象 {} 对应的布尔值都是 true;

数值

在 js 中,所有的数值都是 64 位浮点数的形式进行存储的,也就是说在 js 底层,没有整数只有浮点数;

因为浮点数精度的问题,js 在进行浮点数运算的时候经常会出现问题:

console.log(0.1 + 0.2);
// 0.30000000000000004

进制

  • 十进制:没有前导,直接用数值表示
  • 八进制:有前缀 0o 或者 0O
  • 十六进制:有前缀 0x 或者 0X
  • 二进制:有前缀 0b 或者 0B

默认情况下,js 内部会将八进制,十六进制,二进制转为十进制;

NaN

NaN 是 js 中的特殊值,表示非数字 (Not a Number), 主要出现在字符串解析成数字出错的时候;

  1. NaN 不是独立的数据类型,它是一个特殊值,它的数据类型依然是 Number
  2. NaN 不等于任何值,包括它本身 (不等于本身可以用来检测某个值是否是 NaN)
  3. NaN 和任何数运算,得到的结果都是 NaN;

Infinity

Infinity 用来表示无穷,一般出现在两种场景下:

  1. 正数的数值太大,或者负数的数值太小;
  2. 非 0 的数除以 0, 得到 Infinity
  • js 中数值正向溢出或者负向溢出或者被 0 除都不会报错,所以单纯的数学运算几乎没有可能抛出异常
  • Infinity 大于一切数值 (除了 NaN); -Infinity 小于一切数值 (除了 NaN)
  • Infinity 和 NaN 比较,总是返回 false;

全局 api

parseInt(string[,radix])

parseInt(string[,radix]) 将字符串解析成数值;如果入参非字符串,则会调用入参的 toString 方法转换为字符串再进行转换;如果设置了第二个参数 radix, 则会将字符串按指定的 radix 进制转换成十进制;返回数值或者 NaN

var a = '0xf';
console.log(a, 16); // 将 16 进制的 0xf 转为十进制
// 15

parseFloat(string)

parseFloat(string) 将字符串入参解析成浮点数;返回浮点数或者 NaN ;

var a = "4.567";
console.log(parseFloat(a)); // 4.567

// 当入参有非法字符, 则只保留合法部分进行转换
var b = "4.567abcd";
console.log(parseFloat(b)); // 4.567

// 第二个小数点忽略
var c = "1.2.3";
console.log(parseFloat(c)); // 1.2

// 起始为非法字符则直接返回 NaN
var d = "aaa1.2"; // NaN
console.log(parseFloat(d));

isNaN()

判断某个入参是否是 NaN; 可以利用 NaN 的不等性来进行判断

var a = NaN;
if (a != a ){
    console.log("it is NaN");
}

isFinite()

判断某个入参是否是 Infinity;

字符串

  • 字符串和数组相似,都支持使用 [] 运算符来通过指定索引来获取值;
  • length: 可以获取字符串的长度
  • 字符串不能通过 [] 运算符和索引来修改字符串的值

字符集

js 中使用的字符集为 Unicode 字符集,所有的字符串都使用 Unicode 表示;

var f\u006F\u006F = 'abc';
console.log(f\u006F\u006F);

base64 转码

  • 浏览器:
    • btoa:任意值转为 base64 编码
    • atob:base64 值解码
    • 非 ASCII 码 (如中文) 要转码之后再 base64;
      • encodeURIComponent
      • decodeURIComponent
  • Nodejs
    • var base64encode = Buffer.from("js").toString("base64");
    • var base64decode = Buffer.from(base64encode,'base64').toString();

对象

  • 对象就是一组键值对 key-value 的集合,是一种无序的复合数据集合
  • 对象的每个键名又称为属性 property; 它的值可以是任意数据类型;
  • 如果一个对象的某个属性的值是函数,则通常将这个属性称为该对象的方法,可以像函数一样调用这个方法;
  • 属性可以动态创建,不必在对象声明的时候就全部定义;

对象引用

  • 如果不同的变量名指向同一个对象,那么他们都是对这个对象的引用;也就是说这些变量都指向了同一个内存地址,修改其中任意一个的值都会影响到其他的变量;
  • 如果取消某一个变量对于原对象的引用,不会影响到其他引用该对象的变量
  • 这种引用只局限于对象,如果两个变量指向同一个原始类型的值,这些变量都是对原始类型的值的拷贝,改变任意变量都不会影响其他变量

属性查看

Obejct.keys() 可以查看该对象自身的属性,继承来的属性无法查看

var a = {
    hello: function(){
        consoel.log("hi");
    },
    table: [1, 2, 3],
    name: "kevin",
    age: 21,
    married: false
}

console.log(Object.keys(a));
//[ 'hello', 'table', 'name', 'age', 'married' ]

属性删除

  • delete 用于删除对象的属性,删除成功后返回 true
  • 删除一个不存在的属性,delete 不会报错,而且返回 true
  • 只有一种情况,delete 命令会返回 false; 那就是删除一条存在的属性,但是这条属性被定义成不能删除;(定义该属性不能删除 defineProperty)
  • 只能删除对象自身的属性,继承来的属性不能删除
var a = {
    hello: function(){
        consoel.log("hi");
    },
    table: [1, 2, 3],
    name: "kevin",
    age: 21,
    married: false
}

delete a.age, delete a.hello;
console.log(Object.keys(a));
// [ 'table', 'name', 'married' ]

属性存在判断

  • in 运算符可以用于检查对象是否包含某个属性;检查的是键名,存在这个属性返回 true, 不存在则返回 false;
  • hasOwnProperty(): 判断某个属性是否是该对象自身的属性;
JSvar a = {
    hello: function () {
        consoel.log("hi");
    },
    table: [1, 2, 3],
    name: "kevin",
    age: 21,
    married: false
}
console.log(a.hasOwnProperty('table'));
if ("table" in a) {
    console.log("table is property of a");
}

属性遍历

  • for in 循环可以用于遍历对象的全部属性;不仅可以遍历对象自身的属性,还可以遍历对象继承来的属性;
  • 如果只想遍历对象自身的属性,可以配合 hasOwnProperty() 进行筛选
var a = {
    hello: function(){
        consoel.log("hi");
    },
    table: [1, 2, 3],
    name: "kevin",
    age: 21,
    married: false
}

for (const aKey in a) {
    // 使用 hasOwnProperty 进行筛选
    if (a.hasOwnProperty(aKey)) {
        console.log(aKey);
    }
}

函数

函数声明

js 中函数声明有三种方式

  1. 使用 function 申明 function a(){}
  2. 函数表达式 var a = function(){}
  3. Function 构造函数 var a = Function("a","b", "return a+b") 或者 var a = new Function("a","b", "return a+b") 这两种方式效果一样

函数是一等公民

js 将函数看成是一个值,与其他数据类型一样,凡是可以使用其他数据类型的地方都可以使用函数,例如:

  1. 可以将函数赋值给变量或者对象的属性
  2. 可以将函数作为参数传递给其他函数
  3. 可以将函数作为其他函数的返回值

函数变量名提升

js 中全局变量名存在变量提升,函数内部的局部变量也存在变量提升;

function outer() {
    console.log(a); // undefined 说明全局变量存在变量提升
    console.log(b); // undefined 说明局部变量存在变量提升
    var b = 2;
}
outer();
var a = 1;

js 中函数的声明也存在变量提升,可以先调用该方法,再定义该方法

b();
function b() {
    console.log("b called");
}

函数的属性和方法

  • name (属性):返回函数的名字
  • length (属性):返回函数预期传入的形参数量
  • toString () 方法:返回函数的字符串源码

函数作用域

  • 作用域 scope 是指变量存在的范围
    • es5 中 js 只有两个作用域
      • 全局作用域:变量在整个程序中一直存在,所有地方都可以读取到该变量
      • 函数作用域:变量只在函数内部存在
    • es6 中新增了块级作用域
  • 函数外部声明的变量就是全局变量,它可以在函数内部读取到;
  • 在函数内部声明的变量就是局部变量,函数外部无法读取;
  • 函数本身的作用域就是其声明时所在的作用域,与其运行时的作用域无关;
  • 函数内部声明的函数,作用域绑定函数内部 (闭包)

函数参数省略

js 中函数的参数不是必须的,允许省略函数的参数;

函数的 length 属性只和函数声明时形参的个数有关,和实际调用时传入的参数个数无关;

参数传递方式

  • 函数的参数如果是原始数据类型 (数值,字符串,布尔), 参数传递使用按值传递的方式,在函数内部修改参数的值不会影响函数外部
  • 如果函数的参数是复合数据类型 (数组,对象,其他函数), 参数的传递方式是按址传递,传入的是引用的地址,因此在函数内部修改参数,会影响到原始值;

arguments 对象

  • 因为 js 允许函数有不定数目的参数,所以需要在函数体的内部可以读取到所有参数,这就是 arguments 对象的由来
  • arguments 对象包含了函数运行时的所有参数,这是一个类数组对象,arguments[0] 就是第一个参数;
  • arguments 对象只能在函数内部使用
  • arguments.length 可以获取函数调用时入参的真正个数
  • arguments.callee 属性可以获取对应的原函数
  • arguments 对象是一个类数组对象,如果要让他使用真正的数组方法,需要将 arguments 转换成数组:
    • Array.prototype.slice.call(arguments)
    • 新建数组,遍历 arguments 将元素 push 到新数组中;

闭包

  • 要理解闭包首先要理解 js 的作用域;前面提到的 js 在 es5 中只有两种作用域:
    • 全局作用域
    • 函数作用域
  • 在函数的内部可以全局作用域的变量
  • js 中特有的链式作用域结构,子级会向上一级一级寻找所有父级的变量,父级的所有变量对于子级来说都是可见的,反之不成立;
var a = 1;
var b = 2;
function f() {
    var b = 3;
    console.log(a, b);
    function f1() {
        var b = 4;
        console.log(a, b);
    }
    f1();
}
f(); // 1 3// 1 4

链式作用域查找

子级会优先使用自己的作用域,如果变量存在则使用,不存在则会依次向上寻找,直至全局作用域;

闭包定义

  • 闭包可以简单理解成定义在一个函数内部的函数
  • 闭包最大的特点就是它可以记住自己诞生的环境,本质上,闭包就是将函数内部和函数外部连接起来的桥梁;
  • 闭包最大的用处有两个
    1. 可以直接读取到外层函数内部的变量
    2. 可以让这些变量始终保存在内存中,闭包让自己诞生的环境一直存在

通过闭包实现简单的计数器

function count() {
    var count = 0;
    function f() {
        count++;
        console.log("count", count);
    }
    return f;
}
f = count();
f();
f();
f();

立即调用函数表达式

js 中有三种立即调用函数的方式

  1. var f = function(){}();
  2. (function(){}())
  3. (function(){})()

通常情况下,只对匿名函数使用这种立即执行的表达式,这样有两个目的:

  1. 不必为函数命名,避免污染全局环境
  2. 立即调用函数的内部会形成单独的作用域,可以封装一些外部无法读取的私有变量

eval 命令

  • eval 可以接受一个字符串,并将字符串当做代码执行
  • eval 没有自己的作用域,都是使用当前运行的作用域,所以 eval 会修改当前作用域下的变量的值
  • eval 的本质是在当前作用域中,注入代码,经常用于混淆和反爬

eval 别名调用

eval 的别名调用在 nodejs 下无法跑通,需要在浏览器下运行;

需要注意,在 eval 通过别名调用的时候,作用域永远是全局作用域;

var a = 1;
var e = eval;
(function () {
    var a = 2;
    e("console.log(a);"); // eval 在别名调用的时候使用全局作用域
}())

数组

  • 数组 Array 是按次序排列的一组值
  • 每个值的位置都有对应的索引
  • 数组使用 来表示
  • 任何类型的数据都可以放入数组中
  • 本质上,数组是特殊的对象,typeof 查看数组的类型返回的是 object
  • Object.keys () 可以返回数组的键名 (索引)

数组的属性

  • length: 表示数组的元素个数,这个属性是可写的,可以直接修改数组的 length 属性,来实现清空数组或删除数组中元素的效果
  • 数组本质上是特殊的对象,支持使用点操作符对数组添加属性;
var a = 1;
var e = eval;
(function () {
    var a = 2;
    e("console.log(a);"); // eval 在别名调用的时候使用全局作用域
}
    ())
var a = [1, 1.1, true, {}, [], "hello", null, undefined];
console.log("a.length", a.length);
a.name = "add a.name property";
for (const aKey in a) {
    console.log(aKey, a[aKey]);
}
console.log("Object.keys(a)", Object.keys(a));
a.length = 0;
console.log("a", a);
console.log("a['name']", a['name']);
console.log("a.name", a.name);
console.log("a[0]", a[0]);

数组循环

  • for in 循环
  • for 循环
  • while 循环
  • forEach : 只有数组才有该方法;该方法接受一个回调函数,回调函数入参为 value 和 key;
var a = 1;
var e = eval;
(function () {
    var a = 2;
    e("console.log(a);"); // eval 在别名调用的时候使用全局作用域
}
    ())
var a = [1, 1.1, true, {}, [], "hello", null, undefined];
console.log("a.length", a.length);
a.name = "add a.name property";
for (const aKey in a) {
    console.log(aKey, a[aKey]);
}
console.log("Object.keys(a)", Object.keys(a));
a.length = 0;
console.log("a", a);
console.log("a['name']", a['name']);
console.log("a.name", a.name);
console.log("a[0]", a[0]);
var a = [1, 1.1, true, {}, [], "hello", null, undefined];

for infor(const aKey in a) {
    console.log("aKey:", aKey, "value:", a[aKey]);
}

console.log("-------------------------------")

forfor(var i = 0; i <= a.length; i++) {
    console.log("index:", i, "value:", a[i]);
}

console.log("-------------------------------")

whilevar index = 0;
while (index <= a.length) {
    console.log("index:", index, "value:", a[index]);
    index++;
}

console.log("-------------------------------")

forEacha.forEach(function (value, key) {
    console.log("key:", key, "value:", value);
})

数组空值

js 中的数组支持空值,出现空值时会占用该索引位,但是遍历的时候不会遍历该索引的值

var a = [1, 2, 3, , 5];
a.forEach(function (value, key) {
    console.log("key", key, "value", value);
})
// key 0 value 1
// key 1 value 2
// key 2 value 3
// key 4 value 5

类数组对象

  • 如果一个对象的所有键名都是正整数或者 0, 且有 length 属性,那么这个对象就是类数组对象
  • 典型的类数组对象有 arguments 对象,字符串,以及大部分的 dom 元素集
  • 数组的 slice 方法可以将类似数组的对象变成真正的数组 var arr = Array.prototype.slice.call(arrayLike);
  • 除了将类数组对象转成真正的数组,还可以使用 call () 将数组的方法直接放到类数组对象上使用 Array.prototype.forEach.call(arrayLike, function(){});

数据类型转换

自动数据类型转换

其他类型转字符串

+ 加号作为操作符,且操作数中含有字符串时,会自动将另一个操作数转为字符串;

规则如下:

  1. 字符串 + 基础数据类型:会直接将基础数据类型转为和字面量相同的字符串
  2. 字符串 + 复合数据类型:复合数据类型会先调用 valueOf() 方法,如果该方法返回基础数据类型则将其转为字符串,如果返回的是复合数据类型,则调用 toString() 方法,如果返回的是基础数据类型则将其转为字符串,如果不是则报错;
// 基础类型
// 自动数据类型转换
// + 字符串
// 1. 字符串 + 基础数据类型: 会直接将基础数据类型转为和字面量相同的字符串
var tmp1 = "" + 3;
console.log("tmp1", tmp1); // tmp1 "3"

var tmp2 = "" + true;
console.log("tmp2", tmp2); // tmp2 "true"

var tmp3 = "" + undefined;
console.log("tmp3", tmp3); // tmp3 "undefined"

var tmp4 = "" + null;
console.log("tmp4", tmp4); // tmp4 "null"

// 字符串+复合数据类型: 复合数据类型会先调用 valueOf 方法, 如果此方法返回的是引用类型, 则再调用 toString()方法, 最后将返回值转为字符串类型
var tmp5 = [1, 2, 3] + "";
console.log("tmp5", tmp5); // tmp5 1,2,3

var tmp6 = {} + "";
console.log("tmp6", tmp6); // tmp6 [object Object]

// 重写 toString 方法
var o = {
    toString: function () {
        return 1;
    }
}

var tmp7 = o + "";
console.log("tmp7", tmp7) // tmp7 "1"

// 重写 valueOf 方法
o.valueOf = function () {
    return 2;
}
var tmp8 = "" + o;
console.log("tmp8", tmp8); // tmp8 2

var a = {
    valueOf: function () {
        return {}
    },
    toString: function () {
        return "toString"
    }
};

console.log("" + a); // toString

其他类型转布尔值

数值转布尔值

数值在逻辑判断条件下会自动转成布尔值;±0 和 NaN 为 false, 其他数值都是 true;

if (0) {
    console.log("0 is true");
}else{
    conso

你可能感兴趣的:(JS逆向,javascript,学习,前端)