admin gist felülvizsgálása . Revízióhoz ugrás
1 file changed, 281 insertions
6.md(fájl létrehozva)
@@ -0,0 +1,281 @@ | |||
1 | + | ### 防切屏检测 | |
2 | + | ||
3 | + | chrome浏览器自行安装油猴脚本 | |
4 | + | ||
5 | + | ``` | |
6 | + | // ==UserScript== | |
7 | + | // @name 通用阻止切屏检测 | |
8 | + | // @namespace http://tampermonkey.net/ | |
9 | + | // @version 0.1.0 | |
10 | + | // @description 尝试阻止各类网站的切屏、焦点丢失等检测 | |
11 | + | // @author xxxxxx | |
12 | + | // @match http://*/* | |
13 | + | // @match https://*/* | |
14 | + | // @run-at document-start | |
15 | + | // @grant unsafeWindow | |
16 | + | // @license GPL-3.0 | |
17 | + | // ==/UserScript== | |
18 | + | ||
19 | + | (function () { | |
20 | + | 'use strict'; | |
21 | + | const window = unsafeWindow; // 使用原始 window 对象 | |
22 | + | ||
23 | + | // 黑名单事件,这些事件的监听器将被阻止 | |
24 | + | const blackListedEvents = new Set([ | |
25 | + | "visibilitychange", // 页面可见性改变 | |
26 | + | "blur", // 元素或窗口失去焦点 | |
27 | + | "focus", // 元素或窗口获得焦点 (某些检测可能反向利用focus) | |
28 | + | "pagehide", // 页面隐藏(例如导航到其他页面) | |
29 | + | "freeze", // 页面被冻结 (较新的事件) | |
30 | + | "resume", // 页面从冻结状态恢复 (较新的事件) | |
31 | + | "mouseleave", // 鼠标移出元素(通常是 document 或 body) | |
32 | + | "mouseout", // 鼠标移出元素(更通用的移出,但要小心副作用) | |
33 | + | // "focusout", // 元素将要失去焦点(与blur类似,但更通用,看情况添加) | |
34 | + | // "focusin", // 元素将要获得焦点(与focus类似,看情况添加) | |
35 | + | ]); | |
36 | + | ||
37 | + | // 白名单属性,这些属性在 document 对象上将被伪造 | |
38 | + | const spoofedDocumentProperties = { | |
39 | + | hidden: { value: false, configurable: true }, | |
40 | + | mozHidden: { value: false, configurable: true }, // Firefox (旧版) | |
41 | + | msHidden: { value: false, configurable: true }, // Internet Explorer | |
42 | + | webkitHidden: { value: false, configurable: true }, // Chrome, Safari, Opera (旧版 Blink/WebKit) | |
43 | + | visibilityState: { value: "visible", configurable: true }, | |
44 | + | hasFocus: { value: () => true, configurable: true } | |
45 | + | }; | |
46 | + | ||
47 | + | // 需要清空/置空的事件处理器属性 (on-event handlers) | |
48 | + | const eventHandlersToNullifyDocument = [ | |
49 | + | "onvisibilitychange", | |
50 | + | "onblur", | |
51 | + | "onfocus", | |
52 | + | "onmouseleave", | |
53 | + | "onmouseout", | |
54 | + | // "onfocusout", | |
55 | + | // "onfocusin", | |
56 | + | "onpagehide", | |
57 | + | "onfreeze", | |
58 | + | "onresume" | |
59 | + | ]; | |
60 | + | ||
61 | + | const eventHandlersToNullifyWindow = [ | |
62 | + | "onblur", | |
63 | + | "onfocus", | |
64 | + | "onpagehide", | |
65 | + | "onpageshow", // 有些检测可能通过 pageshow 结合 persisted 属性判断 | |
66 | + | "onfreeze", | |
67 | + | "onresume", | |
68 | + | "onmouseleave", // window 也有 onmouseleave | |
69 | + | "onmouseout" | |
70 | + | ]; | |
71 | + | ||
72 | + | const isDebug = false; // 设置为 true 以启用调试日志 | |
73 | + | const scriptPrefix = "[通用阻止切屏检测]"; | |
74 | + | const log = console.log.bind(console, `%c${scriptPrefix}`, 'color: #4CAF50; font-weight: bold;'); | |
75 | + | const warn = console.warn.bind(console, `%c${scriptPrefix}`, 'color: #FFC107; font-weight: bold;'); | |
76 | + | const error = console.error.bind(console, `%c${scriptPrefix}`, 'color: #F44336; font-weight: bold;'); | |
77 | + | const debug = isDebug ? log : () => { }; | |
78 | + | ||
79 | + | /** | |
80 | + | * 伪装函数的 toString 方法,使其看起来像原始函数。 | |
81 | + | * @param {Function} modifiedFunction 被修改的函数 | |
82 | + | * @param {Function} originalFunction 原始函数 | |
83 | + | */ | |
84 | + | function patchToString(modifiedFunction, originalFunction) { | |
85 | + | if (typeof modifiedFunction !== 'function' || typeof originalFunction !== 'function') { | |
86 | + | warn("patchToString: 传入的参数不是函数。", modifiedFunction, originalFunction); | |
87 | + | return; | |
88 | + | } | |
89 | + | try { | |
90 | + | const originalToStringSource = Function.prototype.toString.call(originalFunction); | |
91 | + | modifiedFunction.toString = () => originalToStringSource; | |
92 | + | ||
93 | + | // 进一步伪装 toString.toString | |
94 | + | const originalToStringToStringSource = Function.prototype.toString.call(originalFunction.toString); | |
95 | + | Object.defineProperty(modifiedFunction.toString, 'toString', { | |
96 | + | value: () => originalToStringToStringSource, | |
97 | + | enumerable: false, | |
98 | + | configurable: true, // 保持可配置,以防万一 | |
99 | + | writable: false | |
100 | + | }); | |
101 | + | debug(`patchToString applied for: ${originalFunction.name || 'anonymous function'}`); | |
102 | + | } catch (e) { | |
103 | + | error("patchToString failed:", e, "for function:", originalFunction.name); | |
104 | + | } | |
105 | + | } | |
106 | + | ||
107 | + | ||
108 | + | /** | |
109 | + | * 劫持并修改对象的 addEventListener 方法。 | |
110 | + | * @param {EventTarget} targetObject 要劫持的对象 (window, document, Element) | |
111 | + | * @param {string} objectName 用于日志记录的对象名称 | |
112 | + | */ | |
113 | + | function patchAddEventListener(targetObject, objectName) { | |
114 | + | if (!targetObject || typeof targetObject.addEventListener !== 'function') { | |
115 | + | warn(`Cannot patch addEventListener for invalid target: ${objectName}`); | |
116 | + | return; | |
117 | + | } | |
118 | + | const originalAddEventListener = targetObject.addEventListener; | |
119 | + | ||
120 | + | targetObject.addEventListener = function (type, listener, optionsOrCapture) { | |
121 | + | if (blackListedEvents.has(type.toLowerCase())) { | |
122 | + | log(`BLOCKED ${objectName}.addEventListener: ${type}`); | |
123 | + | return undefined; // 阻止添加黑名单中的事件监听器 | |
124 | + | } | |
125 | + | debug(`ALLOWED ${objectName}.addEventListener: ${type}`, listener, optionsOrCapture); | |
126 | + | return originalAddEventListener.call(this, type, listener, optionsOrCapture); | |
127 | + | }; | |
128 | + | ||
129 | + | patchToString(targetObject.addEventListener, originalAddEventListener); | |
130 | + | log(`${objectName}.addEventListener patched.`); | |
131 | + | } | |
132 | + | ||
133 | + | /** | |
134 | + | * 劫持并修改对象的 removeEventListener 方法 (可选,但建议一起修改)。 | |
135 | + | * @param {EventTarget} targetObject 要劫持的对象 | |
136 | + | * @param {string} objectName 用于日志记录的对象名称 | |
137 | + | */ | |
138 | + | function patchRemoveEventListener(targetObject, objectName) { | |
139 | + | if (!targetObject || typeof targetObject.removeEventListener !== 'function') { | |
140 | + | warn(`Cannot patch removeEventListener for invalid target: ${objectName}`); | |
141 | + | return; | |
142 | + | } | |
143 | + | const originalRemoveEventListener = targetObject.removeEventListener; | |
144 | + | ||
145 | + | targetObject.removeEventListener = function (type, listener, optionsOrCapture) { | |
146 | + | if (blackListedEvents.has(type.toLowerCase())) { | |
147 | + | log(`Original call to ${objectName}.removeEventListener for blacklisted event '${type}' would have been ignored by our addEventListener patch anyway. Allowing native call if needed.`); | |
148 | + | // 即使我们阻止了 addEventListener,原始的 removeEventListener 仍然应该能安全调用 | |
149 | + | // 因为如果监听器从未被添加,调用 remove 也无害。 | |
150 | + | } | |
151 | + | debug(`PASSTHROUGH ${objectName}.removeEventListener: ${type}`, listener, optionsOrCapture); | |
152 | + | return originalRemoveEventListener.call(this, type, listener, optionsOrCapture); | |
153 | + | }; | |
154 | + | patchToString(targetObject.removeEventListener, originalRemoveEventListener); | |
155 | + | log(`${objectName}.removeEventListener patched.`); | |
156 | + | } | |
157 | + | ||
158 | + | ||
159 | + | /** | |
160 | + | * 修改对象上的属性,使其返回伪造的值。 | |
161 | + | * @param {object} targetObject 目标对象 (e.g., document) | |
162 | + | * @param {object} propertiesToSpoof 属性描述对象 | |
163 | + | * @param {string} objectName 对象名称 | |
164 | + | */ | |
165 | + | function spoofProperties(targetObject, propertiesToSpoof, objectName) { | |
166 | + | if (!targetObject) { | |
167 | + | warn(`Cannot spoof properties for invalid target: ${objectName}`); | |
168 | + | return; | |
169 | + | } | |
170 | + | for (const prop in propertiesToSpoof) { | |
171 | + | if (Object.prototype.hasOwnProperty.call(propertiesToSpoof, prop)) { | |
172 | + | try { | |
173 | + | Object.defineProperty(targetObject, prop, propertiesToSpoof[prop]); | |
174 | + | debug(`Spoofed ${objectName}.${prop}`); | |
175 | + | } catch (e) { | |
176 | + | error(`Failed to spoof ${objectName}.${prop}:`, e); | |
177 | + | } | |
178 | + | } | |
179 | + | } | |
180 | + | log(`${objectName} properties spoofed.`); | |
181 | + | } | |
182 | + | ||
183 | + | /** | |
184 | + | * 清空或置空对象上的事件处理器属性。 | |
185 | + | * @param {object} targetObject 目标对象 | |
186 | + | * @param {string[]} eventHandlerNames 事件处理器名称数组 | |
187 | + | * @param {string} objectName 对象名称 | |
188 | + | */ | |
189 | + | function nullifyEventHandlers(targetObject, eventHandlerNames, objectName) { | |
190 | + | if (!targetObject) { | |
191 | + | warn(`Cannot nullify event handlers for invalid target: ${objectName}`); | |
192 | + | return; | |
193 | + | } | |
194 | + | eventHandlerNames.forEach(handlerName => { | |
195 | + | try { | |
196 | + | Object.defineProperty(targetObject, handlerName, { | |
197 | + | get: () => { | |
198 | + | debug(`Access to ${objectName}.${handlerName} (get), returning undefined.`); | |
199 | + | return undefined; | |
200 | + | }, | |
201 | + | set: (newHandler) => { | |
202 | + | log(`Attempt to set ${objectName}.${handlerName} blocked.`); | |
203 | + | if (typeof newHandler === 'function') { | |
204 | + | // 可以选择性地调用 newHandler,或者完全阻止 | |
205 | + | // debug(`(Blocked) Handler function was:`, newHandler); | |
206 | + | } | |
207 | + | }, | |
208 | + | configurable: true // 保持可配置,以便脚本可以多次运行或被其他脚本修改 | |
209 | + | }); | |
210 | + | debug(`Nullified ${objectName}.${handlerName}`); | |
211 | + | } catch (e) { | |
212 | + | error(`Failed to nullify ${objectName}.${handlerName}:`, e); | |
213 | + | } | |
214 | + | }); | |
215 | + | log(`${objectName} on-event handlers nullified.`); | |
216 | + | } | |
217 | + | ||
218 | + | // --- 开始执行 --- | |
219 | + | ||
220 | + | log("Script starting..."); | |
221 | + | ||
222 | + | // 1. 劫持 window 和 document 的 addEventListener/removeEventListener | |
223 | + | patchAddEventListener(window, "window"); | |
224 | + | patchRemoveEventListener(window, "window"); // 也 patch removeEventListener 以保持一致性 | |
225 | + | patchAddEventListener(document, "document"); | |
226 | + | patchRemoveEventListener(document, "document"); | |
227 | + | ||
228 | + | // 2. 修改 document 的属性 | |
229 | + | spoofProperties(document, spoofedDocumentProperties, "document"); | |
230 | + | ||
231 | + | // 3. 置空 document 和 window 上的事件处理器 | |
232 | + | nullifyEventHandlers(document, eventHandlersToNullifyDocument, "document"); | |
233 | + | nullifyEventHandlers(window, eventHandlersToNullifyWindow, "window"); | |
234 | + | ||
235 | + | // 4. 对于 document.body,需要等待 DOMContentLoaded | |
236 | + | // 使用 MutationObserver 确保 body 存在时立即 patch,比 DOMContentLoaded 更早且更可靠 | |
237 | + | const observer = new MutationObserver((mutations, obs) => { | |
238 | + | if (document.body) { | |
239 | + | patchAddEventListener(document.body, "document.body"); | |
240 | + | patchRemoveEventListener(document.body, "document.body"); | |
241 | + | // 对于 document.body,也可以考虑 nullify onmouseleave, onmouseout 等 | |
242 | + | nullifyEventHandlers(document.body, ["onmouseleave", "onmouseout", "onblur", "onfocus"], "document.body"); | |
243 | + | log("document.body patched via MutationObserver."); | |
244 | + | obs.disconnect(); // 完成任务后断开观察者 | |
245 | + | } | |
246 | + | }); | |
247 | + | ||
248 | + | if (document.body) { // 如果 body 已经存在 (不太可能在 document-start,但以防万一) | |
249 | + | patchAddEventListener(document.body, "document.body"); | |
250 | + | patchRemoveEventListener(document.body, "document.body"); | |
251 | + | nullifyEventHandlers(document.body, ["onmouseleave", "onmouseout", "onblur", "onfocus"], "document.body"); | |
252 | + | log("document.body patched directly."); | |
253 | + | } else { | |
254 | + | observer.observe(document.documentElement || document, { childList: true, subtree: true }); | |
255 | + | } | |
256 | + | ||
257 | + | ||
258 | + | // 5. 调试:劫持计时器 (如果 isDebug 为 true) | |
259 | + | if (isDebug) { | |
260 | + | const originalSetInterval = window.setInterval; | |
261 | + | window.setInterval = function(...args) { | |
262 | + | const id = originalSetInterval.apply(this, args); | |
263 | + | debug("calling window.setInterval", id, args); | |
264 | + | return id; | |
265 | + | }; | |
266 | + | patchToString(window.setInterval, originalSetInterval); | |
267 | + | ||
268 | + | const originalSetTimeout = window.setTimeout; | |
269 | + | window.setTimeout = function(...args) { | |
270 | + | const id = originalSetTimeout.apply(this, args); | |
271 | + | debug("calling window.setTimeout", id, args); | |
272 | + | return id; | |
273 | + | }; | |
274 | + | patchToString(window.setTimeout, originalSetTimeout); | |
275 | + | log("Timer functions (setInterval, setTimeout) wrapped for debugging."); | |
276 | + | } | |
277 | + | ||
278 | + | log("Script execution finished. Monitoring active."); | |
279 | + | ||
280 | + | })(); | |
281 | + | ``` |
Újabb
Régebbi