Hook对象属性读写

比如要Hook xjb对象,现在需要对属性进行监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
let xjb={
name:"小肩膀",
age:18
}

_name=xjb.name
// defineProperty的第一个参数是要监控的对象
// 第二个参数是要监控的属性
Object.defineProperty(xjb,"name",{
configurable:true,//对象是否可以被配置或者删除
enumerable:false, //对象属性是否可以被迭代
// writable:true,//对象属性是否可以被修改
// value:2222, //给指定的属性设置值
get(){
console.log("正在获取name属性")
return _name
// 不能直接return xjb.name,不然死循环了
},
set(a){
console.log("正在设置对象name属性:",a)
_name=a
}
})


console.log(xjb.name)
xjb.name="小肩膀888"
console.log(xjb.name)
// for(let i in xjb){
// console.log(i)
// }

Hook Cookie读写

可以快速定位到Cookie的加密

1
2
3
4
5
6
7
8
9
10
11
12
_cookie=document.cookie
Object.defineProperty(document,"cookie",{
get(){
console.log("正在获取document.cookie");
return _cookie
},
set(value){
console.log("正在设置cookie:",value)
_cookie=value;
debugger;
}
})

这里加上debugger是为了断下来去跟堆栈

toString检测和保护

现在有一个hook函数的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function encrypt(a) {
//xxxx
return a+1;
}

//1.保留原有函数
_encrypt=encrypt
//2.可以任意修改保留的函数
encrypt=function (a) {
console.log("参数:",a)
let res=a+1;
console.log("原有返回值:",res)
return res
}

console.log(encrypt(1))

最简单的检测函数是否被hook了,就是把函数给toString一下,进行比对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
_atob=atob;

function encrypt(a) {
//xxxx
return a+1;
}

atob=function (data) {
console.log("正在进行base6编码:",data)
return _atob(data)
}

console.log(Function.prototype.toString.call(encrypt))

// 重写原型链的toString方法,使用this.name接收函数名
Function.prototype.toString=function () {
// 如果函数名是目标hook的函数名,就返回未被修改的函数
if(this.name=='encrypt'){
return `function encrypt(a) {
//xxxx
return a+1;
}`
}
// 否则返回native code
return `function ${this.name}() { [native code] }`
}
// 使用原型链上的toString方法去调用atob,可以检测是否被篡改
console.log(Function.prototype.toString.call(atob))
// 原型链上的atob函数是native code
console.log(Function.prototype.toString.call(atob)==='function atob() { [native code] }')

console.log(Function.prototype.toString.call(encrypt)===`function encrypt(a) {
//xxxx
return a+1;
}`)

函数native化

native化函数可以把函数变成系统函数,可以过一些检测

思路就是给函数添加一个[native code]属性,当触发Function.prototype.toString.call的时候,通过this可以获取函数名,然后强制返回[native code]属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
!function () {
const _mytoString=Function.prototype.toString
const _symbol=Symbol()

function _toString() {
// 如果调用的是伪造过的函数(前面加了_symbol 属性),就返回伪造的字符串
// 返回的属性就是native code
if(typeof this==="function" && this[_symbol]){
return this[_symbol]
}
// 如果没有,就调用原生的toString
return _mytoString.call(this)
}

//一个函数就拥有了一个nativecode字符串属性
function setNative(func,key,value) {
Object.defineProperty(func,key,{
configurable:true,
enumerable:false,
writable:true,
value:value
})
}
//清除原生的 toString 方法,从而可以重写它
//在某些环境下,如果不delete,可能无法重写
delete Function.prototype.toString
// 给Function.prototype的toString方法改成自写的函数
setNative(Function.prototype,"toString",_toString)
// 防止toString方法本身被检测,Function.prototype.toString.toString()
setNative(Function.prototype.toString,_symbol,'function toString() { [native code] }')

// globalThis是全局对象,在node中是global,在浏览器中是window
// globalThis是为了兼容浏览器,同时把函数给从内部传入到外部,外部调用globalThis.setNative
globalThis.setNative=function (func,funcName){
// 给函数构造如下属性和属性值
// 函数名[_symbol] = 'function 函数名() { [native code] }'
setNative(func,_symbol,`function ${funcName || func.name}() { [native code] }`)
}
}()

function add(a,b) {
return a+b
}
globalThis.setNative(add,"add")
console.log(add.toString())
console.log(Function.prototype.toString.call(add))
console.log(Function.prototype.toString.toString())

函数重命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function add(a,b) {
return a+b
}

add=function cookie(a,b) {
return a+b
}

function reName(func,funcName) {
Object.defineProperty(func,"name",{
configurable:true,
enumerable:false,
writable:false,
value:funcName
})
}
// 把add函数重命名为get cookie
reName(add,"get cookie")

console.log(add.name)
console.log(Object.getOwnPropertyDescriptor(add,"name"))

