背景介绍

书接前面的前置知识,有了那些基础,我们要在nodejs里构建浏览器沙箱环境,如果不做模块化处理,代码长什么样?

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
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) {
//...
}

// 代理对象
window=sandBox.proxy(window,"window")
location=sandBox.proxy(location,"location")
document=sandBox.proxy(document,"document")
navigator=sandBox.proxy(navigator,"navigator")

//webpack代码
...
//调用加密函数
var i, o = window.xjb(350).default;
console.log(o('...'))
console.log('zzb1f669e9dac6rzcxrocuptb9bpibs3q923ed882')

下面要做的就是把这段代码模块化,因为在实际的补环境中,需要补的浏览器对象很多,对象下的属性也有很多,分模块处理之后,对于后续的通用性会更好。

沙箱代码框架结构设计

为了让代码更具有通用性,基本的思路如下,把hook的工具代码单独放到一个文件中,网页一些固定的属性值比如user-agenthost等字段放到一个文件中

总的思路是通过文件读取函数,把多个模块中的代码给读出来,然后拼接,通过node的vm2库去跑拼接起来的代码,(vm2库提供了一个隔离的环境,可以在里面执行任意 JS 代码,同时保证主环境的安全性(防止代码篡改全局、访问文件系统、网络等))

初步项目框架

初步的整个项目框架设计如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Z:.
├─main.js

├─config
│ envs.config.js
│ pages.config.js
│ tools.config.js

├─envs
│ Window.js

├─pages
│ └─xjb
│ asyncCode.js
│ input.js
│ output.js
│ userVar.js

└─tools
globalVar.js
proxyObj.js
toolsFunc.js

envs目录下存放要模拟的浏览器对象,比如Window.jsEventTarget.jsWindowProperties.js等,要模拟哪个就在这里添加

config目录下的代码的功能就是把其他目录下的代码给读出来,比如env.config.js就是把env目录下的代码给读取出来,然后导出一个函数给main.js调用,main.js通过这些导出的函数获取到env目录、pages目录、tools目录下的所有代码,然后拼接起来执行

tools目录下的代码主要是sandbox封装的各种工具方法、代理对象的代码等、网页共性的属性值,也是通过env.tools.js读取出来提供给main.js

pages目录下的代码存放目标网站的js,每个网站一个子目录,子目录名称随意,里面的input.js是网站的js代码,userVar.js是网页特有的属性,不是共性的属性,asyncCode.js是网站异步的js代码,output.js是最后执行完main.js之后生成的最终可执行的代码,也就是拼接之后代码,通过output.js可以直接运行出想要的结果

main.js

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
const {VM,VMScript}=require('vm2')
const fs=require("fs")

// pages.config其实是pages.config.js,里面存放通用的函数,比如读文件等方法,其他同理
const userConfig=require("./config/pages.config")
const toolsConfig=require("./config/tools.config")
const envsConfig=require("./config/envs.config")

// 给当前要做的网页代码命名,这个名字就是pages目录下的文件夹的名字
const name="xjb"
const vm=new VM()

// 工具方法,sandBox封装的方法
const toolsCode=toolsConfig.getFile("toolsFunc")
// 浏览器的DOM BOM
const envsCode=envsConfig.getCode()
// 网页基本不变的一些属性
const globalCode=toolsConfig.getFile("globalVar")
// 网页用户自定义属性
const userCode=userConfig.getFile(name,"userVar")
// 代理的对象
const proxyCode=toolsConfig.getFile("proxyObj")
// 要调试的网页JS
const inputCode=userConfig.getFile(name,"input")
// 异步代码
const asyncCode=userConfig.getFile(name,"asyncCode")

// 把代码拼接起来执行
const jsCode=`${toolsCode}${envsCode}${globalCode}${userCode}${proxyCode}${inputCode}${asyncCode}`

const script=new VMScript(jsCode)
// 加上debug.js之后,可以在这一步打上断点,然后步入debug.js中进行调试
// const script=new VMScript(jsCode,"debug.js")
let result=vm.run(script)
console.log(`result->${result}`)
fs.writeFileSync(`./pages/${name}/output.js`,jsCode)
console.log('done.')

config目录下代码

envs.config.js

读取envs目录下的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const fs=require("fs")

function getFile(filename) {
let result;
try {
result=fs.readFileSync(`./envs/${filename}.js`)
}catch (e) {
console.log(`config|tools -> filename:${filename} error:${e.message}`)
}
return result
}

function getCode() {
let code='';
code+=getFile('Window')
return code
}

module.exports={
getCode:getCode
}
pages.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs=require("fs")

function getFile(name,filename) {
let result;
try {
result=fs.readFileSync(`./pages/${name}/${filename}.js`)
}catch (e) {
console.log(`config|user -> name:${name} filename:${filename} error:${e.message}`)
}
return result
}

module.exports={
getFile:getFile
}
tools.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs=require("fs")

function getFile(filename) {
let result;
try {
result=fs.readFileSync(`./tools/${filename}.js`)
}catch (e) {
console.log(`config|tools -> filename:${filename} error:${e.message}`)
}
return result
}

module.exports={
getFile:getFile
}

其他目录下代码

env目录下window.js,这个目录下可能不止一个window.js,因为浏览器的DOM BOM相关代码有很多

1
// 这个填充的是浏览器的DOM BOM代码,随着网页而不同

pages目录下的userVar.js

1
2
3
4
//用户自定义
window.location={
host:'y.qq.com'
}

tools目录下globalVar.js

1
2
3
4
5
//网页基本不变属性
window.navigator={
userAgent:"Python3.0"
}
window.document={}

tools目录下proxyObj.js

1
2
3
4
5
//代理对象
window=sandBox.proxy(window,"window")
location=sandBox.proxy(location,"location")
document=sandBox.proxy(document,"document")
navigator=sandBox.proxy(navigator,"navigator")

tools目录下toolsFunc.js

1
//自己的工具,hook代码

当然上面都只是初步的框架,后面还会不断完善修改

模拟window原型链

初步简单模拟

要把window的原型链给补成和浏览器中一样,同时还要保护模拟出来的对象,并且Windowwindow都需要模拟,通过setPrototypeOf设置原型链

window.js

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
//浏览器基本环境  DOM BOM  原型链

Window=function Window() {

}
//命名,给Window.prototype添加属性,不然无法设置window的prototype为Window
//使用Object.getOwnPropertyDescriptors(Window.prototype)可以看到原型上有这个属性
Object.defineProperty(Window.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:"Window"
})
//保护Window对象
//因为最后代码都是拼到一起执行的,所以这里不需要导入
sandBox.setNative(Window,"Window")
sandBox.reName(Window,"Window")
//设置原型,把Window.prototype.prototype设置为WindowProperties.prototype
//WindowProperties哪儿来的,浏览器打印一下Window.prototype就行了
Object.setPrototypeOf(Window.prototype,WindowProperties.prototype)

window=new Window()
//设置原型,把window.__proto__设置为Window.prototype
Object.setPrototypeOf(window,Window.prototype)

console.log(window)

WindowProperties.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//WindowProperties
WindowProperties=function WindowProperties() {

}
//命名
Object.defineProperty(WindowProperties.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:"WindowProperties"
})
//保护
sandBox.setNative(WindowProperties,"WindowProperties")
sandBox.reName(WindowProperties,"WindowProperties")
//原型链
Object.setPrototypeOf(WindowProperties.prototype,EventTarget.prototype)

EventTarget.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//EventTarget
EventTarget=function EventTarget() {

}
//命名
Object.defineProperty(EventTarget.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:"EventTarget"
})
//保护
sandBox.setNative(EventTarget,"EventTarget")
sandBox.reName(EventTarget,"EventTarget")

然后注意及时修改envs.config.js里读取代码的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const fs=require("fs")

function getFile(filename) {
let result;
try {
result=fs.readFileSync(`./envs/${filename}.js`)
}catch (e) {
console.log(`config|tools -> filename:${filename} error:${e.message}`)
}
return result
}

function getCode() {
let code='';
code+=getFile('EventTarget')
code+=getFile('WindowProperties')
code+=getFile('Window')
return code
}

module.exports={
getCode:getCode
}

优化1:模拟报错函数

由于Window构造函数在浏览器中是不能new的,所以在window.js中的window也不能new Window()

1
2
3
new Window()
VM896:1 Uncaught TypeError: Failed to construct 'Window': Illegal constructor
at <anonymous>:1:1

现在就是要把这个报错信息给模拟出来,在toolsFunc.js中加上报错函数

1
2
3
4
5
6
7
8
9
//报错函数
sandBox.throwError=function (name,message) {
let myError=new Error()
myError.name=name
myError.message=message
// 因为浏览器运行也会报堆栈错误,所以要打印出来
myError.stack= `${name}: ${message}\n at <anonymous>:1:4`
throw myError
}

然后在window.js中加上new的时候的报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//浏览器基本环境  DOM BOM  原型链

Window=function Window() {
// new的时候会直接报错
sandBox.throwError('TypeError','Illegal constructor')
}
//命名
Object.defineProperty(Window.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:"Window"
})
//保护
sandBox.setNative(Window,"Window")
sandBox.reName(Window,"Window")
//设置原型
Object.setPrototypeOf(Window.prototype,WindowProperties.prototype)

window={}

Object.setPrototypeOf(window,Window.prototype)

console.log(window)

模拟报错信息也是绕过检测的一种

另外,Window原型链上的WindowProperties不是构造函数,不能new,不用补报错信息,EventTarget可以new,不用补报错信息

优化2:模拟base64编码解码函数

比如window下的atobbtoa函数,这两个函数在node中其实也有,是在global下面的,但是浏览器中没有global,只有WindowglobalThis,而且node中的atob.toString()的结果和浏览器中atob.toString()的结果不一样,所以需要模拟浏览器下的base64编码解码函数,就是要自定义atobbtoa函数,把自写的atobbtoa给定义到globalThis上面(window = globalThis)

toolsFunc.js中实现自写的atobbtoa的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//实现base64编码解码
sandBox.base64={}
sandBox.base64.btoa=function btoa(str) {
function base64Encode(str) {
//...
}
return base64Encode(str)
}
sandBox.base64.atob=function atob(str) {
function base64Decode(str) {
//...
}
return base64Decode(str)
}

再修改window.js

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
//浏览器基本环境  DOM BOM  原型链

Window=function Window() {
sandBox.throwError('TypeError','Illegal constructor')
}
//命名
Object.defineProperty(Window.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:"Window"
})
//保护
sandBox.setNative(Window,"Window")
sandBox.reName(Window,"Window")
//设置原型
Object.setPrototypeOf(Window.prototype,WindowProperties.prototype)
// 删掉global,因为浏览器里没有global
delete global

window=globalThis
Object.defineProperty(window,"atob",{
value:function atob(str) {
return sandBox.base64.atob(str)
}
})
sandBox.setNative(window.atob,"atob")
Object.defineProperty(window,"btoa",{
value:function btoa(str) {
return sandBox.base64.btoa(str)
}
})
sandBox.setNative(window.btoa,"btoa")
console.log(window)

优化3:模拟WindowProperties环境

前面说过,在浏览器中WindowProperties是无法new出来的,会直接报错未定义;而且浏览器中的WindowProperties没有constructor属性,所以现在需要修改代码

window.js

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
//浏览器基本环境  DOM BOM  原型链

Window=function Window() {
sandBox.throwError('TypeError','Illegal constructor')
}
//命名
Object.defineProperty(Window.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:"Window"
})
//保护
sandBox.setNative(Window,"Window")
sandBox.reName(Window,"Window")
//设置原型
Object.setPrototypeOf(Window.prototype,WindowProperties.prototype)

//删掉WindowProperties,注意要在设置完原型链之后删除
delete WindowProperties
delete global

window=globalThis
Object.setPrototypeOf(window,Window.prototype)
Object.defineProperty(window,"atob",{
value:function atob(str) {
return sandBox.base64.atob(str)
}
})
sandBox.setNative(window.atob,"atob")
Object.defineProperty(window,"btoa",{
value:function btoa(str) {
return sandBox.base64.btoa(str)
}
})
sandBox.setNative(window.btoa,"btoa")
console.log(window)

WindowProperties.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//WindowProperties
WindowProperties=function WindowProperties() {

}
//删除构造函数
delete WindowProperties.prototype.constructor

//命名
Object.defineProperty(WindowProperties.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:"WindowProperties"
})
//保护
sandBox.setNative(WindowProperties,"WindowProperties")
sandBox.reName(WindowProperties,"WindowProperties")
//原型
Object.setPrototypeOf(WindowProperties.prototype,EventTarget.prototype)

优化4:模拟Window原型对象属性

Window对象还有presenttemporary两个属性,Window.prototype同理

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
//浏览器基本环境  DOM BOM  原型链
Window=function Window() {
sandBox.throwError('TypeError','Illegal constructor')
}
//命名
Object.defineProperty(Window.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:"Window"
})
//保护
sandBox.setNative(Window,"Window")
sandBox.reName(Window,"Window")
//设置原型
Object.setPrototypeOf(Window.prototype,WindowProperties.prototype)

//配置Window属性
Object.defineProperty(Window,"PERSISTENT",{
configurable:false,
enumerable:true,
value:1,
writable:false
})
Object.defineProperty(Window,"TEMPORARY",{
configurable:false,
enumerable:true,
value:0,
writable:false
})
//配置Window.prototype
Object.defineProperty(Window.prototype,"PERSISTENT",{
configurable:false,
enumerable:true,
value:1,
writable:false
})
Object.defineProperty(Window.prototype,"TEMPORARY",{
configurable:false,
enumerable:true,
value:0,
writable:false
})

globalThis.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//window的环境
delete global
delete Buffer
delete WindowProperties

window=globalThis
Object.setPrototypeOf(window,Window.prototype)
Object.defineProperty(window,"atob",{
value:function atob(str) {
return sandBox.base64.atob(str)
}
})
sandBox.setNative(window.atob,"atob")
Object.defineProperty(window,"btoa",{
value:function btoa(str) {
return sandBox.base64.btoa(str)
}
})
sandBox.setNative(window.btoa,"btoa")
console.log(window)

优化5:保护get和set方法

我们知道,window对象下的有些属性,是访问器属性,而前面做的保护都是针对数据属性的

1
2
3
4
5
6
7
//命名
Object.defineProperty(Window.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:"Window"
})

