January 9, 2026
18 Views
Welcome

Breaking Anti-Cheat: A Complete Guide from Event Hijacking to Prototype Chain Pollution

Breaking Anti-Cheat: A Complete Guide from Event Hijacking to Prototype Chain Pollution

⚠️ Disclaimer

This content is intended solely for technical research and security education purposes.

  1. The code examples analyzed in this article are randomly selected technical samples and do not target any specific website, system, or organization
  2. The technical methods and code snippets in this article are used to illustrate web security principles and attack-defense mechanisms
  3. If the examples in this article are similar to any actual system, it is purely a technical coincidence and does not indicate that this article targets that system
  4. Readers must comply with the laws and regulations of their country and region when applying the techniques in this article
  5. Unauthorized testing or attacking others' systems is illegal, and the author assumes no responsibility for any abuse of the techniques in this article
  6. Readers are advised to conduct technical verification only in their own test environments or with explicit authorization

Technical research should serve to improve system security, not to disrupt legitimate services. Please use the knowledge in this article responsibly.


Analysis Target

The complete anti-cheat script I want to bypass:

javascript
1// ULTIMATE Anti-cheat script - CONDITIONAL LOADING
2
3(function () {
4	'use strict';
5
6	// Status indicator (disabled for cleaner UI)
7	function showStatus() {
8		// Red banner removed for better user experience
9		// Protection is still active, just invisible
10	}
11
12	// Universal blocker
13	function universalBlock(e) {
14		// Only block if we should be active
15		if (!shouldActivate()) return;
16
17		const type = e.type;
18		const key = e.key ? e.key.toLowerCase() : '';
19		const ctrl = e.ctrlKey || e.metaKey;
20
21		// Block ALL keyboard shortcuts when Ctrl is pressed
22		if (ctrl) {
23			e.preventDefault();
24			e.stopPropagation();
25			e.stopImmediatePropagation();
26			alert('⚠️ Shortcuts disabled to ensure fair assessment');
27			return false;
28		}
29
30		// Block F12
31		if (key === 'f12') {
32			e.preventDefault();
33			e.stopPropagation();
34			e.stopImmediatePropagation();
35			alert('⚠️ Developer tools disabled to ensure fair assessment');
36			return false;
37		}
38
39		// Block right click
40		if (type === 'contextmenu') {
41			e.preventDefault();
42			e.stopPropagation();
43			e.stopImmediatePropagation();
44			alert('⚠️ Right-click menu disabled to ensure fair assessment');
45			return false;
46		}
47
48		// Block clipboard events
49		if (['copy', 'cut', 'paste'].includes(type)) {
50			e.preventDefault();
51			e.stopPropagation();
52			e.stopImmediatePropagation();
53			alert(
54				`⚠️ ${type === 'copy' ? 'Copy' : type === 'cut' ? 'Cut' : 'Paste'} disabled to ensure fair assessment`
55			);
56			return false;
57		}
58
59		// Block selection
60		if (['selectstart', 'dragstart'].includes(type)) {
61			e.preventDefault();
62			return false;
63		}
64	}
65
66	let isActive = false;
67
68	// Check if we should activate protection based on URL
69	function shouldActivate() {
70		const path = window.location.pathname;
71		// Only activate on assessment pages, NOT survey or admin pages
72		return path.match(/^\/assessment\/[^/]+$/) || path.match(/^\/[^/]+\/assessment\/[^/]+$/);
73	}
74
75	// Immediate activation
76	function activate() {
77		if (!shouldActivate()) {
78			if (isActive) {
79				console.log('🛡️ Anti-cheat deactivated (not on assessment page)');
80				deactivate();
81			}
82			return;
83		}
84
85		if (isActive) return; // Already active
86
87		isActive = true;
88		console.log('🚀 ULTIMATE Anti-cheat loading...');
89		console.log('🛡️ Anti-cheat activated for assessment page');
90
91		// Add universal event listener to EVERYTHING
92		const events = [
93			'keydown',
94			'keyup',
95			'keypress',
96			'contextmenu',
97			'copy',
98			'cut',
99			'paste',
100			'selectstart',
101			'dragstart',
102			'mousedown',
103			'mouseup',
104		];
105
106		events.forEach(eventType => {
107			// Add to document with highest priority
108			document.addEventListener(eventType, universalBlock, {
109				capture: true,
110				passive: false,
111				once: false,
112			});
113
114			// Add to window as backup
115			window.addEventListener(eventType, universalBlock, {
116				capture: true,
117				passive: false,
118				once: false,
119			});
120		});
121
122		// Disable selection everywhere
123		const style = document.createElement('style');
124		style.id = 'anti-cheat-style';
125		style.innerHTML = `
126            *, *::before, *::after {
127                -webkit-user-select: none !important;
128                -moz-user-select: none !important;
129                -ms-user-select: none !important;
130                user-select: none !important;
131                -webkit-touch-callout: none !important;
132                -webkit-user-drag: none !important;
133            }
134            input, textarea, [contenteditable] {
135                -webkit-user-select: text !important;
136                -moz-user-select: text !important;
137                -ms-user-select: text !important;
138                user-select: text !important;
139            }
140        `;
141
142		if (document.head) {
143			document.head.appendChild(style);
144		} else {
145			setTimeout(() => document.head.appendChild(style), 100);
146		}
147
148		showStatus();
149		console.log('✅ ULTIMATE Anti-cheat loaded and ACTIVE');
150	}
151
152	// Deactivate protection
153	function deactivate() {
154		if (!isActive) return;
155		isActive = false;
156
157		// Remove style
158		const style = document.getElementById('anti-cheat-style');
159		if (style) style.remove();
160
161		// Note: Event listeners are harder to remove cleanly, but they'll check shouldActivate()
162	}
163
164	// Check and activate based on current page
165	function checkAndActivate() {
166		activate();
167	}
168
169	// Activate immediately
170	checkAndActivate();
171
172	// Also activate when DOM is ready
173	if (document.readyState === 'loading') {
174		document.addEventListener('DOMContentLoaded', checkAndActivate);
175	}
176
177	// Check every few seconds for navigation changes
178	setInterval(checkAndActivate, 3000);
179})();