函数Hook插件的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// func是函数
// funcInfo是函数的信息
hook=function (func,funcInfo,isDebug,onEnter,onLeval,isExec) {
if(typeof func!='function'){
return func
}
// 如果函数信息未定义的话,就自己赋值
if(funcInfo===undefined){
funcInfo={
objName:"globalThis",
funcName:func.name || ''
}
}
// 默认不断点
if(isDebug===undefined){
isDebug=false
}
if(!onEnter){
onEnter=function (obj){
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行前传入的参数:${JSON.stringify(obj.args)}}`)
}
}
if(!onLeval){
onLeval=function (obj) {
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行后生成的结果:${obj.result.toString()}}`)
}
}
// 默认函数执行
if(isExec===undefined){
isExec=true
}

hookFunc=function () {
if(isDebug){
debugger;
}
let obj={};
obj.args=[]
for(let i=0;i<arguments.length;i++){
obj.args[i]=arguments[i]
}
if(isExec){
//函数执行前
onEnter.call(this,obj)
//函数执行中
let result=func.apply(this,obj.args)
obj.result=result
//函数执行后
onLeval.call(this,obj)
return result
}
}

return hookFunc
}

function add(a,b) {
return a+b
}

// let funcInfo={
// objName:"xjb",
// funcName:"加法"
// }
// onEnter=function (obj) {
// console.log("进入函数我自定义:",obj.args)
// }
// add = hook(add,funcInfo,onEnter)
add=hook(add)

console.log(add(1,2))

集成保护代码

上面的代码可以hook函数,但是无法过检测,这个时候需要把前面的native化和重命名给加进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
sandBox={

}

!function () {
const _mytoString=Function.prototype.toString
const _symbol=Symbol()

function _toString() {
if(typeof this==="function" && this[_symbol]){
return this[_symbol]
}
return _mytoString.call(this)
}

function setNative(func,key,value) {
Object.defineProperty(func,key,{
configurable:true,
enumerable:false,
writable:true,
value:value
})
}

delete Function.prototype.toString
setNative(Function.prototype,"toString",_toString)
setNative(Function.prototype.toString,_symbol,'function toString() { [native code] }')

sandBox.setNative=function (func,funcName){
setNative(func,_symbol,`function ${funcName || func.name}() { [native code] }`)
}
}()

sandBox.reName=function (func,funcName) {
Object.defineProperty(func,"name",{
configurable:true,
enumerable:false,
writable:false,
value:funcName
})
}

hook=function (func,funcInfo,isDebug,onEnter,onLeval,isExec) {
if(typeof func!='function'){
return func
}
if(funcInfo===undefined){
funcInfo={
objName:"globalThis",
funcName:func.name || ''
}
}
if(isDebug===undefined){
isDebug=false
}
if(!onEnter){
onEnter=function (obj){
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行前传入的参数:${JSON.stringify(obj.args)}}`)
}
}
if(!onLeval){
onLeval=function (obj) {
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行后生成的结果:${obj.result.toString()}}`)
}
}
if(isExec===undefined){
isExec=true
}

hookFunc=function hookFunc() {
if(isDebug){
debugger;
}
let obj={};
obj.args=[]
for(let i=0;i<arguments.length;i++){
obj.args[i]=arguments[i]
}
if(isExec){
//函数执行前
onEnter.call(this,obj)
//函数执行中
let result=func.apply(this,obj.args)
obj.result=result
//函数执行后
onLeval.call(this,obj)
return result
}
};
// 保护hookFunc函数代码不被检测
// native化
sandBox.setNative(hookFunc,funcInfo.funcName);
// 把hookFunc重命名为原先函数的名字
sandBox.reName(hookFunc,funcInfo.funcName);

return hookFunc
}

function add(a,b) {
return a+b
}

add=hook(add)

console.log(add(1,2))
console.log(add.toString())
console.log(add.name)
console.log(Function.prototype.toString.call(add))

Hook原型对象属性方法

有两种情况,第一种是属性有writeablevalue的情况,比如Document.prototype.clear(),这种属性是没有getset属性的

还有一种情况是属性有getset属性,但是没有writeablevalue属性的情况,比如Document.prototype.cookie

现在来解释一下,Document.prototype.clear()明明是个函数,怎么会有属性呢?

首先要理解这一句,在 JS 中,“方法”(函数) 只是“一个属性值为函数的属性”而已,clear() 是个函数,函数也是对象,在 JavaScript 中,函数本身就是一种特殊的对象,函数可以像普通对象一样添加属性。

属性类型 说明
数据属性(data descriptor) valuewritable
访问器属性(accessor descriptor) get / set 方法,没有 value / writable

比如获取Document.prototype.clear()属性的属性,Object.getOwnPropertyDescriptor(Document.prototype, "clear")

1
2
3
4
5
6
{
configurable: true,
enumerable: true,
writable: true,
value: function clear() { [native code] }
}

这说明 clear 是一个数据属性,值是一个函数(即方法),所以它会有 valuewritable 属性。

