├── README.MD ├── concealed_proxy.userscript.js ├── index.html └── simple_proxy.userscript.js /README.MD: -------------------------------------------------------------------------------- 1 | ![https://hrt.github.io/TamperDetectJS/](https://i.imgur.com/wzvUtgM.png) 2 | 3 | https://hrt.github.io/TamperDetectJS/ 4 | 5 | 6 | # Detecting alterered native functions / prototypes in Javascript 7 | > "It is impossible to determine whether an object is a proxy or not (transparent virtualization)." - https://exploringjs.com/es6/ch_proxies.html 8 | 9 | Detecting altered prototypes is useful for when you don't want 3rd party scripts from messing with your page - including anti cheats for certain games. This repo explores methods of detecting if a method has been tampered with as well as methods to get around such detections. 10 | 11 | In conclusion, it's possible to determine if a proxy of a native function is a proxy. [There are many tells]( https://github.com/hrt/TamperDetectJS/blob/main/index.html#L12-L198). 12 | 13 | 14 | 15 | --- 16 | 17 | 18 | 19 | `index.html` is a standalone page with embeded javascript that will check if `Array.prototype.join` has been tampered with. 20 | 21 | You can view `index.html` on your browser here https://hrt.github.io/TamperDetectJS/ 22 | 23 | I've included two tampermonkey content script that tampers with `Array.prototype.join`. 24 | 25 | One is a simple proxy that gets detected. The other attempts to conceal the proxy but still fails... 26 | 27 | --- 28 | 29 | Big thonks to [Lemons1337](https://github.com/lemons1337) for chipping in. 30 | 31 | Note: This is meant only for chromium based browsers. You will get false detections on others. 32 | -------------------------------------------------------------------------------- /concealed_proxy.userscript.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Proxy hide solution 3 | // @match https://hrt.github.io/TamperDetectJS/ 4 | // @run-at document-start 5 | // ==/UserScript== 6 | 7 | // Remember to set TamperMonkeys inject mode to "instant" 8 | 9 | (function() { 10 | Error.prepareStackTrace = function(error, structuredStackTrace) { 11 | return error.stack.replace(/.*Object\.apply.*\n/g, '') 12 | .replace(/(.*)Proxy\.(.*)/g, '$1Function.$2') 13 | .replace(/(.*)Object\.(.*)/g, '$1Function.$2'); 14 | }; 15 | var proxyToOriginal = new WeakMap(); 16 | proxyToOriginal.get = WeakMap.prototype.get; // Avoid detection via tampering of WeakMap.prototype.get 17 | const apply = Reflect.apply; // Avoid detection via tampering of Function.prototype.apply / Reflect.apply 18 | function hook(parent, key, restoreOriginals) { 19 | try { 20 | const original = parent[key]; 21 | if (proxyToOriginal.get(original)) { 22 | return; // we're already a proxy 23 | } 24 | parent[key] = new Proxy(original, { 25 | apply: function(target, that, args) { 26 | if (restoreOriginals) { 27 | var newArgs = []; 28 | that = proxyToOriginal.get(that) || that; 29 | for (var i = 0; args && i < args.length; i++) { 30 | newArgs[i] = proxyToOriginal.get(args[i]) || args[i]; 31 | } 32 | args = newArgs; 33 | } 34 | try { 35 | return apply(original, that, args); 36 | } catch (e) { 37 | throw e 38 | } 39 | } 40 | }) 41 | proxyToOriginal.set(parent[key], original); 42 | } catch(e) {} 43 | }; 44 | var descriptors = Object.getOwnPropertyDescriptors(window); 45 | for (var key in descriptors) { 46 | try { 47 | var prototype = window[key].prototype; 48 | // Hook all prototype functions that can be applied to Array.prototype.join that can check if tampered 49 | hook(prototype, 'concat'); 50 | hook(prototype, 'push'); 51 | hook(prototype, 'join'); 52 | hook(prototype, 'sort'); 53 | hook(prototype, 'slice'); 54 | hook(prototype, 'shift'); 55 | hook(prototype, 'unshift'); 56 | hook(prototype, 'splice'); 57 | // toString functions should always attempt to replace proxied "this" to original 58 | hook(prototype, 'toString', true); 59 | hook(prototype, 'toLocaleString', true); 60 | hook(prototype, 'toDateString', true); 61 | hook(prototype, 'toLocaleDateString', true); 62 | hook(prototype, 'toLocaleTimeString', true); 63 | } catch(e) {} 64 | } 65 | // todo: Can be detected by checking if "Error.prepareStackTrace" is defined or by overwriting it 66 | 67 | // todo: Can be detected by checking error message of "ArrayBuffer.prototype in 0". 68 | // The resulting string representation of "ArrayBuffer.prototype" behaves weird when "Object.prototype.toString" is truly non-native. 69 | })(); 70 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 524 | 535 | 536 | 537 | 538 | 539 | 540 | -------------------------------------------------------------------------------- /simple_proxy.userscript.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Simple Proxy 3 | // @include * 4 | // @run-at document-start 5 | // ==/UserScript== 6 | // for this to run on local html files, allow tamper monkey to access local files 7 | (function() { 8 | 'use strict'; 9 | Array.prototype.join = new Proxy(Array.prototype.join, { 10 | apply: function(target, _this, _arguments) { 11 | return target.apply(_this, _arguments); 12 | }}); 13 | console.warn('loaded JoinHook'); 14 | })(); 15 | --------------------------------------------------------------------------------