02--JS02--高级

JavaScript02: 高级进阶

一. 变量声明

1.1 变量提升

// 以下代码,或多或少会有些问题的
function fn(){
    console.log(name);
    var name = '大马猴';
}

fn()


// 问题: 
name变量先使用,再定义   这么写代码,在其他语言里. 绝对是不允许的
但是在js里,不但允许,还能执行,为什么呢? 
因为在js执行的时候,它会首先检测整体代码,发现在代码中会有name使用
则运行时,就会自动变成这样的逻辑:

// 变量提升的逻辑:
function fn(){
    var name;
    console.log(name);
    name = '大马猴';
}
fn()
console.log(a);  // undefined

// 看到了么,实际运行的时候和写代码的顺序可能会不一样
这种 把变量 提升到代码块第一部分运行  的逻辑,被称为变量提升

1.2 let 声明变量

结论一:用let声明变量,是新版本javascript ES6 提倡的一种声明变量的方案

// ES6提出 用let 声明变量,防止变量提升的逻辑

function fn(){
    console.log(name);  // 直接报错, let变量不可以变量提升
    let name = '大马猴'; 
}
fn()

结论二:在同一个作用域内. let声明的变量只能声明一次,其他使用上和var没有差别

function fn(){
    // console.log(name);  // 直接报错, let变量不可以变量提升.
    // let name = '大马猴';
    var name = "周杰伦";
    var name = "王力宏";   // 不会报错
    console.log(name);
}

fn()

// var本意是声明变量,同一个变量,被声明两次(都是函数体内部,局部变量),显然也是不合理的

ES6规定:let声明的变量,在同一个作用域内,只能声明一次

function fn(){
    // console.log(name);  // 直接报错, let变量不可以变量提升.
    // let name = '大马猴';
    let name = "周杰伦";
    console.log(name);
    
    let name = "王力宏";    // 报错,同一个作用域 let 声明同一个变量,只能一次
    console.log(name);
}
fn()


// 注意: 报错是发生在代码检查阶段. 所以上述代码,根本就执行不了

二. 闭包函数

先看一段代码

let name = "周杰伦"
function chi(){
    name = "吃掉"
}
chi();
console.log(name);  // "吃掉"


// 发现没有: 在函数内部修改,外部的变量是十分容易的一件事. 尤其是全局变量. 这是非常危险的. 
// 试想 写了一个函数. 要用到name, 结果被别人写的某个函数给修改掉了...

接下来,看一个案例:

准备两个工具人. 来编写代码. 分别是js01和js02.

// 1号工具人
var name = "alex"

// 定时器,5000秒后执行
setTimeout(function(){
    console.log("一号工具人:" + name)   // 一号工具人还以为是alex呢, 但是该变量是不安全的.
}, 5000);
// 2号工具人
var name = "周杰伦"
console.log("二号工具人", name);

html:

<script src="js01.js"></script>

<script src="js02.js"></script>

此时运行的结果:

很明显, 虽然各自js在编写时是分开的. 但是在运行时, 是在同一个空间内执行的. 他们拥有相同的作用域

此时的变量势必是非常非常不安全的. 那么如何来解决呢?

注意:在js里 变量是有作用域的. 也就是说一个变量的声明和使用是有范围的,不是无限的

// 验证: 变量是有作用域的
function fn(){
    let love = "爱呀"
}
fn()

console.log(love)

// 直接就报错了   也就是说. 在js里 变量作用域是有全局和局部  的概念 

直接声明在最外层的变量,就是全局变量。所有函数、所有代码块都可以共享的

在函数内和代码块内声明的变量,尤其是函数内,声明出来的变量它是一个局部变量,外界是无法进行访问的

我们就可以利用这一点,来给每个工具人创建一个局部空间. 就像这样:

// 1号工具人   都是自运行函数
(function(){
    var name = "alex";
    setTimeout(function(){
        console.log("一号工具人:"+name)
    }, 5000);
})()
// 二号工具人  都是自运行函数
!function(){
    var name = "周杰伦"
    console.log("二号工具人", name);
}()

运行结果

这样,虽然解决了变量的冲突问题

但是想想. 如果在外面,需要函数内部的一些东西,来进行相关操作,怎么办?

比如 一号工具人要提供一个功能(加密),外界要调用, 怎么办?

// 1号工具人