关于原型链就记住这么一句话,凡是实例能够调用的方法,都是原型链上的方法,类的静态方法无法被实例调用。类的“实例方法”都挂在 prototype 上。

关于Documentdocument的区别,Document是一个构造函数,documentDocument 构造函数创建出来的实例对象,浏览器自动为每个页面生成一个 document 实例,代表整个HTML页面。clear()方法是document实例的方法,不是Document的,所以Document.clear()不存在。

对于两种情况要分情况去hook

针对第一种,通过Object.defineProperty给对象的属性赋值,修改value的值为篡改之后的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
sandBox={

}

!function () {
const _mytoString=Function.prototype.toString
const _symbol=Symbol()

function _toString() {
if(typeof this==="function" && this[_symbol]){
return this[_symbol]
}
return _mytoString.call(this)
}

function setNative(func,key,value) {
Object.defineProperty(func,key,{
configurable:true,
enumerable:false,
writable:true,
value:value
})
}

delete Function.prototype.toString
setNative(Function.prototype,"toString",_toString)
setNative(Function.prototype.toString,_symbol,'function toString() { [native code] }')

sandBox.setNative=function (func,funcName){
setNative(func,_symbol,`function ${funcName || func.name}() { [native code] }`)
}
}()

sandBox.reName=function (func,funcName) {
Object.defineProperty(func,"name",{
configurable:true,
enumerable:false,
writable:false,
value:funcName
})
}

sandBox.hook=function (func,funcInfo,isDebug,onEnter,onLeval,isExec) {
if(typeof func!='function'){
return func
}
if(funcInfo===undefined){
funcInfo={
objName:"globalThis",
funcName:func.name || ''
}
}
if(isDebug===undefined){
isDebug=false
}
if(!onEnter){
onEnter=function (obj){
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行前传入的参数:${JSON.stringify(obj.args)}}`)
}
}
if(!onLeval){
onLeval=function (obj) {
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行后生成的结果:${obj.result?obj.result.toString():[]}}`)
}
}
if(isExec===undefined){
isExec=true
}

hookFunc=function hookFunc() {
if(isDebug){
debugger;
}
let obj={};
obj.args=[]
for(let i=0;i<arguments.length;i++){
obj.args[i]=arguments[i]
}
if(isExec){
//函数执行前
onEnter.call(this,obj)
//函数执行中
let result=func.apply(this,obj.args)
obj.result=result
//函数执行后
onLeval.call(this,obj)
return result
}
};
//保护代码
sandBox.setNative(hookFunc,funcInfo.funcName);
sandBox.reName(hookFunc,funcInfo.funcName);

return hookFunc
}

sandBox.hookObj=function (obj,objName,propName,isDebug) {
// obj是原型对象,objName是原型对象的名字,propName是对象的属性名
// 获取原有属性
let oldDescriptor=Object.getOwnPropertyDescriptor(obj,propName)
let newDescriptor={}
if(oldDescriptor.configurable==false){
return;
}
// 给新的属性赋值
newDescriptor.configurable=true
newDescriptor.enumerable=oldDescriptor.enumerable;
if(oldDescriptor.hasOwnProperty("writable")){
newDescriptor.writable=oldDescriptor.enumerable
}
if(oldDescriptor.hasOwnProperty("value")){
let funcInfo={
objName:objName,
funcName:propName
}
newDescriptor.value=sandBox.hook(oldDescriptor.value,funcInfo,isDebug)
}
Object.defineProperty(obj,propName,newDescriptor)
}

使用方法,sandBox.hookObj(Document.prototype,"Document.prototype","clear")

针对第二种情况,也是用Object.defineProperty去篡改一下getset属性,但是注意在重命名的时候需要重命名成原先函数的getset里的name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
sandBox={

}

!function () {
const _mytoString=Function.prototype.toString
const _symbol=Symbol()

function _toString() {
if(typeof this==="function" && this[_symbol]){
return this[_symbol]
}
return _mytoString.call(this)
}

function setNative(func,key,value) {
Object.defineProperty(func,key,{
configurable:true,
enumerable:false,
writable:true,
value:value
})
}

delete Function.prototype.toString
setNative(Function.prototype,"toString",_toString)
setNative(Function.prototype.toString,_symbol,'function toString() { [native code] }')

sandBox.setNative=function (func,funcName){
setNative(func,_symbol,`function ${funcName || func.name}() { [native code] }`)
}
}()

sandBox.reName=function (func,funcName) {
Object.defineProperty(func,"name",{
configurable:true,
enumerable:false,
writable:false,
value:funcName
})
}

sandBox.hook=function (func,funcInfo,isDebug,onEnter,onLeval,isExec) {
if(typeof func!='function'){
return func
}
if(funcInfo===undefined){
funcInfo={
objName:"globalThis",
funcName:func.name || ''
}
}
if(isDebug===undefined){
isDebug=false
}
if(!onEnter){
onEnter=function (obj){
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行前传入的参数:${JSON.stringify(obj.args)}}`)
}
}
if(!onLeval){
onLeval=function (obj) {
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行后生成的结果:${obj.result?obj.result.toString():[]}}`)
}
}
if(isExec===undefined){
isExec=true
}