My Attack Strategy Overview

Target ProtectionImplementationMy Bypass Method
Keyboard Event InterceptionaddEventListener('keydown')Hijack EventTarget.prototype.addEventListener
F12 InterceptionDetect key === 'f12'Event listener not registered
Right-Click Menu InterceptionaddEventListener('contextmenu')Event listener not registered
Copy/Paste InterceptionaddEventListener('copy/paste/cut')Event listener not registered
Text Selection DisabledCSS user-select: noneMutationObserver + Periodic Cleanup
Alert Warningsalert()Hijack window.alert
Periodic ChecksetInterval(3000)Faster cleanup frequency (2000)

Core Breakthrough: Event Hijacking

Target's Protection Mechanism

After analyzing the target script, I discovered it intercepts events using the following method:

javascript
1function universalBlock(e) {
2    if (!shouldActivate()) return;
3    const ctrl = e.ctrlKey || e.metaKey;
4    if (ctrl) {
5        e.preventDefault();
6        e.stopPropagation();
7        e.stopImmediatePropagation();
8        return false;
9    }
10}
11
12events.forEach(eventType => {
13    document.addEventListener(eventType, universalBlock, {
14        capture: true,
15        passive: false,
16    });
17});

It intercepts in the capture phase using stopImmediatePropagation() to prevent propagation. This looks solid, but I found a fatal weakness.

My Solution

My bypass strategy is simple: hijack addEventListener itself before it registers events.

javascript
1const originalAddEventListener = EventTarget.prototype.addEventListener;
2
3EventTarget.prototype.addEventListener = function (type, listener, options) {
4    if (blockedEvents.includes(type)) {
5        return;
6    }
7    return originalAddEventListener.call(this, type, listener, options);
8};

Implementation logic: When the target script calls document.addEventListener('keydown', universalBlock), it actually executes my hijacked version. I simply return empty, and its listener is never registered.

My Attack Timeline

text
1Phase 1: Early Page Load
2       My script executes (Tampermonkey @run-at document-start)
3       Hijack EventTarget.prototype.addEventListener
4
5Phase 2: Target Script Loads
6       Calls document.addEventListener()
7       Actually executes my hijacked version, listener not registered
8
9Phase 3: User Interaction
10       User presses Ctrl+C
11       Browser triggers keydown event
12       No listener responds
13       Copy succeeds

Timing Advantage: My script executes at the document-start timing, tens to hundreds of milliseconds earlier than the target script (depending on network and script size). This time window is sufficient to complete the prototype chain hijacking.