// 局部函数中,对外接口 方式一: return 返回到 全局变量中   ===> 闭包函数
// 1.首先:全局要使用,那js文件中,就不能是自运行函数,要设置一个名字
let jiami = (function(){
    let key = "10086" // 假装我是秘钥
    
    // 我是一个加密函数
    let mi = function(data){  // 数据
        console.log("接下来, 我要加密了,rsa哦. 很厉害的")
        console.log("秘钥:"+key);
        console.log("数据:"+data);
        // 返回密文
        return "我要返回密文";
    }
    
    // 2.其次:外面可能需要用到该功能. 故 需要该变量返回(暴露到全局空间). 返回加密函数
    return mi;
})();



// 局部函数中,对外接口 方式二:借助于window对象,将返回的变量 直接 赋值到全局变量中
(function(){
    let key = "10086" // 假装我是秘钥
    
    // 我是一个加密函数
    let mi = function(data){  // 数据
        console.log("接下来, 我要加密了,rsa哦. 很厉害的")
        console.log("秘钥:"+key);
        console.log("数据:"+data);
        // 返回密文
        return "我要返回密文";
    }
    
    // 对外的接口
    window.mi = mi;
})();

注意:如果封装一个加密js包的时候,就还得准备出解密的功能

并且, 不可能一个js包就一个功能吧。 那也太痛苦了(js文件起名字),那怎么办?

可以返回一个对象,对象里面可以存放好多个功能

而一些不希望外界触碰的功能. 就可以很好的保护起来.

// 1号工具人.
let jiami = (function(){

    let key = "10086" // 加装我是秘钥
   
	// 该函数只属于该模块内部,外界无法访问.  就不返回
    let n = {
        abc:function(){
            console.log("我是abc. 你叫我干什么?")
        }
    }

    
    // 外面需要用到的功能,就进行返回.
    return {
        rsa_jiami: function(data){
            console.log("接下来, 我要加密了,rsa哦. 很厉害的")
            console.log("秘钥:"+this.get_rsa_key() + key);
            n.abc();
            console.log("数据:"+data);
            return "我要返回密文";
        },
        aes_jiami: function(data){
            console.log("接下来, 我要加密了,aes哦. 很厉害的")
            console.log("秘钥:"+this.get_aes_key());
            n.abc();
            console.log("秘钥:"+key);
            console.log("数据:"+data);
            return "我要返回密文";
        },
        get_rsa_key: function() {
            return this.rsa_key = "rsa的key", this.rsa_key
        },
        get_aes_key: function() {
            return this.rsa_key = "aes的key", this.rsa_key
        }
    }
})();

html里面使用时:

<script>
    miwen = jiami.rsa_jiami("吃你的糖葫芦吧");
    console.log(miwen);
</script>

OK. 至此. 何为闭包? 上面这个就是闭包

相信你百度一下就会知道,什么内层函数使用外层函数变量、什么让一个变量常驻内存等等

其实细看,它之所以称之为闭包,它是一个封闭的环境。在内部. 自己和自己玩儿

避免了对该模块内部的冲击和改动. 避免的变量之间的冲突问题

闭包的特点:

  1. 内层函数对外层函数变量的使用
  2. 会让变量常驻于内存

这俩玩意就不解释了, 和python的闭包是一个意思。不懂没关系,能看懂它的执行过程就好

三. JS中的各种操作(非交互)

3.1 定时器

在JS中, 有两种设置定时器的方案

// 延时器:经过xxx时间后, 执行xxx函数
t = setTimeout(函数, 时间)


// 5000毫秒  5秒后打印我爱你
t = setTimeout(function(){
    console.log("我爱你")
}, 5000); 


clearTimeout(t)  // 停止一个定时器
// window.clearTimeout(t)  
// 定时器:每隔 xxx时间, 执行一次xxx函数
t = setInterval(函数, 时间) 


// 每隔5秒钟, 打印`我爱你`
t = setInterval(function(){
    console.log("我爱你")
}, 5000)

window.clearInterval(t)  // 停止一个定时器

for(let i = 0; i <= 9999; i++)window.clearInterval(i);  // 清理掉所有定时器
// 定时器 关于js逆向,常遇到的:


// 1.心跳检测     一般是用来监测用户 是否掉线了,也可以用来监测用户浏览器环境是否健康
// 这个就是  服务端 主动向 浏览器端 发送请求检测
http是被动响应的  服务器端只能不停的间隔发送检测请求,才能时刻监测客户端某个元素的状态(是否点击 或 扫描二维码)等