hookFunc=function hookFunc() {
if(isDebug){
debugger;
}
let obj={};
obj.args=[]
for(let i=0;i<arguments.length;i++){
obj.args[i]=arguments[i]
}
if(isExec){
//函数执行前
onEnter.call(this,obj)
//函数执行中
let result=func.apply(this,obj.args)
obj.result=result
//函数执行后
onLeval.call(this,obj)
return result
}
};
//保护代码
sandBox.setNative(hookFunc,funcInfo.funcName);
sandBox.reName(hookFunc,funcInfo.funcName);

return hookFunc
}

sandBox.hookObj=function (obj,objName,propName,isDebug) {
let oldDescriptor=Object.getOwnPropertyDescriptor(obj,propName)
let newDescriptor={}
if(oldDescriptor.configurable==false){
return;
}
newDescriptor.configurable=true
newDescriptor.enumerable=oldDescriptor.enumerable;
if(oldDescriptor.hasOwnProperty("writable")){
newDescriptor.writable=oldDescriptor.enumerable
}
if(oldDescriptor.hasOwnProperty("value")){
let funcInfo={
objName:objName,
funcName:propName
}
newDescriptor.value=sandBox.hook(oldDescriptor.value,funcInfo,isDebug)
}
if(oldDescriptor.hasOwnProperty("get")){
let funcInfo={
objName:objName,
funcName:`get ${propName}`
}
newDescriptor.get=sandBox.hook(oldDescriptor.get,funcInfo,isDebug)
}
if(oldDescriptor.hasOwnProperty("set")){
let funcInfo={
objName:objName,
funcName:`set ${propName}`
}
newDescriptor.set=sandBox.hook(oldDescriptor.set,funcInfo,isDebug)
}
Object.defineProperty(obj,propName,newDescriptor)
}

使用的话也是sandBox.hookObj(Document.prototype,"Document.prototype","cookie")

Hook原型对象的所有属性方法

前面的方法,需要指定属性名,如果加上遍历Object.getOwnPropertyDescriptors(protoObj),就可以自动遍历所有属性方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
sandBox={

}

!function () {
const _mytoString=Function.prototype.toString
const _symbol=Symbol()

function _toString() {
if(typeof this==="function" && this[_symbol]){
return this[_symbol]
}
return _mytoString.call(this)
}

function setNative(func,key,value) {
Object.defineProperty(func,key,{
configurable:true,
enumerable:false,
writable:true,
value:value
})
}

delete Function.prototype.toString
setNative(Function.prototype,"toString",_toString)
setNative(Function.prototype.toString,_symbol,'function toString() { [native code] }')

sandBox.setNative=function (func,funcName){
setNative(func,_symbol,`function ${funcName || func.name}() { [native code] }`)
}
}()

sandBox.reName=function (func,funcName) {
Object.defineProperty(func,"name",{
configurable:true,
enumerable:false,
writable:false,
value:funcName
})
}

sandBox.hook=function (func,funcInfo,isDebug,onEnter,onLeval,isExec) {
if(typeof func!='function'){
return func
}
if(funcInfo===undefined){
funcInfo={
objName:"globalThis",
funcName:func.name || ''
}
}
if(isDebug===undefined){
isDebug=false
}
if(!onEnter){
onEnter=function (obj){
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行前传入的参数:${JSON.stringify(obj.args)}}`)
}
}
if(!onLeval){
onLeval=function (obj) {
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行后生成的结果:${obj.result?obj.result.toString():[]}}`)
}
}
if(isExec===undefined){
isExec=true
}

hookFunc=function hookFunc() {
if(isDebug){
debugger;
}
let obj={};
obj.args=[]
for(let i=0;i<arguments.length;i++){
obj.args[i]=arguments[i]
}
if(isExec){
//函数执行前
onEnter.call(this,obj)
//函数执行中
let result=func.apply(this,obj.args)
obj.result=result
//函数执行后
onLeval.call(this,obj)
return result
}
};
//保护代码
sandBox.setNative(hookFunc,funcInfo.funcName);
sandBox.reName(hookFunc,funcInfo.funcName);

return hookFunc
}

sandBox.hookObj=function (obj,objName,propName,isDebug) {
let oldDescriptor=Object.getOwnPropertyDescriptor(obj,propName)
let newDescriptor={}
if(oldDescriptor.configurable==false){
return;
}
newDescriptor.configurable=true
newDescriptor.enumerable=oldDescriptor.enumerable;
if(oldDescriptor.hasOwnProperty("writable")){
newDescriptor.writable=oldDescriptor.enumerable
}
if(oldDescriptor.hasOwnProperty("value")){
let funcInfo={
objName:objName,
funcName:propName
}
newDescriptor.value=sandBox.hook(oldDescriptor.value,funcInfo,isDebug)
}
if(oldDescriptor.hasOwnProperty("get")){
let funcInfo={
objName:objName,
funcName:`get ${propName}`
}
newDescriptor.get=sandBox.hook(oldDescriptor.get,funcInfo,isDebug)
}
if(oldDescriptor.hasOwnProperty("set")){
let funcInfo={
objName:objName,
funcName:`set ${propName}`
}
newDescriptor.set=sandBox.hook(oldDescriptor.set,funcInfo,isDebug)
}
Object.defineProperty(obj,propName,newDescriptor)
}