这里只针对window.name属性,浏览器运行Object.getOwnPropertyDescriptor(window,"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
//window的环境
delete global
delete Buffer
delete WindowProperties

window=globalThis

Object.setPrototypeOf(window,Window.prototype)

Object.defineProperty(window,"atob",{
value:function atob(str) {
return sandBox.base64.atob(str)
}
})
sandBox.safeFunc(window.atob,"atob")

Object.defineProperty(window,"btoa",{
value:function btoa(str) {
return sandBox.base64.btoa(str)
}
})
sandBox.safeFunc(window.btoa,"btoa")

Object.defineProperty(window,'name',{
configurable:true,
enumerable:true,
get:function () {
},
set:function () {
}
})
sandBox.safeFunc(Object.getOwnPropertyDescriptor(window,"name").get,"get name")
sandBox.safeFunc(Object.getOwnPropertyDescriptor(window,"name").set,"set name")
console.log(window)

toolsFunc.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//原型重命名
sandBox.reNameProto=function (obj,objName) {
Object.defineProperty(obj.prototype,Symbol.toStringTag,{
configurable:true,
enumerable:false,
writable:false,
value:objName
})
}
//保护原型
sandBox.safeProto=function (obj,objName) {
sandBox.reNameProto(obj,objName)
sandBox.setNative(obj,objName)
}
//保护函数
sandBox.safeFunc=function (func,funcName) {
sandBox.reName(func,funcName)
sandBox.setNative(func,funcName)
}

优化6:重写defineproperty方法

目前针对代码的保护,都是defineproperty之后再safeFunc

1
2
3
4
5
6
7
8
9
10
Object.defineProperty(window,'name',{
configurable:true,
enumerable:true,
get:function () {
},
set:function () {
}
})
sandBox.safeFunc(Object.getOwnPropertyDescriptor(window,"name").get,"get name")
sandBox.safeFunc(Object.getOwnPropertyDescriptor(window,"name").set,"set 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
//自定义defineproperty
sandBox.defineProperty=function (obj,prop,oldDescriptor) {
let newDescriptor={}
newDescriptor.configurable=sandBox.config.proxy|| oldDescriptor.configurable
newDescriptor.enumerable=oldDescriptor.oldDescriptor
if(oldDescriptor.hasOwnProperty("writable")){
newDescriptor.writable=sandBox.config.proxy|| oldDescriptor.writable
}
if(oldDescriptor.hasOwnProperty("value")){
let value=oldDescriptor.value
if(typeof value==='function'){
sandBox.safeFunc(value,prop)
}
newDescriptor.value=value
}
if(oldDescriptor.hasOwnProperty("get")){
let get=oldDescriptor.get
if(typeof get==='function'){
sandBox.safeFunc(get,`get ${prop}`)
}
newDescriptor.get=get
}
if(oldDescriptor.hasOwnProperty("set")){
let set=oldDescriptor.set
if(typeof set==='function'){
sandBox.safeFunc(set,`set ${prop}`)
}
newDescriptor.set=set
}

Object.defineProperty(obj,prop,newDescriptor)
}

优化7:env环境分发

就是把函数的定义给单独放到一个目录下,以实现EventTarget里的addListener属性为例

EventTarget.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//EventTarget
EventTarget=function EventTarget() {

}
//命名
sandBox.safeProto(EventTarget,"EventTarget")

//方法实现
sandBox.defineProperty(EventTarget.prototype,'addEventListener',{
configurable:true,
enumerable:true,
writable:true,
value:function addEventListener() {
return sandBox.dispatch("EventTarget_addEventListener",this,arguments)
}
})

toolsFunc.js

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
//自己的工具
sandBox={
envFuncs:{}
}
sandBox.config={}
sandBox.config.proxy=true

!function () {
!function () {
//...
//函数转发
sandBox.dispatch=function (name,self,argList,defaultValue) {
try{
return sandBox.envFuncs[name].apply(self,argList)
}catch (e) {
// 如果执行我们自写的代码报错,就尝试返回默认值
if(defaultValue){
return defaultValue
}else{
console.log(`{tools|dispatch -> 未定义Env函数:${name} ->Error:${e.message}`)
}
}
}
}
}

envFuncs.js

1
2
3
4
sandBox.envFuncs.EventTarget_addEventListener=function () {
console.log("调用函数")
return "xjb"
}

脱浏览器环境脚本编写

因为浏览器中window等对象有很多属性,而针对每个属性的补环境逻辑实际上是差不多的,所以需要写一个脚本来把浏览器对象里的环境都给脱下来,然后自动生成一个如下框架的代码

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
//浏览器基本环境  DOM BOM  原型链
Window=function Window() {
sandBox.throwError('TypeError','Illegal constructor')
}
//命名 保护
sandBox.safeProto(Window,"Window")

//设置原型
Object.setPrototypeOf(Window.prototype,WindowProperties.prototype)

//配置Window属性
sandBox.defineProperty(Window,"PERSISTENT",{
configurable:false,
enumerable:true,
value:1,
writable:false
})
sandBox.defineProperty(Window,"TEMPORARY",{
configurable:false,
enumerable:true,
value:0,
writable:false
})
//配置Window.prototype
sandBox.defineProperty(Window.prototype,"PERSISTENT",{
configurable:false,
enumerable:true,
value:1,
writable:false
})
sandBox.defineProperty(Window.prototype,"TEMPORARY",{
configurable:false,
enumerable:true,
value:0,
writable:false
})

代码如下

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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// 构造函数对象
// 比如Window,Document这种
getEnvCode=function (proto,instanceObj) {
let code="";
let protoName=proto.name;
code+=`//${protoName}环境\r\n`
code+=`${protoName}=function ${protoName}(){\r\n`
// 尝试new对象,如果失败,需要模拟报错
try{
new proto;
}catch(e){
code+=`\tsandBox.throwError('${e.name}','${e.message}')\r\n`
}
code+=`}\r\n`;
code+=`sandBox.safeProto(${protoName},"${protoName}")\r\n`
// 设置原型链
let protoObject=proto.prototype;
let getProtoName=Object.getPrototypeOf(proto.prototype)[Symbol.toStringTag]
if(getProtoName){
code+=`Object.setPrototypeOf(${protoName}.prototype,${getProtoName}.prototype)\r\n`
}
for(const key in Object.getOwnPropertyDescriptors(proto)){
// 以下四个属性不需要模拟,自动生成
if(key==="arguments"||key==="caller"||key==="length"||key==="name"||key==="prototype"){
continue;
}
// 获取原有属性
let descriptor=Object.getOwnPropertyDescriptor(proto,key)
code+=`sandBox.defineProperty(${protoName},"${key}",{configurable:${descriptor.configurable},enumerable:${descriptor.enumerable},`
if(descriptor.hasOwnProperty("writable")){
code+=`writable:${descriptor.writable},`
}
// 如果是数据属性
if(descriptor.hasOwnProperty("value")){
// 如果数据属性的value值是函数,进行函数分发,通过sandBox.dispatch模拟函数
if(descriptor.value instanceof Object){
if(typeof descriptor.value==='function'){
code+=`value:function ${descriptor.value.name}() {
return sandBox.dispatch("${protoName}_${descriptor.value.name}",this,arguments)
}`
}else{
code+=`value:{}//需要特殊处理`
}
// 如果不是Object,就直接赋值给value
}else if(typeof descriptor.value ==="string"){
code+=`value:"${descriptor.value}"}`
}else if(typeof descriptor.value === 'symbol'){
code+=`value:${descriptor.value.toString()}`
}else{
code+=`value:${descriptor.value}`
}
}
// 如果是访问器属性
if(descriptor.hasOwnProperty("get")){
// 如果get属性的属性值是函数,就调用函数,获取返回值
if(typeof descriptor.get ==="function"){
let defaultValue;
try{
defaultValue=descriptor.get.call(instanceObj)
}catch(e){}
// 如果返回值是对象,不作处理
if(defaultValue instanceof Object || typeof defaultValue ===undefined){
code+=`get:function () {
return sandBox.dispatch("${protoName}_${key}_get",this,arguments)
},`
}else if(typeof defaultValue ==="string"){
code+=`get:function () {
return sandBox.dispatch("${protoName}_${key}_get",this,arguments,${defaultValue})
},`
}else if(typeof defaultValue === 'symbol'){
code+=`get:function () {
return sandBox.dispatch("${protoName}_${key}_get",this,arguments,${defaultValue.toString()})
},`
}else{
code+=`get:function () {
return sandBox.dispatch("${protoName}_${key}_get",this,arguments,${defaultValue})
},`
}

}else{
code+=`get:undefined,`
}
}
// set不需要调用,因为set函数的返回值没有意义
if(descriptor.hasOwnProperty("set")){
if(typeof descriptor.set==="function"){
code+=`set:function () {
return sandBox.dispatch("${protoName}_${key}_set",this,arguments)
}`
}else{
code+=`set:undefined`
}
}

code+=`})\r\n`
}
//浏览器原型对象
for(const key in Object.getOwnPropertyDescriptors(proto.prototype)){
if(key==="constructor"){
continue;
}
let descriptor=Object.getOwnPropertyDescriptor(proto.prototype,key)
code+=`sandBox.defineProperty(${protoName}.prototype,"${key}",{configurable:${descriptor.configurable},enumerable:${descriptor.enumerable},`
if(descriptor.hasOwnProperty("writable")){
code+=`writable:${descriptor.writable},`
}
if(descriptor.hasOwnProperty("value")){
if(descriptor.value instanceof Object){
if(typeof descriptor.value==='function'){
code+=`value:function ${descriptor.value.name}() {
return sandBox.dispatch("${protoName}_${descriptor.value.name}",this,arguments)
}`
}else{
code+=`value:{}//需要特殊处理`
}
}else if(typeof descriptor.value ==="string"){
code+=`value:"${descriptor.value}"}`
}else if(typeof descriptor.value === 'symbol'){
code+=`value:${descriptor.value.toString()}`
}else{
code+=`value:${descriptor.value}`
}
}
if(descriptor.hasOwnProperty("get")){
if(typeof descriptor.get ==="function"){
let defaultValue;
try{
defaultValue=descriptor.get.call(instanceObj)
}catch(e){}
if(defaultValue instanceof Object || typeof defaultValue ===undefined){
code+=`get:function () {
return sandBox.dispatch("${protoName}_${key}_get",this,arguments)
},`
}else if(typeof defaultValue ==="string"){
code+=`get:function () {
return sandBox.dispatch("${protoName}_${key}_get",this,arguments,${defaultValue})
},`
}else if(typeof defaultValue === 'symbol'){
code+=`get:function () {
return sandBox.dispatch("${protoName}_${key}_get",this,arguments,${defaultValue.toString()})
},`
}else{
code+=`get:function () {
return sandBox.dispatch("${protoName}_${key}_get",this,arguments,${defaultValue})
},`
}

}else{
code+=`get:undefined,`
}
}
if(descriptor.hasOwnProperty("set")){
if(typeof descriptor.set==="function"){
code+=`set:function () {
return sandBox.dispatch("${protoName}_${key}_set",this,arguments)
}`
}else{
code+=`set:undefined`
}
}

code+=`})\r\n`
}
console.log(code);
return code;
}

//脱实例对象
//比如window,document
getObjEnvCode=function(obj,objName,instanceObj){
let code="";
code+=`//${objName}环境\r\n`
code+=`${objName}={}\r\n`
let getProtoName=Object.getPrototypeOf(obj)[Symbol.toStringTag]
if(getProtoName){
code+=`Object.setPrototypeOf(${objName},${getProtoName}.prototype)\r\n`
}
for(const key in Object.getOwnPropertyDescriptors(obj)){
let descriptor=Object.getOwnPropertyDescriptor(obj,key)
code+=`sandBox.defineProperty(${objName},"${key}",{configurable:${descriptor.configurable},enumerable:${descriptor.enumerable},`
if(descriptor.hasOwnProperty("writable")){
code+=`writable:${descriptor.writable},`
}
if(descriptor.hasOwnProperty("value")){
if(descriptor.value instanceof Object){
if(typeof descriptor.value==='function'){
code+=`value:function ${descriptor.value.name}() {
return sandBox.dispatch("${objName}_${descriptor.value.name}",this,arguments)
}`
}else{
console.log('//需要特殊处理')
code+=`value:{}`
}
}else if(typeof descriptor.value ==="string"){
code+=`value:"${descriptor.value}"}`
}else if(typeof descriptor.value === 'symbol'){
code+=`value:${descriptor.value.toString()}`
}else{
try{
code+=`value:${descriptor.value}`
}catch(e){
code+=`value:${JSON.stringify(descriptor.value)}`
}

}
}
if(descriptor.hasOwnProperty("get")){
if(typeof descriptor.get ==="function"){
let defaultValue;
try{
defaultValue=descriptor.get.call(instanceObj)
}catch(e){}
if(defaultValue instanceof Object || typeof defaultValue ===undefined){
code+=`get:function () {
return sandBox.dispatch("${objName}_${key}_get",this,arguments)
},`
}else if(typeof defaultValue ==="string"){
code+=`get:function () {
return sandBox.dispatch("${objName}_${key}_get",this,arguments,'${defaultValue}')
},`
}else if(typeof defaultValue === 'symbol'){
code+=`get:function () {
return sandBox.dispatch("${objName}_${key}_get",this,arguments,${defaultValue.toString()})
},`
}else{
code+=`get:function () {
return sandBox.dispatch("${objName}_${key}_get",this,arguments,${defaultValue})
},`
}

}else{
code+=`get:undefined,`
}
}
if(descriptor.hasOwnProperty("set")){
if(typeof descriptor.set==="function"){
code+=`set:function () {
return sandBox.dispatch("${objName}_${key}_set",this,arguments)
}`
}else{
code+=`set:undefined`
}
}

code+=`})\r\n`
}
console.log(code);
return code;
}

补环境流程

主要是澄清流程,大概怎么模拟,怎么不断的补环境

拿到js代码,先在浏览器中运行,这个时候是可以直接拿到结果的

然后把js代码丢到input.js里,运行,大概率会报错,因为上面模拟的环境有可能不全

debug代码,查看报错信息,在debug的控制台中会输出缺少哪个实例的哪个方法,这里假如是localStorage,那么就用上面的脱环境脚本去脱localStorage的环境,因为是实例,所以用getObjEnvCode(localStorage,'localStorage')

在脱环境脚本运行的结果中,可以看到Object.setPrototypeOf(localStorage,Storage.prototype),在envs目录下新建Storage.js,把脱环境的结果先放进去,此时localStorage的环境已经有了,还需要补原型链上的Storage.prototype,对于Storage.prototype,因为是构造函数对象,就用getEnvCode(Storage,localStorage)(第二个参数是当前的实例,因为在函数中会用实例调用get方法),再把Storage的环境代码放到localStorage的上面

然后在envs.config.js中的getCode()方法里读取Storage.js的代码

此时再参照debug时候的报错信息,比如报错了localStoragegetItem方法,此时只是定义了这个方法,但是没有实现,需要在envFuncs.js中实现这个函数,getItem的作用就是从localStorage中获取参数的属性值,直接返回即可

1
2
3
sandBox.envFuncs.Storage_getItem=function (key) {
return this[key]||''
}

上面是原型链比较短的,如果说报错的是document这种原型链大于2的,先getObjEnvCode(document,'document',document),这里传几个参数我建议都试一下,有时候最后一个实例不用传,运行结果中找到Object.setPrototypeOf(document,HTMLDocument.prototype),然后在envs下新建HTMLDocument.js,把document脱下来的代码放到里面,然后getEnvCode(HTMLDocument,document),脱出来的代码放到HTMLDocument.js的上面,在脱出来的代码中发现有Object.setPrototypeOf(HTMLDocument.prototype,Document.prototype)。然后再新建Document.js,运行getEnvCode(Document,document),把脱出来的结果放到Document.js中,脱出来的结果里有Object.setPrototypeOf(Document.prototype,Node.prototype)。然后再新建Node.js,运行getEnvCode(Node,document),把脱出来的结果放到Node.js中,以此类推。

如果有的情况下,运行不报错,但是运行的结果和浏览器对不上,此时要看output.js的输出,输出结果中有没有调用一些没模拟的环境,有的环境即使没有模拟,也不会报错,比如navigator

注意getCode()里添加读取代码的时候,建议都加在最后的读取globalThis的上面,还有可以在proxyObj中把常用的对象都代理起来

实例-补某js代码的location检测

首先拿到这个js代码,在pages下新建locationTest目录,把这个js代码放到里面命名为input.js

1
2
3
4
5
// 需要调试的代码
debugger;


!(function(_0x499145,_0x129c44){function _0x5e8408(_0x5af2bb,_0xe9c4c6,_0x24195e,_0x355044,_0x31c24d){return _0x5595(_0x355044-0x1cb,_0x24195e);}var _0x200725=_0x499145();function _0x57c075(_0x4511d9,_0x19aa02,_0x4d2b92,_0x4901cb,_0x8f76b2){return _0x5595(_0x4d2b92- -0xbb,_0x4901cb);}function _0x1f0bdd(_0x5b7e5c,_0x499281,_0x14e3fa,_0x4fa202,_0xb4c6d5){return _0x5595(_0x5b7e5c- -0x79,_0x14e3fa);}function _0x4f2dd1(_0x9ae254,_0x538260,_0x4c0e29,_0x187984,_0x58eed0){return _0x5595(_0x4c0e29-0x1d6,_0x9ae254);}function _0x28add5(_0xdbbc62,_0x1b7b4a,_0x433417,_0x54b72c,_0x448d7b){return _0x5595(_0x54b72c-0xe7,_0x1b7b4a);}while(!![]){try{var _0x44e87e=parseInt(_0x57c075(-0x26,-0x4c,0x27,'osc1',0x7c))/(-0x145e+0x219c+0x1*-0xd3d)*(parseInt(_0x57c075(0x6,-0x93,-0x3c,'jd5b',-0x53))/(-0x56*0x56+0x1e9d+-0x1*0x1b7))+-parseInt(_0x57c075(-0x66,0x72,0x4,'F@Wa',0x6c))/(0x626+-0x19f4*-0x1+-0x2017)*(-parseInt(_0x5e8408(0x35d,0x31f,'l2G8',0x2f5,0x2eb))/(0x1f22+0x5c5+0x13*-0x1f1))+parseInt(_0x4f2dd1('iDNV',0x31e,0x333,0x395,0x2f0))/(-0xe99+0x1d*0xe5+0xd*-0xdf)+parseInt(_0x28add5(0x1f9,'I5Hz',0x241,0x1fb,0x257))/(0x356+0x2*-0x51c+-0x374*-0x2)*(parseInt(_0x28add5(0x11f,'5nL3',0x1ab,0x16e,0x110))/(0x3*0xad+-0x5*0xe5+0x3*0xd3))+-parseInt(_0x5e8408(0x2f9,0x2a2,'XDs6',0x2d7,0x2af))/(-0x3*-0xcc1+0x1a14+-0x404f*0x1)+parseInt(_0x57c075(-0x4d,-0x85,-0x30,'osc1',0xb))/(0x23fc+0x1a9d+0x38*-0x11e)+-parseInt(_0x5e8408(0x358,0x2d3,'Upf%',0x312,0x34d))/(0x17c+-0x105*-0x25+0x25*-0x10f);if(_0x44e87e===_0x129c44)break;else _0x200725['push'](_0x200725['shift']());}catch(_0xc51be7){_0x200725['push'](_0x200725['shift']());}}}(_0x33af,-0x28*-0x3470+-0xb8d29+0xcb1*0xba),!(function(){function _0x292cd0(_0x188a93,_0xf57741,_0x3af290,_0x52f291,_0x29d11c){return _0x5595(_0xf57741-0x2fe,_0x188a93);}var _0x5aad28={'Tpfhr':function(_0x5b887f,_0x25dec4){return _0x5b887f(_0x25dec4);},'ydXrx':_0x292cd0('eEzp',0x40d,0x44c,0x3e3,0x3f8)+_0x2d051c(-0x1d7,-0x2ae,'Xdza',-0x241,-0x269),'gGsvH':function(_0x237f7a,_0x7a9cdc){return _0x237f7a===_0x7a9cdc;},'kxlUq':_0x180661(-0x1b,-0xb8,'#U#y',-0xb5,-0x61)+_0x180661(-0x19,-0xba,'eEzp',-0xc6,-0x6b)+'9f','qkKeS':_0x180661(0x1c,-0x22,'eEzp',0x41,-0x28)+_0x2d051c(-0x20a,-0x1c9,'W1Q1',-0x234,-0x268)+'4=','RPdqN':_0x2d051c(-0x14e,-0x1b2,'5nL3',-0x1ae,-0x145)+_0x180661(-0x8f,0x3d,'*S9z',-0x85,-0x1d)+'Bl','CDhGb':function(_0x239567,_0x32b0c3){return _0x239567==_0x32b0c3;},'xtwKH':_0x2d051c(-0x1dd,-0x19d,'[QWB',-0x209,-0x223)+_0x2d051c(-0x26d,-0x20e,'MUAu',-0x20d,-0x270),'gxlcY':_0x180661(-0xa7,-0x67,'oyYO',-0xb0,-0x40),'RWBUO':_0x565472(-0xb9,-0xa0,-0x71,-0xe4,'I5Hz'),'fTWDM':function(_0x51048f,_0x3702bc){return _0x51048f!==_0x3702bc;},'cyzZu':_0x565472(-0x10c,-0x130,-0x113,-0xc9,'6LF^'),'gmcXe':_0x180661(-0x2d,-0xb,'F@Wa',0x69,0x38),'TFOdI':_0x2d051c(-0x225,-0x16d,'Oq]1',-0x1ca,-0x225),'oIXzh':_0x565472(-0xb3,-0x91,-0x6f,-0xad,']u2q'),'BeDKP':function(_0x2c7ccf,_0x48dc79){return _0x2c7ccf===_0x48dc79;},'uDpSl':_0x26d6bb('W1Q1',-0xcf,-0xc5,-0x90,-0x11f),'JzLnD':_0x292cd0('jd5b',0x38b,0x319,0x3fa,0x39c),'NKawG':_0x565472(-0x134,-0x152,-0x152,-0xec,'Z#7R'),'TylVo':_0x2d051c(-0x240,-0x208,'eLE&',-0x254,-0x20a)+_0x292cd0('#U#y',0x377,0x322,0x38d,0x35d)+'+$','ALtos':function(_0x555a6a,_0x197631){return _0x555a6a==_0x197631;},'kmyWS':function(_0x32157c,_0x3d16cb){return _0x32157c(_0x3d16cb);},'lYywY':function(_0x2590fd,_0x5f3271,_0x1bf81e){return _0x2590fd(_0x5f3271,_0x1bf81e);},'KiMMJ':function(_0x16f107){return _0x16f107();},'JhePu':function(_0x30732c,_0x356613){return _0x30732c===_0x356613;},'cbhXA':_0x2d051c(-0x24f,-0x22b,'9[yf',-0x211,-0x1f4),'Qgymr':function(_0x5db7de,_0x322a3a){return _0x5db7de(_0x322a3a);},'zctVv':_0x180661(-0x69,0x2a,'nBgy',-0x56,-0x49)+_0x292cd0('TJeu',0x42a,0x460,0x464,0x423)+'Y=','WDnHx':function(_0x5e65a6,_0x160e7a){return _0x5e65a6===_0x160e7a;},'vaazE':_0x26d6bb('PP(U',-0x146,-0x1b3,-0xd2,-0x153)+_0x26d6bb('MUAu',-0xeb,-0xdd,-0xcf,-0x102)+_0x292cd0(')#TO',0x38d,0x3cc,0x3df,0x362)+_0x565472(-0xc6,-0x101,-0x101,-0xf7,'9[yf')+_0x292cd0('YpYO',0x455,0x46d,0x44e,0x449)+_0x292cd0('*S9z',0x439,0x45a,0x499,0x463)+_0x292cd0('Oq]1',0x3a7,0x3eb,0x41a,0x415)+_0x565472(-0xfb,-0xf6,-0xc9,-0x11a,'XDs6')+_0x2d051c(-0x1ce,-0x218,'p0Ua',-0x239,-0x1d8)+_0x292cd0('VFA0',0x3df,0x3c8,0x36e,0x413)+'==','BvNco':function(_0x4f47f4,_0x4f989a){return _0x4f47f4!==_0x4f989a;},'oBmAg':_0x180661(-0x45,0x4,'PP(U',-0x19,0xc),'jOUjG':_0x292cd0('VFA0',0x42d,0x486,0x452,0x470),'mfWPb':function(_0x454084,_0x56c20a){return _0x454084===_0x56c20a;},'jfqKy':function(_0x2cea1e,_0x15ecd4){return _0x2cea1e(_0x15ecd4);},'UGrMr':function(_0xd002ae,_0x1dbc4f){return _0xd002ae===_0x1dbc4f;},'xvxCC':_0x180661(-0x4a,0x37,'7QQq',-0x95,-0x29),'GkNto':_0x26d6bb('TJeu',-0x144,-0x176,-0x188,-0x10b),'MgXbU':function(_0x8011e2,_0x16b22f){return _0x8011e2===_0x16b22f;},'uIwot':_0x26d6bb('5nL3',-0x13c,-0xdc,-0xe8,-0xc7),'oNLQy':function(_0x58da57,_0x5d009a){return _0x58da57(_0x5d009a);},'EoKCJ':_0x292cd0('eVp8',0x406,0x3b6,0x3d6,0x439),'mDdyk':_0x26d6bb('#U#y',-0xde,-0xf6,-0x126,-0xc1),'AaxWv':function(_0xba5415,_0x17a94f){return _0xba5415(_0x17a94f);}};function _0x565472(_0x17aee8,_0x1701bf,_0x18c5d7,_0x25399d,_0x2a01dc){return _0x5595(_0x17aee8- -0x1ca,_0x2a01dc);}var _0x3dc722=(function(){function _0x4c1069(_0x3e44df,_0x3a4a60,_0x16509d,_0x38aa2b,_0x4fef42){return _0x180661(_0x3e44df-0x194,_0x3a4a60-0xbb,_0x3a4a60,_0x38aa2b-0x19e,_0x4fef42- -0x1d7);}function _0x5a0e23(_0x3d6699,_0x17537,_0x4c3b5f,_0x42341e,_0x4fb0e4){return _0x292cd0(_0x17537,_0x42341e- -0x1fe,_0x4c3b5f-0xc7,_0x42341e-0x150,_0x4fb0e4-0xc8);}var _0x4a0166={'MREfx':function(_0xadf9be,_0x5b5a41){function _0x2feaae(_0x441a60,_0x45fc61,_0x422df7,_0x40bf2e,_0x3cb097){return _0x5595(_0x441a60-0x61,_0x40bf2e);}return _0x5aad28[_0x2feaae(0xe1,0xfb,0x7b,'^y!y',0x133)](_0xadf9be,_0x5b5a41);},'lhkKP':_0x5aad28[_0x5b7dfe(-0xdf,-0x13c,-0x186,'l2G8',-0x17f)]};function _0x4a5cb2(_0x275d14,_0x59702f,_0xc377d6,_0x1a7533,_0x2d124f){return _0x292cd0(_0x275d14,_0x2d124f- -0x1ab,_0xc377d6-0x7b,_0x1a7533-0xe7,_0x2d124f-0x33);}function _0x5b7dfe(_0x5165c9,_0x4d2120,_0xf64981,_0x558c41,_0x3a080e){return _0x292cd0(_0x558c41,_0x4d2120- -0x538,_0xf64981-0xb6,_0x558c41-0x1da,_0x3a080e-0x182);}function _0x3aa916(_0x1a8e7f,_0x5c163b,_0x4fef61,_0xcc96e4,_0x133d25){return _0x292cd0(_0x1a8e7f,_0xcc96e4- -0x352,_0x4fef61-0xeb,_0xcc96e4-0xa9,_0x133d25-0x140);}if(_0x5aad28[_0x5b7dfe(-0x169,-0x190,-0x145,'I5Hz',-0x183)](_0x5aad28[_0x5b7dfe(-0xad,-0xed,-0x13d,'B63H',-0x9e)],_0x5aad28[_0x5b7dfe(-0x1bc,-0x1a5,-0x1ec,'W1Q1',-0x1e6)])){var _0x1192af=!![];return function(_0x488e32,_0x8f7fd4){function _0x5b019(_0x47d138,_0x1b6f57,_0x18aae7,_0x420dd1,_0x25fb06){return _0x5b7dfe(_0x47d138-0x9c,_0x1b6f57-0x3ee,_0x18aae7-0x1df,_0x47d138,_0x25fb06-0xff);}function _0x48e138(_0x3cdab8,_0x49fa9b,_0x5e29c0,_0x54a58d,_0xc06241){return _0x5b7dfe(_0x3cdab8-0x6d,_0x54a58d-0x538,_0x5e29c0-0x14f,_0x5e29c0,_0xc06241-0x83);}function _0x38be8f(_0x55df00,_0x38b755,_0x503637,_0x444407,_0xb2ca20){return _0x5b7dfe(_0x55df00-0x31,_0x38b755-0x38f,_0x503637-0xca,_0x55df00,_0xb2ca20-0x1e4);}function _0x32dceb(_0x52ef08,_0x3610a3,_0x170dd6,_0x4d9972,_0x5de02f){return _0x5a0e23(_0x52ef08-0x6d,_0x3610a3,_0x170dd6-0x104,_0x5de02f-0x2bd,_0x5de02f-0xb9);}var _0x202e51={'vKzla':function(_0x24244f,_0x538c40){function _0xb9e33f(_0x30b6a4,_0xff95e9,_0x5981a1,_0x1b0704,_0xc907bb){return _0x5595(_0xff95e9- -0x2a4,_0x1b0704);}return _0x5aad28[_0xb9e33f(-0x24e,-0x1dc,-0x185,'VnMB',-0x1f2)](_0x24244f,_0x538c40);},'GwIjF':_0x5aad28[_0x48e138(0x3e2,0x385,'[QWB',0x3d4,0x41b)],'KUVxd':function(_0x53699c,_0xefd52a){function _0x9fc22c(_0x2bcde6,_0x19b1db,_0x43c7c1,_0x11003e,_0x542dcb){return _0x48e138(_0x2bcde6-0x99,_0x19b1db-0x8,_0x542dcb,_0x43c7c1- -0x16f,_0x542dcb-0x22);}return _0x5aad28[_0x9fc22c(0x33c,0x2f3,0x2d8,0x28b,'XDs6')](_0x53699c,_0xefd52a);},'LhcFQ':_0x5aad28[_0x32dceb(0x3ee,'W1Q1',0x3ff,0x476,0x437)],'NfjzN':_0x5aad28[_0x30eaa5(0x424,'eLE&',0x3d4,0x3c2,0x3e4)],'IZwsz':_0x5aad28[_0x48e138(0x3fd,0x420,'bh[g',0x3ff,0x401)],'rMjhe':function(_0x1cc109,_0x1d82a3){function _0xd415dd(_0x24bb1b,_0xf7bc7,_0x203c53,_0x17329f,_0x51435d){return _0x5b019(_0xf7bc7,_0x24bb1b- -0x4a6,_0x203c53-0x4f,_0x17329f-0xad,_0x51435d-0x18b);}return _0x5aad28[_0xd415dd(-0x1b6,'*!b!',-0x15f,-0x1c5,-0x1ab)](_0x1cc109,_0x1d82a3);},'TMkgo':_0x5aad28[_0x32dceb(0x416,'F@Wa',0x486,0x44c,0x48a)],'yWeri':_0x5aad28[_0x38be8f('l2G8',0x2ab,0x2de,0x28b,0x282)],'LPPEy':function(_0x3f47f1,_0x349392){function _0x252bee(_0x1de2bf,_0x29bf6f,_0x4a754b,_0x1a4a6b,_0x2893e7){return _0x30eaa5(_0x1de2bf-0x134,_0x29bf6f,_0x4a754b-0x3b,_0x4a754b- -0x4a5,_0x2893e7-0xa);}return _0x5aad28[_0x252bee(-0xc4,'z]x8',-0x88,-0xed,-0xe9)](_0x3f47f1,_0x349392);},'TSYuW':_0x5aad28[_0x5b019('^yPc',0x26e,0x216,0x262,0x2a1)],'kJESK':function(_0x5f262,_0x504a2b){function _0x5b04dd(_0x5e8213,_0x47d030,_0x6c6d1c,_0x500ed6,_0x3be1c1){return _0x5b019(_0x5e8213,_0x6c6d1c-0x60,_0x6c6d1c-0xdd,_0x500ed6-0xcc,_0x3be1c1-0xc9);}return _0x5aad28[_0x5b04dd(')#TO',0x2d2,0x2d1,0x29f,0x31f)](_0x5f262,_0x504a2b);},'uBmKK':_0x5aad28[_0x5b019('U%M3',0x2d8,0x273,0x2b6,0x33a)],'fTpBm':_0x5aad28[_0x32dceb(0x49e,'*!b!',0x4a0,0x492,0x470)]};function _0x30eaa5(_0x42ae42,_0x179acc,_0x13afeb,_0x1e1e39,_0x394c33){return _0x4c1069(_0x42ae42-0x105,_0x179acc,_0x13afeb-0x8b,_0x1e1e39-0x1ea,_0x1e1e39-0x5c8);}if(_0x5aad28[_0x32dceb(0x4db,'PP(U',0x4bf,0x470,0x46f)](_0x5aad28[_0x32dceb(0x4d6,'W1Q1',0x487,0x459,0x4a5)],_0x5aad28[_0x48e138(0x449,0x3ce,'VScl',0x417,0x477)]))_0x4b232b[_0x48e138(0x440,0x435,'eEzp',0x44c,0x3eb)](_0x202e51[_0x48e138(0x3d9,0x436,'VnMB',0x44e,0x4c2)](_0x4d7963,_0x202e51[_0x38be8f('[QWB',0x243,0x299,0x29d,0x261)]));else{var _0x375e78=_0x1192af?function(){function _0x23be77(_0x4c790a,_0x405169,_0x22ab2e,_0x4940fd,_0x3ed2df){return _0x32dceb(_0x4c790a-0x63,_0x3ed2df,_0x22ab2e-0x97,_0x4940fd-0xd3,_0x22ab2e- -0x799);}function _0x3a1edd(_0x33b1e5,_0x57fa01,_0x18ea04,_0x57b514,_0x657371){return _0x48e138(_0x33b1e5-0xae,_0x57fa01-0x51,_0x57fa01,_0x57b514- -0x29d,_0x657371-0x111);}function _0x1da695(_0x5aa133,_0x30e84d,_0x459218,_0x4f401f,_0x5e0741){return _0x32dceb(_0x5aa133-0x130,_0x5e0741,_0x459218-0x13b,_0x4f401f-0x1b9,_0x5aa133- -0x2f9);}var _0xe9ab81={'wCloO':function(_0x505753,_0x22be90){function _0x63c308(_0x111119,_0x15339c,_0x3e74ad,_0x4fa591,_0x4cca08){return _0x5595(_0x15339c-0x175,_0x4cca08);}return _0x202e51[_0x63c308(0x246,0x28f,0x24e,0x238,'I5Hz')](_0x505753,_0x22be90);},'zKurV':function(_0x575a65,_0x38d69a){function _0x2f74a6(_0x196f57,_0x48ddd7,_0xffe112,_0x168e91,_0x4a8ea2){return _0x5595(_0x48ddd7- -0x1b9,_0x4a8ea2);}return _0x202e51[_0x2f74a6(-0x96,-0xca,-0xdb,-0xb7,'XGgH')](_0x575a65,_0x38d69a);},'AbTTt':_0x202e51[_0x5d8bb5(0x359,0x355,0x33b,0x336,'W(BC')],'ZZZbN':_0x202e51[_0x23be77(-0x2a2,-0x231,-0x298,-0x2e3,'VnMB')],'YETan':_0x202e51[_0x23be77(-0x24e,-0x22e,-0x29d,-0x246,'VFA0')],'xyQBI':function(_0x22ab11,_0x38b0c1){function _0x236d2c(_0x57e7fc,_0x4c2d32,_0x42c244,_0x10a77a,_0x36186e){return _0x3a1edd(_0x57e7fc-0x19d,_0x36186e,_0x42c244-0xce,_0x4c2d32- -0x352,_0x36186e-0x16b);}return _0x202e51[_0x236d2c(-0x1ac,-0x1bb,-0x193,-0x177,'Oq]1')](_0x22ab11,_0x38b0c1);},'ieRgr':_0x202e51[_0x3a1edd(0x16f,'eLE&',0x170,0x117,0x16a)],'fjQoW':_0x202e51[_0x23be77(-0x298,-0x2dc,-0x297,-0x222,'*!b!')],'gDbEY':function(_0x19245b,_0x20218d){function _0x5da2e2(_0x3c639e,_0x2c5d36,_0x4ab72c,_0x4a20ef,_0xba0b91){return _0x23be77(_0x3c639e-0x153,_0x2c5d36-0xbd,_0x4ab72c-0x417,_0x4a20ef-0xdc,_0xba0b91);}return _0x202e51[_0x5da2e2(0x13c,0xfd,0x116,0x135,'oyYO')](_0x19245b,_0x20218d);},'hSutQ':_0x202e51[_0x1da695(0x213,0x211,0x231,0x1bb,')#TO')]};function _0x56019e(_0xba292c,_0x5be1e3,_0x545311,_0x3f4e5f,_0x1937ae){return _0x38be8f(_0x1937ae,_0x5be1e3-0x5f,_0x545311-0x155,_0x3f4e5f-0x11d,_0x1937ae-0x68);}function _0x5d8bb5(_0x4a2ee1,_0x22b2e2,_0x50ba8a,_0x55b5df,_0x47c297){return _0x5b019(_0x47c297,_0x50ba8a-0x4a,_0x50ba8a-0xf8,_0x55b5df-0x15f,_0x47c297-0x1e2);}if(_0x202e51[_0x23be77(-0x36a,-0x37a,-0x309,-0x2af,'B63H')](_0x202e51[_0x23be77(-0x2d5,-0x2fb,-0x32d,-0x300,'nBgy')],_0x202e51[_0x5d8bb5(0x2a8,0x2c3,0x308,0x2aa,'7QQq')])){if(_0xe9ab81[_0x1da695(0x216,0x1f7,0x255,0x25a,'Z#7R')](_0x3b747e[_0xe9ab81[_0x1da695(0x1e3,0x1b3,0x1e5,0x1fc,'5nL3')](_0x355fd8,_0xe9ab81[_0x56019e(0x2e5,0x28d,0x2fa,0x246,'9pFz')])],_0x278765[_0xe9ab81[_0x1da695(0x150,0x175,0x165,0x1b9,'NvQJ')](_0x3120a7,_0xe9ab81[_0x3a1edd(0x10e,'MUAu',0x117,0x102,0x15c)])][_0xe9ab81[_0x5d8bb5(0x39a,0x394,0x33f,0x2e0,'U%M3')](_0x2ff126,_0xe9ab81[_0x3a1edd(0x14e,'p0Ua',0x178,0x125,0x149)])])){if(_0xe9ab81[_0x23be77(-0x300,-0x2bc,-0x2c1,-0x2a1,'eVp8')](_0x46042a[_0x1da695(0x21f,0x21a,0x22b,0x231,'nBgy')+_0x56019e(0x224,0x25a,0x207,0x226,'v53p')+_0x1da695(0x164,0x138,0xf0,0x175,'Z#7R')+_0x56019e(0x235,0x25c,0x21a,0x27d,'6LF^')+_0x23be77(-0x2a2,-0x237,-0x2a9,-0x2de,'*S9z')](_0x2b1eea,_0xe9ab81[_0x3a1edd(0x89,'Upf%',0x139,0xd7,0xcc)])[_0x23be77(-0x3a5,-0x308,-0x331,-0x366,'PP(U')+_0x3a1edd(0x1be,'VFA0',0x103,0x16f,0x186)+'le'],'')){_0x1dfe96[_0x56019e(0x2c9,0x2ac,0x2cd,0x2d9,'nBgy')](_0xe9ab81[_0x3a1edd(0x19b,')#TO',0x1c2,0x1a3,0x1e5)](_0x4cb7f7,_0xe9ab81[_0x1da695(0x1da,0x191,0x1d7,0x17c,'6LF^')]));return;}else{_0x10d54d[_0x3a1edd(0x9d,'W1Q1',0x97,0x104,0xfa)](_0xe9ab81[_0x56019e(0x228,0x247,0x20f,0x202,'*!b!')](_0x4b70a2,_0xe9ab81[_0x3a1edd(0x1d8,'XDs6',0x14f,0x173,0x12b)]));return;}}}else{if(_0x8f7fd4){if(_0x202e51[_0x3a1edd(0x1e4,'^yPc',0x145,0x1a1,0x191)](_0x202e51[_0x23be77(-0x2c1,-0x31d,-0x2b9,-0x276,'1XFK')],_0x202e51[_0x23be77(-0x2c9,-0x306,-0x2b9,-0x2e9,'1XFK')])){var _0x56fc66=_0x8f7fd4[_0x56019e(0x28c,0x2c9,0x277,0x2ae,'VFA0')](_0x488e32,arguments);return _0x8f7fd4=null,_0x56fc66;}else{var _0x2af2c2=_0x146c11[_0x1da695(0x20a,0x1b1,0x1ad,0x206,'7QQq')](_0x15d0ef,arguments);return _0x21b485=null,_0x2af2c2;}}}}:function(){};return _0x1192af=![],_0x375e78;}};}else{_0x582a5e[_0x5a0e23(0x265,'Z#7R',0x27f,0x210,0x1a7)](_0x4a0166[_0x5b7dfe(-0x1c6,-0x19f,-0x12f,'B63H',-0x1fe)](_0x414233,_0x4a0166[_0x4a5cb2('p0Ua',0x19d,0x21d,0x23b,0x1c6)]));return;}}());function _0x26d6bb(_0x4de21a,_0x8cd399,_0xbae320,_0xdefa05,_0x576e74){return _0x5595(_0x8cd399- -0x1d8,_0x4de21a);}function _0x2d051c(_0x54939c,_0x2073ae,_0x38285e,_0x2f75b6,_0x16b7d1){return _0x5595(_0x2f75b6- -0x302,_0x38285e);}var _0x225310=_0x5aad28[_0x292cd0('PP(U',0x387,0x37b,0x3db,0x37d)](_0x3dc722,this,function(){function _0x41b19a(_0x42c61a,_0xd411ec,_0x1ec76b,_0xd406de,_0x2991b3){return _0x565472(_0x1ec76b-0x509,_0xd411ec-0xa8,_0x1ec76b-0x1f4,_0xd406de-0x14f,_0x2991b3);}function _0x5b5005(_0x530ab1,_0x12a5de,_0x2339e0,_0x24f626,_0x1caeb0){return _0x292cd0(_0x530ab1,_0x24f626- -0x3cc,_0x2339e0-0x154,_0x24f626-0x126,_0x1caeb0-0x71);}function _0x45161e(_0xcf377b,_0x52fd2a,_0x9ce90b,_0x1d2ef6,_0x23d695){return _0x565472(_0x23d695- -0x4a,_0x52fd2a-0xc8,_0x9ce90b-0xaf,_0x1d2ef6-0x19d,_0xcf377b);}function _0x86f188(_0x3f4c69,_0x5869dd,_0x210617,_0x33e337,_0x356938){return _0x292cd0(_0x3f4c69,_0x5869dd- -0x33f,_0x210617-0x3,_0x33e337-0x78,_0x356938-0x82);}function _0x1f7ee1(_0x5b2317,_0x29af8a,_0x20ab7a,_0x5d0f46,_0x5d6a87){return _0x180661(_0x5b2317-0xf4,_0x29af8a-0x1ee,_0x5d6a87,_0x5d0f46-0x1e8,_0x29af8a- -0x103);}if(_0x5aad28[_0x41b19a(0x40c,0x421,0x405,0x406,'eLE&')](_0x5aad28[_0x86f188('PP(U',0x3c,0x23,0xb2,0x14)],_0x5aad28[_0x86f188('Z#7R',0x44,0x33,0xb9,0xa5)])){var _0x1a1a64=_0x2294e9?function(){function _0x40d279(_0x286091,_0xd4bd93,_0x2948a6,_0x8b70b9,_0x418a9b){return _0x1f7ee1(_0x286091-0x14b,_0xd4bd93-0x3a3,_0x2948a6-0x2a,_0x8b70b9-0xa8,_0x2948a6);}if(_0x414409){var _0x497bf3=_0x4eed22[_0x40d279(0x1c2,0x222,'l2G8',0x28c,0x229)](_0x3ce6a9,arguments);return _0x40cbbc=null,_0x497bf3;}}:function(){};return _0x3659fd=![],_0x1a1a64;}else return _0x225310[_0x5b5005('Upf%',0xf,-0x23,-0x59,-0xf)+_0x41b19a(0x416,0x45e,0x416,0x42b,'^yPc')]()[_0x45161e(')#TO',-0x1cf,-0x1e5,-0x1b1,-0x17a)+'h'](_0x5aad28[_0x1f7ee1(-0x1b0,-0x18a,-0x1ea,-0x1d4,']u2q')])[_0x5b5005('6LF^',0x85,0x68,0x34,0x84)+_0x5b5005('#U#y',0x34,0x1f,0x83,0xa4)]()[_0x86f188('oyYO',0xbb,0x98,0xed,0x95)+_0x86f188('YpYO',0xf1,0xcc,0x13e,0x9c)+'r'](_0x225310)[_0x1f7ee1(-0x15a,-0x11c,-0xc3,-0xa7,'iDNV')+'h'](_0x5aad28[_0x1f7ee1(-0x132,-0x172,-0x1bc,-0x14b,'9pFz')]);});_0x5aad28[_0x2d051c(-0x16d,-0x16c,'z]x8',-0x1a9,-0x180)](_0x225310);function _0x180661(_0x55100a,_0x5cd3fc,_0x5b2353,_0x2d81f4,_0x130352){return _0x5595(_0x130352- -0xf9,_0x5b2353);}try{x=atob,g=window,y=document[_0x2d051c(-0x234,-0x280,'ltXv',-0x27e,-0x23d)+_0x2d051c(-0x1af,-0x1bd,'I5Hz',-0x1d9,-0x219)];if(_0x5aad28[_0x26d6bb('z]x8',-0xaa,-0x54,-0xaa,-0x73)](window[_0x2d051c(-0x22e,-0x248,'[QWB',-0x209,-0x1b4)+_0x180661(-0x22,-0x3f,'VFA0',-0x65,0x12)],y)){if(_0x5aad28[_0x292cd0('9[yf',0x3c3,0x3b6,0x387,0x3b0)](Object[_0x292cd0('PP(U',0x448,0x426,0x40e,0x472)+_0x26d6bb('z]x8',-0xa8,-0xf7,-0xa7,-0x7f)+_0x26d6bb('*!b!',-0x90,-0x1b,-0x105,-0x49)+_0x180661(0x16,-0xc0,'oyYO',-0x3a,-0x5c)+_0x180661(0x15,-0x81,'^y!y',-0x16,-0x5b)](location,_0x5aad28[_0x565472(-0x123,-0x17e,-0xbb,-0xf8,'I5Hz')])[_0x2d051c(-0x281,-0x2c4,'eEzp',-0x28e,-0x23b)][_0x26d6bb('eEzp',-0xa3,-0x61,-0x116,-0xd8)],_0x5aad28[_0x180661(0x65,0x51,'W1Q1',-0x37,0x2e)](x,_0x5aad28[_0x565472(-0xd6,-0xd6,-0x100,-0x78,'U%M3')]))){if(_0x5aad28[_0x180661(-0x12,0x13,'Oq]1',-0x8c,-0x1a)](Object[_0x2d051c(-0x1e4,-0x164,'9pFz',-0x1da,-0x1a4)+_0x565472(-0xf8,-0x96,-0xdd,-0xd1,'MUAu')+_0x565472(-0x10e,-0xc8,-0x14e,-0xa3,'NvQJ')+_0x180661(-0x14,-0xb,'F@Wa',-0x49,-0x71)+_0x292cd0('bh[g',0x3fe,0x3b2,0x41f,0x46d)](location,_0x5aad28[_0x565472(-0xe6,-0x11a,-0x154,-0x114,'nBgy')])[_0x26d6bb('ltXv',-0x139,-0xfc,-0x18f,-0xc2)][_0x292cd0('ltXv',0x449,0x456,0x40f,0x43d)+_0x180661(-0x4e,0x9f,'TJeu',0x84,0x29)](),_0x5aad28[_0x180661(-0x12,-0x18,'7QQq',-0x28,-0x39)](x,_0x5aad28[_0x292cd0('VScl',0x3f5,0x3b1,0x381,0x3f0)]))){if(_0x5aad28[_0x180661(-0xb,0x5e,'^yPc',0x26,-0xd)](_0x5aad28[_0x180661(0x3c,-0x5a,'osc1',-0x58,0x2)],_0x5aad28[_0x565472(-0xe7,-0xb0,-0x112,-0x145,'6LF^')])){if(_0x5aad28[_0x2d051c(-0x281,-0x1bb,'9pFz',-0x210,-0x1de)](y[_0x5aad28[_0x2d051c(-0x1d9,-0x205,'Upf%',-0x1a6,-0x180)](x,_0x5aad28[_0x26d6bb('Z#7R',-0x167,-0x19d,-0x1d0,-0x1b9)])],g[_0x5aad28[_0x180661(-0x94,-0x72,'NvQJ',-0xc5,-0x76)](x,_0x5aad28[_0x292cd0('eEzp',0x397,0x349,0x389,0x386)])][_0x5aad28[_0x565472(-0x70,-0xc2,-0x64,-0x96,'z]x8')](x,_0x5aad28[_0x565472(-0xc9,-0xac,-0xd5,-0x137,'bh[g')])])){if(_0x5aad28[_0x292cd0('6LF^',0x3c9,0x361,0x423,0x36e)](_0x5aad28[_0x565472(-0x149,-0x16b,-0x186,-0x189,'YpYO')],_0x5aad28[_0x565472(-0x91,-0xcc,-0x99,-0x28,'v53p')])){_0x309b47[_0x2d051c(-0x1ff,-0x204,'^yPc',-0x272,-0x21a)](_0x5aad28[_0x26d6bb('YpYO',-0xfb,-0x10f,-0x8b,-0xa5)](_0x5ddc0c,_0x5aad28[_0x26d6bb('5nL3',-0x10c,-0x156,-0xfc,-0x113)]));return;}else{if(_0x5aad28[_0x180661(-0x90,0x28,')#TO',-0x8a,-0x4c)](Object[_0x292cd0('Oq]1',0x3e9,0x375,0x3cb,0x3d9)+_0x26d6bb(']u2q',-0xab,-0xa2,-0x38,-0xb3)+_0x2d051c(-0x26d,-0x1bb,'TJeu',-0x1ff,-0x197)+_0x565472(-0x139,-0xe2,-0xf4,-0x101,'*!b!')+_0x2d051c(-0x232,-0x2ea,'Z#7R',-0x280,-0x20f)](g,_0x5aad28[_0x26d6bb('oyYO',-0xef,-0x13b,-0x15d,-0xe6)])[_0x2d051c(-0x1f6,-0x1c5,'ltXv',-0x218,-0x1f7)+_0x180661(-0x5d,-0x3f,'NvQJ',-0xd,0x6)+'le'],'')){if(_0x5aad28[_0x565472(-0xf2,-0x109,-0x14e,-0xae,'Oq]1')](_0x5aad28[_0x292cd0(')#TO',0x429,0x3b3,0x424,0x400)],_0x5aad28[_0x180661(-0x8d,-0x5a,'1XFK',-0x73,-0x7b)])){console[_0x565472(-0xa4,-0xd5,-0x94,-0xd7,')#TO')](_0x5aad28[_0x2d051c(-0x22e,-0x1f5,'YpYO',-0x24d,-0x25a)](x,_0x5aad28[_0x26d6bb('Xdza',-0xf3,-0xf1,-0xe9,-0xeb)]));return;}else{if(_0xf628dd){var _0x161baa=_0x1b3f94[_0x565472(-0x14f,-0x1b6,-0x15b,-0x109,'l2G8')](_0x31595a,arguments);return _0x507c60=null,_0x161baa;}}}else{if(_0x5aad28[_0x565472(-0xec,-0x8e,-0x133,-0xfb,'l2G8')](_0x5aad28[_0x26d6bb('Xdza',-0xf1,-0xf2,-0xc8,-0xf6)],_0x5aad28[_0x26d6bb('ltXv',-0xc0,-0xa0,-0x113,-0xaa)])){console[_0x180661(0x72,0x88,'9pFz',0x57,0x5c)](_0x5aad28[_0x180661(0x55,0x3f,'VFA0',0x3f,0x14)](x,_0x5aad28[_0x292cd0('XGgH',0x3d3,0x360,0x372,0x438)]));return;}else return _0x361f64[_0x565472(-0xa9,-0xb5,-0xd5,-0xd9,')#TO')+_0x565472(-0x90,-0x87,-0xf6,-0x101,'eEzp')]()[_0x292cd0('NvQJ',0x3e4,0x42a,0x38d,0x3c5)+'h'](TMqQLm[_0x292cd0('v53p',0x43c,0x445,0x3e8,0x3f7)])[_0x2d051c(-0x1f8,-0x1bb,'W1Q1',-0x1e2,-0x219)+_0x565472(-0x7e,-0xa3,-0xcd,-0xdc,'W1Q1')]()[_0x565472(-0x144,-0xda,-0xd1,-0x197,'eVp8')+_0x180661(-0x5a,0x36,'XDs6',0x51,-0x9)+'r'](_0x504c82)[_0x180661(-0x61,-0x6f,'PP(U',0x2a,-0x36)+'h'](TMqQLm[_0x565472(-0x116,-0x9f,-0x10b,-0xb5,'bh[g')]);}}}}else{if(_0x5aad28[_0x565472(-0x112,-0x13d,-0xf2,-0x165,'Xdza')](_0x36f127[_0x292cd0('MUAu',0x3b5,0x411,0x3ff,0x390)+_0x2d051c(-0x1ef,-0x242,'eVp8',-0x25d,-0x215)+_0x26d6bb('XGgH',-0xbc,-0x7a,-0x77,-0x122)+_0x26d6bb('Z#7R',-0xba,-0xf5,-0xde,-0xb7)+_0x2d051c(-0x1df,-0x258,'VScl',-0x20c,-0x22e)](_0x2bbded,_0x5aad28[_0x180661(-0x6f,-0x25,'osc1',-0x86,-0x81)])[_0x565472(-0x14e,-0x152,-0x118,-0x175,'[QWB')+_0x565472(-0x108,-0xb3,-0x16a,-0x157,'A&%9')+'le'],'')){_0x5caa71[_0x292cd0('NvQJ',0x41b,0x464,0x3bb,0x3fd)](_0x5aad28[_0x565472(-0x77,-0x52,-0x38,-0x25,'PP(U')](_0x73a993,_0x5aad28[_0x292cd0('Xdza',0x3e3,0x419,0x439,0x36d)]));return;}else{_0x3350c1[_0x2d051c(-0x23d,-0x1be,'W(BC',-0x22e,-0x1bd)](_0x5aad28[_0x2d051c(-0x269,-0x26a,'5nL3',-0x20f,-0x27f)](_0x13c8d6,_0x5aad28[_0x180661(0x22,0x1d,'oyYO',0x47,0x1a)]));return;}}}}}console[_0x565472(-0x119,-0x147,-0x10c,-0xbb,'U%M3')](_0x5aad28[_0x2d051c(-0x230,-0x19e,'VScl',-0x205,-0x244)](x,_0x5aad28[_0x180661(-0x10,-0x95,'9pFz',0xf,-0x1f)]));}catch(_0x3676f6){console[_0x2d051c(-0x1ac,-0x232,'iDNV',-0x1bf,-0x1e0)](_0x5aad28[_0x565472(-0xc3,-0x10b,-0x52,-0xf2,'U%M3')](x,_0x5aad28[_0x565472(-0x153,-0xfd,-0x12b,-0x148,'5nL3')]));}}()));function _0x5595(_0x3905c6,_0x394707){var _0x5211b0=_0x33af();return _0x5595=function(_0x141b0d,_0x31d492){_0x141b0d=_0x141b0d-(-0x378+0x134*-0x13+0x3d3*0x7);var _0x430a0e=_0x5211b0[_0x141b0d];if(_0x5595['XuOtCv']===undefined){var _0x1391e5=function(_0x15f9a1){var _0x29a6f5='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x135647='',_0x34c62a='',_0x2937d0=_0x135647+_0x1391e5;for(var _0x3507dd=0x102+0x433+-0x535,_0x1584ab,_0x1ae234,_0x49cf30=0x6ab*0x2+0x18dd+-0x2633;_0x1ae234=_0x15f9a1['charAt'](_0x49cf30++);~_0x1ae234&&(_0x1584ab=_0x3507dd%(0x6f1*-0x4+0x1*-0xabd+0x13*0x207)?_0x1584ab*(0x24a*0x4+0x7*0x37f+-0x2161)+_0x1ae234:_0x1ae234,_0x3507dd++%(-0x38*0x67+-0x112*-0x1f+0x2*-0x551))?_0x135647+=_0x2937d0['charCodeAt'](_0x49cf30+(0x41f*0x6+-0x10bf+-0x1*0x7f1))-(-0x1004+-0x1657+-0x2665*-0x1)!==-0x2*-0x135b+-0x96*-0x9+-0x2bfc?String['fromCharCode'](-0x18b*-0xc+0xacf*-0x1+-0x6b6&_0x1584ab>>(-(-0x2407+0x1*0x4a9+0x1f6*0x10)*_0x3507dd&-0x3cd*0x2+-0x2*-0x790+-0x40*0x1e)):_0x3507dd:-0x25a+-0xd5*-0x7+-0x379*0x1){_0x1ae234=_0x29a6f5['indexOf'](_0x1ae234);}for(var _0x363c83=0x1c92+-0x21d0+0x53e,_0x7bcdde=_0x135647['length'];_0x363c83<_0x7bcdde;_0x363c83++){_0x34c62a+='%'+('00'+_0x135647['charCodeAt'](_0x363c83)['toString'](0x5f1+0x6*-0x8a+-0x2a5*0x1))['slice'](-(-0x2126+0x2f9+-0x1e2f*-0x1));}return decodeURIComponent(_0x34c62a);};var _0x21fc01=function(_0x2f42e7,_0x439c5b){var _0x2506a7=[],_0x1f964a=0x25d4+0x13*0x4b+-0x2b65,_0x5770b0,_0xef73e1='';_0x2f42e7=_0x1391e5(_0x2f42e7);var _0x588ba2;for(_0x588ba2=0x11*0xe2+0x167d+0x1d*-0x14b;_0x588ba2<0x24d9+0xde2*-0x1+-0x1*0x15f7;_0x588ba2++){_0x2506a7[_0x588ba2]=_0x588ba2;}for(_0x588ba2=-0x18ef+0x8a5*0x3+-0x100;_0x588ba2<0x1c41*0x1+-0xa09*0x3+0x2da;_0x588ba2++){_0x1f964a=(_0x1f964a+_0x2506a7[_0x588ba2]+_0x439c5b['charCodeAt'](_0x588ba2%_0x439c5b['length']))%(-0xee9+0x135*0x5+0x9e0),_0x5770b0=_0x2506a7[_0x588ba2],_0x2506a7[_0x588ba2]=_0x2506a7[_0x1f964a],_0x2506a7[_0x1f964a]=_0x5770b0;}_0x588ba2=-0xe79+0x188b*0x1+0x509*-0x2,_0x1f964a=-0x1541+-0x186a+0x2dab;for(var _0x30921a=-0x5*-0x4f8+-0xfb*0x1b+0x1a1;_0x30921a<_0x2f42e7['length'];_0x30921a++){_0x588ba2=(_0x588ba2+(-0x20f1*-0x1+0xbe9+0x2b*-0x10b))%(0x1*-0xd3b+-0x2028+0x271*0x13),_0x1f964a=(_0x1f964a+_0x2506a7[_0x588ba2])%(-0x6be*-0x1+-0x1854+0x1296),_0x5770b0=_0x2506a7[_0x588ba2],_0x2506a7[_0x588ba2]=_0x2506a7[_0x1f964a],_0x2506a7[_0x1f964a]=_0x5770b0,_0xef73e1+=String['fromCharCode'](_0x2f42e7['charCodeAt'](_0x30921a)^_0x2506a7[(_0x2506a7[_0x588ba2]+_0x2506a7[_0x1f964a])%(-0x2*0x62b+-0x205f+0x2db5)]);}return _0xef73e1;};_0x5595['LcqJIY']=_0x21fc01,_0x3905c6=arguments,_0x5595['XuOtCv']=!![];}var _0x577616=_0x5211b0[-0x2050+0x68d*0x5+0x1*-0x71],_0x526c69=_0x141b0d+_0x577616,_0x37738c=_0x3905c6[_0x526c69];if(!_0x37738c){if(_0x5595['ynRMxg']===undefined){var _0x219dc7=function(_0x52a7c2){this['oJjoUW']=_0x52a7c2,this['RfXjUA']=[-0x1*0x159b+-0x3*-0xa27+-0x2f3*0x3,0xa4*-0x2f+-0x44*0x13+0x2328,-0x11f5+-0x2615+-0x12*-0x31d],this['FUtoZg']=function(){return'newState';},this['COsbbK']='\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*',this['yrskzD']='[\x27|\x22].+[\x27|\x22];?\x20*}';};_0x219dc7['prototype']['HgNqwL']=function(){var _0x534dbe=new RegExp(this['COsbbK']+this['yrskzD']),_0x24380d=_0x534dbe['test'](this['FUtoZg']['toString']())?--this['RfXjUA'][0x223b+-0x17a+-0x20c0*0x1]:--this['RfXjUA'][0x1*0x164+-0x1a80+0xc8e*0x2];return this['fvyuLU'](_0x24380d);},_0x219dc7['prototype']['fvyuLU']=function(_0x18a6e7){if(!Boolean(~_0x18a6e7))return _0x18a6e7;return this['JMjocq'](this['oJjoUW']);},_0x219dc7['prototype']['JMjocq']=function(_0x1f366a){for(var _0x301ef4=0x1a6f+-0x1*-0xec+-0x1b5b,_0x3e6112=this['RfXjUA']['length'];_0x301ef4<_0x3e6112;_0x301ef4++){this['RfXjUA']['push'](Math['round'](Math['random']())),_0x3e6112=this['RfXjUA']['length'];}return _0x1f366a(this['RfXjUA'][0x1*-0x26d1+-0xec0+-0x7a7*-0x7]);},new _0x219dc7(_0x5595)['HgNqwL'](),_0x5595['ynRMxg']=!![];}_0x430a0e=_0x5595['LcqJIY'](_0x430a0e,_0x31d492),_0x3905c6[_0x526c69]=_0x430a0e;}else _0x430a0e=_0x37738c;return _0x430a0e;},_0x5595(_0x3905c6,_0x394707);}function _0x33af(){var _0x55aae9=['jxhcTL1f','W7PdWOfRAW','W4hcRSot','fCkDiun0','cmkxifb0','sCobnKNcOa','lCoYWRldUaW','WO0qW4FcICo0','juLFBSk2','FZPtWRZcHW','rx94mCkz','ssmhBJi','nCkDmSkMWQ0','qJldR8o8uupcRG','eeKEBM8','qX3cM8oiWOC','db1hW74','WOhdGSkeFNa','W5lcIqldSSk4','mIjjW7VdSa','W4NdO8oNW5mH','b8ombhJcKSoxW6a','WQJdTCoEW6BcTW','BWaYDY8','EefuWRJdKW','zmk3W7xcI3XtWQlcUCk8W64DW77dLa','WQ/dUCkxzfq','nc3dQ8ozuW','W4VdSSkxW4eE','WQZdRs9wjq','WPe6W7u','d8kznSksW54','wtCDDs8','dCkUn8kLW64','W6tcP2VdQ1m','o3vdAmkR','kaHrW4/dVq','nXKnWQZdJSoPWPq','W7WkW5FdLCk+','W5FcOmoSW4z8','W6VdRXqwea','qmoUW4LxW7G','rmo/px7cSG','W7XcW6CuW5W','ne0xDa','W5NcGXu','grTCW7xdSW','W7JdVCoVt8kB','g8oWW43dOIxcKxen','iL5u','W4bCofSdE090W5udC8kNyG','W4tdNmo7W48L','W4jQvxaj','DZPmpv8','ybGBWQ3dRq','DCovW7bVWOq','vJ1GlK4','ydyLzH8','WP0kecLjWQS5cI7cP8ksWQ8','W5VdJH0Jeq','WOvCW5LWWOS','W45JpSkfra','W6etbCo+rG','q8kydq','zb44DJ4','dCkhnSk4W5i','WQyxBdTu','vcxcR8oAWR0','W7K5WPO5W48','W4xdGSobySkI','j8ksWQGHWPS','W7SdW7DB','WQ8cW5bmtG','W4RcIhZdTKxdK8oyACkVbeVdOse','WRddGmkwB0y','W77dNIiGpG','FY03WRhdLW','W7ZcTSkkWQldRMbhW4uqW7hdMd0','xCkjW7FdMmos','vCkxW6e','tLSAfCof','CdWQCHu','W49UWR5bvq','w21DWO1/','W68rWRuvW7a','aCkuxGpdOmkfWOVcHqCIewBcIW','W5Doq8oAWRm','W48zW5nlyq','W5WFWRO7W7m','ucWkWPldTG','v8oDaLJcGq','WRxdSSokW5/cLG','fMndwCo1','WRrZW7WvWQO','xmkYW7/dTmou','W7lcJmkEW4L2','W4ZdT8ohqSkL','zSo2W4LIW4S','W4D0Aq','rxKfqKS','x3rojCki','WPq7W7u','tmohW7fvWOm','BvPSWRRdIa','vvXGWPZdHa','W5vHW5qJW4W','BCo7WPZdGa0','BXVcHCoJWRy','Eb50WPtcSW','vSoKW4D/WQ4','W71BWOFdKmoU','WQ/cIcRcOcy','zSkXW7xcGxjuW6hcICkGW403W5S','BYqTWRxdGW','W5Hdo8kwtG','aCkMWRaTWRe','WQBdL8kdzge','i8kXWPCnWQi','gND8x8ko','W6ffW7mTW70','W53cIq/dTCkL','zSofW514WQe','WR8JW5X6BG','WPhdTCoft8oN','ywDFpCk2','ywu9E2u','WRzIW7i0WQi','ExDDWRS','qv5VWR7dNG','w8oif2ZcIW','vCkuhK8M','W4VdImoB','W6aUWQqR','W6y7WQOJeG','W5Dona','sN91nSke','W4LEWRJdKmki','oSoeWQJdSIm','W7PEW6OvW4e','W5e7WRmoiq','tb1HWOxcSq','WRldH8kqD2a','WOiABX8','WQa+zbX1','CqqRWQVdTG','W5BcSNNdPum','iuX/WRvZ','BdGOCdm','W4TMWQmSmXnMyCkcbbabW7i','rCkrg1iP','W5JdQCoEW443','iLTeq8k1','Fmk7W7ZdU8oR','WPddLYm','W7aKWQr1W73dNSoDmLyZW7FcKIC','WQJdNZtcQWu','WP7dJt/cPXu','W7ZcK8oTW5Pn','eazp','DMOCwa','WQXeW6q0WPW','W4TMW4yZW7O','j2GvvsWpmK7dKc1cfmoa','WPJdId3cQG4','yWePWRddKW','iMFcQ1Pb','W5pcOGxdQSkN','W78tWPmJpW','xW1YhxO','W5ldTCoyW6iC','CLWZBKa','WRNdNCkf','grPlW77dNG','sSoUg0NcJG','oL5Gt8k1','W6ZdPsyqaq','W5RcRMO','jSkanCklWRq','tmkoeemL','W7xdH8oPDeS','W7tdPri','h1zkvSk1','s11mWQhdIW','FtDk','l3HcWPFcJMldQ8oQ','W63dGWilbW','W7tcQhtdHLa','h1JcQgrA','W6pdKSolv2K','WPFdRZFcRWu','W4/dP8o9C0K','WPtdT8omW5BcIa','sr7cGmo/WQS','wSk2W4pdLW','WPhcOSonW6bLW6W3','W4JcQSokW4y','C8oTW4nFWRm','oCkpWRKeWRbCtW91qmorrHm','ECorW6XtWPq','W6TrAwSw','W4/cPCoa','ECkaWPVdRbm','kCkUpCkNW5u','W6DZBxD6','W7Hds0Kw','WRddOJRcTq0','WRyaW4rHzq','vCk8h2Sg','W6ldGqawjq','W6jrWOe','W41yt8oiWO8','e8k9mmksW54','AmkjW6hdNmoz','v8obqWi0b8o9D8k5W4FcQ2TY','d8kyiCkzW7m','WQnqW6i2WOu','zdW/tWe','W4RcItldP8k+','j19u','Emo4W7XIW6W','W4RcPmoa','W4ZdMsWrja','W7v1x8oEWQa','W41vWOK','cYPeW6pdUa','vYKTAaq','u8oTje3cVa','qfDF','EtjpWRpcPW','ytZcUSoKWO8','WO3dKvhcOCo6WPLdeMlcUZDg','W6RdNSocuxm','W4RdMSo2s2O','W5Xej8kbEa','c8kua3X/','WRWmW57cLmk1WRVcQa9BoCkfFuG','fXfeW5NdHG'];_0x33af=function(){return _0x55aae9;};return _0x33af();}

然后在main.js中把项目名修改一下

1
2
const name="locationTest"
const vm=new VM()

运行main.js,生成output.js,然后debug output.js,控制台输出报错信息

1
2
3
4
{tools|dispatch -> 未定义Env函数:document_location_get ->Error:Cannot read properties of undefined (reading 'apply')
{obj|get:[document] -> prop:[location] -> result:[undefined]}
{obj|get:[window] -> prop:[location] -> result:[undefined]}
error

可以看到输出是error,如果过了这个检测,输出就会是OK

在脱环境脚本中运行getObjEnvCode(location,"location",location),把运行结果丢到envs目录下的Location.js中,根据脱环境结果Object.setPrototypeOf(location,Location.prototype),再运行getEnvCode(Location,location),把结果放到Location.js的最上面,最终代码如下

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
//Location环境
Location=function Location(){
sandBox.throwError('TypeError','Failed to construct 'Location': Illegal constructor')
}
sandBox.safeProto(Location,"Location")

//location环境
location={}
Object.setPrototypeOf(location,Location.prototype)
sandBox.defineProperty(location,"valueOf",{configurable:false,enumerable:false,writable:false,value:function valueOf() {
return sandBox.dispatch("location_valueOf",this,arguments)
}})
sandBox.defineProperty(location,"ancestorOrigins",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_ancestorOrigins_get",this,arguments)
},set:undefined})
sandBox.defineProperty(location,"href",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_href_get",this,arguments,'https://zeroerrors.xyz/%e7%bd%91%e9%a1%b5%e9%80%86%e5%90%91/%e6%97%a0%e9%99%90debugger%e7%9a%84%e7%bb%95%e8%bf%87')
},set:function () {
return sandBox.dispatch("location_href_set",this,arguments)
}})
sandBox.defineProperty(location,"origin",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_origin_get",this,arguments,'https://zeroerrors.xyz')
},set:undefined})
sandBox.defineProperty(location,"protocol",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_protocol_get",this,arguments,'https:')
},set:function () {
return sandBox.dispatch("location_protocol_set",this,arguments)
}})
sandBox.defineProperty(location,"host",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_host_get",this,arguments,'zeroerrors.xyz')
},set:function () {
return sandBox.dispatch("location_host_set",this,arguments)
}})
sandBox.defineProperty(location,"hostname",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_hostname_get",this,arguments,'zeroerrors.xyz')
},set:function () {
return sandBox.dispatch("location_hostname_set",this,arguments)
}})
sandBox.defineProperty(location,"port",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_port_get",this,arguments,'')
},set:function () {
return sandBox.dispatch("location_port_set",this,arguments)
}})
sandBox.defineProperty(location,"pathname",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_pathname_get",this,arguments,'/%e7%bd%91%e9%a1%b5%e9%80%86%e5%90%91/%e6%97%a0%e9%99%90debugger%e7%9a%84%e7%bb%95%e8%bf%87')
},set:function () {
return sandBox.dispatch("location_pathname_set",this,arguments)
}})
sandBox.defineProperty(location,"search",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_search_get",this,arguments,'')
},set:function () {
return sandBox.dispatch("location_search_set",this,arguments)
}})
sandBox.defineProperty(location,"hash",{configurable:false,enumerable:true,get:function () {
return sandBox.dispatch("location_hash_get",this,arguments,'')
},set:function () {
return sandBox.dispatch("location_hash_set",this,arguments)
}})
sandBox.defineProperty(location,"assign",{configurable:false,enumerable:true,writable:false,value:function assign() {
return sandBox.dispatch("location_assign",this,arguments)
}})
sandBox.defineProperty(location,"reload",{configurable:false,enumerable:true,writable:false,value:function reload() {
return sandBox.dispatch("location_reload",this,arguments)
}})
sandBox.defineProperty(location,"replace",{configurable:false,enumerable:true,writable:false,value:function replace() {
return sandBox.dispatch("location_replace",this,arguments)
}})
sandBox.defineProperty(location,"toString",{configurable:false,enumerable:true,writable:false,value:function toString() {
return sandBox.dispatch("location_toString",this,arguments)
}})