js逆向中:心跳检测
就是正常浏览器是 不停的心跳检测 且有数据返回,
js逆向代码时,可能只发一次,服务端就给做反爬 禁IP什么的


// 2.无限debugger
// 无限debugger的核心
setInterval(function(){
    debugger;   // 设置断点
}, 1000)

网页页面中,会正常显示其他的html代码,但是一旦F12调试,就会一直处理断点中


// 解决原理:
在source源代码中,setInderval 这一行(没进入定时器体内之前),左键点击行号(设置断点)

刷新页面,页面会调试暂停到 设置断点这一行

再在控制台(console)中,将定时器干掉 (重置定时器为普通的空函数 setInterval=function(){}; )

3.2 关于时间

eg:http://www.baidu.com/s?word=jfdsaf&t=1640090719637 参数t就是时间戳

var d = new Date(); // 获取当前系统时间
var d = new Date("2018-12-01 15:32:48");  // YYYY-MM-DD HH:mm:ss得到一个具体时间

// 时间格式化
year = d.getFullYear();   // 年份
month = d.getMonth() + 1; // 月份. 注意月份从0开始
date = d.getDate();       // 日期
hour = d.getHours();      // 小时
minute = d.getMinutes();  // 分钟
seconds = d.getSeconds(); // 秒

format_date = year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + seconds;



// 时间戳     表示从1970-1-1 00:00:00 到现在一共经过了多少毫秒
var d = new Date(); 
console.log(d.getTime())


// 注意1:python的时间戳 单位是秒
import time
print( int(time.time() * 1000) )  # 扩大一千倍,再取整


// 注意2: 有些时候,前端实例化日期对象时,会少一个调用括号
var d = new Date;     // 坑人写法,也可以
console.log(d.getTime())

3.3 eval函数(必须会)

http://tools.jb51.net/password/evalencode 一个在线JS处理eval的网站. 大多数的eval加密. 都可以搞定了.

// eval:
可以动态的把字符串,当成js代码运行   // 从功能上讲非常简单,和python里面的eval是一样的

s = "console.log('我爱你')";
eval(s);


// 重点:eval加密
拓展使用 --> 前端利用eval的特性来完成反爬操作

// 解决核心:
eval函数,里面传递的应该是  即将要执行的 代码(字符串)



// eg:
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0.1(\'我爱你\')',62,2,'console|log'.split('|'),0,{}))


// 记住eval()里面是字符串  !!!
这一坨看起来, 肯定很不爽. 怎么变成看着很舒服的样子呢?  想看看这个字符串长什么样?  
就把eval()里面的东西拷贝出来
执行一下  // 注意:一般是个自运行函数,直接粘贴会报错  故加个括号 eg: (eval中的代码) 
         //  或 和下面截图一样  赋值给变量,再打印变量
最终一定会得到一个字符串,要不然eval()执行不了的

3.4 prototype 原型

prototype 原型,作用是 给类增加功能扩展 的一种模式

写个面向对象来看看.

function People(name, age){
    this.name = name;         // this 相当于python的self
    this.age = age;
    this.run = function(){
        console.log(this.name+"在跑")
    }
}

p1 = new People("张三", 18);
p2 = new People("李四", 19);

p1.run();
p2.run();

现在代码写完了. 突然之间, 感觉好像少了个功能. 人不应该就一个功能. 光会吃是不够的. 还得能够ooxx. 怎么办?

直接改代码? 可以,但不够好. 如果这个类不是我写的呢? 随便改别人代码是很不礼貌的,也很容易出错. 怎么办?

可以在我们自己代码中,对某个类型动态增加功能。此时就用到了prototype

function People(name, age){
    this.name = name;
    this.age = age;
    this.run = function(){
        console.log(this.name+"在跑")
    }
}

// 通过prototype,可以给People 类(构造器) 增加功能  属性或方法
People.prototype.xxoo = function(){
    console.log(this.name+"还可以xxoo");
}

p1 = new People("张三", 18);
p2 = new People("李四", 19);

p1.run();
p2.run();

p1.xxoo();
p2.xxoo();
// eg:给日期对象 增加常用的格式化功能

Date.prototype.fromat = function(){
    let Y = this.getFullYear();
    let M = this.getMonth() + 1;
    let d = this.getDate();
    let h = this.getHours();
    let m = this.getMinutes();
    let s = this.getSeconds();
    
    return Y + "-" + M + "-" + d + " " + h + ":" + m + ":" + s;
}