CSS Countermeasures

Target's CSS Protection

The target script injects styles to disable selection:

javascript
1const style = document.createElement('style');
2style.id = 'anti-cheat-style';
3style.innerHTML = `
4    * {
5        user-select: none !important;
6    }
7    input, textarea {
8        user-select: text !important;
9    }
10`;
11document.head.appendChild(style);

My Multi-Layer Defense

I adopted a four-layer defense strategy:

Layer 1: Remove Target Styles

javascript
1function removeAntiCheatStyles() {
2    const style = document.getElementById('anti-cheat-style');
3    if (style) {
4        style.remove();
5    }
6}

Layer 2: Inject My Counter Styles

javascript
1function enableTextSelection() {
2    const style = document.createElement('style');
3    style.id = 'bypass-style';
4    style.innerHTML = `
5        * {
6            user-select: text !important;
7        }
8    `;
9    document.head.appendChild(style);
10}

Layer 3: Real-time DOM Monitoring

javascript
1const observer = new MutationObserver(mutations => {
2    mutations.forEach(mutation => {
3        mutation.addedNodes.forEach(node => {
4            if (node.id === 'anti-cheat-style') {
5                removeAntiCheatStyles();
6                enableTextSelection();
7            }
8        });
9    });
10});
11
12// Only monitor direct child additions to <head>, avoiding performance overhead
13observer.observe(document.head, {
14    childList: true,
15    subtree: false
16});

Layer 4: Periodic Cleanup

javascript
1function periodicCleanup() {
2    removeAntiCheatStyles();
3    setTimeout(periodicCleanup, 2000);
4}

Alert Interception

I hijacked the alert function to block annoying popups:

javascript
1const originalAlert = window.alert;
2
3window.alert = function (message) {
4    if (message && typeof message === 'string' && message.includes('to ensure fair assessment')) {
5        return;
6    }
7    return originalAlert.call(window, message);
8};

Time Competition Advantage

I designed a faster cleanup frequency than the target:

TimeTarget BehaviorMy Behavior
0sFirst activation, inject stylesHijacking complete, remove styles
2s-1st cleanup
3sCheck, attempt to re-injectAlready cleaned
4s-2nd cleanup
6sCheck, MutationObserver detects immediatelyRemove immediately

I maintain a 1-second time advantage.

Execution Timing is Key

I exploited the browser loading flow:

text
11. Parse HTML
22. Tampermonkey (@run-at document-start) ← My script here
33. Parse <head>
44. Parse <body>
55. Execute <script> ← Target script here
66. DOMContentLoaded
77. Load resources
88. load event

I complete hijacking in step 2, while the target starts executing in step 5. By the time it wakes up, the game is already over.

The Power of Prototype Chain Hijacking

javascript
1// After my hijacking
2EventTarget.prototype.addEventListener = My hijacked version
3
4// Target attempts to register
5document.addEventListener('keydown', handler)
6My hijacked version.call(document, 'keydown', handler)
7return; // Not registered
8
9// Target cannot recover
10// Original function saved in my closure, inaccessible externally

Target might attempt countermeasures:

javascript
1// Attempt 1: Detect hijacking
2if (EventTarget.prototype.addEventListener.toString().includes('blocked')) {
3    // Discovered, but cannot recover original function
4}
5
6// Attempt 2: Use original reference
7const original = EventTarget.prototype.addEventListener;
8// Already my hijacked version
9
10// Attempt 3: Obtain from iframe
11const iframe = document.createElement('iframe');
12const clean = iframe.contentWindow.EventTarget.prototype.addEventListener;
13// My Tampermonkey executes in all frames

My Defense System

text
11. Event Hijacking      ← Core, destroys target's foundation
22. Alert Interception   ← Blocks harassment
33. CSS Removal          ← Restores functionality
44. MutationObserver     ← Real-time response
55. Periodic Cleanup     ← Continuous protection
66. Console Monitoring   ← Early warning

Even if one layer fails, other layers still work.

Target Weaknesses I Discovered

Single Point of Dependency

The target's fatal weakness: all events are registered through addEventListener. I only need to hijack this one API, and the entire protection system collapses.

Fixed ID

javascript
1style.id = 'anti-cheat-style';

Too easy to locate. I can directly getElementById then remove().

If I were writing anti-cheat, I would use a random ID:

javascript
1style.id = `ac-${Math.random().toString(36).substr(2, 9)}`;

Check Frequency Too Slow

The target checks every 3 seconds, I clean every 2 seconds. In this competition, my cleanup frequency is consistently higher, maintaining a time advantage.

If I were implementing this, I would adopt a layered checking strategy:

Approach 1: Moderate Polling (Recommended)

javascript
1// Check once per second, balancing performance and response speed
2setInterval(checkAndActivate, 1000);

Approach 2: Event-Driven Checking

javascript
1// Trigger checks at critical moments, better performance
2window.addEventListener('focus', checkAndActivate);
3document.addEventListener('visibilitychange', checkAndActivate);
4
5// Check on route changes
6const observer = new PerformanceObserver((list) => {
7    for (const entry of list.getEntries()) {
8        if (entry.entryType === 'navigation') {
9            checkAndActivate();
10        }
11    }
12});
13observer.observe({ entryTypes: ['navigation'] });

Approach 3: Lightweight DOM Monitoring

javascript
1// Only monitor style element additions in <head>
2const observer = new MutationObserver((mutations) => {
3    for (const mutation of mutations) {
4        for (const node of mutation.addedNodes) {
5            if (node.tagName === 'STYLE' && node.id === 'anti-cheat-style') {
6                checkAndActivate();
7                break;
8            }
9        }
10    }
11});
12
13// Only monitor direct child node additions in <head>, avoiding global monitoring performance overhead
14observer.observe(document.head, {
15    childList: true,
16    subtree: false
17});

Precautions:

  • Avoid: Monitoring document.documentElement + subtree: true

    • Monitors all changes to the entire DOM tree
    • May trigger hundreds of times per second on complex pages
    • Performance overhead similar to requestAnimationFrame
  • Recommended: Only monitor specific targets (like document.head)

    • Precise targeting, triggered on demand
    • Performance impact negligible

Why not use requestAnimationFrame?

  • 60fps = 60 calls per second, excessive performance overhead
  • Anti-cheat checks don't need animation-level precision
  • Affects page smoothness and battery life
  • Attackers can also hijack requestAnimationFrame

Best practice: Combine Approach 1 and Approach 2 to ensure coverage while controlling performance overhead.

Missing Integrity Check

The target never detects if APIs have been hijacked.

Improvement approach:

javascript
1const originalString = EventTarget.prototype.addEventListener.toString();
2
3function checkIntegrity() {
4    const current = EventTarget.prototype.addEventListener.toString();
5    if (current !== originalString) {
6        // Hijacking detected
7    }
8}

But I can further hijack toString():

javascript
1Function.prototype.toString = function() {
2    if (this === EventTarget.prototype.addEventListener) {
3        return originalToString.call(originalAddEventListener);
4    }
5    return originalToString.call(this);
6};

This leads to an infinite attack-defense loop.

Real-World Scenarios

Scenario 1: User Presses Ctrl+C

Target's expectation:

keydown event → universalBlock → preventDefault → alert → Fail

Actual (after my script takes effect):

keydown event → No listener → Browser default behavior → Success

Scenario 2: User Selects Text

Target's expectation:

user-select: none → Cannot select

Actual:

My bypass-style (user-select: text) → MutationObserver monitoring → Success

Scenario 3: Target Reactivates After 3 Seconds

Target's expectation:

setInterval → Re-register → Re-inject CSS → Recover

Actual:

text
1setInterval → Calls my hijacked addEventListener → Not registered
2appendChild → My MutationObserver detects → Remove immediately → Fail

Advanced Countermeasures

If Target Uses Code Obfuscation

javascript
1var _0x1a2b=['addEventListener','keydown'];
2(function(_0x4a5b,_0x6c7d){
3    var _0x8e9f=function(_0x10g11){
4        while(--_0x10g11){
5            _0x4a5b['push'](_0x4a5b['shift']());
6        }
7    };
8})(_0x1a2b,0x123));

Obfuscation only increases reading difficulty, it doesn't change execution logic. I hijack low-level APIs, no need to understand obfuscated code.

If Target Uses Iframe Sandbox

Target might attempt to obtain clean APIs through iframe:

javascript
1const iframe = document.createElement('iframe');
2iframe.style.display = 'none';
3document.body.appendChild(iframe);
4
5// Obtain unhijacked API from iframe
6const clean = iframe.contentWindow.EventTarget.prototype.addEventListener;
7clean.call(document, 'keydown', handler);