sandBox.hookProto=function (obj,isDebug) {
let protoObj=obj.prototype
for(const key in Object.getOwnPropertyDescriptors(protoObj)){
sandBox.hookObj(protoObj,obj.name,key,isDebug)
}
console.log(`{hookProto|${protoObj}}`)
}

Hook全局window的函数和原型

对于原生内置函数,比如atobalert等,这种函数是没有prototype的,对于Document这些函数,是有prototype的,这些函数都定义在window对象中

需要区分这两种函数的hook方法,对于内置函数,用最开始介绍的hook方法,对于其他的函数,需要用前面原型对象的属性方法的hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
sandBox={

}

!function () {
const _mytoString=Function.prototype.toString
const _symbol=Symbol()

function _toString() {
if(typeof this==="function" && this[_symbol]){
return this[_symbol]
}
return _mytoString.call(this)
}

function setNative(func,key,value) {
Object.defineProperty(func,key,{
configurable:true,
enumerable:false,
writable:true,
value:value
})
}

delete Function.prototype.toString
setNative(Function.prototype,"toString",_toString)
setNative(Function.prototype.toString,_symbol,'function toString() { [native code] }')

sandBox.setNative=function (func,funcName){
setNative(func,_symbol,`function ${funcName || func.name}() { [native code] }`)
}
}()

sandBox.reName=function (func,funcName) {
Object.defineProperty(func,"name",{
configurable:true,
enumerable:false,
writable:false,
value:funcName
})
}