// 使用
let d = new Date();
console.log(d.fromat())

几个重要概念

3.4.1 构造器

构造一个对象的函数. 叫构造器. 是用于 初始化对象

function People(name, age){   // People 就是构造器 constractor
    this.name = name;         // 带有this,99%都是构造器了,而不是定义函数了
    this.age = age;  
}

var p1 = new People();    // new 构造器   调用构造器,p1 是返回的对象

var p2 =  People();       // 没有new,就是普通函数调用, p2 是函数的返回值



// 调用 对象的隐藏属性  构造器
p.constractor == People; // true

3.4.2 原型对象

每一个js对象中,都有一个隐藏属性__proto__,指向该对象的 原型对象

在执行该对象的方法或者查找属性时:

首先, 对象自己(构造器中声明的 或 对象自己赋值的 )是否存在该属性或者方法

如果存在, 就执行自己的. 如果自己不存在. 就去找 原型对象

function Friend(){
    this.chi = function(){
        console.log("我的朋友在吃");
    }
};

// 指定Friend 的原型对象    是全部重写 原型对象
Friend.prototype = {
    chi: function(){
        console.log("我的原型在吃")
    }
};

// 或者这种写法        原型对象 增加某个方法或属性
Friend.prototype.chi = function(){
    console.log("我的原型在吃")
};


f = new Friend();
f.chi();   // 运行结果: 我的朋友在吃


// 属性查找顺序:
先查找该对象( 构造器中声明的 或 对象自己赋值的 )中  是否有chi这个方法
再找,它的原型对象上( f.__proto__ ) 是否有chi这个方法





// 总结: !!!
Friend    //  构造器

f         //  对象

f.__proto__  <===> Friend.prototype  // 构造器的prototype属性 和 对象的 __proto__,都是指向f对象 的 原型对象

3.4.3 原型链

原型链(prototype chain):是属性查找的方式

当调用一个对象的属性时,如果对象没有该属性,从对象的原型对象上去找该属性,

如果原型上也没有该属性,那就去找原型的原型,直到最后返回null为止,null没有原型。

// 前提:
每个对象身体里. 都隐藏着 __proto__属性 也就是它的 原型对象
同时 原型对象 也是对象, 也就是说 原型对象 也有  __proto__ 属性


类似于.....这样:

f.__proto__.__proto__  ===> Friend.prototype.__proto__ ===>  Object.prototype // Object的原型对象 

// 再多一层,就到null了
f.__proto__.__proto__.__proto__  ===> Object.prototype.__proto__  ===> null // Object的原型对象 的原型对象 就是null

打印出来的效果是这样的:

// 故:在执行 f.toString() 的时候不会报错. 可以正常运行的原因,就在这里

执行过程:  
先找 f对象 中是否有 toString 没有
找它的 原型对象,原型对象 中没有
继续找 原型对象的原型对象
直至找到Object的原型对象为止,如果还没有,就报错了. 


f.hahahahahahah()  // 报错 

// 综上:
原型链是js 方法查找的路径指示标

3.4.4 原型链的延伸使用

用原型链能做什么? 网站反爬(恶心): 有些页面网站,会让你F12时,一直无限debug ---> 反调试

看一段神奇的代码

(function(){debugger})();   // 这样一段代码可以看到. 浏览器进入了debugger断点


// 注意:
在js代码执行时,每一个function的对象,都是通过Function()来创建的 
也就是说 函数是Function()的对象,函数体的代码 是通过Function构造器 的字符串参数 传递进行的


// 校验:
function fn(){};
console.log(fn.__proto__.constructor);  // fn函数的原型对象 的 构造器是 ƒ Function() { [native code] }
console.log(fn.constructor);  // fn函数的构造器是 ƒ Function() { [native code] }


// 故也可以通过Function来构建一个函数
new Function('debugger')();  // 效果一样的  (function(){debugger})();



// 上述的核心就是
普通定义函数:function(){函数体代码}   ---> 函数体代码 是js的语句

Function构造器 (声明 Function 对象) 定义的函数:new Function('函数体代码'), ---> 函数体代码 是js的普通字符串

是字符串 就可以做各种的混淆操作  // 恶心之处

OK. 这东西对我们来说有什么用. 上代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="haha.js"></script>
  <script>
    txsdefwsw();
  </script>
