> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Object 静态方法与 Hook 使用场景
- URL: https://blog.vercanti.com/object-jing-tai-fang-fa-yu-hook-shi-yong-chang-jing/
- Published: 2026-08-28T14:34:55.000Z
- Updated: 2026-08-28T14:57:41.000Z
- Description: Object 的静态方法是 JS 逆向中 Hook 技术的底层基础。理解每个方法的能力边界，直接决定你能不能绕过检测、能不能稳定注入、能不能还原被篡改的环境。 descriptor 两种形式（不可混用）： 部分网站检测 fn.toString() 是否包含 native code： 一次性定义多个属性，等同于多次调用 defineProperty。 获取对象自身某个属性的完整描述符对象，是 Hook 前的必要准备动作。 见上方 defineProperty 场景 2，核心就是先用这个方法取到原生 getter/setter，再包一层。 返回对象所有自身属
- Author: yellowdog
- Tags: js逆向, JS基础

> 官方文档：<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global%5FObjects/Object>  
> 适用版本：ES5+（2026-05-08 核实）

Object 的静态方法是 JS 逆向中 Hook 技术的底层基础。理解每个方法的能力边界，直接决定你能不能绕过检测、能不能稳定注入、能不能还原被篡改的环境。

---

## 目录

- [Object.defineProperty](#objectdefineproperty)
- [Object.defineProperties](#objectdefineproperties)
- [Object.getOwnPropertyDescriptor](#objectgetownpropertydescriptor)
- [Object.getOwnPropertyDescriptors](#objectgetownpropertydescriptors)
- [Object.getPrototypeOf / Object.setPrototypeOf](#objectgetprototypeof-objectsetprototypeof)
- [Object.create](#objectcreate)
- [Object.freeze / Object.isFrozen](#objectfreeze-objectisfrozen)
- [Object.seal / Object.isSealed](#objectseal-objectissealed)
- [Object.preventExtensions / Object.isExtensible](#objectpreventextensions-objectisextensible)
- [Object.getOwnPropertyNames](#objectgetownpropertynames)
- [Object.getOwnPropertySymbols](#objectgetownpropertysymbols)
- [Object.assign](#objectassign)
- [Object.keys / Object.values / Object.entries](#objectkeys-objectvalues-objectentries)
- [Object.hasOwn](#objecthasown)
- [Object.fromEntries](#objectfromentries)
- [综合场景：检测与反检测对抗](#%E7%BB%BC%E5%90%88%E5%9C%BA%E6%99%AF%EF%BC%9A%E6%A3%80%E6%B5%8B%E4%B8%8E%E5%8F%8D%E6%A3%80%E6%B5%8B%E5%AF%B9%E6%8A%97)

---

## Object.defineProperty

### 基础

```js
Object.defineProperty(obj, prop, descriptor)

```

`descriptor` 两种形式（不可混用）：

| 字段           | 数据描述符                         | 访问器描述符    |
| ------------ | ----------------------------- | --------- |
| value        | 属性值                           | —         |
| writable     | 是否可写                          | —         |
| get          | —                             | getter 函数 |
| set          | —                             | setter 函数 |
| enumerable   | 是否可枚举（for...in / Object.keys） | 同左        |
| configurable | 是否可再次 defineProperty / delete | 同左        |

### Hook 场景 1：拦截属性写入（定位赋值来源）

```js
// 监控 window.sign 被谁写入
(function () {
    let _val;
    Object.defineProperty(window, 'sign', {
        configurable: true,
        enumerable: true,
        get() { return _val; },
        set(v) {
            console.log('[sign SET]', v, new Error().stack);
            _val = v;
        }
    });
})();

```

### Hook 场景 2：Cookie 写入监控（带条件断点）

```js
(function () {
    // 先从 Document.prototype 获取原生访问器，避免循环调用
    const desc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
    const nativeGet = desc.get;
    const nativeSet = desc.set;

    Object.defineProperty(document, 'cookie', {
        configurable: true,
        get() { return nativeGet.call(document); },
        set(v) {
            if (v.includes('_token')) debugger; // 条件断点
            console.log('[cookie SET]', v);
            nativeSet.call(document, v);
        }
    });
})();

```

> 直接用 `document.cookie` 取原生访问器会触发自己的 getter，必须从 `Document.prototype` 上取。

### Hook 场景 3：将 configurable 设为 false 来阻止被二次 Hook

```js
// 锁死某个属性，让别人无法再次 defineProperty
Object.defineProperty(window, 'navigator', {
    configurable: false, // 锁死
    get() { return myFakeNavigator; }
});

// 此后再 defineProperty 会抛 TypeError: Cannot redefine property: navigator

```

### Hook 场景 4：伪造 toString 防检测

部分网站检测 `fn.toString()` 是否包含 `[native code]`：

```js
const _fetch = window.fetch;
window.fetch = function (...args) {
    console.log('[fetch]', args[0]);
    return _fetch.apply(this, args);
};

// 让 toString 返回原生字符串
Object.defineProperty(window.fetch, 'toString', {
    value: () => 'function fetch() { [native code] }',
    configurable: false
});
// 或者更简单：
window.fetch.toString = _fetch.toString.bind(_fetch);

```

### 踩坑

- `configurable: false` 之后无法再修改描述符，也无法 `delete`，Hook 注入必须在此之前
- 数据描述符和访问器描述符不能混合，已经是数据描述符的属性若要改成访问器，必须先 `configurable: true`
- `writable: false` 的属性严格模式下赋值抛错，非严格模式下静默失败

---

## Object.defineProperties

一次性定义多个属性，等同于多次调用 `defineProperty`。

```js
Object.defineProperties(obj, {
    prop1: { value: 1, writable: true, enumerable: true, configurable: true },
    prop2: { get() { return this._p2; }, set(v) { this._p2 = v; } }
})

```

### Hook 场景：批量伪造 navigator 属性

```js
// 补环境时批量设置 navigator 的各个字段
Object.defineProperties(navigator, {
    webdriver: {
        configurable: true,
        get: () => false    // 关键：隐藏自动化标志
    },
    platform: {
        configurable: true,
        get: () => 'Win32'
    },
    languages: {
        configurable: true,
        get: () => ['zh-CN', 'zh', 'en']
    },
    plugins: {
        configurable: true,
        get: () => fakePluginArray   // 伪造插件列表
    }
});

```

---

## Object.getOwnPropertyDescriptor

获取对象自身某个属性的完整描述符对象，是 Hook 前的必要准备动作。

```js
const desc = Object.getOwnPropertyDescriptor(obj, prop);
// 返回: { value, writable, enumerable, configurable }
// 或:   { get, set, enumerable, configurable }
// 属性不存在时返回 undefined

```

### Hook 场景 1：安全 Hook cookie（取原生访问器）

见上方 `defineProperty` 场景 2，核心就是先用这个方法取到原生 getter/setter，再包一层。

```js
const cookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
// cookieDesc.get  → 原生 cookie getter
// cookieDesc.set  → 原生 cookie setter

```

### Hook 场景 2：检测属性是否可被 Hook

```js
function canHook(obj, prop) {
    const desc = Object.getOwnPropertyDescriptor(obj, prop);
    if (!desc) return true; // 不存在，可定义
    return desc.configurable === true; // configurable 为 true 才能重定义
}

if (canHook(window, 'XMLHttpRequest')) {
    // 安全 Hook
} else {
    console.warn('XMLHttpRequest 已被锁死，无法 Hook');
}

```

### Hook 场景 3：完整保存原始描述符用于恢复

```js
// 保存原始描述符
const originalDesc = Object.getOwnPropertyDescriptor(window, 'localStorage');

// 执行 Hook
Object.defineProperty(window, 'localStorage', { /* ... */ });

// 需要恢复时
Object.defineProperty(window, 'localStorage', originalDesc);

```

---

## Object.getOwnPropertyDescriptors

返回对象所有自身属性的描述符集合（以对象形式）。

```js
const descs = Object.getOwnPropertyDescriptors(obj);
// { prop1: descriptor1, prop2: descriptor2, ... }

```

### Hook 场景 1：深拷贝原型（保留 getter/setter）

普通的 `Object.assign` 会丢失 getter/setter，只拷贝值：

```js
// 错误做法：getter/setter 会被求值后复制为普通值
const bad = Object.assign({}, sourceProto);

// 正确做法：完整保留描述符
const copy = Object.create(
    Object.getPrototypeOf(sourceProto),
    Object.getOwnPropertyDescriptors(sourceProto)
);

```

### Hook 场景 2：扫描对象，找出所有访问器属性（可能是 Hook 埋点）

```js
function findAccessors(obj) {
    const descs = Object.getOwnPropertyDescriptors(obj);
    return Object.entries(descs)
        .filter(([, d]) => typeof d.get === 'function' || typeof d.set === 'function')
        .map(([key, d]) => ({ key, get: !!d.get, set: !!d.set }));
}

// 检测 window 上有哪些属性被定义了 getter/setter（可能是反调试注入）
console.table(findAccessors(window));

```

### Hook 场景 3：一次性复制整个对象的 Hook 状态到另一个对象

```js
// 将 document 的所有属性描述符整体复制到 fakeDoc（补环境常用）
const fakeDoc = Object.create(null);
Object.defineProperties(fakeDoc, Object.getOwnPropertyDescriptors(document));

```

---

## Object.getPrototypeOf / Object.setPrototypeOf

```js
Object.getPrototypeOf(obj)      // 等同于 obj.__proto__，但更标准
Object.setPrototypeOf(obj, proto) // 修改原型链

```

### Hook 场景 1：检测对象是否是某个类的实例（绕过 instanceof 检测）

某些网站用 `obj instanceof XMLHttpRequest` 检测请求是否被代理：

```js
const fakeXHR = {};
Object.setPrototypeOf(fakeXHR, XMLHttpRequest.prototype);
// 现在 fakeXHR instanceof XMLHttpRequest === true

```

### Hook 场景 2：通过原型链 Hook 批量拦截所有实例

Hook 原型上的方法，比 Hook 每个实例更高效：

```js
const _open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
    console.log('[XHR open]', method, url);
    return _open.apply(this, arguments);
};
// 所有已存在的和未来创建的 XHR 实例都会被拦截

```

### Hook 场景 3：检测 Proxy 对象（Proxy 无法伪装 getPrototypeOf 陷阱之外的行为）

```js
// 某些反爬代码通过原型链检测 Proxy
function isProxy(obj) {
    try {
        // Proxy 在 getPrototypeOf 上可以返回任意值，但 Object.getPrototypeOf 绕过陷阱
        return Object.getPrototypeOf(obj) !== Object.getPrototypeOf(Object.getPrototypeOf(obj));
    } catch {
        return true;
    }
}

```

---

## Object.create

```js
Object.create(proto, propertiesObject?)
// 创建一个以 proto 为原型的新对象，可选第二参数同 defineProperties 的格式

```

### Hook 场景 1：创建干净的无原型对象作为 Map 使用

```js
// 避免原型上的 hasOwnProperty/toString 等方法干扰 key 查找
const store = Object.create(null);
store['constructor'] = 'someValue'; // 不会有冲突

```

### Hook 场景 2：补环境——创建带正确原型的假对象

```js
// 创建一个假的 navigator，原型链指向真实 Navigator.prototype
const fakeNavigator = Object.create(Navigator.prototype, {
    userAgent: { get: () => 'Mozilla/5.0 ...', configurable: true },
    webdriver: { get: () => false, configurable: true },
    platform:  { get: () => 'Win32', configurable: true },
});

// navigator instanceof Navigator === true，通过原型检测
Object.defineProperty(window, 'navigator', {
    get: () => fakeNavigator,
    configurable: true
});

```

### Hook 场景 3：创建继承了原始对象所有方法的"代理壳"

```js
// 创建一个和 localStorage 同原型的假对象，但可以拦截所有方法
const fakeStorage = Object.create(Storage.prototype);
['getItem', 'setItem', 'removeItem', 'clear'].forEach(method => {
    fakeStorage[method] = function (...args) {
        console.log(`[localStorage.${method}]`, ...args);
        return localStorage[method].apply(localStorage, args);
    };
});

```

---

## Object.freeze / Object.isFrozen

`freeze` 让对象同时满足：`configurable: false`、`writable: false`、不可新增属性。冻结是**浅冻结**，嵌套对象不受影响。

```js
Object.freeze(obj)
Object.isFrozen(obj) // 返回 boolean

```

### Hook 场景 1：保护 console 不被网站覆盖

```js
// 在页面脚本执行前冻结 console，防止网站用 console.log = ()=>{} 屏蔽调试输出
Object.freeze(console);
// 此后 console.log = xxx 静默失败（严格模式抛错）

```

### Hook 场景 2：检测对象是否被冻结（判断是否可以 Hook）

```js
if (Object.isFrozen(window.crypto)) {
    console.warn('crypto 已被冻结，无法直接 Hook，需要用 Proxy 替换整个对象');
} else {
    // 可以直接 defineProperty
}

```

### Hook 场景 3：冻结后的对象只能用 Proxy 整体替换

```js
// 假设 window.performance 被冻结了
const _performance = window.performance;
const proxyPerformance = new Proxy(_performance, {
    get(target, prop) {
        if (prop === 'now') {
            return function () {
                console.log('[performance.now]');
                return target.now();
            };
        }
        return Reflect.get(target, prop);
    }
});
// 注意：window.performance 本身如果 configurable:false 则这一步也做不到
Object.defineProperty(window, 'performance', {
    get: () => proxyPerformance
});

```

### 踩坑

- `freeze` 只是浅冻结，`obj.nested.key = 1` 仍然可以修改
- 冻结后既不能 Hook 属性，也不能 delete，逆向时遇到冻结对象只能 Proxy 整体替换

---

## Object.seal / Object.isSealed

`seal` 比 `freeze` 弱一档：不可新增/删除属性，但**已有属性的值仍然可以修改**（`writable` 不变，`configurable` 全设为 false）。

```js
Object.seal(obj)
Object.isSealed(obj)

```

### Hook 场景：seal 后的对象可以修改属性值但不能 defineProperty

```js
const config = { mode: 'normal' };
Object.seal(config);

config.mode = 'hacked'; // 允许，seal 不改变 writable
// Object.defineProperty(config, 'mode', { get: ()=>'hacked' }); // 抛错，configurable:false

```

逆向时如果遇到 sealed 对象要 Hook，同样只能用 Proxy 整体替换。

---

## Object.preventExtensions / Object.isExtensible

最弱的限制：只阻止**新增属性**，已有属性的 `configurable`/`writable` 不变。

```js
Object.preventExtensions(obj)
Object.isExtensible(obj) // 默认 true，preventExtensions 后变 false

```

### Hook 场景：检测对象是否可以新增属性（决定是否可直接挂载 Hook）

```js
if (!Object.isExtensible(window)) {
    // window 被锁定，不能新增属性（极少见）
    console.warn('window 不可扩展');
}

```

三种限制的对比：

| 方法                | 新增属性 | 删除属性 | 修改值 | 修改描述符 |
| ----------------- | ---- | ---- | --- | ----- |
| preventExtensions | 禁止   | 允许   | 允许  | 允许    |
| seal              | 禁止   | 禁止   | 允许  | 禁止    |
| freeze            | 禁止   | 禁止   | 禁止  | 禁止    |

---

## Object.getOwnPropertyNames

返回对象自身所有属性名（**包含不可枚举属性**），不包含 Symbol 属性。

```js
Object.getOwnPropertyNames(obj)
// 对比：Object.keys(obj) 只返回可枚举属性

```

### Hook 场景 1：找出对象上所有隐藏的不可枚举属性（侦察目标对象结构）

```js
// Object.keys 看不到不可枚举属性，getOwnPropertyNames 全部暴露
const hidden = Object.getOwnPropertyNames(window).filter(
    key => !Object.prototype.propertyIsEnumerable.call(window, key)
);
console.log('不可枚举属性:', hidden);

```

### Hook 场景 2：遍历原型链上的所有方法，批量 Hook

```js
function getAllMethodNames(obj) {
    const names = new Set();
    let proto = obj;
    while (proto && proto !== Object.prototype) {
        Object.getOwnPropertyNames(proto)
            .filter(n => typeof proto[n] === 'function')
            .forEach(n => names.add(n));
        proto = Object.getPrototypeOf(proto);
    }
    return [...names];
}

// 获取 XMLHttpRequest 原型链上所有方法名
getAllMethodNames(XMLHttpRequest.prototype);
// ['open', 'send', 'abort', 'setRequestHeader', ...]

```

### Hook 场景 3：检测原型上是否存在可疑方法（反调试侦察）

```js
// 检测 Array.prototype 上是否有被注入的方法
const nativeMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'slice', 'map', 'filter', 'reduce'];
Object.getOwnPropertyNames(Array.prototype).forEach(name => {
    if (!nativeMethods.includes(name)) {
        console.warn('[可疑] Array.prototype 上发现非原生方法:', name);
    }
});

```

---

## Object.getOwnPropertySymbols

返回对象自身所有 Symbol 属性键，普通枚举完全无法发现这些属性。

```js
Object.getOwnPropertySymbols(obj)

```

### Hook 场景 1：找出用 Symbol 隐藏的私有状态

```js
// 某些库用 Symbol 存储内部状态，外部无法直接访问
const syms = Object.getOwnPropertySymbols(targetObj);
syms.forEach(sym => {
    console.log(sym.toString(), targetObj[sym]);
});
// 可能发现：Symbol(kState), Symbol(fetch state) 等内部属性

```

### Hook 场景 2：Hook Symbol.toPrimitive（影响类型转换）

```js
// Symbol.toPrimitive 控制对象被转换为原始值时的行为
const originalFn = targetObj[Symbol.toPrimitive];
targetObj[Symbol.toPrimitive] = function (hint) {
    console.log('[toPrimitive] hint =', hint);
    return originalFn ? originalFn.call(this, hint) : String(this);
};

```

### Hook 场景 3：Hook Symbol.iterator（拦截 for...of / 解构）

```js
const _iter = Array.prototype[Symbol.iterator];
Array.prototype[Symbol.iterator] = function () {
    console.log('[Array iterator] 被调用，length =', this.length);
    return _iter.call(this);
};

```

---

## Object.assign

将一个或多个源对象的**可枚举自身属性**浅拷贝到目标对象。

```js
Object.assign(target, ...sources)

```

### Hook 场景 1：快速拷贝原始方法备份

```js
// 保存一批原生方法的引用，防止被污染后丢失
const nativeMethods = Object.assign(Object.create(null), {
    defineProperty: Object.defineProperty.bind(Object),
    getOwnPropertyDescriptor: Object.getOwnPropertyDescriptor.bind(Object),
    freeze: Object.freeze.bind(Object)
});

// 即使后续 Object.defineProperty 被 Hook，依然可以用 nativeMethods.defineProperty

```

### Hook 场景 2：合并 Hook 配置

```js
const defaultConfig = { logGet: true, logSet: true, logCall: false };
const userConfig = { logSet: false, logCall: true };
const config = Object.assign({}, defaultConfig, userConfig);
// { logGet: true, logSet: false, logCall: true }

```

### 踩坑

`Object.assign` 无法复制 getter/setter，只会把 getter 的返回值当成普通值复制过去：

```js
const src = { get foo() { return 'bar'; } };
const dst = Object.assign({}, src);
// dst.foo === 'bar'（普通值），getter 丢失了
// 需要用 Object.getOwnPropertyDescriptors 才能保留 getter/setter

```

---

## Object.keys / Object.values / Object.entries

只返回对象自身的**可枚举**属性（不包括原型链，不包括 Symbol）。

```js
Object.keys(obj)    // 返回属性名数组
Object.values(obj)  // 返回属性值数组
Object.entries(obj) // 返回 [key, value] 对数组

```

### Hook 场景 1：遍历请求参数对象，找出签名字段

```js
// 拦截 fetch 后解析请求体，找出 sign / token 字段
window.fetch = async function (url, options) {
    if (options?.body) {
        try {
            const body = JSON.parse(options.body);
            Object.entries(body).forEach(([k, v]) => {
                if (['sign', 'token', '_signature', 'nonce'].includes(k)) {
                    console.log(`[参数签名] ${k} = ${v}`);
                    debugger;
                }
            });
        } catch {}
    }
    return originalFetch(url, options);
};

```

### Hook 场景 2：批量 Hook 某个命名空间下所有方法

```js
// 将 CryptoJS 命名空间下所有加密方法全部 Hook
Object.entries(CryptoJS).forEach(([name, algo]) => {
    if (algo && typeof algo.encrypt === 'function') {
        const _enc = algo.encrypt.bind(algo);
        algo.encrypt = function (...args) {
            console.log(`[CryptoJS.${name}.encrypt]`, args);
            return _enc(...args);
        };
    }
});

```

### Hook 场景 3：检测 window 上被注入的可疑全局变量

```js
// 记录页面加载前 window 的 key 集合
const beforeKeys = new Set(Object.keys(window));

// 等待页面脚本执行后
setTimeout(() => {
    const afterKeys = new Set(Object.keys(window));
    const newKeys = [...afterKeys].filter(k => !beforeKeys.has(k));
    console.log('[新增全局变量]', newKeys);
}, 3000);

```

---

## Object.hasOwn

```js
Object.hasOwn(obj, prop)  // ES2022，替代 obj.hasOwnProperty(prop)

```

`hasOwnProperty` 存在被原型污染的风险（如果对象是 `Object.create(null)` 的，它根本没有这个方法）。`Object.hasOwn` 是静态方法，更安全。

### Hook 场景：安全检测某个属性是否是对象自有属性

```js
// 不推荐
if (obj.hasOwnProperty('sign')) { ... } // 若 obj 原型被污染则出错

// 推荐
if (Object.hasOwn(obj, 'sign')) { ... }

// 在 Hook 代码中判断请求参数是否包含签名字段
function hasSignField(params) {
    return ['sign', 'token', '_ts', 'nonce'].some(k => Object.hasOwn(params, k));
}

```

---

## Object.fromEntries

将 `[key, value]` 对的可迭代对象（如 `Map`、`entries()` 结果）转回对象。

```js
Object.fromEntries(entries)

```

### Hook 场景 1：修改请求参数后重新组装

```js
window.fetch = async function (url, options) {
    if (typeof url === 'string' && url.includes('/api/')) {
        // 解析并修改 URL 查询参数
        const urlObj = new URL(url);
        const params = Object.fromEntries(urlObj.searchParams.entries());

        console.log('[fetch params]', params);

        // 修改某个参数（如替换 timestamp 为固定值方便重放）
        params.ts = '1700000000';
        const newSearch = new URLSearchParams(params).toString();
        url = `${urlObj.origin}${urlObj.pathname}?${newSearch}`;
    }
    return originalFetch(url, options);
};

```

### Hook 场景 2：将 Map 形式的 Header 转为普通对象打印

```js
XMLHttpRequest.prototype.send = function (body) {
    // 将 Headers 对象转为普通对象，方便打印
    if (this._headers) {
        const headersObj = Object.fromEntries(
            Object.entries(this._headers)
        );
        console.log('[XHR headers]', headersObj);
    }
    return _send.call(this, body);
};

```

---

## 综合场景：检测与反检测对抗

### 场景 A：检测 window 属性是否被 Hook（反向侦察）

网站用来检测逆向者是否注入了 Hook：

```js
// 网站侧：检测 XMLHttpRequest.prototype.open 是否被替换
function detectHook(obj, method) {
    const desc = Object.getOwnPropertyDescriptor(obj, method);
    if (!desc) return false;
    const fn = desc.value || desc.get?.();
    if (!fn) return false;
    // 检测函数 toString 是否包含 native code
    return !/\[native code\]/.test(fn.toString());
}

if (detectHook(XMLHttpRequest.prototype, 'open')) {
    console.warn('open 被 Hook 了！');
}

```

逆向者的反制——让 toString 返回原生字符串：

```js
const _open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (...args) {
    console.log('[XHR open]', args[1]);
    return _open.apply(this, args);
};

// 关键：伪造 toString
Object.defineProperty(XMLHttpRequest.prototype.open, 'toString', {
    value: () => 'function open() { [native code] }',
    configurable: false  // 锁死，让别人也无法再改
});

```

### 场景 B：保存一份干净的 Object 方法引用（防止被污染）

```js
// 在所有脚本执行之前，保存原生方法引用
// 即使后续 Object.defineProperty 被 Hook，这些引用依然指向原生实现
const safeDefineProperty   = Object.defineProperty.bind(Object);
const safeGetDescriptor    = Object.getOwnPropertyDescriptor.bind(Object);
const safeGetProto         = Object.getPrototypeOf.bind(Object);
const safeFreeze           = Object.freeze.bind(Object);
const safeGetOwnPropNames  = Object.getOwnPropertyNames.bind(Object);

// 后续 Hook 操作全部使用 safe 前缀的版本
safeDefineProperty(window, 'myHook', { value: true, configurable: false });

```

### 场景 C：检测 defineProperty 本身是否被 Hook（深度防御）

```js
// 如果连 Object.defineProperty 都被 Hook 了，上面所有操作都不可信
function isDefinePropertyHooked() {
    return !/\[native code\]/.test(Object.defineProperty.toString());
}

if (isDefinePropertyHooked()) {
    // 尝试从 iframe 中取干净的原生方法
    const iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    const cleanDefineProperty = iframe.contentWindow.Object.defineProperty;
    document.body.removeChild(iframe);
    // 用 cleanDefineProperty 注入 Hook
}

```

### 场景 D：完整的 Cookie Hook 最佳实践

综合运用上述方法，写出最稳定的 Cookie Hook：

```js
(function () {
    // 1. 保存原生方法引用（防止递归/被污染）
    const _defineProperty        = Object.defineProperty.bind(Object);
    const _getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor.bind(Object);

    // 2. 从 Document.prototype 取原生访问器（不能从 document 实例取，否则触发自己）
    const cookieDesc = _getOwnPropertyDescriptor(Document.prototype, 'cookie');
    if (!cookieDesc || !cookieDesc.configurable) {
        console.warn('[Cookie Hook] 无法注入，cookie 描述符不可配置');
        return;
    }
    const nativeGet = cookieDesc.get;
    const nativeSet = cookieDesc.set;

    // 3. 注入 Hook
    _defineProperty(document, 'cookie', {
        configurable: true,
        enumerable: true,
        get() {
            return nativeGet.call(document);
        },
        set(v) {
            // 4. 条件断点：只在写入目标 cookie 时断下
            if (v.startsWith('_acl') || v.includes('sign=')) {
                console.log('[Cookie SET 命中]', v, new Error().stack);
                debugger;
            }
            nativeSet.call(document, v);
        }
    });

    console.log('[Cookie Hook] 注入成功');
})();

```

---

---

## 最佳实践

**优先使用 `Object.defineProperty` 做精确属性 Hook**：Hook 单个属性时，`Object.defineProperty` 可以设置 `get`/`set` 拦截器，并控制 `configurable`/`enumerable`/`writable`，比直接赋值更细粒度，也更难被检测到。

**用 `Object.getOwnPropertyDescriptor` 在 Hook 前保存原始描述符**：

```javascript
const orig = Object.getOwnPropertyDescriptor(navigator, 'userAgent')
Object.defineProperty(navigator, 'userAgent', {
  get: () => { console.log('[UA accessed]'); return orig.get.call(this) }
})

```

先保存再 Hook，既能监控访问又保留原始行为，避免 Hook 后功能异常。

**`Object.freeze` 保护 Hook 代码自身**：注入 Hook 后，用 `Object.freeze(hookedObject)` 防止目标代码用 `delete`/赋值覆盖 Hook；`Object.isFrozen` 检测是否已被保护。

**`Object.getPrototypeOf` 追踪原型链**：混淆代码有时将方法定义在原型上而非实例上，用 `Object.getPrototypeOf(obj)` 找到实际原型，然后 Hook 原型上的方法，比 Hook 实例属性影响范围更广。

**`Object.entries` \+ `Object.fromEntries` 做深拷贝前的检测**：检测目标对象有哪些可枚举属性时，`Object.entries(obj)` 比 `for...in` 更安全（不包含原型链属性）；`Object.fromEntries` 可以快速重建过滤后的对象。

---

## 常见陷阱

### 陷阱：`Object.freeze` 后 Hook 失效

**现象：** 尝试 `Object.defineProperty` Hook 某属性，但静默失败（严格模式下抛 TypeError）。  
**原因：** 目标对象或其原型已被 `Object.freeze()` 保护，被冻结后所有属性修改都被阻止。  
**解决：** 检查 `Object.isFrozen(obj)`；若已冻结，需要替换整个对象引用（`window.targetObj = new Proxy(originalObj, {...})`）而非修改已冻结对象的属性。

### 陷阱：`configurable: false` 的属性无法用 `defineProperty` 重定义

**现象：** 对某属性 `defineProperty` 抛出 `TypeError: Cannot redefine property`。  
**原因：** 属性描述符中 `configurable: false` 禁止重新定义；原生浏览器 API（如 `navigator.userAgent`）通常都是 `configurable: false`。  
**解决：** 先用 `Object.defineProperty` 替换父对象（如整个 `navigator`）；或使用 `Proxy` 代理父对象而非直接修改属性。

### 陷阱：`Object.assign` 只做浅拷贝，深层属性 Hook 失效

**现象：** 用 `Object.assign` 创建目标对象副本后再 Hook，但原始对象的深层属性访问没被拦截。  
**原因：** `Object.assign` 只复制第一层可枚举属性，嵌套对象还是同一引用；Hook 了副本的属性，不影响原始对象的访问路径。  
**解决：** Hook 需要在原始对象引用上操作；或用深拷贝（`JSON.parse(JSON.stringify(obj))`）但注意会丢失函数属性；或直接 Hook 原始对象。

---

## 参见

[hook](https://blog.vercanti.com/hook-dai-ma-duan/)  
[代码片段大全](https://blog.vercanti.com/js-ni-xiang-hook-yu-dai-ma-pian-duan-da-quan/)  
[补环境](https://blog.vercanti.com/bu-huan-jing/)  
[js逆向调试技巧](https://blog.vercanti.com/js-ni-xiang-diao-shi-ji-qiao/)  
[Proxy对象与Hook](https://blog.vercanti.com/proxy-dui-xiang-yu-hook/)