902 字
3 分钟
js逆向补环境入门
补环境知识总结:对象类型与属性详解
一、this 问题详解
1.1 问题现象
在 Proxy 的 get handler 中,如果直接返回函数,调用时的 this 会指向 Proxy 而不是原始对象:
const target = { value: 42, getData: function() { return this.value; // this 应该指向 target }};
const proxy = new Proxy(target, { get(target, property, receiver) { const value = target[property]; return value; // 问题:函数被返回后,this 不再是 target }});
proxy.getData(); // this 指向 proxy,而不是 target// this.value === undefined(proxy 没有 value 属性)1.2 解决方案
方案一:使用 bind 绑定 this
get(target, property, receiver) { const value = target[property]; if (typeof value === 'function') { return value.bind(target); // 显式绑定 this 到 target } return value;}缺点:每次调用都会创建新函数,有性能开销
方案二:使用 Reflect.get(推荐)
get(target, property, receiver) { // Reflect.get 会自动处理 receiver,确保正确的 this return Reflect.get(target, property, receiver);}优点:
- 自动处理 this 上下文
- 保持 Proxy 的响应式特性
- 性能更好
方案三:返回函数但不改变 this(当需要代理行为时)
get(target, property, receiver) { const value = target[property]; if (typeof value === 'function') { // 使用闭包记录原始函数,但仍让 this 通过 receiver 传递 return function(...args) { // 这里的 this 会正确传递到原函数 return value.apply(this, args); }; } return value;}1.3 set handler 的 this 问题
set(target, property, newValue, receiver) { // 必须使用 Reflect.set 保持 receiver 上下文 // 否则 this 的属性修改会作用在错误的对象上 return Reflect.set(target, property, newValue, receiver);}二、JavaScript 对象类型分类
在补环境时,浏览器对象主要分为以下几种类型:
| 类型 | 示例 | 特征 |
|---|---|---|
| Function | window.alert、document.createElement | 可调用,有 name |
| Object | window.history、navigator | 普通对象,有原型链 |
| Symbol | Symbol.toStringTag、Symbol.iterator | 唯一标识符,有 description |
| Constructor | Window、Document、Navigator | 可 new 实例化,有 prototype |
三、各类型对象的属性详解
3.1 Function 类型
3.1.1 核心属性
| 属性 | 说明 | 示例 |
|---|---|---|
name | 函数名称 | window.alert.name === "alert" |
length | 参数个数 | String.prototype.indexOf.length === 2 |
prototype | 原型对象 | 用于 prototype 链继承 |
call | 调用函数 | func.call(thisArg, ...args) |
apply | 调用函数 | func.apply(thisArg, [args]) |
bind | 绑定 this | func.bind(thisArg, ...args) |
3.1.2 toString 输出
浏览器原生函数:
window.alert.toString();// "function alert() { [native code] }"
document.createElement.toString();// "function createElement() { [native code] }"普通函数:
function test() {}test.toString();// "function test() {}"3.1.3 补环境要点
// 模拟原生函数const fakeCreateElement = function createElement(tagName) { return { tagName: tagName.toUpperCase() };};
// 设置 name 属性Object.defineProperty(fakeCreateElement, 'name', { value: 'createElement', writable: false, enumerable: false, configurable: true});
// 使用 safeFunction 处理 toStringsafeFunction(fakeCreateElement);
// 设置 prototypefakeCreateElement.prototype = {};3.2 Object 类型
3.2.1 普通属性
| 属性 | 说明 |
|---|---|
hasOwnProperty | 检查自身属性 |
propertyIsEnumerable | 检查是否可枚举 |
isPrototypeOf | 检查原型链 |
valueOf | 返回原始值 |
toString | 转换为字符串 |
3.2.2 Symbol 属性
| 属性 | 说明 | 浏览器输出 |
|---|---|---|
Symbol.toStringTag | 对象类型标识 | Object.prototype.toString.call(obj) |
Symbol.toPrimitive | 类型转换 | 当对象参与运算时的转换 |
Symbol.iterator | 迭代器 | for...of 循环支持 |
Symbol.hasInstance | instanceof | instanceof 检测 |
3.2.3 补环境要点
const fakeNavigator = { userAgent: 'Mozilla/5.0...', appName: 'Netscape',};
// 必须设置 Symbol.toStringTagObject.defineProperty(fakeNavigator, Symbol.toStringTag, { value: 'Navigator', writable: false, enumerable: false, configurable: false});
// 可选:设置 Symbol.toPrimitiveObject.defineProperty(fakeNavigator, Symbol.toPrimitive, { value: function(hint) { if (hint === 'string') return '[object Navigator]'; return null; }, writable: false, enumerable: false, configurable: false});3.3 Symbol 类型
3.3.1 Symbol 的特性
// Symbol 是唯一标识符const s1 = Symbol('test');const s2 = Symbol('test');s1 === s2 // false
// Symbol 有 descriptions1.description // "test"
// Symbol 不能被 for...in 枚举// Symbol 作为属性键时,需要用 Object.getOwnPropertySymbols 获取3.3.2 常见的浏览器 Symbol
| Symbol | 用途 | 示例 |
|---|---|---|
Symbol.toStringTag | Object.prototype.toString.call() 的返回值 | '[object Window]' |
Symbol.toPrimitive | 类型转换 | Number(obj) 时调用 |
Symbol.iterator | 迭代协议 | for...of、... 展开 |
Symbol.hasInstance | instanceof | obj instanceof Class |
Symbol.isConcatSpreadable | concat 展开 | [].concat(obj) |
Symbol.unscopables | with 语句 | with(obj) { ... } |
3.3.3 补环境要点
// 模拟 Symbol.toStringTagconst obj = {};Object.defineProperty(obj, Symbol.toStringTag, { value: 'HTMLDocument', enumerable: false, configurable: false});
Object.prototype.toString.call(obj);// "[object HTMLDocument]"
// 模拟 Symbol.iteratorconst arrLike = { 0: 'a', 1: 'b', length: 2 };Object.defineProperty(arrLike, Symbol.iterator, { value: function*() { for (let i = 0; i < this.length; i++) { yield this[i]; } }, enumerable: false, configurable: false});
// 现在可以 for...offor (const item of arrLike) { console.log(item); // 'a', 'b'}3.4 Constructor 类型
3.4.1 构造函数的结构
function Window() {}Window.prototype = { constructor: Window, // 原型方法 alert: function() {}, document: null,};
// 实例通过 prototype 链访问方法const win = new Window();win.alert(); // 通过 prototype 链找到3.4.2 构造函数的属性
| 属性 | 说明 |
|---|---|
name | 构造函数名称 |
length | 参数个数 |
prototype | 原型对象 |
prototype.constructor | 指回构造函数 |
3.4.3 补环境要点
// 创建构造函数function createConstructor(name, prototypeMethods) { const Constructor = function() {};
// 设置 name Object.defineProperty(Constructor, 'name', { value: name, writable: false, enumerable: false, configurable: true });
// 设置 prototype Constructor.prototype = prototypeMethods || {};
// 确保 constructor 属性正确 Object.defineProperty(Constructor.prototype, 'constructor', { value: Constructor, writable: false, enumerable: false, configurable: false });
// 设置 toStringTag Object.defineProperty(Constructor, Symbol.toStringTag, { value: name, writable: false, enumerable: false, configurable: false });
return Constructor;}四、属性描述符详解
4.1 数据描述符 vs 访问器描述符
// 数据描述符:有 value 的属性Object.defineProperty(obj, 'prop1', { value: 123, writable: true, // 可写 enumerable: true, // 可枚举 configurable: true // 可配置});
// 访问器描述符:有 get/set 的属性Object.defineProperty(obj, 'prop2', { get: function() { return 456; }, set: function(v) { console.log('set:', v); }, enumerable: true, configurable: true});4.2 描述符属性说明
| 属性 | 说明 | 默认值 |
|---|---|---|
value | 属性值 | undefined |
writable | 是否可写 | false |
enumerable | 是否可枚举 | false |
configurable | 是否可删除/修改描述符 | false |
get | getter 函数 | undefined |
set | setter 函数 | undefined |
4.3 检测手段与对策
// 检测方法1:直接访问obj.prop1; // 返回 valueobj.prop2; // 调用 get
// 检测方法2:Object.getOwnPropertyDescriptorconst desc = Object.getOwnPropertyDescriptor(obj, 'prop');if (desc) { console.log('value' in desc); // 数据描述符有 value console.log('get' in desc); // 访问器描述符有 get console.log('writable:', desc.writable); console.log('enumerable:', desc.enumerable); console.log('configurable:', desc.configurable);}
// 检测方法3:Object.keys / for...inObject.keys(obj); // 只返回 enumerable:true 的属性for (const k in obj) {} // 遍历 enumerable:true 的属性
// 检测方法4:JSON.stringifyJSON.stringify(obj); // 只序列化 enumerable:true 的属性4.4 浏览器对象的描述符特征
// 浏览器中,navigator.userAgent 是访问器描述符const desc = Object.getOwnPropertyDescriptor(navigator, 'userAgent');// { get: [Function], set: undefined, enumerable: true, configurable: true }
// 而我们自己定义的属性可能是数据描述符const fakeNav = { userAgent: '...' };// { value: '...', writable: true, enumerable: true, configurable: true }
// 检测代码可能区分这两种if (desc.get) { // 浏览器原生的} else { // 可能是伪造的}4.5 补环境时的描述符设置
// 模拟浏览器使用 getterObject.defineProperty(fakeNavigator, 'userAgent', { get: function() { return 'Mozilla/5.0...'; }, enumerable: true, configurable: true});
// 模拟只读属性Object.defineProperty(fakeNavigator, 'appName', { value: 'Netscape', writable: false, // 不可写 enumerable: true, configurable: false // 不可删除});
// 模拟 Symbol 属性Object.defineProperty(fakeNavigator, Symbol.toStringTag, { value: 'Navigator', writable: false, enumerable: false, // Symbol 属性默认不可枚举 configurable: false});五、环境检测与对策总结
5.1 检测类型一览
| 检测类型 | 检测代码 | 对策 |
|---|---|---|
| 全局对象 | typeof window === 'undefined' | 定义 window 对象 |
| process | 'process' in window | delete global.process |
| Buffer | typeof Buffer !== 'undefined' | delete global.Buffer |
| toString | Object.prototype.toString.call(w) | 设置 Symbol.toStringTag |
| Function.toString | fn.toString() 检查 [native code] | 使用 safeFunction |
| 属性描述符 | Object.getOwnPropertyDescriptor | 使用 defineProperty |
| 原型链 | window instanceof Window | setPrototypeOf |
| webdriver | navigator.webdriver | 设为 false |
| plugins | navigator.plugins.length | 提供 PluginArray |
5.2 Proxy 拦截器与检测对应
| Proxy Handler | 检测代码 | 说明 |
|---|---|---|
get | obj.prop | 属性读取 |
set | obj.prop = value | 属性设置 |
has | prop in obj | 属性存在检测 |
ownKeys | Object.keys(obj) | 属性枚举 |
deleteProperty | delete obj.prop | 属性删除 |
getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor | 描述符查询 |
apply | proxy(...args) | 函数调用 |
六、完整补环境清单
6.1 基础对象清单
window├── document│ ├── createElement()│ ├── createEvent()│ ├── createTextNode()│ ├── getElementById()│ ├── getElementsByTagName()│ ├── querySelector()│ ├── querySelectorAll()│ ├── head (Element)│ ├── body (Element)│ └── cookie (String)├── navigator│ ├── userAgent (String)│ ├── platform (String)│ ├── language (String)│ ├── languages (Array)│ ├── appName (String)│ ├── appVersion (String)│ ├── hardwareConcurrency (Number)│ ├── deviceMemory (Number)│ ├── webdriver (Boolean)│ ├── plugins (PluginArray)│ └── connection (NetworkInfo)├── location│ ├── href (String)│ ├── protocol (String)│ ├── host (String)│ ├── hostname (String)│ ├── pathname (String)│ ├── search (String)│ ├── hash (String)│ ├── assign()│ ├── replace()│ └── reload()├── screen│ ├── width (Number)│ ├── height (Number)│ ├── availWidth (Number)│ ├── availHeight (Number)│ ├── colorDepth (Number)│ └── pixelDepth (Number)├── history│ ├── length (Number)│ ├── back()│ ├── forward()│ └── go()├── localStorage│ ├── getItem()│ ├── setItem()│ ├── removeItem()│ ├── clear()│ └── key()├── sessionStorage (同 localStorage)└── chrome (Object) └── runtime (Object)6.2 Symbol 清单
Window├── Symbol.toStringTag = 'Window'└── Symbol.toPrimitive
Navigator├── Symbol.toStringTag = 'Navigator'└── Symbol.toPrimitive
Document / HTMLDocument├── Symbol.toStringTag = 'HTMLDocument'└── Symbol.toPrimitive
Element / HTMLElement├── Symbol.toStringTag = 'HTMLDivElement' (等)└── Symbol.iterator (for...of 支持)
Location├── Symbol.toStringTag = 'Location'└── Symbol.toPrimitive
Array├── Symbol.toStringTag = 'Array'├── Symbol.iterator└── Symbol.isConcatSpreadable
Object├── Symbol.toString└── Symbol.hasInstance6.3 构造函数清单
EventTarget└── prototype ├── addEventListener() ├── removeEventListener() └── dispatchEvent()
Node (extends EventTarget)└── prototype ├── appendChild() ├── removeChild() ├── insertBefore() ├── getAttribute() ├── setAttribute() └── textContent
Window (extends WindowProperties -> EventTarget)└── prototype ├── alert() ├── confirm() ├── setTimeout() ├── setInterval() ├── eval() ├── document ├── navigator ├── location └── ...
HTMLDocument└── prototype ├── getElementById() ├── getElementsByTagName() ├── querySelector() └── ...
HTMLElement└── prototype ├── style ├── innerHTML ├── textContent ├── appendChild() └── ...6.4 需要 delete 的 Node.js 特征
// 必须在加载目标代码前删除delete global.Buffer;delete global.process;delete global.module;delete global.exports;delete global.require;delete global.__dirname;delete global.__filename;delete Error.prepareStackTrace;七、总结
7.1 对象类型与关键属性
| 类型 | 关键属性 | 防检测要点 |
|---|---|---|
| Function | name, length, prototype, toString | safeFunction 处理 |
| Object | hasOwnProperty, toString, valueOf | Symbol.toStringTag |
| Symbol | description, 各种 well-known Symbol | 正确设置值 |
| Constructor | name, prototype, constructor | prototype 链 |
7.2 代理 this 问题解决
// 关键:get 时绑定 thisget(target, property, receiver) { const value = target[property]; if (typeof value === 'function') { return value.bind(target); // 或使用 Reflect.get } return value;}
// 关键:set 时保持 receiverset(target, property, newValue, receiver) { return Reflect.set(target, property, newValue, receiver);}7.3 补环境三大原则
- 类型正确:每种类型的属性要完整(Function 要有 name,Symbol 要有 description)
- 描述符正确:使用 defineProperty 设置正确的 enumerable/writable/configurable
- 原型链正确:使用 setPrototypeOf 设置正确的继承关系
部分信息可能已经过时