My Countermeasure 1: Tampermonkey Global Hijacking

javascript
1// @match *://*/*
2// @run-at document-start
3// @grant none
4
5// Tampermonkey executes in all frames by default, including dynamically created iframes
6// APIs inside newly created iframes are also hijacked

My Countermeasure 2: Monitor iframe Creation

javascript
1const originalCreate = document.createElement;
2document.createElement = function(tag) {
3    const el = originalCreate.call(document, tag);
4    if (tag.toLowerCase() === 'iframe') {
5        // Use original addEventListener (saved in closure)
6        originalAddEventListener.call(el, 'load', () => {
7            // After iframe loads, hijack its internal APIs
8            if (el.contentWindow) {
9                const iframeProto = el.contentWindow.EventTarget.prototype;
10                const iframeOriginal = iframeProto.addEventListener;
11                iframeProto.addEventListener = function(type, listener, options) {
12                    if (blockedEvents.includes(type)) return;
13                    return iframeOriginal.call(this, type, listener, options);
14                };
15            }
16        });
17    }
18    return el;
19};

Limitation: If iframe uses strict same-origin policy or sandbox attributes, accessing contentWindow may be blocked. But in that case, the target script also cannot access APIs inside the iframe.

If Target Uses Service Worker

javascript
1navigator.serviceWorker.register('/anti-cheat-sw.js');

My countermeasure:

javascript
1const originalRegister = navigator.serviceWorker.register;
2navigator.serviceWorker.register = function(...args) {
3    return Promise.reject(new Error('Blocked'));
4};

JavaScript Features I Exploited

javascript
1// JavaScript allows me to rewrite anything
2Object.prototype.toString = function() { return 'hacked'; };
3window.alert = function() { console.log('blocked'); };
4EventTarget.prototype.addEventListener = function() {};

JavaScript has no true private members. All objects can be accessed and modified by me. Even using Object.freeze(), I can hijack before freezing.

Closure-Protected Code

javascript
1(function () {
2    const original = EventTarget.prototype.addEventListener;
3
4    EventTarget.prototype.addEventListener = function(...args) {
5        return original.call(this, ...args);
6    };
7})();
8
9// External code cannot access original
10// Target cannot recover original function

Conclusion

Client-side protection can never be completely secure. Execution timing, prototype chain hijacking, and JavaScript's dynamic nature give the attacker an inherent advantage.

True security must rely on server-side validation. Client-side anti-cheat can only serve as an auxiliary measure, combined with behavior analysis and multi-factor authentication to raise the barrier.

But for pure client-side protection, there's always a way to bypass it.

References

[1] Google Developers. (n.d.). Chrome extensions API reference. https://developer.chrome.com/docs/extensions/reference/

[2] Google Developers. (n.d.). How browsers work. Web.dev. https://web.dev/articles/howbrowserswork

[3] Greasespot Wiki. (n.d.). Greasemonkey manual: API. https://wiki.greasespot.net/Greasemonkey_Manual:API

[4] Mozilla. (n.d.). Closures. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

[5] Mozilla. (n.d.). Content security policy (CSP). MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

[6] Mozilla. (n.d.). Critical rendering path. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path

[7] Mozilla. (n.d.). Event.stopImmediatePropagation(). MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/API/Event/stopImmediatePropagation

[8] Mozilla. (n.d.). EventTarget.addEventListener(). MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

[9] Mozilla. (n.d.). MutationObserver. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

[10] Mozilla. (n.d.). Same-origin policy. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy

[11] OWASP Foundation. (n.d.). Web security testing guide. https://owasp.org/www-project-web-security-testing-guide/

[12] PortSwigger. (n.d.). DOM-based cross-site scripting. Web Security Academy. https://portswigger.net/web-security/cross-site-scripting/dom-based

[13] PortSwigger. (n.d.). Prototype pollution. Web Security Academy. https://portswigger.net/web-security/prototype-pollution

[14] Tampermonkey. (n.d.). Documentation. https://www.tampermonkey.net/documentation.php

Enjoyed this article?

Share it with your friends and colleagues!

Welcome
Last updated: January 9, 2026
相关文章
正在检查服务状态...
Breaking Anti-Cheat: A Complete Guide from Event Hijacking to Prototype Chain Pollution - ICTRUN