Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stronger Proxy detection #238

Open
Illya9999 opened this issue Aug 26, 2023 · 5 comments
Open

Add stronger Proxy detection #238

Illya9999 opened this issue Aug 26, 2023 · 5 comments

Comments

@Illya9999
Copy link

I believe stronger proxy detection as well as ways to recover the target object should be added to this project. I wrote up an example for some basic proxy detection/target recovery. Its fairly easy to bypass but for some reason people almost never add bypasses for this sort of basic detection method. There are also some easy changes here you could make to improve its resiliency to tampering and make it slightly more reliable.

I would also be interested in discussing more advanced detections/bypasses. Do you have some platform I can contact you directly on?

const getProxyTarget = (potentialProxy) => {
	let source = potentialProxy;
	let foundSource = false;
	let props = ['apply', 'construct', 'defineProperty', 'deleteProperty', 'get', 'getOwnPropertyDescriptor', 'getPrototypeOf', 'has', 'isExtensible', 'ownKeys', 'set', 'setPrototypeOf'];
	props.forEach(prop => {
		try {
			Object.defineProperty(Object.prototype, prop, {
				configurable: true,
				enumerable: false,
				writable: false,
				value: function (target) {
					/* you should be doing basic tests to ensure you found the correct source and you didnt just grab some random object because of the proxy */
					foundSource = true;
					source = target;
				}
			})
		} catch (_) {}
	});

	/* ideally you would check if you should return after each try/catch */
	try {
		potentialProxy();
	} catch (_) {}

	try {
		new potentialProxy();
	} catch (_) {}

	try {
		/* optionally you can do comparisons to make sure the value and property descriptor is correct */
		Object.defineProperty(potentialProxy, '__testKey__', { value: 5, enumerable: false, configurable: true, writable: true });
	} catch (_) {}

	try {
		potentialProxy.__testKey__ = 6;
	} catch (_) {}

	try {
		
		potentialProxy.__testKey__;
	} catch (_) {}
	
	try {
		Object.keys(potentialProxy);
	} catch (_) {}

	try {
		'__testKey__' in potentialProxy;
	} catch (_) {}

	try {
		Object.getOwnPropertyDescriptor(potentialProxy, '__testKey__');
	} catch (_) {}

	try {
		delete potentialProxy.__testKey__;
	} catch (_) {}

	try {
		Object.setPrototypeOf(potentialProxy, Object.getPrototypeOf(potentialProxy));
	} catch (_) {}

	try {
		Object.isExtensible(potentialProxy);
	} catch (_) {}

	props.forEach(prop => {
		try {
			delete Object.prototype[prop];
		} catch (_) {}
	});

	return {
		foundSource,
		source
	}
}
@abrahamjuliot
Copy link
Owner

Nice! This looks incredible.

This could be a valuable addition to the test page, allowing for a comparative analysis of its performance across various engines. Additionally, I am contemplating eliminating the majority of lie detections and a significant portion of the main script on the client side, and instead focusing on detecting anomalies and tracing fingerprints in the server-side time series. The main objective of the idea is to make the client-side code so simple that it will appear embarrassing and easy to fool. All the client-side analysis, including proxy detection, will probably be moved to the test pages.

I'm best reached here or anywhere in open-source repos on GitHub, discussions or issues.

@Illya9999
Copy link
Author

Illya9999 commented Aug 26, 2023

The main objective of the idea is to make the client-side code so simple that it will appear embarrassing and easy to fool

Ah I see. Well if you are interested in an example of some stronger proxy code for testing, this is the code I like to use. It's still in a very early WIP stage but it still works fairly nicely for most things. Even if you assume all the todo comments were completed, there are still quite a few ways to detect it. However it should still bypass most ways to detect things with just the current code. Also keep in mind this code is primarily written for chromium on windows.

const hooked = new WeakMap();
const getHook = WeakMap.prototype.get.bind(hooked);
const setHook = WeakMap.prototype.set.bind(hooked);
const hasHook = WeakMap.prototype.has.bind(hooked);

const proxies = new WeakMap();
const getProxy = WeakMap.prototype.get.bind(proxies);
const setProxy = WeakMap.prototype.set.bind(proxies);
const hasProxy = WeakMap.prototype.has.bind(proxies);

const initWin = new WeakSet();
const addInit = WeakSet.prototype.add.bind(initWin);
const hasInit = WeakSet.prototype.has.bind(initWin);

const Prox = Proxy;