sandBox.hook=function (func,funcInfo,isDebug,onEnter,onLeval,isExec) {
if(typeof func!='function'){
return func
}
if(funcInfo===undefined){
funcInfo={
objName:"globalThis",
funcName:func.name || ''
}
}
if(isDebug===undefined){
isDebug=false
}
if(!onEnter){
onEnter=function (obj){
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行前传入的参数:${JSON.stringify(obj.args)}}`)
}
}
if(!onLeval){
onLeval=function (obj) {
console.log(`{hook|${funcInfo.objName}[${funcInfo.funcName}]函数执行后生成的结果:${obj.result?obj.result.toString():[]}}`)
}
}
if(isExec===undefined){
isExec=true
}

hookFunc=function hookFunc() {
if(isDebug){
debugger;
}
let obj={};
obj.args=[]
for(let i=0;i<arguments.length;i++){
obj.args[i]=arguments[i]
}
// b'TlRNd05qVXhOall3'
if(isExec){
//函数执行前
onEnter.call(this,obj)
//函数执行中
let result=func.apply(this,obj.args)
obj.result=result
//函数执行后
onLeval.call(this,obj)
return result
}
};
//保护代码
sandBox.setNative(hookFunc,funcInfo.funcName);
sandBox.reName(hookFunc,funcInfo.funcName);

return hookFunc
}

sandBox.hookObj=function (obj,objName,propName,isDebug) {
let oldDescriptor=Object.getOwnPropertyDescriptor(obj,propName)
let newDescriptor={}
if(oldDescriptor.configurable==false){
return;
}
newDescriptor.configurable=true
newDescriptor.enumerable=oldDescriptor.enumerable;
if(oldDescriptor.hasOwnProperty("writable")){
newDescriptor.writable=oldDescriptor.enumerable
}
if(oldDescriptor.hasOwnProperty("value")){
let funcInfo={
objName:objName,
funcName:propName
}
newDescriptor.value=sandBox.hook(oldDescriptor.value,funcInfo,isDebug)
}
if(oldDescriptor.hasOwnProperty("get")){
let funcInfo={
objName:objName,
funcName:`get ${propName}`
}
newDescriptor.get=sandBox.hook(oldDescriptor.get,funcInfo,isDebug)
}
if(oldDescriptor.hasOwnProperty("set")){
let funcInfo={
objName:objName,
funcName:`set ${propName}`
}
newDescriptor.set=sandBox.hook(oldDescriptor.set,funcInfo,isDebug)
}
Object.defineProperty(obj,propName,newDescriptor)
}

sandBox.hookProto=function (obj,isDebug) {
let protoObj=obj.prototype
for(const key in Object.getOwnPropertyDescriptors(protoObj)){
sandBox.hookObj(protoObj,obj.name,key,isDebug)
}
console.log(`{hookProto|${protoObj}}`)
}

sandBox.hookGlobal=function (isDebug){
for(const key in Object.getOwnPropertyDescriptors(window)){
if(typeof window[key]=='function'){
if(typeof window[key].prototype=='object'){
sandBox.hookProto(window[key],isDebug)
}
if(typeof window[key].prototype=='undefined'){
let funcInfo={
objName:"globalThis",
funcName:key
}
sandBox.hook(window[key],funcInfo,isDebug)
}
}
}
console.log("{hook|globalThis}")
}

使用的话就sandBox.hookGlobal()就行

Proxy代理介绍

使用Proxy进行hook,和前面的其实区别不大,Proxy属于比较新的Hook方法,在一些老版本的浏览器上可能无法运行

Hook对象属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

let xjb={
name:"小肩膀",
age:18,
getName:function () {
console.log("获取name")
return xjb.name;
}
}

// Proxy的第一个参数是要Hook的对象
// 第二个参数是要hook的方法
xjb=new Proxy(xjb,{
// target是要代理的对象,p是属性值
get(target, p, receiver) {
console.log(`正在获取${p}`)
// 如果目标属性是函数
if(typeof target[p]==="function"){
// 代理函数
return new Proxy(target[p],{
apply(target, thisArg, argArray) {
console.log("正在执行函数:",p)
return Reflect.apply(target, thisArg, argArray)
}
})
}
// Reflect.get调用原get方法
return Reflect.get(target, p, receiver)
},
set(target, p, newValue, receiver) {
console.log(`正在设置 ${p} ${newValue}`)
// Reflect.set调用原set方法
Reflect.set(target, p, newValue,receiver)
}
})

console.log(xjb.getName())

插件化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
sandBox={}
sandBox.config={}
sandBox.config.proxy=true

sandBox.getType=function (obj) {
return Object.prototype.toString.call(obj)
}

sandBox.proxy=function (obj,objName) {
let handler={
get(target,p,receiver){
let result;
try{
result=Reflect.get(target,p,receiver)
let type=sandBox.getType(result)
// 为了防止属性的属性值还是对象,再做一次代理
if(result instanceof Object){
console.log(`{obj|get:[${objName}] -> prop:[${p.toString()}] -> type:[${type}]}`)
result=sandBox.proxy(result,`${objName}.${p.toString()}`)
}else{
console.log(`{obj|get:[${objName}] -> prop:[${p.toString()}] -> result:[${result}]}`)
}
}catch (e) {
console.log(`{obj|get:${objName} -> prop:${p.toString()} -> error:${e.message}}`)
}
return result
},
set(target, p, newValue, receiver){
let result;
try{
result=Reflect.set(target,p,receiver)
if(result instanceof Object){
console.log(`{obj|set:[${objName}] -> prop:[${p.toString()}] -> newValue_type:[${sandBox.getType(result)}]}`)
}else{
console.log(`{obj|set:[${objName}] -> prop:[${p.toString()}] -> newValue:[${newValue}]}`)
}
}catch (e) {
console.log(`{obj|set:${objName} -> prop:${p.toString()} -> error:${e.message}}`)
}
return result
},
// 拦截getOwnPropertyDescriptor方法来获取属性描述符
// 也就是前面用过的Object.getOwnPropertyDescriptor(objname,propname...)
// 当执行Object.getOwnPropertyDescriptor(objname,propname...)的时候,如果objname是代理之后的对象,就会被hook
getOwnPropertyDescriptor(target, p) {
let result;
try{
result=Reflect.getOwnPropertyDescriptor(target,p)
let type=sandBox.getType(result)
// 如果获取到对应的属性值
if(typeof result!=="undefined"){
// 再次代理,获取属性值
// 这么做的原因是比如clear方法是个对象,有4个属性值
// configurable: true
// enumerable: true
// value: ƒ clear()
// writable: true
// 再次代理clear对象,获取4个属性的值
result=sandBox.proxy(result,`${objName}.${p.toString()}.property`)
}
console.log(`{obj|getOwnPropertyDescriptor:[${objName}] -> prop:[${p.toString()}] -> result:[${type}]}`)
}catch (e) {
console.log(`{obj|getOwnPropertyDescriptor:${objName} -> prop:${p.toString()} -> error:${e.message}}`)
}
return result
},
defineProperty(target, p, attributes) {
let result;
try{
result=Reflect.defineProperty(target,p,attributes)
console.log(`{obj|defineProperty:[${objName}] -> prop:[${p.toString()}] -> result:[${JSON.stringify(attributes)}]}`)
}catch (e) {
console.log(`{obj|defineProperty:${objName} -> prop:${p.toString()} -> error:${e.message}}`)
}
return result
},
// 代理函数调用apply
apply(target, thisArg, argArray) {
let result;
try{
result=Reflect.apply(target, thisArg, argArray)
let type=sandBox.getType(result)
if(result instanceof Object){
console.log(`{func|apply:[${objName}] -> type:[${type}]}`)
}else if(typeof result ==="symbol"){
console.log(`{func|apply:[${objName}] -> result:[${result.toString()}]}`)
}else{
console.log(`{func|apply:[${objName}] -> result:[${result}]}`)
}
}catch (e) {
console.log(`{func|apply:${objName} -> error:${e.message}}`)
}
return result
},
// 代理构造函数
construct(target, argArray, newTarget) {
let result;
try{
result=Reflect.construct(target, argArray, newTarget)
let type=sandBox.getType(result)
console.log(`{obj|construct:[${objName}] -> type:[${type}]}`)
}catch (e) {
console.log(`{obj|construct:${objName} -> error:${e.message}}`)
}
return result
},
// 代理has方法,就是判断对象中有没有某个属性
has(target, p) {
let result;
try{
result=Reflect.has(target,p)
console.log(`{obj|has:[${objName}] -> prop:[${p.toString()}] -> has:[${result}]}`)
}catch (e) {
console.log(`{obj|has:${objName} -> error:${e.message}}`)
}
return result
},
deleteProperty(target, p) {
let result;
try{
result=Reflect.deleteProperty(target,p)
console.log(`{obj|deleteProperty:[${objName}] -> prop:[${p.toString()}] -> result:[${result}]}`)
}catch (e) {
console.log(`{obj|deleteProperty:${objName} -> error:${e.message}}`)
}
return result
},
getPrototypeOf(target) {
let result;
try{
result=Reflect.getPrototypeOf(target)
console.log(`{obj|getPrototypeOf:[${objName}]}`)
}catch (e) {
console.log(`{obj|getPrototypeOf:${objName} -> error:${e.message}}`)
}
return result
},
setPrototypeOf(target, v) {
let result;
try{
result=Reflect.setPrototypeOf(target,v)
console.log(`{obj|setPrototypeOf:[${objName}]}`)
}catch (e) {
console.log(`{obj|setPrototypeOf:${objName} -> error:${e.message}}`)
}
return result
},
preventExtensions(target){
let result = Reflect.preventExtensions(target);
try{
console.log(`{obj|preventExtensions:[${objName}]}`);
}catch (e) {
console.log(`{obj|preventExtensions:${objName} -> error:${e.message}}`)
}
return result;
},
isExtensible(target){
let result = Reflect.isExtensible(target);
try{
console.log(`{obj|isExtensible:[${objName}]}`);
}catch (e) {
console.log(`{obj|isExtensible:${objName} -> error:${e.message}}`)
}
return result;
},
ownKeys: function (target){
let result = Reflect.ownKeys(target);
try{
console.log(`{obj|ownKeys:[${objName}]}`);
}catch (e) {
console.log(`{obj|ownKeys:${objName} -> error:${e.message}}`)
}
return result
},
}
return new Proxy(obj,handler)
}
let xjb=function (){}
xjb=sandBox.proxy(xjb,"xjb")
console.log(xjb.__proto__)
let test={}
Object.setPrototypeOf(xjb,{})
Object.isExtensible(xjb)
Object.preventExtensions(xjb)

利用proxy代理补环境

简单来说就是代理window对象,看有没有调用里面的方法,然后代理常见的对象,看获取了里面什么属性,比如代理navigator对象,获取了userAgent属性,那就补userAgent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
window=globalThis;
window.xjb=null;

window.location={
host:'y.qq.com'
}
window.navigator={
userAgent:"Python3.0"
}
window.document={}

sandBox={}
sandBox.config={}
sandBox.config.proxy=true

sandBox.getType=function (obj) {
return Object.prototype.toString.call(obj)
}

sandBox.proxy=function (obj,objName) {
if(!sandBox.config.proxy){
return obj
}
let handler={
get(target,p,receiver){
let result;
try{
result=Reflect.get(target,p,receiver)
let type=sandBox.getType(result)
if(result instanceof Object){
console.log(`{obj|get:[${objName}] -> prop:[${p.toString()}] -> type:[${type}]}`)
result=sandBox.proxy(result,`${objName}.${p.toString()}`)
}else if(typeof result==="symbol"){
console.log(`{obj|get:[${objName}] -> prop:[${p.toString()}] -> result:[${result.toString()}]}`)
}else{
console.log(`{obj|get:[${objName}] -> prop:[${p.toString()}] -> result:[${result}]}`)
}
}catch (e) {
console.log(`{obj|get:${objName} -> prop:${p.toString()} -> error:${e.message}}`)
}
return result
},
set(target, p, newValue, receiver){
let result;
try{
result=Reflect.set(target, p, newValue, receiver)
if(newValue instanceof Object){
console.log(`{obj|set:[${objName}] -> prop:[${p.toString()}] -> newValue_type:[${sandBox.getType(result)}]}`)
}else if(typeof newValue==="symbol"){
console.log(`{obj|set:[${objName}] -> prop:[${p.toString()}] -> result:[${newValue.toString()}]}`)
}else{
console.log(`{obj|set:[${objName}] -> prop:[${p.toString()}] -> newValue:[${newValue}]}`)
}
}catch (e) {
console.log(`{obj|set:${objName} -> prop:${p.toString()} -> error:${e.message}}`)
}
return result
},
getOwnPropertyDescriptor(target, p) {
let result;
try{
result=Reflect.getOwnPropertyDescriptor(target,p)
let type=sandBox.getType(result)
// if(typeof result!=="undefined"){
// result=sandBox.proxy(result,`${objName}.${p.toString()}.property`)
// }
console.log(`{obj|getOwnPropertyDescriptor:[${objName}] -> prop:[${p.toString()}] -> result:[${type}]}`)
}catch (e) {
console.log(`{obj|getOwnPropertyDescriptor:${objName} -> prop:${p.toString()} -> error:${e.message}}`)
}
return result
},
defineProperty(target, p, attributes) {
let result;
try{
result=Reflect.defineProperty(target,p,attributes)
console.log(`{obj|defineProperty:[${objName}] -> prop:[${p.toString()}] -> result:[${JSON.stringify(attributes)}]}`)
}catch (e) {
console.log(`{obj|defineProperty:${objName} -> prop:${p.toString()} -> error:${e.message}}`)
}
return result
},
apply(target, thisArg, argArray) {
let result;
try{
result=Reflect.apply(target, thisArg, argArray)
let type=sandBox.getType(result)
if(result instanceof Object){
console.log(`{func|apply:[${objName}] -> type:[${type}]}`)
}else if(typeof result ==="symbol"){
console.log(`{func|apply:[${objName}] -> result:[${result.toString()}]}`)
}else{
console.log(`{func|apply:[${objName}] -> result:[${result}]}`)
}
}catch (e) {
console.log(`{func|apply:${objName} -> error:${e.message}}`)
}
return result
},
construct(target, argArray, newTarget) {
let result;
try{
result=Reflect.construct(target, argArray, newTarget)
let type=sandBox.getType(result)
console.log(`{obj|construct:[${objName}] -> type:[${type}]}`)
}catch (e) {
console.log(`{obj|construct:${objName} -> error:${e.message}}`)
}
return result
},
has(target, p) {
let result;
try{
result=Reflect.has(target,p)
console.log(`{obj|has:[${objName}] -> prop:[${p.toString()}] -> has:[${result}]}`)
}catch (e) {
console.log(`{obj|has:${objName} -> error:${e.message}}`)
}
return result
},
deleteProperty(target, p) {
let result;
try{
result=Reflect.deleteProperty(target,p)
console.log(`{obj|deleteProperty:[${objName}] -> prop:[${p.toString()}] -> result:[${result}]}`)
}catch (e) {
console.log(`{obj|deleteProperty:${objName} -> error:${e.message}}`)
}
return result
},
getPrototypeOf(target) {
let result;
try{
result=Reflect.getPrototypeOf(target)
console.log(`{obj|getPrototypeOf:[${objName}]}`)
}catch (e) {
console.log(`{obj|getPrototypeOf:${objName} -> error:${e.message}}`)
}
return result
},
setPrototypeOf(target, v) {
let result;
try{
result=Reflect.setPrototypeOf(target,v)
console.log(`{obj|setPrototypeOf:[${objName}]}`)
}catch (e) {
console.log(`{obj|setPrototypeOf:${objName} -> error:${e.message}}`)
}
return result
},
preventExtensions(target){
let result = Reflect.preventExtensions(target);
try{
console.log(`{obj|preventExtensions:[${objName}]}`);
}catch (e) {
console.log(`{obj|preventExtensions:${objName} -> error:${e.message}}`)
}
return result;
},
isExtensible(target){
let result = Reflect.isExtensible(target);
try{
console.log(`{obj|isExtensible:[${objName}]}`);
}catch (e) {
console.log(`{obj|isExtensible:${objName} -> error:${e.message}}`)
}
return result;
},
ownKeys: function (target){
let result = Reflect.ownKeys(target);
try{
console.log(`{obj|ownKeys:[${objName}]}`);
}catch (e) {
console.log(`{obj|ownKeys:${objName} -> error:${e.message}}`)
}
return result
},
}
return new Proxy(obj,handler)
}


window=sandBox.proxy(window,"window")
location=sandBox.proxy(location,"location")
document=sandBox.proxy(document,"document")
navigator=sandBox.proxy(navigator,"navigator")

//webpack代码
!function(e) {
function t(t) {
//...
}
}
var i, o = window.xjb(350).default;
console.log(o('{"comm":{"cv":4747474,"ct":24,"format":"json","inCharset":"utf-8","outCharset":"utf-8","notice":0,"platform":"yqq.json","needNewCode":1,"uin":"1152921504968805528","g_tk_new_20200303":377412991,"g_tk":377412991},"req_1":{"method":"DoSearchForQQMusicDesktop","module":"music.search.SearchCgiService","param":{"remoteplace":"txt.yqq.center","searchid":"67988778323096897","search_type":0,"query":"奢香1","page_num":1,"num_per_page":10}}}'))
console.log('zzb1f669e9dac6rzcxrocuptb9bpibs3q923ed882')