@wulu007/stealth-hook 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/index.cjs +211 -0
- package/dist/index.d.cts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.global.js +209 -0
- package/dist/index.js +184 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wulu007
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
|
|
2
|
+
# StealthHook
|
|
3
|
+
|
|
4
|
+
A lightweight JavaScript hook library for intercepting and modifying function behavior in the browser. Designed for stealth modifications with automatic iframe detection and recursive hooking.
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/stealth-hook)
|
|
7
|
+
[](https://github.com/wulu007/StealthHook/blob/main/LICENSE)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🎯 **Stealth Hooking** - Modify functions while passing `toString()` checks
|
|
12
|
+
- 🔄 **Auto iframe Detection** - Automatically detect and hook functions in iframes
|
|
13
|
+
- 🛡️ **Type Safe** - Full TypeScript support
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### npm
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install stealth-hook
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### pnpm
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add stealth-hook
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### In Userscripts
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
// @require https://unpkg.com/stealth-hook@latest/dist/index.js
|
|
33
|
+
// @grant unsafeWindow
|
|
34
|
+
// @run-at document-start
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Basic Example
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
import { hookScope } from 'stealth-hook'
|
|
43
|
+
|
|
44
|
+
hookScope(({ hookMethod }, win) => {
|
|
45
|
+
// Note: use the win parameter
|
|
46
|
+
hookMethod(win.console, 'log', (target, args, thisArg) => {
|
|
47
|
+
console.log('[Hooked]', ...args)
|
|
48
|
+
return target(...args)
|
|
49
|
+
})
|
|
50
|
+
}, rootWindow)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Userscript Example
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
// ==UserScript==
|
|
57
|
+
// @name StealthHook Example
|
|
58
|
+
// @namespace https://example.com/
|
|
59
|
+
// @version 0.1.0
|
|
60
|
+
// @description Hook Example
|
|
61
|
+
// @author You
|
|
62
|
+
// @match *://*/*
|
|
63
|
+
// @require https://unpkg.com/stealth-hook@latest/dist/index.js
|
|
64
|
+
// @grant unsafeWindow
|
|
65
|
+
// @run-at document-start
|
|
66
|
+
// ==/UserScript==
|
|
67
|
+
|
|
68
|
+
(function () {
|
|
69
|
+
StealthHook.hookScope(({ hookMethod }, win) => {
|
|
70
|
+
// Note: use the win parameter
|
|
71
|
+
hookMethod(win.console, 'log', (target, args, thisArg) => {
|
|
72
|
+
console.log('[Custom Log]', ...args)
|
|
73
|
+
return target(...args)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
hookMethod(win.console, 'table', (target, args, thisArg) => {
|
|
77
|
+
console.log('[Custom Table]', args)
|
|
78
|
+
return target(...args)
|
|
79
|
+
})
|
|
80
|
+
}, unsafeWindow)
|
|
81
|
+
})()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### Parameters
|
|
85
|
+
|
|
86
|
+
| Parameter | Type | Description |
|
|
87
|
+
| --------- | ------------ | ---------------------------------------------- |
|
|
88
|
+
| `target` | Function | Original function with `this` auto-bound |
|
|
89
|
+
| `args` | Array | Arguments array of the original function |
|
|
90
|
+
| `thisArg` | this type | Current `this` context (caller) |
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT © [wulu007](https://github.com/wulu007)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
hookScope: () => hookScope
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/descriptor.ts
|
|
28
|
+
var getDescriptor = /* @__PURE__ */ (() => {
|
|
29
|
+
const $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
30
|
+
const $getPrototypeOf = Object.getPrototypeOf;
|
|
31
|
+
return function(obj, prop) {
|
|
32
|
+
let curr = obj;
|
|
33
|
+
while (curr) {
|
|
34
|
+
const desc = $getOwnPropertyDescriptor(curr, prop);
|
|
35
|
+
if (desc)
|
|
36
|
+
return desc;
|
|
37
|
+
curr = $getPrototypeOf(curr);
|
|
38
|
+
}
|
|
39
|
+
return void 0;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
|
|
43
|
+
// src/fake.ts
|
|
44
|
+
var createWrapper = /* @__PURE__ */ (() => {
|
|
45
|
+
const $ownKeys = Reflect.ownKeys;
|
|
46
|
+
const $defineProperty = Object.defineProperty;
|
|
47
|
+
return function(handler, fn) {
|
|
48
|
+
const wrapper = {
|
|
49
|
+
[fn.name](...args) {
|
|
50
|
+
return handler(fn, args, this);
|
|
51
|
+
}
|
|
52
|
+
}[fn.name];
|
|
53
|
+
const keys = $ownKeys(fn);
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
if (key === "arguments" || key === "caller")
|
|
56
|
+
continue;
|
|
57
|
+
const desc = getDescriptor(fn, key);
|
|
58
|
+
if (desc) {
|
|
59
|
+
$defineProperty(wrapper, key, desc);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return wrapper;
|
|
63
|
+
};
|
|
64
|
+
})();
|
|
65
|
+
|
|
66
|
+
// src/hook.ts
|
|
67
|
+
var hook = (() => {
|
|
68
|
+
const nativeStrMap = /* @__PURE__ */ new WeakMap();
|
|
69
|
+
const nativeFnMap = /* @__PURE__ */ new WeakMap();
|
|
70
|
+
const $toString = Function.prototype.toString;
|
|
71
|
+
const $defineProperty = Object.defineProperty;
|
|
72
|
+
const $reflectApply = Reflect.apply;
|
|
73
|
+
return {
|
|
74
|
+
method(obj, fnName, handler) {
|
|
75
|
+
const original = obj[fnName];
|
|
76
|
+
const desc = getDescriptor(obj, fnName);
|
|
77
|
+
if (!desc || !original || typeof original !== "function")
|
|
78
|
+
return;
|
|
79
|
+
const wrapper = createWrapper(handler, original);
|
|
80
|
+
nativeStrMap.set(wrapper, $toString.call(original));
|
|
81
|
+
nativeFnMap.set(wrapper, original);
|
|
82
|
+
$defineProperty(obj, fnName, { ...desc, value: wrapper });
|
|
83
|
+
},
|
|
84
|
+
bindThisMethod(obj, fnName, handler) {
|
|
85
|
+
this.method(obj, fnName, (target, args, thisArg) => {
|
|
86
|
+
handler(() => $reflectApply(target, thisArg, args), args, thisArg);
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
constructor(obj, fnName, handler) {
|
|
90
|
+
const original = obj[fnName];
|
|
91
|
+
const desc = getDescriptor(obj, fnName);
|
|
92
|
+
if (!desc || !original || typeof original !== "function")
|
|
93
|
+
return;
|
|
94
|
+
const wrapper = createWrapper(handler, original);
|
|
95
|
+
if (wrapper.prototype && "constructor" in wrapper.prototype) {
|
|
96
|
+
wrapper.prototype.constructor = wrapper;
|
|
97
|
+
}
|
|
98
|
+
nativeStrMap.set(wrapper, $toString.call(original));
|
|
99
|
+
nativeFnMap.set(wrapper, original);
|
|
100
|
+
$defineProperty(obj, fnName, { ...desc, value: wrapper });
|
|
101
|
+
},
|
|
102
|
+
getNativeString(fn) {
|
|
103
|
+
if (nativeStrMap.has(fn))
|
|
104
|
+
return nativeStrMap.get(fn);
|
|
105
|
+
return $reflectApply($toString, fn, []);
|
|
106
|
+
},
|
|
107
|
+
getNativeFunction(hookedFn) {
|
|
108
|
+
return nativeFnMap.get(hookedFn);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
})();
|
|
112
|
+
|
|
113
|
+
// src/logger.ts
|
|
114
|
+
var createLogger = /* @__PURE__ */ (() => {
|
|
115
|
+
const $log = console.log;
|
|
116
|
+
const $warn = console.warn;
|
|
117
|
+
const $error = console.error;
|
|
118
|
+
return (prefix) => ({
|
|
119
|
+
log: (...args) => $log(`[${prefix}]`, ...args),
|
|
120
|
+
warn: (...args) => $warn(`[${prefix}]`, ...args),
|
|
121
|
+
error: (...args) => $error(`[${prefix}]`, ...args)
|
|
122
|
+
});
|
|
123
|
+
})();
|
|
124
|
+
|
|
125
|
+
// src/index.ts
|
|
126
|
+
function hookScope(callback, rootWindow) {
|
|
127
|
+
const interceptorSetupMap = /* @__PURE__ */ new WeakSet();
|
|
128
|
+
const hookedWindows = /* @__PURE__ */ new WeakMap();
|
|
129
|
+
let windowCounter = 0;
|
|
130
|
+
const $reflectApply = Reflect.apply;
|
|
131
|
+
const getWindowId = (win) => {
|
|
132
|
+
if (!hookedWindows.has(win)) {
|
|
133
|
+
const id = win === rootWindow ? "main" : `iframe-${++windowCounter}`;
|
|
134
|
+
hookedWindows.set(win, id);
|
|
135
|
+
}
|
|
136
|
+
return hookedWindows.get(win);
|
|
137
|
+
};
|
|
138
|
+
const setupToStringHook = (win) => {
|
|
139
|
+
hook.method(win.Function.prototype, "toString", (target, args, thisArg) => {
|
|
140
|
+
const fakeString = hook.getNativeString(thisArg);
|
|
141
|
+
if (fakeString)
|
|
142
|
+
return fakeString;
|
|
143
|
+
return $reflectApply(target, thisArg, args);
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
function setupIframeInterceptor(win, applyCallback) {
|
|
147
|
+
if (interceptorSetupMap.has(win))
|
|
148
|
+
return;
|
|
149
|
+
interceptorSetupMap.add(win);
|
|
150
|
+
const logger = createLogger(getWindowId(win));
|
|
151
|
+
try {
|
|
152
|
+
const NodeProto = win.Node.prototype;
|
|
153
|
+
const hookInsertMethod = (methodName) => {
|
|
154
|
+
hook.method(NodeProto, methodName, (target, args, thisArg) => {
|
|
155
|
+
const result = $reflectApply(target, thisArg, args);
|
|
156
|
+
try {
|
|
157
|
+
const node = args[0];
|
|
158
|
+
if (!node || node.tagName !== "IFRAME")
|
|
159
|
+
return result;
|
|
160
|
+
const iframe = node;
|
|
161
|
+
const handleIframe = () => {
|
|
162
|
+
const childWin = iframe.contentWindow;
|
|
163
|
+
if (childWin) {
|
|
164
|
+
applyCallback(childWin);
|
|
165
|
+
setupIframeInterceptor(childWin, applyCallback);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
handleIframe();
|
|
169
|
+
} catch (e) {
|
|
170
|
+
logger.warn(`Error in ${String(methodName)} hook:`, e);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
hookInsertMethod("appendChild");
|
|
176
|
+
hookInsertMethod("insertBefore");
|
|
177
|
+
hookInsertMethod("replaceChild");
|
|
178
|
+
} catch (e) {
|
|
179
|
+
logger.warn("\u274C Failed to setup iframe interceptor", e);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const applyToWindow = (win) => {
|
|
183
|
+
if (hookedWindows.has(win))
|
|
184
|
+
return;
|
|
185
|
+
const logger = createLogger(getWindowId(win));
|
|
186
|
+
setupToStringHook(win);
|
|
187
|
+
const utils = {
|
|
188
|
+
hookMethod: (obj, fnName, handler) => {
|
|
189
|
+
hook.method(obj, fnName, handler);
|
|
190
|
+
},
|
|
191
|
+
hookBindThisMethod(obj, fnName, handler) {
|
|
192
|
+
hook.bindThisMethod(obj, fnName, handler);
|
|
193
|
+
},
|
|
194
|
+
getNativeFunction: hook.getNativeFunction,
|
|
195
|
+
getNativeString: hook.getNativeString,
|
|
196
|
+
getCurrentWindowId: () => getWindowId(win)
|
|
197
|
+
};
|
|
198
|
+
try {
|
|
199
|
+
callback(utils, win);
|
|
200
|
+
logger.log("\u2705 StealthHook applied");
|
|
201
|
+
} catch (e) {
|
|
202
|
+
logger.warn("User callback failed:", e);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
applyToWindow(rootWindow);
|
|
206
|
+
setupIframeInterceptor(rootWindow, applyToWindow);
|
|
207
|
+
}
|
|
208
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
209
|
+
0 && (module.exports = {
|
|
210
|
+
hookScope
|
|
211
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type AnyFunction = (...args: any[]) => any;
|
|
2
|
+
interface HookHandler<T extends AnyFunction = AnyFunction> {
|
|
3
|
+
/**
|
|
4
|
+
* @param target 原函数
|
|
5
|
+
* @param args 原函数的参数数组
|
|
6
|
+
* @param thisArg 当前的 this 上下文 (调用者)
|
|
7
|
+
*/
|
|
8
|
+
(target: T, args: Parameters<T>, thisArg: ThisType<T>): ReturnType<T>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare const hook: {
|
|
12
|
+
method<T extends object>(obj: T, fnName: keyof T, handler: HookHandler): void;
|
|
13
|
+
bindThisMethod<T extends object>(obj: T, fnName: keyof T, handler: HookHandler): void;
|
|
14
|
+
constructor<T extends AnyFunction>(obj: T, fnName: keyof T, handler: HookHandler): void;
|
|
15
|
+
getNativeString(fn: AnyFunction): string | undefined;
|
|
16
|
+
getNativeFunction<T extends AnyFunction>(hookedFn: T): T | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
declare global {
|
|
20
|
+
interface Window {
|
|
21
|
+
Node: typeof Node;
|
|
22
|
+
Function: typeof Function;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
interface HookUtils {
|
|
26
|
+
hookMethod: <T extends object, K extends keyof T>(obj: T, fnName: K, handler: HookHandler<T[K] extends AnyFunction ? T[K] : AnyFunction>) => void;
|
|
27
|
+
hookBindThisMethod: <T extends object, K extends keyof T>(obj: T, fnName: K, handler: HookHandler<T[K] extends AnyFunction ? T[K] : AnyFunction>) => void;
|
|
28
|
+
getCurrentWindowId: () => string | number;
|
|
29
|
+
getNativeFunction: typeof hook.getNativeFunction;
|
|
30
|
+
getNativeString: typeof hook.getNativeString;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 开启 Hook 作用域
|
|
34
|
+
* @param callback 在此处定义你的 Hook 逻辑
|
|
35
|
+
* @param rootWindow 初始目标窗口
|
|
36
|
+
*/
|
|
37
|
+
declare function hookScope(callback: (utils: HookUtils, win: Window) => void, rootWindow: Window): void;
|
|
38
|
+
|
|
39
|
+
export { type HookUtils, hookScope };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type AnyFunction = (...args: any[]) => any;
|
|
2
|
+
interface HookHandler<T extends AnyFunction = AnyFunction> {
|
|
3
|
+
/**
|
|
4
|
+
* @param target 原函数
|
|
5
|
+
* @param args 原函数的参数数组
|
|
6
|
+
* @param thisArg 当前的 this 上下文 (调用者)
|
|
7
|
+
*/
|
|
8
|
+
(target: T, args: Parameters<T>, thisArg: ThisType<T>): ReturnType<T>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare const hook: {
|
|
12
|
+
method<T extends object>(obj: T, fnName: keyof T, handler: HookHandler): void;
|
|
13
|
+
bindThisMethod<T extends object>(obj: T, fnName: keyof T, handler: HookHandler): void;
|
|
14
|
+
constructor<T extends AnyFunction>(obj: T, fnName: keyof T, handler: HookHandler): void;
|
|
15
|
+
getNativeString(fn: AnyFunction): string | undefined;
|
|
16
|
+
getNativeFunction<T extends AnyFunction>(hookedFn: T): T | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
declare global {
|
|
20
|
+
interface Window {
|
|
21
|
+
Node: typeof Node;
|
|
22
|
+
Function: typeof Function;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
interface HookUtils {
|
|
26
|
+
hookMethod: <T extends object, K extends keyof T>(obj: T, fnName: K, handler: HookHandler<T[K] extends AnyFunction ? T[K] : AnyFunction>) => void;
|
|
27
|
+
hookBindThisMethod: <T extends object, K extends keyof T>(obj: T, fnName: K, handler: HookHandler<T[K] extends AnyFunction ? T[K] : AnyFunction>) => void;
|
|
28
|
+
getCurrentWindowId: () => string | number;
|
|
29
|
+
getNativeFunction: typeof hook.getNativeFunction;
|
|
30
|
+
getNativeString: typeof hook.getNativeString;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 开启 Hook 作用域
|
|
34
|
+
* @param callback 在此处定义你的 Hook 逻辑
|
|
35
|
+
* @param rootWindow 初始目标窗口
|
|
36
|
+
*/
|
|
37
|
+
declare function hookScope(callback: (utils: HookUtils, win: Window) => void, rootWindow: Window): void;
|
|
38
|
+
|
|
39
|
+
export { type HookUtils, hookScope };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var StealthHook = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
hookScope: () => hookScope
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// src/descriptor.ts
|
|
28
|
+
var getDescriptor = /* @__PURE__ */ (() => {
|
|
29
|
+
const $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
30
|
+
const $getPrototypeOf = Object.getPrototypeOf;
|
|
31
|
+
return function(obj, prop) {
|
|
32
|
+
let curr = obj;
|
|
33
|
+
while (curr) {
|
|
34
|
+
const desc = $getOwnPropertyDescriptor(curr, prop);
|
|
35
|
+
if (desc)
|
|
36
|
+
return desc;
|
|
37
|
+
curr = $getPrototypeOf(curr);
|
|
38
|
+
}
|
|
39
|
+
return void 0;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
|
|
43
|
+
// src/fake.ts
|
|
44
|
+
var createWrapper = /* @__PURE__ */ (() => {
|
|
45
|
+
const $ownKeys = Reflect.ownKeys;
|
|
46
|
+
const $defineProperty = Object.defineProperty;
|
|
47
|
+
return function(handler, fn) {
|
|
48
|
+
const wrapper = {
|
|
49
|
+
[fn.name](...args) {
|
|
50
|
+
return handler(fn, args, this);
|
|
51
|
+
}
|
|
52
|
+
}[fn.name];
|
|
53
|
+
const keys = $ownKeys(fn);
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
if (key === "arguments" || key === "caller")
|
|
56
|
+
continue;
|
|
57
|
+
const desc = getDescriptor(fn, key);
|
|
58
|
+
if (desc) {
|
|
59
|
+
$defineProperty(wrapper, key, desc);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return wrapper;
|
|
63
|
+
};
|
|
64
|
+
})();
|
|
65
|
+
|
|
66
|
+
// src/hook.ts
|
|
67
|
+
var hook = (() => {
|
|
68
|
+
const nativeStrMap = /* @__PURE__ */ new WeakMap();
|
|
69
|
+
const nativeFnMap = /* @__PURE__ */ new WeakMap();
|
|
70
|
+
const $toString = Function.prototype.toString;
|
|
71
|
+
const $defineProperty = Object.defineProperty;
|
|
72
|
+
const $reflectApply = Reflect.apply;
|
|
73
|
+
return {
|
|
74
|
+
method(obj, fnName, handler) {
|
|
75
|
+
const original = obj[fnName];
|
|
76
|
+
const desc = getDescriptor(obj, fnName);
|
|
77
|
+
if (!desc || !original || typeof original !== "function")
|
|
78
|
+
return;
|
|
79
|
+
const wrapper = createWrapper(handler, original);
|
|
80
|
+
nativeStrMap.set(wrapper, $toString.call(original));
|
|
81
|
+
nativeFnMap.set(wrapper, original);
|
|
82
|
+
$defineProperty(obj, fnName, { ...desc, value: wrapper });
|
|
83
|
+
},
|
|
84
|
+
bindThisMethod(obj, fnName, handler) {
|
|
85
|
+
this.method(obj, fnName, (target, args, thisArg) => {
|
|
86
|
+
handler(() => $reflectApply(target, thisArg, args), args, thisArg);
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
constructor(obj, fnName, handler) {
|
|
90
|
+
const original = obj[fnName];
|
|
91
|
+
const desc = getDescriptor(obj, fnName);
|
|
92
|
+
if (!desc || !original || typeof original !== "function")
|
|
93
|
+
return;
|
|
94
|
+
const wrapper = createWrapper(handler, original);
|
|
95
|
+
if (wrapper.prototype && "constructor" in wrapper.prototype) {
|
|
96
|
+
wrapper.prototype.constructor = wrapper;
|
|
97
|
+
}
|
|
98
|
+
nativeStrMap.set(wrapper, $toString.call(original));
|
|
99
|
+
nativeFnMap.set(wrapper, original);
|
|
100
|
+
$defineProperty(obj, fnName, { ...desc, value: wrapper });
|
|
101
|
+
},
|
|
102
|
+
getNativeString(fn) {
|
|
103
|
+
if (nativeStrMap.has(fn))
|
|
104
|
+
return nativeStrMap.get(fn);
|
|
105
|
+
return $reflectApply($toString, fn, []);
|
|
106
|
+
},
|
|
107
|
+
getNativeFunction(hookedFn) {
|
|
108
|
+
return nativeFnMap.get(hookedFn);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
})();
|
|
112
|
+
|
|
113
|
+
// src/logger.ts
|
|
114
|
+
var createLogger = /* @__PURE__ */ (() => {
|
|
115
|
+
const $log = console.log;
|
|
116
|
+
const $warn = console.warn;
|
|
117
|
+
const $error = console.error;
|
|
118
|
+
return (prefix) => ({
|
|
119
|
+
log: (...args) => $log(`[${prefix}]`, ...args),
|
|
120
|
+
warn: (...args) => $warn(`[${prefix}]`, ...args),
|
|
121
|
+
error: (...args) => $error(`[${prefix}]`, ...args)
|
|
122
|
+
});
|
|
123
|
+
})();
|
|
124
|
+
|
|
125
|
+
// src/index.ts
|
|
126
|
+
function hookScope(callback, rootWindow) {
|
|
127
|
+
const interceptorSetupMap = /* @__PURE__ */ new WeakSet();
|
|
128
|
+
const hookedWindows = /* @__PURE__ */ new WeakMap();
|
|
129
|
+
let windowCounter = 0;
|
|
130
|
+
const $reflectApply = Reflect.apply;
|
|
131
|
+
const getWindowId = (win) => {
|
|
132
|
+
if (!hookedWindows.has(win)) {
|
|
133
|
+
const id = win === rootWindow ? "main" : `iframe-${++windowCounter}`;
|
|
134
|
+
hookedWindows.set(win, id);
|
|
135
|
+
}
|
|
136
|
+
return hookedWindows.get(win);
|
|
137
|
+
};
|
|
138
|
+
const setupToStringHook = (win) => {
|
|
139
|
+
hook.method(win.Function.prototype, "toString", (target, args, thisArg) => {
|
|
140
|
+
const fakeString = hook.getNativeString(thisArg);
|
|
141
|
+
if (fakeString)
|
|
142
|
+
return fakeString;
|
|
143
|
+
return $reflectApply(target, thisArg, args);
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
function setupIframeInterceptor(win, applyCallback) {
|
|
147
|
+
if (interceptorSetupMap.has(win))
|
|
148
|
+
return;
|
|
149
|
+
interceptorSetupMap.add(win);
|
|
150
|
+
const logger = createLogger(getWindowId(win));
|
|
151
|
+
try {
|
|
152
|
+
const NodeProto = win.Node.prototype;
|
|
153
|
+
const hookInsertMethod = (methodName) => {
|
|
154
|
+
hook.method(NodeProto, methodName, (target, args, thisArg) => {
|
|
155
|
+
const result = $reflectApply(target, thisArg, args);
|
|
156
|
+
try {
|
|
157
|
+
const node = args[0];
|
|
158
|
+
if (!node || node.tagName !== "IFRAME")
|
|
159
|
+
return result;
|
|
160
|
+
const iframe = node;
|
|
161
|
+
const handleIframe = () => {
|
|
162
|
+
const childWin = iframe.contentWindow;
|
|
163
|
+
if (childWin) {
|
|
164
|
+
applyCallback(childWin);
|
|
165
|
+
setupIframeInterceptor(childWin, applyCallback);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
handleIframe();
|
|
169
|
+
} catch (e) {
|
|
170
|
+
logger.warn(`Error in ${String(methodName)} hook:`, e);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
hookInsertMethod("appendChild");
|
|
176
|
+
hookInsertMethod("insertBefore");
|
|
177
|
+
hookInsertMethod("replaceChild");
|
|
178
|
+
} catch (e) {
|
|
179
|
+
logger.warn("\u274C Failed to setup iframe interceptor", e);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const applyToWindow = (win) => {
|
|
183
|
+
if (hookedWindows.has(win))
|
|
184
|
+
return;
|
|
185
|
+
const logger = createLogger(getWindowId(win));
|
|
186
|
+
setupToStringHook(win);
|
|
187
|
+
const utils = {
|
|
188
|
+
hookMethod: (obj, fnName, handler) => {
|
|
189
|
+
hook.method(obj, fnName, handler);
|
|
190
|
+
},
|
|
191
|
+
hookBindThisMethod(obj, fnName, handler) {
|
|
192
|
+
hook.bindThisMethod(obj, fnName, handler);
|
|
193
|
+
},
|
|
194
|
+
getNativeFunction: hook.getNativeFunction,
|
|
195
|
+
getNativeString: hook.getNativeString,
|
|
196
|
+
getCurrentWindowId: () => getWindowId(win)
|
|
197
|
+
};
|
|
198
|
+
try {
|
|
199
|
+
callback(utils, win);
|
|
200
|
+
logger.log("\u2705 StealthHook applied");
|
|
201
|
+
} catch (e) {
|
|
202
|
+
logger.warn("User callback failed:", e);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
applyToWindow(rootWindow);
|
|
206
|
+
setupIframeInterceptor(rootWindow, applyToWindow);
|
|
207
|
+
}
|
|
208
|
+
return __toCommonJS(index_exports);
|
|
209
|
+
})();
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// src/descriptor.ts
|
|
2
|
+
var getDescriptor = /* @__PURE__ */ (() => {
|
|
3
|
+
const $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
4
|
+
const $getPrototypeOf = Object.getPrototypeOf;
|
|
5
|
+
return function(obj, prop) {
|
|
6
|
+
let curr = obj;
|
|
7
|
+
while (curr) {
|
|
8
|
+
const desc = $getOwnPropertyDescriptor(curr, prop);
|
|
9
|
+
if (desc)
|
|
10
|
+
return desc;
|
|
11
|
+
curr = $getPrototypeOf(curr);
|
|
12
|
+
}
|
|
13
|
+
return void 0;
|
|
14
|
+
};
|
|
15
|
+
})();
|
|
16
|
+
|
|
17
|
+
// src/fake.ts
|
|
18
|
+
var createWrapper = /* @__PURE__ */ (() => {
|
|
19
|
+
const $ownKeys = Reflect.ownKeys;
|
|
20
|
+
const $defineProperty = Object.defineProperty;
|
|
21
|
+
return function(handler, fn) {
|
|
22
|
+
const wrapper = {
|
|
23
|
+
[fn.name](...args) {
|
|
24
|
+
return handler(fn, args, this);
|
|
25
|
+
}
|
|
26
|
+
}[fn.name];
|
|
27
|
+
const keys = $ownKeys(fn);
|
|
28
|
+
for (const key of keys) {
|
|
29
|
+
if (key === "arguments" || key === "caller")
|
|
30
|
+
continue;
|
|
31
|
+
const desc = getDescriptor(fn, key);
|
|
32
|
+
if (desc) {
|
|
33
|
+
$defineProperty(wrapper, key, desc);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return wrapper;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
|
|
40
|
+
// src/hook.ts
|
|
41
|
+
var hook = (() => {
|
|
42
|
+
const nativeStrMap = /* @__PURE__ */ new WeakMap();
|
|
43
|
+
const nativeFnMap = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
const $toString = Function.prototype.toString;
|
|
45
|
+
const $defineProperty = Object.defineProperty;
|
|
46
|
+
const $reflectApply = Reflect.apply;
|
|
47
|
+
return {
|
|
48
|
+
method(obj, fnName, handler) {
|
|
49
|
+
const original = obj[fnName];
|
|
50
|
+
const desc = getDescriptor(obj, fnName);
|
|
51
|
+
if (!desc || !original || typeof original !== "function")
|
|
52
|
+
return;
|
|
53
|
+
const wrapper = createWrapper(handler, original);
|
|
54
|
+
nativeStrMap.set(wrapper, $toString.call(original));
|
|
55
|
+
nativeFnMap.set(wrapper, original);
|
|
56
|
+
$defineProperty(obj, fnName, { ...desc, value: wrapper });
|
|
57
|
+
},
|
|
58
|
+
bindThisMethod(obj, fnName, handler) {
|
|
59
|
+
this.method(obj, fnName, (target, args, thisArg) => {
|
|
60
|
+
handler(() => $reflectApply(target, thisArg, args), args, thisArg);
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
constructor(obj, fnName, handler) {
|
|
64
|
+
const original = obj[fnName];
|
|
65
|
+
const desc = getDescriptor(obj, fnName);
|
|
66
|
+
if (!desc || !original || typeof original !== "function")
|
|
67
|
+
return;
|
|
68
|
+
const wrapper = createWrapper(handler, original);
|
|
69
|
+
if (wrapper.prototype && "constructor" in wrapper.prototype) {
|
|
70
|
+
wrapper.prototype.constructor = wrapper;
|
|
71
|
+
}
|
|
72
|
+
nativeStrMap.set(wrapper, $toString.call(original));
|
|
73
|
+
nativeFnMap.set(wrapper, original);
|
|
74
|
+
$defineProperty(obj, fnName, { ...desc, value: wrapper });
|
|
75
|
+
},
|
|
76
|
+
getNativeString(fn) {
|
|
77
|
+
if (nativeStrMap.has(fn))
|
|
78
|
+
return nativeStrMap.get(fn);
|
|
79
|
+
return $reflectApply($toString, fn, []);
|
|
80
|
+
},
|
|
81
|
+
getNativeFunction(hookedFn) {
|
|
82
|
+
return nativeFnMap.get(hookedFn);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
})();
|
|
86
|
+
|
|
87
|
+
// src/logger.ts
|
|
88
|
+
var createLogger = /* @__PURE__ */ (() => {
|
|
89
|
+
const $log = console.log;
|
|
90
|
+
const $warn = console.warn;
|
|
91
|
+
const $error = console.error;
|
|
92
|
+
return (prefix) => ({
|
|
93
|
+
log: (...args) => $log(`[${prefix}]`, ...args),
|
|
94
|
+
warn: (...args) => $warn(`[${prefix}]`, ...args),
|
|
95
|
+
error: (...args) => $error(`[${prefix}]`, ...args)
|
|
96
|
+
});
|
|
97
|
+
})();
|
|
98
|
+
|
|
99
|
+
// src/index.ts
|
|
100
|
+
function hookScope(callback, rootWindow) {
|
|
101
|
+
const interceptorSetupMap = /* @__PURE__ */ new WeakSet();
|
|
102
|
+
const hookedWindows = /* @__PURE__ */ new WeakMap();
|
|
103
|
+
let windowCounter = 0;
|
|
104
|
+
const $reflectApply = Reflect.apply;
|
|
105
|
+
const getWindowId = (win) => {
|
|
106
|
+
if (!hookedWindows.has(win)) {
|
|
107
|
+
const id = win === rootWindow ? "main" : `iframe-${++windowCounter}`;
|
|
108
|
+
hookedWindows.set(win, id);
|
|
109
|
+
}
|
|
110
|
+
return hookedWindows.get(win);
|
|
111
|
+
};
|
|
112
|
+
const setupToStringHook = (win) => {
|
|
113
|
+
hook.method(win.Function.prototype, "toString", (target, args, thisArg) => {
|
|
114
|
+
const fakeString = hook.getNativeString(thisArg);
|
|
115
|
+
if (fakeString)
|
|
116
|
+
return fakeString;
|
|
117
|
+
return $reflectApply(target, thisArg, args);
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
function setupIframeInterceptor(win, applyCallback) {
|
|
121
|
+
if (interceptorSetupMap.has(win))
|
|
122
|
+
return;
|
|
123
|
+
interceptorSetupMap.add(win);
|
|
124
|
+
const logger = createLogger(getWindowId(win));
|
|
125
|
+
try {
|
|
126
|
+
const NodeProto = win.Node.prototype;
|
|
127
|
+
const hookInsertMethod = (methodName) => {
|
|
128
|
+
hook.method(NodeProto, methodName, (target, args, thisArg) => {
|
|
129
|
+
const result = $reflectApply(target, thisArg, args);
|
|
130
|
+
try {
|
|
131
|
+
const node = args[0];
|
|
132
|
+
if (!node || node.tagName !== "IFRAME")
|
|
133
|
+
return result;
|
|
134
|
+
const iframe = node;
|
|
135
|
+
const handleIframe = () => {
|
|
136
|
+
const childWin = iframe.contentWindow;
|
|
137
|
+
if (childWin) {
|
|
138
|
+
applyCallback(childWin);
|
|
139
|
+
setupIframeInterceptor(childWin, applyCallback);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
handleIframe();
|
|
143
|
+
} catch (e) {
|
|
144
|
+
logger.warn(`Error in ${String(methodName)} hook:`, e);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
hookInsertMethod("appendChild");
|
|
150
|
+
hookInsertMethod("insertBefore");
|
|
151
|
+
hookInsertMethod("replaceChild");
|
|
152
|
+
} catch (e) {
|
|
153
|
+
logger.warn("\u274C Failed to setup iframe interceptor", e);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const applyToWindow = (win) => {
|
|
157
|
+
if (hookedWindows.has(win))
|
|
158
|
+
return;
|
|
159
|
+
const logger = createLogger(getWindowId(win));
|
|
160
|
+
setupToStringHook(win);
|
|
161
|
+
const utils = {
|
|
162
|
+
hookMethod: (obj, fnName, handler) => {
|
|
163
|
+
hook.method(obj, fnName, handler);
|
|
164
|
+
},
|
|
165
|
+
hookBindThisMethod(obj, fnName, handler) {
|
|
166
|
+
hook.bindThisMethod(obj, fnName, handler);
|
|
167
|
+
},
|
|
168
|
+
getNativeFunction: hook.getNativeFunction,
|
|
169
|
+
getNativeString: hook.getNativeString,
|
|
170
|
+
getCurrentWindowId: () => getWindowId(win)
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
callback(utils, win);
|
|
174
|
+
logger.log("\u2705 StealthHook applied");
|
|
175
|
+
} catch (e) {
|
|
176
|
+
logger.warn("User callback failed:", e);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
applyToWindow(rootWindow);
|
|
180
|
+
setupIframeInterceptor(rootWindow, applyToWindow);
|
|
181
|
+
}
|
|
182
|
+
export {
|
|
183
|
+
hookScope
|
|
184
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wulu007/stealth-hook",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "A stealth JavaScript hook library with automatic iframe detection and function toString spoofing.",
|
|
6
|
+
"author": "wulu007",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/wulu007/stealth-hook",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/wulu007/stealth-hook.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"hook",
|
|
15
|
+
"scriptcat",
|
|
16
|
+
"Tampermonkey",
|
|
17
|
+
"monkey patch",
|
|
18
|
+
"stealth"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"default": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"require": "./dist/index.cjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"files": [
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"README.md",
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@antfu/eslint-config": "^7.1.0",
|
|
38
|
+
"@types/node": "^25.0.9",
|
|
39
|
+
"eslint": "^9.39.2",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"tsx": "^4.21.0",
|
|
42
|
+
"typescript": "^5.9.3"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"registry": "https://registry.npmjs.org/"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"dev": "tsup --watch",
|
|
51
|
+
"typecheck": "tsc --noEmit"
|
|
52
|
+
}
|
|
53
|
+
}
|