const init = (win) => {
	if(hasInit(win))
		return;
	const getOwnPropDesc = win.Object.getOwnPropertyDescriptor.bind(win.Object);
	const defineProp = win.Object.defineProperty.bind(win.Object);
	const apply = win.Reflect.apply.bind(win.Reflect);
	const toString = win.Function.prototype.call.bind(win.Function.prototype.toString);

	const createArray = win.Array.from.bind(win.Array);
	
	const hook = (func, applyF) => {
		if(hasHook(func))
			return getHook(func);
		const proxy = new Prox(func, {
			__proto__: null,
	
			apply: applyF
		});
		setHook(func, proxy);
		setProxy(proxy, func);
		return proxy;
	}

	win.Function.prototype.toString = hook(win.Function.prototype.toString, function(target, thisArg, argumentsList) {
		if(hasProxy(thisArg)) {
			return toString(getProxy(thisArg));
		} else {
			return apply(target, thisArg, argumentsList)
		}
	});

	/* I hate this but its required. I think its probably the biggest weakpoint of the entire script */
	win.Object.create = hook(win.Object.create, function (target, thisArg, argumentsList) {
		/* the reason this is done like this is to emulate how apply would work with a proxy as the arguments array without giving away internal info to it */
		let len = argumentsList.length - 0 | 0,
			arr = createArray({ length: len, __proto__: null});
		for(let i = 0; i < len; i++) {
			arr[i] = argumentsList[i];
		}

		if(len > 0 && hasProxy(arr[0])) {
			arr[0] = getProxy(arr[0]);
		}


		/* i believe this can cause a problem due to it activating .length getter on the arr object, should be an easy fix */
		return apply(target, thisArg, arr)
	});


	/* i believe there can be some issues with this due to cross origin iframes. a solution needs to be created (chrome extension api?) */
	const initFrame = (frame) => {
		const contentWin = getOwnPropDesc(frame.prototype, 'contentWindow');
		contentWin.get = hook(contentWin.get, function(target, thisArg, argumentsList) {
			const frameWin = apply(target, thisArg, argumentsList);
			if(frameWin && !hasInit(frameWin)) {
				init(frameWin)
			}
			return frameWin;
		});
		defineProp(frame.prototype, 'contentWindow', contentWin);
		/* make sure to get contentDocument as well */

	}
	/* do for the other frame elements not just iframe */
	initFrame(win.HTMLIFrameElement);
	initFrame(win.HTMLFrameElement);


	/* make sure to add hooks for adding elements as well so incase they dont access the prop and the frame has code that would do stuff before u can hook */
	/* make sure to add hooks for innerHTML and such adding in a frame element */
	/* hook the error stack trace */
	/* add hooks for popup windows like the iframes */

	/* a solution should be added for service workers and such (chrome extension api?) */

	addInit(win);


	/* hooks here */
	win.alert = hook(win.alert, function() { return 'lol no'});
}
init(window);

@amadeocomo
Copy link

Hello, @abrahamjuliot ! I have seen the idea of implementing server side fingerprinting in your posts more than once. Can you suggest some articles to learn more about it?

@JWally
Copy link
Contributor

JWally commented Aug 30, 2023 via email

@abrahamjuliot
Copy link
Owner

What I'm doing is mostly experimental and straightforward. There is a confidence score that is satisfied by unique reporters per date over the span of a few days, and it gets marked for death if it becomes dormant. To review and purge data, I run 24-hour cron jobs.

Time series include multiple series that tell the code what segment of time we are in, such as the minute of 1444 in a day, or the current 10 minute and 30-minute episode in a day. The system then assembles pre-decided attributes into tiny spectra and observes erratic occurrences in the sequence. For instance, the code is capable of detecting in a 30-minute segment that features a and b are encountering an atypical amount of variation in feature c or c-z. It can do this for 100s or 1000s of different combinations and react to them.

I would recommend checking out the concepts below.

https://www.researchgate.net/publication/330357393_Deep_Learning_for_Anomaly_Detection_A_Survey
https://www.researchgate.net/publication/362296986_Anomaly_detection_in_time_series_a_comprehensive_evaluation

Biological fingerprinting has some interesting techniques that can be transferred to browser fingerprinting.
https://www.researchgate.net/publication/229886628_Fingerprinting_time_series_Dynamic_patterns_in_self-report_and_performance_measures_uncovered_by_a_graphical_non-linear_method
https://www.researchgate.net/publication/333783134_Fingerprint_Presentation_Attack_Detection_utilizing_Time-Series_Color_Fingerprint_Captures

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants