声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在CSDN 私信 联系作者立即删除!
- Proxy 是 JavaScript 中的一种强大的元编程工具,用于捕获并拦截对象的各种操作。在逆向工程中,我们可以使用 Proxy 对象来修改 JavaScript 对象的行为,以实现一些特殊的需求
Proxy是什么:
- Proxy可以对目标对象的读取,函数调用等操作进行拦截,并对其进行操作处理。
- Proxy不直接操作对象,而是像代理模式一样,对对象进行操作,同时还可以添加一些额外的操作
Proxy基础用法
1. 首先定义一个目标对象(需要被代理的对象) 和一个处理器对象(handler)。处理器对象声明了对目标对象的指定行为
// 目标对象
var target = {
name: 'JACK',
age: 18,
}
// 代理行为对象
var handler = {
get:function (target,property,receiver) {
/*
* target: 被代理对象
* property: 属性名
* receiver: 代理后的对象
* */
console.log("get: ",target,property,target[property])
return target[property]
},
set:function (target,property,value) {
/*
* target: 被代理对象
* property: 属性名
* value: 重新设置的值
* */
console.log("set:",target,property,value)
return Reflect.set(...arguments)
}
}
// 使用Proxy构造函数实例化新的target对象
let proxiedTarget = new Proxy(target,handler)
// todo 如果进行取值操作,就会触发代理行为的get方法
console.log(proxiedTarget.name)
// todo 如果会值进行设置,就会触发代理行为的set方法
proxiedTarget.name = "雷点"
console.log(proxiedTarget.name)
Proxy在补环境中的作用
在js逆向中,对某个加密算法确定之后,要对其导出到本地进行调试直到能准确的出值。但是如果某个网站检测了浏览器指纹,等浏览器环境,我们就需要深入分析进行补环境,以得到导出的算法能准确的使用
通常,运行导出的算法后,通过undefined或报错来逐步分析和补环境,这样比较耗时
我们可以使用 Proxy 对全局遍历 window
、document
、navigator
等常见环境检测点进行代理,拦截读取、函数调用等操作,并通过控制台输出。这样,我们就能够实现检测环境自吐的功能,后续再针对吐出来的环境进行统一的补环境,更加方便
Proxy代理学习
一. Proxy代理与Reflect反射简介
Proxy (代理):
- Proxy是ES6中引入的新特性,允许拦截并自定义对象的操作
- 通过常见一个代理对象,可以在访问目标对象时添加额外的操作或修改行为
- 代理对象与被代理对象实现相同的接口,接受并处理对被代理的访问操作
- 主要用于拦截对象的,读取,设置,调用等操作,从而实现预处理或附加操作
Proxy (代理) 在js逆向中的使用场景:
- 环境补全:在js逆向中,可以使用 Proxy对常见的环境监测点(如
window
、document
、navigator
等)进行代理,拦截并读取和函数调用等操作,并输出环境信息,达到高效的补全环境
Reflect (反射):
- Refect是ES6中的另一个内置对象,提供了一系列方法,让开发者通过调用这些方法访问底层功能
- 反射允许我们在运行时获取,操作,修改对象的状态和行为
- 反射可以用于判断属性是否存在,调用函数,设置属性等操作
Reflect (反射) 常见使用场景:
- 属性操作:使用 Reflect 可以读取和设置对象的属性值,判断属性是否存在,删除属性等。
- 函数调用:可以动态调用函数,绑定 this 和参数列表。
- 构造函数:通过 Reflect.construct 创建对象实例。
- 属性描述符:获取和设置属性的描述符。
二. Proxy基本使用
语法:const p = new Proxy(target,handler)
- target: 要使用Proxy代理的目标对象(可以是任何类型的对象,包括原生数组,函数等)
- handler:函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
- handler对象的方法:
- getPrototypeOf: Object.getPrototypeOf 方法的拦截器
- setPrototypeOf: Object.setPrototypeOf 方法的拦截器
- isExtensible:Object.isExtensible() 方法的拦截器
- preventExtensions:Object.preventExtensions() 方法的拦截器
- getOwnPropertyDescriptor:Object.getOwnPropertyDescriptor() 方法的拦截器
- defineProperty:Object.defineProperty() 方法的拦截器
- get: 属性读取操作的拦截器
- set: 属性设置操作的拦截器
- deleteProperty: deleteProperty()是 delete操作符的拦截器
- apply: 函数调用操作时的拦截器
- construct:new 时的拦截器
- handler对象的方法:
- 想要了解更多关于Proxy的方法请前往:Proxy - JavaScript | MDN
-
想要了解更多关于Reflect的方法请前往:Proxy - JavaScript | MDNReflect - JavaScript | MDNProxy - JavaScript | MDN
代理对象:
// 使用Proxy拦截Obj对象
let Obj = {
name:"小明"
}
// 定义拦截器
let handler = {
get(target,property,obj){
/*
* target: 原对象
* property: 属性名
* obj: 代理后的对象
* */
console.log(`读取了: ${property}属性`)
return target[property]
},
set(target,property,value){
/*
* target: 原对象
* property: 属性名
* value: 要修改的值
* */
console.log(`设置了: ${property}属性为${value}`);
target[property] = value
}
}
// 实例化代理对象
let proxy = new Proxy(Obj,handler)
// 读取操作是触发get方法
console.log(proxy.name)
// 设置操作触发set方法
proxy.name = "雷点"
代理函数:
function sum(a,b){
return a + b
}
// 定义拦截器
let handler = {
apply:function (func,thisArg,argumentsList) {
/*
* func: 代理的原函数
* thisArg: func调用者
* argumentsList: 函数参数
* */
console.log(func)
console.log(thisArg)
console.log(argumentsList)
return func.apply(thisArg, argumentsList);
}
}
let proxy = new Proxy(sum,handler)
console.log(proxy(1, 2));
三. Proxy与Reflect基本使用
/*
* Proxy作用: 监控对象操作
* Reflect作用: 执行原始操作
* */
let symbol = Symbol(123)
// 定义一个需要被代理的对象
let user = {
"name":"小明",
1:2,
[symbol]:"symbol111"
}
// 参数1: 需要代理的对象或函数等
// 参数2: 拦截器
let proxy = new Proxy(user,{
get:function (target, prop, receiver) {
/*
* target: 原始对象
* prop: 属性名,只有两种类型,string|symbol,其他传入的类型都会转为string类型
* receiver: 被代理后的对象
* */
// 直接通过原始对象target[prop]获取属性值
// 不会递归
// let result = target[prop]
// 通过Reflect.get(target, prop, receiver)获取属性值
// 可以递归
let result = Reflect.get(target,prop,receiver)
console.log(`get方法 对象:${JSON.stringify(target)} 属性:${prop.toString()} 返回值:${result}`)
// if (typeof prop=== 'string'){
// console.log(`get方法 对象:${JSON.stringify(target)} 属性:${prop} 返回值:${result}`)
// }else if (typeof prop === "symbol"){
// // symbol不能使用模板字符串,不然会报错,所以这里做了一层判断
// console.log("正在获取: ",prop)
// }
return result
},
set:function (target, prop, newValue, receiver) {
/*
* target: 原始对象
* prop: 属性名
* newValue: 修改的值
* receiver: 被代理后的对象
* */
console.log(`set方法 对象:${JSON.stringify(target)} 属性:${prop} 修改的值:${newValue}`)
target[prop] = newValue
}
})
// 使用name属性时触发get方法
console.log(proxy.name);
// 设置name属性时触发set方法
proxy.name = "雷点1"
// ===================================
console.log(proxy[symbol])
// ===================================
console.log(proxy[1])
// ===================================
// 输出undefined,因为当前代理的对象中不存在这个属性,
// 在补环境中使用代理时,当一个属性或者方法被调用得到的结果是undefined时就需要去进行补环境
console.log(proxy.age)
四. 简单模拟proxy应用场景
let window = {
}
// 代理,window
window = new Proxy(window,{
get(target, p, receiver) {
let result = Reflect.get(target,p,receiver)
console.log(`get方法 对象:${JSON.stringify(target)} 属性:${p.toString()} 返回值:${result}`)
return result
},
set:function (target, p, newValue, receiver) {
console.log(`set方法 对象:${JSON.stringify(target)} 属性:${p.toString} 修改的值:${newValue}`)
target[p] = newValue
}
})
// 假设以下是抠出来的代码,然后调用href,但是href我们在window中没有定义,那么就会输出 返回值undefined,这时候我们就要去补这个href属性
// 根据调用的属性去当前目标网站根据控制台输出,拿到结果然后补到window身上,也可以使用脱环境的方式去拿到浏览器中的环境
console.log(window.href);
这时候window.href属性是undefined,我们就要去当前调试的目标网站将href属性抠出来补到window身上
注:如果还不了解的话到后面会有实战案例,尽情期待!
五. Proxy封装-1
function BrokerProxy(obj,objName){
/*
* obj: 需要代理的对象
* objName: 代理对象的名字
* 疑问点: 为什么还要传入对象的名字,因为不能直接通过对象来获取对象的名字,所以需要传进来
* */
return new Proxy(
obj,
{ // 拦截器方法
get(target, prop, receiver){
/*
* target: 目标对象
* prop: 被获取的属性名
* receiver: 被代理后的对象
* */
let result = Reflect.get(target, prop, receiver) // 反射,执行原始的操作
console.debug(`get方法: 对象: ${objName} 属性名: ${prop.toString()} 属性值: ${result}`)
return result
},
set(target, prop, value){
/*
* target: 目标对象
* prop: 被修改的属性名
* value: 新属性值
* */
console.debug(`set方法: 对象: ${objName} 设置的属性名: ${prop.toString()} 设置的属性值: ${value}`)
target[prop] = value
}
}
)
}
let symbol = Symbol(123)
let window = {
name:"雷点",
true:2,
[symbol]:"symbol123",
age:123
}
window = BrokerProxy(window,'window')
console.log(window.name);
window.name = "javascript"
代码解读:
- 首先定义一个函数:BrokerProxy
- BrokerProxy函数接收两个参数:obj,objName
- obj: 需要被代理的对象
- objName: 被代理对象的名称
- 进入函数内部后,return new Proxy(obj,{get,set}) 返回代理后的对象
- new Proxy(obj,{get,set})
- obj:当前被代理的对象
- get 拦截器方法:调用被代理对象的属性时触发
- set 拦截器方法:设置被代理对象的属性时触发
- new Proxy(obj,{get,set})
- 在get拦截器方法中接收三个参数:target,prop,receiver
- target:目标对象
- prop: 被获取的属性名
- receiver:被代理后的对象
- 在get拦截器中的操作:
- 通过Reflect.get() 执行原始操作,返回被获取的属性
- let result = Reflect.get(target,prop,receiver)
- 日志输出:详细输出了,被调用的对象名,调用的属性名,属性值
- console.debug(`get方法: 对象: ${objName} 属性名: ${prop.toString()} 属性值: ${result}`)
- 通过Reflect.get() 执行原始操作,返回被获取的属性
- 在set拦截器方法中接收三个参数:target,prop,value
- target:目标对象
- prop: 被设置的属性名
- value:新属性值你
- 在set拦截器中的操作
- 日志输出:详细输出了,被设置的对象名,设置的属性名,设置的值
- console.debug(`set方法: 对象: ${objName} 设置的属性名: ${prop.toString()} 设置的属性值: ${value}`)
- 日志输出:详细输出了,被设置的对象名,设置的属性名,设置的值
- 走到函数外部:
- 定义了一个window对象:window = BrokerProxy(window,'window')
- 在window对象加载后使用自定义封装的BrokerProxy函数对window进行代理,返回被代理后的window对象
- 通过返回被代理的window对象获取name操作:console.log(window.name); 这时候就会触发 拦截器方法get方法,并且执行get方法里面的操作
- 通过返回被代理的window对象设置name操作:window.name = "javascript"; 这时候就会触发 拦截器方法set,并且执行set方法里面的操作
六. Proxy封装-2
关于proxy封装的第二篇不在做多余的讲解,详细的注释已经在代码中写上,如果还不了解可以前往:Proxy() 构造函数 - JavaScript | MDN 进一步详细的了解
/*
* 代理器方法封装
* */
function getType(obj){
return Object.prototype.toString.call(obj)
}
function BrokerProxy(obj,objName){
/*
* obj: 需要代理的对象
* objName: 代理对象的名字
* 疑问点: 为什么还要传入对象的名字,因为不能直接通过对象来获取对象的名字,所以需要传进来
* */
return new Proxy(
obj,{
// 用于拦截对象的 获取值的 操作
get(target, prop, receiver){
/*
* target: 目标对象
* prop: 被获取的属性名
* receiver: 被代理后的对象
* */
let result;
try{
result = Reflect.get(target, prop, receiver) // 反射,执行原始的操作
let type = getType(result)
if(result instanceof Object){
console.debug(`get方法[获取]: ${objName}.${prop.toString()} [属性值]: ${JSON.stringify(result)} [类型]:${type} `)
// 如果属性是一个对象的话就进行递归代理
result = BrokerProxy(result,`${objName}.${prop.toString()}`)
}else if(typeof result === "symbol"){
console.debug(`get方法[获取]: ${objName}.${prop.toString()} [属性值]: ${result.toString()}`)
}else{
console.debug(`get方法[获取]: ${objName}.${prop.toString()} [属性值]: ${result}`)
}
}catch (e) {
console.error(`get方法[获取]: 对象: ${objName} [属性]:${prop.toString()} [错误信息]: ${e.message}`)
}
return result
},
// 用于拦截对象的 设置值的 操作
set(target, prop, value){
/*
* target: 目标对象
* prop: 被修改的属性名
* value: 新属性值
* */
// 使用 Reflect.set
try{
let success = Reflect.set(target, prop, value);
let type = getType(value)
if(value instanceof Object){
console.debug(`set方法[设置]: ${objName}.${prop.toString()} [设置的属性值]: ${JSON.stringify(value)} [类型]: ${type}`)
}else if (typeof value === "symbol"){
console.debug(`set方法[设置]: ${objName}.${prop.toString()} [设置的属性值]: ${value.toString()}`)
}else{
console.debug(`set方法[设置]: ${objName}.${prop.toString()} [设置的属性值]: ${value}`)
}
}catch (e) {
console.error(`设置属性失败: ${objName}.${prop.toString()}`);
}
},
// 用于拦截对象的 Object.getOwnPropertyDescriptor() 的操作
// Object.getOwnPropertyDescriptor() 方法,用于获取属性描述符信息
getOwnPropertyDescriptor(target, prop) {
/*
* target: 目标对象
* prop: 需要返回属性名称的描述符的,属性名
* getOwnPropertyDescriptor 方法必须返回一个 object 或 undefined。
* */
try {
result = Reflect.getOwnPropertyDescriptor(target, prop)
if (typeof result !== "undefined"){
console.debug(`getOwnPropertyDescriptor方法[获取属性描述符]: ${objName}.${prop.toString()} [获取属性描述符]: ${JSON.stringify(result)}`)
// result = BrokerProxy(result,`${objName}.${prop.toString()}`)
}else{
console.debug(`getOwnPropertyDescriptor方法[获取属性描述符]: ${objName}.${prop.toString()} [获取属性描述符]: ${JSON.stringify(result)} [提示]: ${prop.toString()}属性不存在`)
}
}catch (e) {
console.error(`getOwnPropertyDescriptor方法[获取属性描述符]: 对象: ${objName} [获取属性描述符]:${prop.toString()} [错误信息]: ${e.message}`)
}
return result
},
// 用于拦截对象的 Object.defineProperty() 操作
// Object.defineProperty() 方法,用于设置属性描述符信息
defineProperty(target, prop, descriptor) {
/*
* target: 目标对象
* prop: 需要修改属性描述符的,属性名称
* descriptor: 需要重新定义属性描述符对象
* */
try {
let result = Reflect.defineProperty(target, prop, descriptor);
console.debug(`defineProperty方法[设置属性描述符]: ${objName}.${prop.toString()} [设置的属性描述符]: ${JSON.stringify(descriptor)}`)
}catch (e) {
console.error(`defineProperty方法[获取属性描述符]: 对象: ${objName} [设置属性描述符]:${prop.toString()} [错误信息]: ${e.message}`)
}
return result
},
// 当代理的对象是一个函数时,返回的函数对象触发apply方法
// 或当调用对象中的属性值是一个函数时,触发apply方法
apply(target, thisArg, argArray) {
/*
* target: 目标对象
* thisArg: 被调用时的上下文对象
* argArray: 被调用时的参数数组,例如:[2,3]
* */
let result
try {
result = Reflect.apply(target, thisArg, argArray)
// console.debug(`apply方法[调用函数]: ${objName} [函数参数]: ${argArray} [返回结果]: ${JSON.stringify(result)}`)
if (result instanceof Object){
console.debug(`apply方法[调用函数]: ${objName} [返回结果]: ${JSON.stringify(result)}`)
}else if (typeof result === "symbol"){
console.debug(`apply方法[调用函数]: ${objName} [返回结果]: ${result.toString()}`)
}else{
console.debug(`apply方法[调用函数]: ${objName} [返回结果]: ${result}`)
}
}catch (e) {
console.error(`apply方法[调用函数]: ${objName} [函数参数]: ${argArray} [错误信息]: ${e.message}`)
}
return result
},
// construct 方法用于拦截 new 操作符
construct(target, argArray, newTarget) {
/*
* target: 目标对象
* argArray: constructor 的参数列表。
* newTarget: 代理后的对象
* */
let result
try {
result = Reflect.construct(target, argArray, newTarget)
let type = getType(result)
console.debug(`construct方法[new ${objName}]: ${objName} [类型]: ${type}`)
}catch (e) {
console.error(`construct方法[new ${objName}]: ${objName} [错误信息]: ${e.message}`)
}
return result
},
// 拦截对象自身属性的 delete 操作
// 当对对象某个属性进行删除: delete user.name 时触发
deleteProperty(target, prop) {
/*
* target: 目标对象
* prop: 要进行delete操作的属性名
* */
let result = Reflect.deleteProperty(target,prop)
console.debug(`deleteProperty方法[删除]: delete ${objName}.${prop} [是否可删除]: ${result}`)
return result
},
// 当对自身属性进行遍历时触发
ownKeys(target) {
/*
* target: 目标对象
* */
let result = Reflect.ownKeys(target)
console.debug(`ownKeys方法[正在属性遍历的对象]: ${objName}`)
return result
},
// 当获取代理对象的原型时,触发
// 对象.__proto__时触发
// 或 Object.getPrototypeOf 时触发
getPrototypeOf(target) {
/*
* target: 目标对象
* */
let result = Reflect.getPrototypeOf(target)
console.debug(`getPrototypeOf方法[获取原型对象] ${objName}.__proto__`)
return result
},
// 当Object.setPrototypeOf设置对象原型时,触发
setPrototypeOf(target, value) {
/*
* target: 目标对象
* value: 要设置的值
* */
let result = Reflect.setPrototypeOf(target,value)
console.debug(`setPrototypeOf方法 [设置原型对象] ${objName}.__proto__ = [设置内容]: ${value}`)
return result
}
}
)
}
let window = {
name:"雷点",
info:{
name:"小红",
age:10,
info2:{
name:"红",
age:10,
}
},
add:function (a, b) {
return a + b
}
}
// 代理的使用: 需要在window对象加载后使用
window = BrokerProxy(window,'window')
console.log("==============get方法===================")
window.name
window.info.name
window.info.info2.name
console.log("==============set方法===================")
window.name = "javascript"
window.clas = {name:"小星星"}
window.clas
window.clas.name
console.log("==============getOwnPropertyDescriptor方法===================")
// 获取window对象的name属性操作符时,触发getOwnPropertyDescriptor拦截器
// 当获取的是一个不存在的属性时,得到的属性描述符结果是一个: undefined
// Object.getOwnPropertyDescriptor(window,"aaaaaaaaaaa")
// 当获取的是存在的属性时,得到的属性描述符结果是对应的描述符信息: {"value":"javascript","writable":true,"enumerable":true,"configurable":true}
Object.getOwnPropertyDescriptor(window,"name")
console.log("==============defineProperty方法===================")
// 当window给name属性设置属性描述符的时候触发 defineProperty拦截器
Object.defineProperty(window,"name",{
writable:false,
configurable:true,
value:"设置属性值"
})
console.log("==============apply方法===================")
// 情况1: 当调用的是window对象中的函数时,触发apply方法
window.add(10,20)
// 情况2: 单独代理的是一个函数时,调用函数,触发apply方法
function add2(a,b) {
return a * b
}
add2 = BrokerProxy(add2,"add2")
add2(100,200)
function add3(a,b) {
return a
}
add3 = BrokerProxy(add3,"add3")
add3({"name":"小红"},200)
add3(Symbol(),200)
console.log("==============construct方法===================")
function LeiDian() {
}
LeiDian = BrokerProxy(LeiDian,"LeiDian")
l = new LeiDian()
console.log("================deleteProperty=================")
// 触发代理对象的deleteProperty拦截器
// 如果属性描述符configurable是false那么就不能delete操作
delete window.name
console.log("================ownKeys=================")
// 触发代理对象的ownKeys拦截器
// 获取对象所有自身可枚举属性组成的一个数组
console.log(Object.keys(window));
console.log("================getPrototypeOf=================")
window.__proto__
Object.getPrototypeOf(window)
console.log("================setPrototypeOf=================")
let aaaaa = {
}
aaaaa = BrokerProxy(aaaaa,"aaaaa")
Object.setPrototypeOf(aaaaa, {});
Proxy实战
首先抠出来的js代码,我提前导出了出值的方法
将代码拖到浏览器进行执行,可以看到是可以拿到结果的并且是动态的,由于浏览器自带的环境所以浏览器运行是没有报错的
接下来拖到本地node环境中执行,报错了一个window未定义,我们在顶部定义一下:
window = globalThis;
补了windnow之后继续运行,又报userAgent未定义
这时候发现都是每次运行才报错什么什么未定义,能不能直接知道他做了什么我们直接去补呢,这时候就需要使用到proxy代理,
将window挂到代理上后,运行就能看到下面的js调用了那些方法,做了什么,然后哪里undefined就补哪里
最后这是我补的
然后运行和网站的对比,这个导出的函数生成的值不是固定的,长度一样
还有如果你正在调试某个网站,根据proxy输出的方法或者函数不懂的话,可以在当前网站的控制台调试输出,或者百度,例如:window.setTimeout等......