</head>
<body>
    有内鬼. 终止交易
</body>
</html>

haha.js 中的内容如下:

function txsdefwsw() {
    var r = "V", n = "5", e = "8";

    function o(r) {
        if (!r) return "";
        for (var t = "", n = 44106, e = 0; e < r.length; e++) {
            var o = r.charCodeAt(e) ^ n;
            n = n * e % 256 + 2333, t += String.fromCharCode(o)
        }
        return t
    }

    try {
        var a = ["r", o("갯"), "g", o("갭"), function (t) {
            if (!t) return "";
            for (var o = "", a = r + n + e + "7", c = 45860, f = 0; f < t.length; f++) {
                var i = t.charCodeAt(f);
                c = (c + 1) % a.length, i ^= a.charCodeAt(c), o += String.fromCharCode(i)
            }
            return o
        }("@"), "b", "e", "d"].reverse().join("");
        !function c(r) {
            (1 !== ("" + r / r).length || 0 === r) && function () {
            }.constructor(a)(), c(++r)    // 核心:Function('debugger')()
        }(0)
    } catch (a) {
        setTimeout(txsdefwsw, 100);
    }
}

结果:页面跑起来没什么问题. 但是会无限debugger;

解决方案:

  1. 找到断点 行处 . 右键 ----> never pause here (不在这里暂停)

  2. 写js hook代码

    // 解决原理:
    既然 你无限debugger的混淆原理,是Function + 字符串的混淆操作
    
    那么 解决原理:在Function的原型对象的 构造器 (因为通过 Function() 声明函数时,会调用该构造器)  做一个拦截处理
    
    如果 Function构造器的 参数是 'debugger',就返回空 不执行构建函数 
    如果 Function构造器的 参数不是 'debugger',就执行 返回原生的构造器,进行声明函数
    
    
    
    // 1.先将 原本的 Function原型对象的构造器,复制出来
    var xxxx = Function.prototype.constructor;
    
    // 普通函数对象func.constructor === Function.constructor  === Function.prototype.constructor
    
    
    // 2.再改造 Function原型对象的构造器
    Function.prototype.constructor = function(){
        if (arguments[0] === 'debugger'){
            return;
        } else {      
            return xxxx.apply(this, arguments);  // 不能用.call  参数数量不固定
        }
    }
    

    更加详细的hook. 在下面.

3.5 window对象

window 窗口对象是一个很神奇的东西,可以理解成javascript的全局,是整个浏览器的全局作用域

如果默认不用任何东西,访问一个标识符,那么默认是在用window对象

注意:
1.是浏览器有这个,node.js没有该对象。

2.在浏览器上 delete window; 是默认删不掉的

故 用js逆向时,需要在node.js上 自定义伪造window对象

但 有些网站 防爬,就会时不时 delete window; 用来区分是程序伪装,还是浏览器。

解决:就是找到 网站源码,删除该js代码(delete window;)

例如:

eval === window.eval    // true
setInterval === window.setInterval  // true

var a = 10; 
a === window.a    // true

function fn(){}
fn === window.fn  // true

window.mm = "爱你"

console.log(mm); //"爱你"



// window 中有很多功能对象,还可以控制页面跳转

window.location.herf = "新地址"  // 当前窗口 跳转到新地址url

综上:全局变量可以用window.xxx来表示

ok. 接下来注意看. 我要搞事情了

(function(){
    let chi = function(){
        console.log("我是吃")
    }
    window.chi = chi
})();

chi()


// 换一种写法. 你还认识么?
(function(w){
    let chi = function(){
        console.log("我是吃")
    }
    w.chi = chi
})(window);



// 再复杂一点
(function(w){
    let tools = {
        b64: function(){
            console.log("我是计算B64");
            return "b64";
        },
        md5: function(){
            console.log("我是计算MD5");
            return "MD5"
        }
    }
    w.jiami = {
        AES: function(msg){
            return tools.b64(),
                tools.md5(),
                'god like';
        },
        DES: function(){
            console.log("我是DES");
        },
        RSA: function(){
            console.log("我是RSA");
        }
    }
})(window);

jiami.AES("吃了么");

3.6 call和apply

对于逆向工程师而言,并不需要深入的理解call和apply的本质作用. 只需要知道这玩意执行起来的逻辑顺序是什么即可

作用: 其实也是 为对象 增加额外的方法