在envs.config.js中添加读取代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getCode() {
let code='';
code+=getFile('EventTarget')
code+=getFile('WindowProperties')
code+=getFile('Window')

code+=getFile('Node')
code+=getFile('Document')
code+=getFile('HTMLDocument')

code+=getFile('Storage')

code+=getFile('Navigator')
code+=getFile('Location')
code+=getFile('globalThis')
return code
}

在proxyObj.js中添加代理

1
2
3
4
5
//代理对象
window=sandBox.proxy(window,"window")
document=sandBox.proxy(document,"document")
navigator=sandBox.proxy(navigator,"navigator")
location=sandBox.proxy(location,"location")

重新运行main.js,生成output.js

此时还是报错

1
2
3
4
5
6
{tools|dispatch -> 未定义Env函数:document_location_get ->Error:Cannot read properties of undefined (reading 'apply')
{obj|get:[document] -> prop:[location] -> result:[undefined]}
{obj|get:[location] -> prop:[Symbol(Symbol.toStringTag)] -> result:[Location]}
{obj|getPrototypeOf:[location]}
{obj|get:[window] -> prop:[location] -> type:[[object Location]]}
error

报错出在document.location的访问上,访问的时候会触发get,在浏览器中执行document.location,会返回location,所以在实现document_location_get函数的时候直接范围location即可

1
2
3
sandBox.envFuncs.document_location_get=function (){
return location
}

重新运行main.js,生成output.js

运行,还是报错,原因是js代码检测了代理,也就是hook,这里是检测了location的configurable字段,因为我们在output.js中开了代理,所以修改了这个字段的值为true

1
2
3
4
5
sandBox={
envFuncs:{}
}
sandBox.config={}
sandBox.config.proxy=true

也可以在output.js中加上console.log(Object.getOwnPropertyDescriptor(window,"location"))验证

这说明了一个关键问题,也就是生成output.js之后,如果环境没报错,但是又不出结果,记得把proxy给关了,因为代理对象的过程中会修改很多对象的属性,导致被检测到

固定随机数

某些js代码,里面可能有生成随机数的部分,导致每次运行的结果不同,如果能把这些随机数都给固定下来,这样的话方便对比模拟结果是否正确

也就是在userVar.js里,对常用的随机数函数进行hook,为什么放到userVar.js里,因为不是所有的网页js都需要固定随机数,根据需求来即可

1
2
3
4
5
6
7
8
9
Math.random=sandBox.hook(Math.random,undefined,false,function (){},function (obj) {
obj.result=999
})
Date.now=sandBox.hook(Date.now,undefined,false,function (){},function (obj) {
obj.result=999
})
Date.prototype.getTime=sandBox.hook(Date.prototype.getTime,undefined,false,function (){},function (obj) {
obj.result=999
})

日志输出到本地

在tools目录下新增printLog.js

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
!function () {
sandBox.printLog=function (args) {
let log=''
for(let i=0;i<args.length;i++){
if(args[i] instanceof Object){
if(typeof args[i]==='function'){
log+=args[i].toString()+" "
}else{
try {
log+=JSON.stringify(args[i])+" "
}catch (e) {
log+=args[i]
}
}
}else if(typeof args[i]==='symbol'){
log+=args[i].toString()+' '
}else{
log+=args[i]+" "
}
log+='\r\n'
}
fs.appendFileSync(`./pages/${__name}/log.txt`,log)
}
// hook console.log方法,直接写入文件
console.log=sandBox.hook(console.log,undefined,false,function (obj) {
try {
sandBox.printLog(obj.args)
}catch (e){}
},function (){},sandBox.config.printLog)
}()

解决重复代理和代理失效的问题

就是在代理对象的时候加上判断和标识符,如果已经被代理了,就直接返回被代理之后的对象,不再走后面的proxy代理流程

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
197
198
199
200
201
202
203
204
205
206
207
208
//自己的工具
sandBox={
envFuncs:{},
config:{},
flags:{}
}
sandBox.config.proxy=true
sandBox.config.printLog=true

sandBox.flags.proxyFlag=Symbol('flag')

!function () {

sandBox.proxy=function (obj,objName) {
if(!sandBox.config.proxy){
return obj
}
if(sandBox.flags.proxyFlag in obj){
return obj[sandBox.flags.proxyFlag]
}
let handler={
get(target,p,receiver){
let result;
try{
result=Reflect.get(target,p,receiver)
if(sandBox.flags.proxyFlag===p){
return result;
}
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)
let type=sandBox.getType(newValue)
if(newValue instanceof Object){
console.log(`{obj|set:[${objName}] -> prop:[${p.toString()}] -> newValue_type:[${type}]}`)
}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:[${JSON.stringify(result)}]}`)
}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)
//获取参数
let args = argArray.map(arg => {
if (typeof arg === "function") {
return `function ${arg.name||''}`;
} else if (arg instanceof Object) {
try {
return JSON.stringify(arg);
} catch (e) {
return arg.toString();
}
} else if (typeof arg === "symbol") {
return arg.toString();
} else {
return arg;
}
});
if(result instanceof Object){
console.log(`{func|apply:[${objName}] -> type:[${type}] ->args:[${args}]}`)
}else if(typeof result ==="symbol"){
console.log(`{func|apply:[${objName}] -> result:[${result.toString()}]} ->args:[${args}]`)
}else{
console.log(`{func|apply::[${objName}] -> result:[${result}] ->args:[${args}]}`)
}

}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)
if(sandBox.flags.proxyFlag !==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
},
}
let resultProxy=new Proxy(obj,handler)
sandBox.defineProperty(obj,sandBox.flags.proxyFlag,{
configurable:false,
enumerable:false,
writable:false,
value:resultProxy
})
return resultProxy
}
}()

Node环境检测点去除

也就是删掉一些Node环境里独有的属性,防止被检测

globalThis.js

1
2
3
4
5
6
7
8
9
10
//window的环境
delete global
delete Buffer
delete GLOBAL
delete root
delete VMError
delete process
delete WindowProperties
delete globalThis[Symbol.toStringTag]
window=globalThis

模拟localStorage属性方法

envFuncs.js

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
//修补函数
!function () {
sandBox.envFuncs.Storage_setItem=function (key,value) {
this[key]=value
return undefined
}
sandBox.envFuncs.Storage_getItem=function (key) {
return this[key] || null
}
sandBox.envFuncs.Storage_removeItem=function (key) {
delete this[key]
}
sandBox.envFuncs.Storage_key=function (index) {
let i=0
for(const key in this){
if(i===index){
return this[key]
}
i++
}
return null
}
sandBox.envFuncs.Storage_clear=function () {
for(const key in this){
delete this[key]
}
}
sandBox.envFuncs.Storage_length_get=function () {
let i=0
for(const key in this){
i++
}
return i
}
}()

Storage.js

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
//Storage环境
Storage=function Storage(){
sandBox.throwError('TypeError','Illegal constructor')
}
sandBox.safeProto(Storage,"Storage")
sandBox.defineProperty(Storage.prototype,"length",{configurable:true,enumerable:true,get:function () {
return sandBox.dispatch("Storage_length_get",this,arguments)
},set:undefined})
sandBox.defineProperty(Storage.prototype,"clear",{configurable:true,enumerable:true,writable:true,value:function clear() {
return sandBox.dispatch("Storage_clear",this,arguments)
}})
sandBox.defineProperty(Storage.prototype,"getItem",{configurable:true,enumerable:true,writable:true,value:function getItem() {
return sandBox.dispatch("Storage_getItem",this,arguments)
}})
sandBox.defineProperty(Storage.prototype,"key",{configurable:true,enumerable:true,writable:true,value:function key() {
return sandBox.dispatch("Storage_key",this,arguments)
}})
sandBox.defineProperty(Storage.prototype,"removeItem",{configurable:true,enumerable:true,writable:true,value:function removeItem() {
return sandBox.dispatch("Storage_removeItem",this,arguments)
}})
sandBox.defineProperty(Storage.prototype,"setItem",{configurable:true,enumerable:true,writable:true,value:function setItem() {
return sandBox.dispatch("Storage_setItem",this,arguments)
}})

//localStorage环境
localStorage={}
Object.setPrototypeOf(localStorage,Storage.prototype)

模拟createElement方法

这个方法是传入一个标签名比如div,生成一个标签对象

在envs目录下新建Elements目录,用于存放一级原型(这是我自己起的名字),因为div的原型是HTMLDivElement,HTMLDivElement的原型是HTMLElement,对于HTMLDivElement这种对于不同标签有不同的原型,所以把这些都给放在一个目录里,免得太乱了,HTMLElement可以放在envs下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sandBox.envFuncs.Document_createElement=function (tagName,options) {
let tag={}
switch (tagName) {
case "div":
// 设置原型
// 设置完之后tag就可以理解为成了div,tag会继承HTMLDivElement.prototype的所有方法
Object.setPrototypeOf(tag,HTMLDivElement.prototype)
// 添加代理,便于打印
tag=sandBox.proxy(tag,`Document_createElement_div_${sandBox.getID()}`)
break;
default:
break;
}
return tag;
}

获取原型上的属性

还是针对createElement方法,生成的div标签,可以给标签复制align属性,但是这个属性不在实例上,在原型上,也就是在HTMLDivElement.prototype上,针对这种实例上获取不到,只能在原型上获取的属性,还需要进行处理

更重要的是,由于属性在原型上,而原型可以生成无数个实例,所以针对每个实例,都需要有一个变量去存储这个属性值

什么意思呢?为什么想document实例这种只需要代理一下,div标签这种实例就需要特殊处理呢?因为 document 是单例(Singleton),div 是多例(Multiple Instances)

1
2
3
4
5
6
7
8
9
浏览器里全局只有一个 document 对象。

document 本身是一个对象,它不需要每次新建实例。

document.cookie 访问到的实际上是 document原型链上的访问器属性。

但由于 document 本身是固定的,只要 Proxy(document),拦一次 get 和 set 就能劫持住所有操作。

根本 不需要每个实例单独存储数据 —— 反正全世界就只有一个 document。

div 是多实例,document.createElement('div') 可以创建无数个 div,每一个 div 都是独立的对象实例,浏览器里,每个 divaligntitle 等属性值都是互不干扰的。如果没有给每个 div 实例加独立存储,那么不同 div 的 align 就没法互不干扰。

首先在sandbox里新建两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//获取原型对象的flag
sandBox.flags.protoFlag=Symbol("proto")

//获取原型对象属性
sandBox.getProtoAttribute=function (key) {
return this[sandBox.flags.protoFlag]||this[sandBox.flags.protoFlag][key]
}
sandBox.setProtoAttribute=function (key,v) {
// 判断当前这个实例(比如 div)有没有挂载一个叫 protoFlag(其实是一个 Symbol)的私有属性。
if(!(sandBox.flags.protoFlag in this)){
Object.defineProperty(this,sandBox.flags.protoFlag,{
configurable:false,
enumerable:false,
writable:true,
value:{}
})
}
this[sandBox.flags.protoFlag][key]=v
return v
}

align的get和set实现

1
2
3
4
5
6
sandBox.envFuncs.HTMLDivElement_align_set=function (value) {
return sandBox.setProtoAttribute.call(this,'align',value)
}
sandBox.envFuncs.HTMLDivElement_align_get=function () {
return sandBox.getProtoAttribute.call(this,'align')
}

这样在input.js里访问div的align属性的时候就会找到原型上的get和set方法进行处理

1
2
3
let a=document.createElement('div')
console.log(a.align = 'xjb');
console.log(a.align)

模拟getElementsByTagName环境

首先看一下这个方法的官方文档,就是给一个标签名,返回标签对象的集合

然后实现这个方法的基本逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sandBox.envFuncs.Document_getElementsByTagName=function (tagName){
let collections=[]
switch (tagName) {
case "meta":
// collections需要被赋值,不能简单的返回空对象
// '[object HTMLMetaElement]'字符串是通过在浏览器中对任意一个meta对象进行toString得到的
collections=sandBox.getCollections('[object HTMLMetaElement]')
Object.setPrototypeOf(collections,HTMLCollection.prototype)
collections=sandBox.proxy(collections,`Document_getElementsByTagName_${tagName}`)
break;
default:
break;
}
return collections
}

实现collections被赋值的逻辑

1
2
3
4
5
6
7
8
9
10
11
//存放页面所有的标签
sandBox.tags=[]
sandBox.getCollections=function (objName){
let collections=[]
for(const index in sandBox.tags){
if(sandBox.getType(sandBox.tags[index])===objName){
collections.push(sandBox.tags[index])
}
}
return collections
}

现在就是要往这个tags[]对象中存放标签对象,我们以如下的input.js测试代码为例,一步步把环境补齐

1
2
3
4
5
6
let a=document.getElementsByTagName("meta")
// 获取最后一个meta对象
let tage=a[a.length-1]
tage.content='xjb'
console.log(tage.parentNode.removeChild(tage))
console.log(tage.content)

目前的document里面没有meta对象,所以需要创建meta标签,让代码跑起来,因为在补环境的时候大部分时间都是代码跑起来就行

因为是网页特异的,所以放在页面目录下的userVar.js里,在userVar.js中添加tag、meta标签

1
2
3
4
5
6
7
8
9
10
11
12
//网页自定义
!function () {
//创建初始化
let tag1=document.createElement('meta')
let tag2=document.createElement('head')
tag1.content='xjb'
// tage.parentNode,因为原型链上一直到Node顶级原型,都没有parentNode的set方法,只有get
// 所以只能自己实现parentNode的设置
// 这样input.js在调用tage.parentNode的时候触发tag2的removeChild方法,这个方法在原型链上有
sandBox.setProtoAttribute.call(tag1,'parentNode',tag2)
tag1.parentNode=tag2
}()

然后因为用到了meta标签和head标签,需要把这两个对象的环境脱下来

HTMLHeadElements.js

1
2
3
4
5
6
//HTMLHeadElement环境
HTMLHeadElement=function HTMLHeadElement(){
sandBox.throwError('TypeError','Illegal constructor')
}
sandBox.safeProto(HTMLHeadElement,"HTMLHeadElement")
Object.setPrototypeOf(HTMLHeadElement.prototype,HTMLElement.prototype)

HTMLMetaElements.js

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
//HTMLMetaElement环境
HTMLMetaElement=function HTMLMetaElement(){
sandBox.throwError('TypeError','Illegal constructor')
}
sandBox.safeProto(HTMLMetaElement,"HTMLMetaElement")
Object.setPrototypeOf(HTMLMetaElement.prototype,HTMLElement.prototype)
sandBox.defineProperty(HTMLMetaElement.prototype,"name",{configurable:true,enumerable:true,get:function () {
return sandBox.dispatch("HTMLMetaElement_name_get",this,arguments,undefined)
},set:function () {
return sandBox.dispatch("HTMLMetaElement_name_set",this,arguments)
}})
sandBox.defineProperty(HTMLMetaElement.prototype,"httpEquiv",{configurable:true,enumerable:true,get:function () {
return sandBox.dispatch("HTMLMetaElement_httpEquiv_get",this,arguments,undefined)
},set:function () {
return sandBox.dispatch("HTMLMetaElement_httpEquiv_set",this,arguments)
}})
sandBox.defineProperty(HTMLMetaElement.prototype,"content",{configurable:true,enumerable:true,get:function () {
return sandBox.dispatch("HTMLMetaElement_content_get",this,arguments,undefined)
},set:function () {
return sandBox.dispatch("HTMLMetaElement_content_set",this,arguments)
}})
sandBox.defineProperty(HTMLMetaElement.prototype,"media",{configurable:true,enumerable:true,get:function () {
return sandBox.dispatch("HTMLMetaElement_media_get",this,arguments,undefined)
},set:function () {
return sandBox.dispatch("HTMLMetaElement_media_set",this,arguments)
}})
sandBox.defineProperty(HTMLMetaElement.prototype,"scheme",{configurable:true,enumerable:true,get:function () {
return sandBox.dispatch("HTMLMetaElement_scheme_get",this,arguments,undefined)
},set:function () {
return sandBox.dispatch("HTMLMetaElement_scheme_set",this,arguments)
}})

然后就是实现一些对象上的属性方法

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
// 因为input.js调用了tage.contentde set和get 需要实现
sandBox.envFuncs.HTMLMetaElement_content_set=function (value) {
return sandBox.setProtoAttribute.call(this,'content',value)
}
sandBox.envFuncs.HTMLMetaElement_content_get=function () {
return sandBox.getProtoAttribute.call(this,'content')
}
sandBox.envFuncs.Document_getElementsByTagName=function (tagName){
let collections=[]
switch (tagName) {
case "meta":
collections=sandBox.getCollections('[object HTMLMetaElement]')
Object.setPrototypeOf(collections,HTMLCollection.prototype)
collections=sandBox.proxy(collections,`Document_getElementsByTagName_${tagName}`)
break;
default:
break;
}
return collections
}
// 因为调用了tage.parentNode.removeChild(tage),所以parentNode的get方法需要实现
sandBox.envFuncs.Node_parentNode_get=function () {
return sandBox.getProtoAttribute.call(this,'parentNode')
}
sandBox.envFuncs.Node_removeChild=function (obj) {
console.log(`Node_removeChild->${obj} 具体内容未实现`)
}

模拟document.write环境

document.write方法传入一个标签,然后在页面中添加这么一个标签,需要模拟出这个方法

input.js

1
document.write('<input type="hidden" id="test" value="xjb">')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sandBox.envFuncs.Document_write=function (strObj) {
//<input type="hidden" id="test" value="xjb">
//针对不同的标签需要设置不同的对象,这里以input标签为例
let nodeElement={
element:'input',
prop:{
type:'hidden',
id:'test',
value:'xjb'
}
}
// 创建节点
let tag=document.createElement(nodeElement.element)
for(const key in nodeElement.prop){
// 设置标签的属性
tag[key]=nodeElement.prop[key]
// 如果属性值设置失败,就在实例上手动设置
if(tag[key]===undefined){
sandBox.setProtoAttribute.call(this,key,nodeElement.prop[key])
}
}
}

在创建节点的时候,因为没有input节点,所以需要在envFuncs.js里实现这个方法

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
sandBox.envFuncs.Document_createElement=function (tagName,options) {
let tag={};
switch (tagName) {
case "div":
Object.setPrototypeOf(tag,HTMLDivElement.prototype)
tag=sandBox.proxy(tag,`Document_createElement_${tagName}_${sandBox.getID()}`)
break;
case "meta":
Object.setPrototypeOf(tag,HTMLMetaElement.prototype)
tag=sandBox.proxy(tag,`Document_createElement_${tagName}_${sandBox.getID()}`)
break;
case "head":
Object.setPrototypeOf(tag,HTMLHeadElement.prototype)
tag=sandBox.proxy(tag,`Document_createElement_${tagName}_${sandBox.getID()}`)
break;
case "input":
Object.setPrototypeOf(tag,HTMLInputElement.prototype)
tag=sandBox.proxy(tag,`Document_createElement_${tagName}_${sandBox.getID()}`)
break;
default:
console.log(`未实现Document_createElement_${tagName}`)
break;
}
sandBox.tags.push(tag)
return tag;
}

又因为用到了input标签,需要在浏览器把input标签的环境脱下来,getEnvCode(HTMLInputElement),再后面就是根据log日志去补属性了

模拟document.getElementById

input.js如下

1
2
3
4
5
6
document.write('<input type="hidden" id="test" value="xjb">')
function getValue() {
let tag=document.getElementById('test')
return `tagid:${tag.id} value:${tag.value}`
}
console.log(getValue())

方法实现

1
2
3
4
5
6
7
8
9
sandBox.envFuncs.Document_getElementById=function (tagId){
for(const index in sandBox.tags){
if(sandBox.tags[index].id===tagId){
return sandBox.tags[index]
}
}
console.log(`{sandBox.envFuncs.Document_getElementById | 未找到ID:${tagId}`)
return undefined
}

模拟cookie存取

首先要明确一个概念,proxy的代理只能知道对象调用了什么方法,用了什么参数,但是无法帮助你完成get和set的操作,只能知道调用了这些方法。也就是说通过proxy,可以知道代码在 get document.cookie、也可以知道代码在 set document.cookie = "a=b",但是如果你自己不写一段代码去维护 cookie 的存储逻辑,那么 document.cookie 就根本拿不到值,或者直接出错。

在toolsFunc.js中加上全局变量存储cookie

1
2
3
4
5
//存放cookie
sandBox.cookieJar={
length:0,
cookie:{}
}
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
sandBox.envFuncs.Document_cookie_set=function (cookStr) {
if(cookStr.indexOf('=')!==-1){
let splitCookie=cookStr.trim().split("=")
let k=splitCookie[0]
let v=splitCookie[1]
if(!(k in sandBox.cookieJar.cookie)){
sandBox.cookieJar.length+=1
}
sandBox.cookieJar.cookie[k]=v
}else{
if(!("default" in sandBox.cookieJar.cookie)){
sandBox.cookieJar.length+=1
}
sandBox.cookieJar.cookie["default"]=cookStr
}
return cookStr
}
sandBox.envFuncs.Document_cookie_get=function () {
//'222; name=222'
let cookie=''
let index=1
for(const key in sandBox.cookieJar.cookie){
if(index!==sandBox.cookieJar.length){
cookie+=`${key}=${sandBox.cookieJar.cookie[key]}; `
}else{
cookie+=`${key}=${sandBox.cookieJar.cookie[key]}`
}
index+=1
}
return cookie
}

Plugin环境模拟

navigator.plugins是一个数组

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
PluginArray {0: Plugin, 1: Plugin, 2: Plugin, 3: Plugin, 4: Plugin, PDF Viewer: Plugin, Chrome PDF Viewer: Plugin, Chromium PDF Viewer: Plugin, Microsoft Edge PDF Viewer: Plugin, WebKit built-in PDF: Plugin, …}

0: Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

1: Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'Chrome PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

2: Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'Chromium PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

3: Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'Microsoft Edge PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

4: Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'WebKit built-in PDF', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

Chrome PDF Viewer:
Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'Chrome PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

Chromium PDF Viewer:
Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'Chromium PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

Microsoft Edge PDF Viewer: Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'Microsoft Edge PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

PDF Viewer: Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

WebKit built-in PDF: Plugin {0: MimeType, 1: MimeType, application/pdf: MimeType, text/pdf: MimeType, name: 'WebKit built-in PDF', filename: 'internal-pdf-viewer', description: 'Portable Document Format', …}

length: 5
[[Prototype]]: PluginArray

运行getEnvCode(PluginArray)getEnvCode(Plugin),分别丢到PluginArray.js和Plugin.js里

然后要做的就是创建Plugin实例,把实例丢到PluginArray

对于每个plugin实例,里面又有很多属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0: Plugin

0: MimeType {type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: Plugin}

1: MimeType {type: 'text/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: Plugin}

application/pdf:
MimeType {type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: Plugin}

text/pdf:
MimeType {type: 'text/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: Plugin}

description: "Portable Document Format"

filename: "internal-pdf-viewer"

length: 2

name: "PDF Viewer"

然后脱MimeType环境,丢到MimeType.js里

创建插件数组的逻辑就是定义数据,定义属性,实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建插件
sandBox.createPlugin=function (data) {
let plugin={}
Object.setPrototypeOf(plugin,Plugin.prototype)
plugin=sandBox.proxy(plugin,`Plugin`)
sandBox.setProtoAttribute.call(plugin,'description',data.description)
sandBox.setProtoAttribute.call(plugin,'filename',data.filename)
sandBox.setProtoAttribute.call(plugin,'name',data.name)
sandBox.setProtoAttribute.call(plugin,'length',data.mimetypes.length)
// for循环做的是模拟创建一个上面浏览器里的Plugin实例
for(let i=0;i<data.mimetypes.length;i++){
let mimetype=sandBox.createMimetype(data.mimetypes[i],plugin)
plugin[i]=mimetype
Object.defineProperty(plugin,data.mimetypes[i].type,{value: mimetype, writable: false, enumerable: false, configurable: true})
}
sandBox.pushPluginArray(plugin)
return plugin
}

注意setProtoAttribute什么时候调用的问题,比如对于某个实例,它是多例,而且需要设置的属性对于每个实例而言,属性值都不一样,那么就调用这个函数去设置属性,把属性值设置在实例上(可以这么理解)

创建插件数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//插件数组
sandBox.pushPluginArray=function (plugin) {
let pluginArray=sandBox.pluginArray;
if(pluginArray===undefined){
pluginArray={}
Object.setPrototypeOf(pluginArray,PluginArray.prototype)
pluginArray=sandBox.proxy(pluginArray,`pluginArray`)
sandBox.setProtoAttribute.call(pluginArray,'length',0)
}
pluginArray[pluginArray.length]=plugin
Object.defineProperty(pluginArray,plugin.name,{value: plugin, writable: false, enumerable: false, configurable: true})
// 因为pluginArray的length属性只有get,没有set
// 所以不能直接pluginArray.length+=1
sandBox.setProtoAttribute.call(pluginArray,'length',pluginArray.length+1)
sandBox.pluginArray=pluginArray
return pluginArray
}

这里单独解释一下,为什么不能直接pluginArray.length+=1,而是要sandBox.setProtoAttribute.call(pluginArray,'length',pluginArray.length+1)

因为length 没有 set 方法,所以,不能直接 pluginArray.length += 1,因为这不是简单的数值加法问题,是「属性赋值」的问题

首先理解以下几点

1
2
3
4
5
6
pluginArray.length是一个getter属性(只有 get,没有 set);

所以每次访问 pluginArray.length,是「调用函数拿到值」,而不是直接访问一个数值。

当写pluginArray.length += 1,它实际上会变成:
pluginArray.length = pluginArray.length + 1

也就是:

1
2
3
4
5
先触发pluginArray.length的getter,拿到一个数字;

加 1;

然后尝试给pluginArray.length赋值,也就是set。

但是,这个属性没有 set 方法(在 defineProperty 里定义的是只有 getset: undefined),所以在赋值时,JS引擎发现getter 有,setter 没有,根据规范,禁止赋值,在严格模式("use strict")下甚至会直接抛异常。

简单说:访问可以,加法可以,但赋值不行。

比如有个对象

1
2
3
4
5
6
7
let obj = {};
Object.defineProperty(obj, 'foo', {
get() {
return 123;
},
set: undefined,
});

调用obj.foo += 1; ,实际过程是:obj.foo → 调用 get() → 拿到 123;然后123 + 1 → 得到 124;最后尝试 obj.foo = 124 → 失败,因为 没有 setter

创建mimeType

1
2
3
4
5
6
7
8
9
10
11
12
//mimeType
sandBox.createMimetype=function (mimeData,plugin) {
let mimetype={}
Object.setPrototypeOf(mimetype,MimeType.prototype)
mimetype=sandBox.proxy(mimetype,`mimetype`)
sandBox.setProtoAttribute.call(mimetype,'description',mimeData.description)
sandBox.setProtoAttribute.call(mimetype,'enabledPlugin',plugin)
sandBox.setProtoAttribute.call(mimetype,'suffixes',mimeData.suffixes)
sandBox.setProtoAttribute.call(mimetype,'type',mimeData.type)
sandBox.pushMimeTypeArray(mimetype)
return mimetype
}

上面调用setProtoAttribute的时机也有点不同,因为原型上只有MimeType_suffixes_get,没有对应的set方法,所以直接把属性定义在实例上

创建mimetypeArray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//mimeType数组
sandBox.pushMimeTypeArray=function (mimetype) {
let mimetypeArray=sandBox.mimetypeArray;
if(mimetypeArray===undefined){
mimetypeArray={}
Object.setPrototypeOf(mimetypeArray,MimeTypeArray.prototype)
mimetypeArray=sandBox.proxy(mimetypeArray,`mimetypeArray`)
sandBox.setProtoAttribute.call(mimetypeArray,'length',0)
}
let flag=true
for(let i=0;i<mimetypeArray.length;i++){
if(mimetypeArray[i].type===mimetype.type){
flag=false
}
}
if(flag){
mimetypeArray[mimetypeArray.length]=mimetype
Object.defineProperty(mimetypeArray,mimetype.type,{value: mimetype, writable: false, enumerable: false, configurable: true})
sandBox.setProtoAttribute.call(mimetypeArray,'length',mimetypeArray.length+1)
}
sandBox.mimetypeArray=mimetypeArray
return mimetypeArray
}

后面要做的就是补齐一些属性的get和set方法

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
//插件
sandBox.envFuncs.Navigator_plugins_get=function () {
return sandBox.pluginArray;
}
sandBox.envFuncs.Navigator_mimeTypes_get =function () {
return sandBox.mimetypeArray;
}
sandBox.envFuncs.MimeTypeArray_length_get=function () {
return sandBox.getProtoAttribute.call(this,'length')
}
sandBox.envFuncs.MimeType_type_get=function () {
return sandBox.getProtoAttribute.call(this,'type')
}
sandBox.envFuncs.PluginArray_length_get=function () {
return sandBox.getProtoAttribute.call(this,'length')
}
sandBox.envFuncs.Plugin_name_get=function () {
return sandBox.getProtoAttribute.call(this,'name')
}
sandBox.envFuncs.Plugin_description_get =function () {
return sandBox.getProtoAttribute.call(this,'description')
}
sandBox.envFuncs.Plugin_filename_get =function () {
return sandBox.getProtoAttribute.call(this,'filename')
}
sandBox.envFuncs.Plugin_length_get =function () {
return sandBox.getProtoAttribute.call(this,'length')
}
sandBox.envFuncs.MimeType_description_get =function () {
return sandBox.getProtoAttribute.call(this,'description')
}
sandBox.envFuncs.MimeType_enabledPlugin_get =function () {
return sandBox.getProtoAttribute.call(this,'enabledPlugin')
}
sandBox.envFuncs.MimeType_suffixes_get =function () {
return sandBox.getProtoAttribute.call(this,'suffixes')
}

创建插件方法的调用,可以放在globalVar.js中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sandBox.createPlugin({
mimetypes:[
{
description:"Portable Document Format",
suffixes:"pdf",
type:"application/pdf"
},
{
description:"Portable Document Format",
suffixes:"pdf",
type:"text/pdf"
}
],
description:"Portable Document Format",
filename:"internal-pdf-viewer",
name:"WebKit built-in PDF"
})

后面再补原型链上的其他方法,也就是PluginArray.prototype上的itemnamedItemrefresh方法,还有前面脱环境脱下来的一些方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sandBox.envFuncs.PluginArray_item  =function (index) {
return this[index]
}
sandBox.envFuncs.PluginArray_namedItem =function (name) {
return this[name]
}
sandBox.envFuncs.PluginArray_refresh =function () {
return undefined
}
sandBox.envFuncs.Plugin_item =function (index) {
return this[index]
}
sandBox.envFuncs.Plugin_namedItem =function (name) {
return this[name]
}
sandBox.envFuncs.MimeTypeArray_item =function (index) {
return this[index]
}
sandBox.envFuncs.MimeTypeArray_namedItem =function (name) {
return this[name]
}

Webstorm浏览器联调

打开cmd,运行那npm install -g node-inspect

pycharm中右上角,有edit configurations,在node parameters里添加参数--inspect=127.0.0.1:9222

在 Chrome 地址栏中输入 chrome://inspect/#devices 并回车,勾选“Discover network targets”复选框,点击该复选框旁的“Configure…”按钮,配置端口,然后点击Open dedicated DevTools for Node,就可以选择目标端口,然后环境就断在了浏览器中

addEventListener事件补齐

input.js

1
2
3
4
5
6
7
8
debugger;
console.log('同步代码')
function onloadFunc() {
console.log(`onload运行`)
}
window.addEventListener('onload',onloadFunc);
console.log('同步代码结束')
debugger;

定义全局事件

1
sandBox.asyncCode.eventListener= {}

在envFuncs.js中补方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//异步事件
sandBox.envFuncs.EventTarget_addEventListener=function (type,func,options) {
// 触发事件必须绑定this,不然不知道是哪个对象触发的事件
// 而且如果事件中有回调函数,访问不到this就会报错
let event={
'this':this,
'type':type,
'func':func,
'options':options
}
// 事件初始化
if(sandBox.asyncCode.eventListener[type]===undefined){
sandBox.asyncCode.eventListener[type]=[]
}
// 把事件添加到列表中
sandBox.asyncCode.eventListener[type].push(event)
console.log(`{EventTarget_addEventListener | 需要添加异步事件:${type} 函数:${func.toString()}`)
}

此时运行报错,需要添加异步事件onload函数

然后在页面的asyncCode.js中添加异步代码

1
2
3
4
5
6
7
8
9
10
!function () {
// 如果onload事件不为空,就取出里面的事件挨个执行
if(sandBox.asyncCode.eventListener['onload']!==undefined){
let onloadFuncs=sandBox.asyncCode.eventListener['onload']
for(let i=0;i<onloadFuncs.length;i++){
let event=onloadFuncs[i]
event['func'].call(event['this'])
}
}
}

当然在最后的output.js中,全都是同步执行的,但是为了模拟代码的运行,需要把异步的代码都跑一边

模拟setTimeout事件

input.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
debugger;
console.log('同步代码')
function onloadFunc(a,b) {
console.log(`异步1代码timeout运行:${a+b}`)
}
// setTimeout第一个参数是函数,可以是字符串或者是函数
// 第一个参数是自执行函数,第二个参数是时间
let s2=window.setTimeout("!function (){console.log('异步2')}()",2200)
let s3=window.setTimeout("!function (){console.log('异步3')}()",2500)
// 第一个参数是函数,第二个参数是时间,最后俩是函数的参数
let s1=window.setTimeout(onloadFunc,1000,2,3)
window.clearTimeout(s2)
console.log('同步代码结束')
debugger;

在envFuncs.js中补方法

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
sandBox.envFuncs.window_setTimeout=function () {
sandBox.asyncCode.timeoutID+=1
// 获取参数
let func=arguments[0]
let delay=arguments[1]
let args=[]
for(let i=2;i<arguments.length;i++){
args.push(arguments[i])
}
let type='func'
if(typeof func==='function'){
type='func'
}else{
type='func_str'
}
let event={
'this':this,
'type':type,
'func':func,
'delay':delay,
'args':args,
'timeoutID':sandBox.asyncCode.timeoutID
}
sandBox.asyncCode.setTimeout.push(event)
// 根据时间参数进行排序,需要按时间的顺序执行
sandBox.asyncCode.setTimeout.sort(function (a,b) {
return a.delay-b.delay
})
return sandBox.asyncCode.timeoutID
}

全局定义事件ID和列表

1
2
sandBox.asyncCode.setTimeout= []
sandBox.asyncCode.timeoutID=-1

然后在页面的asyncCode.js中添加异步代码,设置事件执行

1
2
3
4
5
6
7
8
9
10
11
if(sandBox.asyncCode.setTimeout!==[]){
let timeoutFunc=sandBox.asyncCode.setTimeout
for(let i=0;i<timeoutFunc.length;i++){
let event=timeoutFunc[i]
if(event['type']==='func'){
event['func'].apply(event['this'],event['args'])
}else{
eval(event['func'])
}
}
}

模拟clearTimeout事件

上面的setTimeOut的时候给每个事件设置了ID,删除的时候就是根据ID删除

1
2
3
4
5
6
7
8
sandBox.envFuncs.window_clearTimeout=function (timeoutID){
for(let i=0;i<sandBox.asyncCode.setTimeout.length;i++){
let event=sandBox.asyncCode.setTimeout[i]
if(event['timeoutID']===timeoutID){
sandBox.asyncCode.setTimeout.splice(i, 1);
}
}
}

获取浏览器鼠标轨迹

这个是为了做滑块的,首先在开发软件中无法模拟鼠标轨迹,不可能在WebStorm中监听鼠标事件,把WebStorm中的鼠标事件给监听下来,所以需要在浏览器中进行hook,hook浏览器中的鼠标事件,然后把拿到的轨迹放到WebStorm中的node代码中

鼠标轨迹获取,下面是模拟生成鼠标轨迹,js代码去生成鼠标轨迹,无非就是mousedown和mouseup

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
let isMouseDown =false;// 标记鼠标是否按下
let mouseTrace = [];//用于存储鼠标轨迹
//监听鼠标按下事件
document.addEventListener('mousedown',function(event) {
if (event.button === 0) { // 检查是否是鼠标左键按下
isMouseDown = true;
mouseTrace = [];//清空轨迹
}
});

//监听鼠标移动事件
document.addEventListener('mousemove',function(event) {
if (isMouseDown){//将鼠标位置和时间戳添加到轨迹中
mouseTrace.push({ x: event.clientX, y: event.clientY, timestamp: Date.now() });

//如果轨迹列表长度超过50,移除最早的轨迹
if (mouseTrace.length > 20){
mouseTrace.shift();
}
}
});

//监听鼠标释放事件
document.addEventListener('mouseup',function(event){
if (event.button === 0 && isMouseDown) { // 检查是否是鼠标左键释放
isMouseDown = false;//停止搜集轨迹

//将轨迹转换为JSON字符串
let traceJSON = JS0N.stringify(mouseTrace);
//将轨迹进行base64编码并打印出来
let base64Trace = btoa(traceJSON);
console.log(base64Trace);
}
});

hook鼠标轨迹,要想获取到上面的鼠标轨迹,就是hook addEventListener方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let my_tracks=[]
let _addEventListener = document.addEventListener;
document.addEventListener = function(type,func){
let _func = func;
func = function(event) {
let _e = {
clientX:event.clientX,
clientY:event.clientY,
type:event.type,
button:event.button
};
my_tracks.push(_e);
// 当鼠标抬起的时候,断下来
if(type==='mouseup'){
debugger;
}
return _func(event);
}
return _addEventListener(type,func);
}

当断下来的时候,可以控制台打印my_tracks,从而拿到轨迹数据

1
2
3
4
5
6
7
8
9
10
my_tracks
(77) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
0: {clientX: 366, clientY: 374, type: 'mousemove', button: 0}
1: {clientX: 367, clientY: 374, type: 'mousemove', button: 0}
2: {clientX: 368, clientY: 374, type: 'mousemove', button: 0}
3: {clientX: 370, clientY: 374, type: 'mousemove', button: 0}
4: {clientX: 371, clientY: 374, type: 'mousemove', button: 0}
5: {clientX: 372, clientY: 375, type: 'mousemove', button: 0}
6: {clientX: 375, clientY: 375, type: 'mousemove', button: 0}
7: {clientX: 376, clientY: 375, type: 'mousemove', button: 0}

然后把轨迹丢到asyncCode.js中进行模拟

node获取轨迹加密结果

上面拿到了鼠标的轨迹,每个轨迹都是一个event对象,根据上面的demo可以看出,对于每个event,都调用了addEventListener进行轨迹的操作,不管是push到轨迹列表也好,还是最终释放鼠标进行加密也好。那么这里模拟js中加密的操作也很简单,根据前面addEventListener拿到的事件,每个事件有type属性,如果type属性是轨迹event的type属性,说明这个事件是用于轨迹的(也可能用于别的,但是至少大概率有用于轨迹加密),那么就传入event进行事件调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let MouseEvents=[
{
"clientX": 154,
"clientY": 317,
"type": "mousemove",
"button": 0
},
{
"clientX": 152,
"clientY": 316,
"type": "mousemove",
"button": 0
}
]
for(let i=0;i<MouseEvents.length;i++){
let mouseEvent=MouseEvents[i]
let type=mouseEvent['type']
let listeners=sandBox.asyncCode.eventListener[type]
for(let j=0;j<listeners.length;j++){
let listen=listeners[j]
listen['func'].call(listen['this'],mouseEvent)
}
}

注意

在做window对象的时候,不建议一次性把所有的脱环境之后的结果都丢到window.js里去模拟,太多了可能会报错,用到哪个属性就加哪个属性即可

添加标签的document.write代码,因为每个网页不同,可以放到userVar.js里去添加,也就是放到userVar.js里去实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sandBox.envFuncs.Document_write=function (strObj) {
console.log(`{sandBox.envFuncs.Document_write | 请在userVar里边定义write结构}`)
// let nodeElement={
// element:'input',
// prop:{
// type:'hidden',
// id:'test',
// value:'xjb'
// }
// }
// let tag=document.createElement(nodeElement.element)
// for(const key in nodeElement.prop){
// tag[key]=nodeElement.prop[key]
// if(tag[key]===undefined){
// sandBox.setProtoAttribute.call(this,key,nodeElement.prop[key])
// }
// }
}