1.可以 执行函数

2.可以指定 执行函数 的对象

在运行时,正常的js调用:

function People(name, age){
    this.name = name;
    this.age = age;
    this.chi = function(){
        console.log(this.name, "在吃东西")
    }
}

p1 = new People("alex", 18);
p2 = new People("wusir", 20);

p1.chi();
p2.chi();

接下来,可以使用call和apply也完成同样的函数调用

function People(name, age){
    this.name = name;
    this.age = age;
    this.chi = function(what_1, what_2){
        console.log(this.name, "在吃", what_1, what_2);
    }
}

p1 = new People("alex", 18);
p2 = new People("wusir", 20);

p1.chi("馒头", "大饼");
p2.chi("大米饭", "金坷垃");


// 普通函数 eat  属于是window对象
function eat(what_1, what_2){
    // this 没有明确的指向,最后会在window中  获取name
    console.log(this.name, "在吃", what_1, what_2);
}


// call的语法是: 函数.call(对象, 参数1, 参数2, 参数3....)
// 执行逻辑是: 执行函数. 并把对象传递给函数中的this.  其他参数照常传递给函数
eat.call(p1, "查克拉", "元宇宙");



p2.chi.call(p1, "查克拉", "元宇宙");  // wusir 在吃 查克拉 元宇宙
// p2.chi 相当于一个普通函数,调用的this 是传递的p1对象

apply和他几乎一模一样. 区别是: apply传递参数要求是一个数组

eat.apply(p1, ["苞米茬子", "大饼子"]);

3.7 ES6中的箭头函数

在ES6中简化了函数的声明语法.

// 函数无参数
var fn = function(){};

var fn = () => {};


// 函数1个参数  括号可以省略  !!! 容易岔
var fn = function(name){}

var fn = (name) => {}
var fn = name => {}



// 函数多个参数
var fn = function(name, age){}

var fn = (name, age) => {}

3.8 ES6中的promise(难)

具体执行过程和推理过程. 请看视频. 这里很饶腾.

function send(url){
    return new Promise(function(resolve, reject){
        console.log("我要发送ajax了", url)
        setTimeout(function(){
            console.log("我发送ajax回来了")
            // 成功了, 要去处理返回值
            resolve("数据", url);
        }, 3000);
    });
}

send("www.baidu.com").then(function(data){
    console.log("我要处理数据了啊", data);
    return send("www.google.com");
}).then(function(data, url){
    console.log("我又来处理数据了", data);
});

3.9 逗号运算符

function s(){
    console.log(1), console.log(2), console.log(3);  // 从前向后执行 1,2,3
    
    let s = (1, 2, 3);  // 整体进行赋值的时候. s 取的是最后一个值 3
    console.log(s);     // 注意: 这个括号,在作为返回值时,可省略  return 1, 2, 3
    
    
    var a;
    return a=10,        // return 返回时,以逗号分割依次执行,同样只取最后一个   和python的差别  !!!
    a++,
    a+=100,
    {name:"alex", "a":a};
}

let r = s();
console.log(r);  // {name: 'alex', a: 111}

3.10 三元运算符

// 三元运算符
条件? 值1 :值2      // 条件成立时,返回 ?后面的  反之,返回 :后面的

let a = 10;
let b = 20;
let d = a > b? a :b ; 
console.log(d);  // 20 


// 看一个恶心的: 逗号运算符 + 三元运算符
let a = 10;
let b = 20;
let d = 17;
let c = 5;
let e;
let m;

e = (e = a > 3 ? b : c, m = e < b++ ? c-- : a = 3 > b % d ? 27: 37, m++)

// 解析
e = (e = 20, m = a = 37, m++)
e = m++

console.log(e);  // 37
console.log(c);  // 5
console.log(m);  // 38

3.11 JS Hook

Hook又称钩子,可以在调用系统函数之前,先执行我们的函数. 钩子函数

在逆向时, 常用的主要有: hook eval 、hook Function 、hook JSON.stringify、JSON.parse 、hook cookie、hook window对象

其他参考:https://zhuanlan.zhihu.com/p/584684377

3.11.0 设置 script 断点

注意:想要正常Hook 成功,需要在执行目标函数之前,先把Hook函数植入

在Sources面板 ---> Event Listener Breakpoints( 事件监听断点) ---> 设置 script 断点

目的:是为了让浏览器,在每加载一个JS,就被断下来,以方便进行 Hook

// Hook函数 添加操作

1.设置script断点后,刷新页面,会暂停到第一个JS文件加载前  

2.复制你的Hook 函数,到控制台

3.先取消设置script断点,再释放断点 (第一个js script的断点)

4.正常情况下:sources面板会跳转到 Hook函数的 断点处,进行溯源分析

3.11.1 Hook eval

// 先保存系统的eval函数
var eval_ = eval;

// 钩子函数中添加断点,就可以溯源   在Call Stack调用栈中,查看到哪个函数在调用该方法
eval = function(s){
    console.log(s);
    debugger;
    return eval_(s);
};


// 注意:钩子函数替换的eval,调用某些方法和原生就不一样了,有些网站可能会检测这个

// eg: toString方法
eval.toString()  
  // 'function(s){\n    console.log(s);\n    debugger;\n    return eval_(s);\n}'



// 措施:需要再伪装 补救,换成原生的样子     具体需要补救哪些方法,看实际网站检测哪些
// 修改一下eval.toString 
eval.toString = function(){return 'function eval() { [native code] }'}

// 或者如下:
var _old = Function.prototype.toString.call

Function.prototype.toString.call = function (arg) {
    if (arg === eval) {
        return "function eval() { [native code] }"
    }
    return _old(arg);

3.11.2 Hook Function

主要为了解决无限debugger

### 1 debugger函数的几种写法

# 1.普通 debugger函数
(function(){debugger})()


# 2.通过函数的构造器 写debugger函数
(function(){}).constructor("debugger")()


# 3.通过 Function 生成函数对象 写debugger函数
new Function("debugger")()




### 2 对应的Hook方法

# 1. 普通debugger函数, 一般是写进定时器里,实现无限debug
直接将定时器置空   # 详见下面hook 定时器



# 2.函数的构造器  的debugger函数
Hook 函数的构造器


var fnc_ = Function.prototype.constructor;

Function.prototype.constructor = function(){
    if(arguments[0]==='debugger'){
        return function(){};  // 返回空函数对象
    } else {
        return fnc_.apply(this, arguments);
    }
}

# 更通用:
var Function_prototype_constructor_back = Function.prototype.constructor;

Function.prototype.constructor = function() {
    if(arguments && typeof arguments[0]==='string'){
        //alert("new function: "+ arguments[0]);
        if("debugger" === arguments[0]){
            //arguments[0]="console.log(\"anti debugger\");";
            //arguments[0]=";";
            return function(){};  // 返回空函数对象
        }
    }
   return Function_prototype_constructor_back.apply(this,arguments);
}



# 3. Function 生成函数对象  的debugger函数
Hook  Function


var Function_ = Function

Function = function() {
    if(arguments && typeof arguments[0]==='string'){
        if("debugger" === arguments[0]){
            return function(){};  // 返回空函数对象
        }
    }
   return Function_.apply(this,arguments);
}

3.11.3 Hook setInterval

// 1.定时器 与业务代码无关时      定时器只是用做无限debugger时
// 直接把该定时器,重置为空函数  
var setInterval = function(){}




// 2.定时器 与业务代码有关时       用钩子函数勾出来,重新做判断,处理无限debugger
var setInterval_back = window.setInterval

setInterval = function(a,b){
    if(a.toString().indexOf('debugger') != -1){
      return null;
    }
    return setInterval_back(a, b);
}

3.11.4 Hook JSON

// hook JSON字符串的处理  特别常用:网站基本都是传递json数据,且伴随着前端的 加密 解密


// Hook JSON.stringify
var JSON_stringify_back = window.JSON.stringify;

// 在 js的数据格式,转化成json字符串 前 添加断点,溯源查找数据加密的方法
JSON.stringify = function(s){
    debugger;
    return JSON_stringify_back(s);
};



// Hook JSON.parse
var JSON_parse_back = JSON.parse; 

// 在 json字符串 转化成 js的数据格式, 前 添加断点,溯源查找数据解密的方法
JSON.parse = function(s){
    debugger;
    return JSON_parse_back(s);
};

3.11.5 Hook ajax请求的请求头

// 发送Ajax请求,学了两个框架:1.jQuery  2.axios
// 但本质都是 通过 浏览器原生XMLHttpRequest对象,发送ajax



// hook ajax请求的请求头  
// 本质是hook XMLHttpRequest对象.prototype原型的setRequestHeader属性


func_ = window.XMLHttpRequest.prototype.setRequestHeader; // 参数1 请求头的属性,参数2 请求头的值

func_ = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function(name, value){
    if(name === '某值'){
        debugger
    }
    return func_.apply(this, arguments)
};

3.11.6 Hook 属性

// Object对象.defineProperty 
静态方法,会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象



// eg:Hook document.cookie属性  cookie属性就两个方法:get取值、set设置值


// 先定义一个变量,临时用来接受属性的值
var v;


Object.defineProperty(document, "cookie", {
    // 两种形式
    set: function(val) {
        console.log("有人来存cookie了");
    	v = val;
        debugger;     
        return val;
    },
    
    set(val) {
        console.log("有人来存cookie了");
    	v = val;
        debugger;     
        return val;
    },
    
    get() {
        console.log("有人提取cookie了");
        debugger;
        return v;
    }
});

剩下的,就不再赘述了.

四. JS和HTML交互

在HTML中,可以直接在标签上给出一些事件的触发

例如:页面上的一个按钮

<input type="button" value="点我就爱你"/>

该标签在页面中会产生一个按钮,但是该按钮无论如何进行点击. 都不会触发任何事件

但此时, 其实触发了. 只是没处理而已. 在点击该按钮的时候. 浏览器其实收集到了点击事件.

可以通过onclick属性. 来给点击事件添加上具体要做什么

<input type='button' value="点我就爱你" onclick="fn()" />


当发生点击事件时去执行fn(). fn() 是什么? fn就是我们javascript的一个函数. 

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        function fn(){
            alert("臭不要脸")
        }
    </script>
</head>
<body>
    <input type="button" value="点我就爱你" onclick="fn()">
</body>
</html>

至此, 成功实现了 从HTML中调用JS

那么在HTML中有多少种事件可以触发呢? 非常多....记住几个就好了

// html中的事件
click		点击事件
focus		获取焦点
blur		失去焦点
submit		提交表单
change		更换选项
scroll		滚动条滚动
mouseover	鼠标滑过
mouseout	鼠标滑出
mousemove	鼠标滑动

上述是第一种绑定事件的方案. 可以直接在html标签中,使用onxxx系列属性来完成事件的绑定

同时js,还提供了以下事件绑定方案:

<input type="button" id="btn" value="别点我了">
    
<script>
    // 注意:必须等到页面加载完毕了. 才可以这样
    document.querySelector("#btn").addEventListener("click", function(){
        console.log("你点我干什么?? ")
    })
</script>

document.querySelector() 给出一个css选择器, 就可以得到一个html页面上标签元素的句柄(控制该标签).

获取句柄的方案有好多. 常见的有:

document.getElementById();         // 根据id的值 获取句柄
document.getElementsByClassName(); // 根据class的值 获取句柄

// <form name='myform'><input type="myusername"/></form>
document.form的name.表单元素的name;  // document.myform.myusername;

现在相当于,可以从html转到JS中了,并且在js中可以捕获到html中的内容了

此时 对应的表单验证,也可以完成了

<form action="服务器地址" id="login_form">
    <label for="username">用户名:</label><input type="text" name="username" id="username"><span id="username_info"></span><br/>
    <label for="password">密码:</label><input type="text" name="password" id="password"><span id="password_info"></span><br/>
    <input type="button" id="btn" value="点我登录">
</form>
<script>
    // window.onload,等待页面加载完毕,在执行后面函数
    window.onload = function(){
        
        // let btnEle = document.getElementById('btn')
        // btnEle.onclick = function(){
        // 等价于上面
        document.getElementById('btn').addEventListener("click", function(){
            // 清空提示信息
            document.getElementById('username_info').innerText = ""; 
            document.getElementById('password_info').innerText = "";

            let username = document.getElementById('username').value;  // 获取username标签中的value属性
            let password = document.getElementById('password').value;  // 获取密码
            let flag = true;  // 最终是否可以提交表单?
            if(!username){
                document.getElementById('username_info').innerText = "用户名不能为空";
                flag = false;
            }

            if(!password){
                document.getElementById('password_info').innerText = "密码不能为空";
                flag = false;
            }

            if (flag){
                document.getElementById('login_form').submit();
            }
        })
    }
</script>
posted @ 2024-05-14 01:11  Edmond辉仔  阅读(15)  评论(0编辑  收藏  举报