This is the second article of our series about anti-detect browsers. In the first article, we gave an overview of anti-detect browsers, their main features and what they’re used for. In this second article, we do a deep dive into Undetectable, a popular anti-detect browser.
We start by providing 1) an overview of Undetectable, its main features and the different fingerprinting attributes it randomizes. 2) Then, we analyze JS files injected by Undetectable as well as its binaries to better understand how it operates. 3) Finally, we propose a JavaScript challenge that can be used to detect Undetectable specifically.
Overview of Undetectable
Undetectable can be downloaded for free and is available on MacOS and Windows

Even though it can be used for free, they have different pricing plans that offer more or less features, including when it comes to the possible to use diverse browser fingerprints (aka browser configurations on their site).

Undetectable, as most of the anti-detect browser, is organized around the concept of “profile”. A profile can be seen as a browser instance configuration.
For a given profile you can define different parameters of the associated device fingerprinting, such as:
- the OS
- the browser and its version
- characteristics related to the device like the number of CPU cores, the amount of RAM and the screen resolution
- contextual information such as the timezone, the languages
- whether or not you want to randomize canvas and audio fingerprinting
- The proxies linked to this profile

Browser instances are isolated from each other. Thus, each of them have their own configuration, their own browser file, their own cookies. It avoid sharing accounts and session cookies between different browser profiles that claimed to operate from different IPs and countries for example.
Analyzing Undetectable
In this section we analyze Undetectable to see how it operates. The end-goal is to find or create signals that we could use to detect its presence.
The anti-detect browser directory contains dozens of binary files. To speed up our analysis, we create a bash script that iterate recursively from the root of the anti-detect repository and dump all the strings of binary files only if they contain certain special substrings that are related to fingerprinting signals.
#!/bin/bash
# Define the output directory
OUTPUT_DIR="$HOME/code/strings_output"
mkdir -p "$OUTPUT_DIR"
# Find executable binary files and process them
find . -type f | while read -r file; do
if file "$file" | grep -qE 'ELF|Mach-O|PE32'; then # Check if the file is an executable binary
filename=$(basename "$file")
output_file="$OUTPUT_DIR/str_output_${filename}"
# Extract all readable strings
strings_output=$(strings "$file")
# Check if any of the specified patterns exist in the output
if echo "$strings_output" | grep -qE 'webdriver|toDataURL|webglConfig|CanvasWebglRandomParameter|mediaDevices|nmasked|peechSynthesis|nativeCanvas|botScriptUpd|webdriverAlwaysOff|randonPresureOnMobile|batary|hasOwnProperty'; then
echo "$strings_output" > "$output_file"
echo "Processed: $file -> $output_file"
fi
fi
done
echo "All done. Outputs are stored in: $OUTPUT_DIR"
Among the thousands of strings extracted from the chrome helper binary we notice some potentially interesting JS code:

To understand if the code is used by the anti-detect browser, we start an instance of undetectable and visit example.com to see if the script is injected by the browser. However, we don’t notice anything in the devtools.
Then, we try to force the code to reveal its presence if it is actually running in the browser. To achieve this, we leverage the fact that when a DOM node is inserted, then the code calls the getElementsByTagName
function. Thus, we override the getElementsByTagName
native function to log something in the console when it’s called. To trigger the event, we create and insert an iframe into the DOM as follows:
// We override the getElementsByTagName function
document.getElementsByTagName = (elt) => {console.log('was called')}
// We create and insert an iframe
const iframe = document.createElement('iframe')
document.body.appendChild(iframe)
When we run the previous code with an undetectable browser instance our getElementsByTagName
function is called. We see the was called
string in the browser console, and it also triggers an error because we don’t override our function properly (on purpose) to easily catch the caller of getElementsByTagName
.

The script responsible for calling getElementsByTagName
is VM5:44
. If we click on VM5:44
in the stack trace, this brings us to the source of the error:

Bingo! This is the JavaScript code snippet we identified in the Chrome helper binary,
This is part of an obfuscated file that contains code related to browser fingerprinting.

We use https://obf-io.deobfuscate.io/ to deobfuscate the script since it has been obfuscated with obfuscator.io
Overview of the injected anti-detect script
The deobfuscated script is ~2500 lines once deobfuscated. It starts with a list of attribute exceptions that should not be altered later in the code.
var exceptionList = ["credentials", "mediaDevices", "cookieEnabled", "mediaCapabilities", "clipboard", "serviceWorker", "scheduling", "locks", "storage", "webkitTemporaryStorage", "webkitPersistentStorage", "plugins", "mimeTypes", "virtualKeyboard", "connection", "webdriver", "userAgentData", "webkitRequestFileSystem", "RTCPeerConnection", "RTCSessionDescription", "webkitRTCPeerConnection", "webkitResolveLocalFileSystemURL", "webkitStorageInfo", "webkitRequestAnimationFrame", "webkitCancelAnimationFrame", "openDatabase", "javaEnabled", "getUserMedia", "bluetooth", "MediaStreamTrack"];
Then, there are ~800 lines that define the main
object. It contains all the data needed to lie about the user's device fingerprint.
var main = {
"window.screen": {
"availWidth": 1920,
"availHeight": 1055,
"width": 1920,
"height": 1080,
"colorDepth": 24,
"pixelDepth": 24,
"availLeft": 1470,
"availTop": 25,
"orientation": {
"angle": 0,
"type": "landscape-primary",
"onchange": null,
"lock": [""],
"unlock": [""],
"addEventListener": [""],
"dispatchEvent": [""],
"removeEventListener": [""],
toString: function () {
return "[object ScreenOrientation]";
}
},
"onchange": null,
"isExtended": false,
"addEventListener": {
toString: function () {
return "function addEventListener() { [native code] }";
}
},
// ...
// Other properties
"gpuDataObj": {
"driver": "",
"vendor": "apple",
"device": "",
"description": "",
"architecture": "common-3"
}
}
Then from line 878 to 2550 they define the function _0x4e6f8c(_0x59a9bf)
function. This function is the core of the program and is called with _0x59a9bf
= window
It contains all the logic to override the native functions and attributes part of the browser fingerprint. Note that the script doesn’t always apply all the lies. It depends on:
- The user configuration defined in the anti-detect UI
- The script version (hard-coded to 9 in our case)
- The site visited. Indeed, they have more aggressive versions for sites like bet365 that are heavily protected
Finally, the last part of the script, ~~25 lines, keeps an eye on iframe creation.
It detects if Iframes are inserted into the DOM using Mutation observers.
function iframeCheck(ev) {
if (ev.tagName == 'IFRAME') {
ready();
}
}
;
const observer = new MutationObserver(mutationList => mutationList.filter(m => m.type === 'childList').forEach(m => {
m.addedNodes.forEach(iframeCheck);
}));
observer.observe(document, {
childList: true,
subtree: true
});
When that’s the case, it calls the ready
function we presented before where it sets the configuration for iframes with setConfig(allFrames[i].contentWindow);
Note that the setConfig
function is not defined in this script so it’s unclear what the purpose of this part.
Deep dives into the fingerprinting lies
We focus on the _0x4e6f8c
function where all the lies occur. Note that in the following analysis, we manually updated some of the code snippets to make them more readable. We just selected a few sections of the functions since it’s quite long.
We notice that the JS script enforces fingerprint modifications only when the browser is different from Chrome and Edge. It makes sense since Undetectable is based on Chromium. Thus, if the user pretends to be Safari or Firefox, there are more lies to apply.
For example, for the screen
object that provides information about the screen. It needs to handle properties that exist in Chrome but not in the fake browser we pretend to have. Thus, in the code snippet below we see that they define a window.screen.type
property.
Object.defineProperty(window.ScreenOrientation.prototype, 'type', {
'get': function () {
return main["window.screen"][_0x378993].type;
},
'enumerable': true,
'configurable': true
});
This type of lie can be easily detected from JavaScript since it has observable side effects. Indeed, if we analyze the getter of screen.orientation.type
and look at its toString representation, we observe obfuscated code linked to the anti-detect browser script rather than some browser native code.
Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(screen.orientation),
'type'
).get

If the browser chosen in the anti-detect UI is different from Chrome and Edge, they completely lie about certain attributes, such as WebGL.
if (browserType != 'chrome' && browserType != 'edge') {
var webglConfig = {
"webgl": {
"GL Version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)",
"Shading Language Version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)",
"Vendor": "WebKit",
"Renderer": "WebKit WebGL"
},
"webgl2": {
"GL Version": "WebGL 2.0 (OpenGL ES 3.0 Chromium)",
"Shading Language Version": "WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)",
"Vendor": "WebKit",
"Renderer": "WebKit WebGL"
},
"Unmasked Vendor": "Google Inc. (Apple)",
"Unmasked Renderer": "ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version)"
};
// ...
var _0x54ada7 = 10.0366;
var _0x28a7f3 = "WebGL 1.0";
var _0x393341 = "WebGL GLSL ES 1.0";
var _0x520974 = "Mozilla";
var _0x3ce6dd = 'Mozilla';
var _0x29332a = "Google Inc.";
var _0x50b906 = "ANGLE (Intel(R) HD Graphics Family Direct3D11 vs_5_0 ps_5_0)";
var _0x4af697 = "WebGL 2.0";
var _0x8f803a = "WebGL GLSL ES 2.0";
var _0xaecd1e = 'Mozilla';
var _0x441bd9 = "Mozilla";
if (webglConfig != undefined) {
_0x50b906 = "ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version)";
}
;
if (webglConfig != undefined) {
_0x29332a = "Google Inc. (Apple)";
}
if (webglConfig != undefined) {
_0x28a7f3 = webglConfig.webgl["GL Version"];
_0x393341 = webglConfig.webgl["Shading Language Version"];
_0x520974 = webglConfig.webgl.Vendor;
_0x3ce6dd = webglConfig.webgl.Renderer;
if (webglConfig.webgl2["GL Version"] != undefined) {
_0x4af697 = webglConfig.webgl2["GL Version"];
_0x8f803a = webglConfig.webgl2["Shading Language Version"];
_0xaecd1e = webglConfig.webgl2.Vendor;
_0x441bd9 = webglConfig.webgl2.Renderer;
} else {
delete window.WebGL2RenderingContext;
}
}
Note that the obfuscated file is generated dynamically. Some values like the webglConfig variable are different depending on the browser config you create in the anti-detect UI. For a fake Safari browser, it looks as follows.
var webglConfig = {
"webgl": {
"GL Version": "WebGL 1.0",
"Shading Language Version": "WebGL GLSL ES 1.0 (1.0)",
"Vendor": "WebKit",
"Renderer": "WebKit WebGL"
},
"webgl2": {
"GL Version": "WebGL 2.0",
"Shading Language Version": "WebGL GLSL ES 3.00",
"Vendor": "WebKit",
"Renderer": "WebKit WebGL"
},
"Unmasked Vendor": "Apple Inc.",
"Unmasked Renderer": "Apple GPU"
};
To make the anti-detect browser less suspects. they:
- Erase certain attributes and functions that exist in Chrome but not in the claimed browser.
- Define attributes and functions that should exist for the claimed browser but don’t exist in Chrome (since Undetectable is based on Chrome)
if (browserName == "firefox") {
window.InstallTrigger = {};
window.mozRTCPeerConnection = window.RTCPeerConnection;
delete window.webkitRTCPeerConnection;
} else {
if (browserName != 'chrome' || browserName == "edge") {
delete window.webkitRTCPeerConnection;
} else {
window.webkitRTCPeerConnection = window.RTCPeerConnection;
;
}
}
// ...
if (browserName != "chrome" && browserName != "edge") {
delete window.webkitRequestFileSystem;
delete window.webkitRequestAnimationFrame;
delete window.webkitCancelAnimationFrame;
}
// ...
if (window.Navigator.prototype.getVRDisplays != undefined) {
window.Navigator.prototype.getVRDisplays.toString = function () {
return "function getVRDisplays() { [native code] }";
};
}
They take care of changing the toString representations of native functions to avoid revealing the fact that it’s been overridden.
if (browserName == "firefox" || browserName == "safari") {
window.eval.toString = function () {
return "function eval() { [native code] }";
};
}
To summarize, the script injected by Undetectable only apply lies for non-Chromium based browsers. This was confirmed by Undetectable support.

Investigating Chromium-based fingerprinting lies
We saw that the previous JS script injected by anti-detect is only used when the user claims to be a non-Chromium-based browser, so how does Undetectable forge attributes when the user is on Chrome?
For Chromium-based browsers, the lies are directly applied within Chromium. To learn more, we use Ghidra to reverse engineer the key binaries used by Undetectable.
We start with the chrome_helper
binary. If we look at the strings extracted by Ghidra, we notice the scriptVersion
strings that we found in the JS script we analyzed in the previous section.
This string is used in a function that read the user profile/browser configuration defined in the Undetectable UI and generates the JS program dynamically based on the parameter, cf screenshot below.

It injects all the parameters like "CanvasWebglRandomParameter"
dynamically.

The binary is also handling CDP commands when Undectable is used as a bot, but it’s not doing much more.
The fingerprinting lies occur in the chromium_framework
binary. It read the profile properties and load them into chromium. In the screenshot below we see the different properties like navigator.deviceMemory
and navigator.hardwareConcurrency
that are part of the parameters that can be configured in the Undetectable UI.

They also load the parameters related to WebGPU:

Since the lies are applied directly from within the browser code and not at the JS level, there won’t be any obvious side effects like modified functions toStrings or modified prototypes.
We stop our analysis here and, in the next section, we discuss how we can detect undetectable browsers.
How can we detect the Undetectable anti-detect browser?
Based on our previous analysis, we can distinguish two situations:
- When the browser profile selected in the Undetectable UI claims to be Safari or Firefox, i.e. a non-Chromium based browser;
- When the browser profile selected is Chrome or Edge.
Detecting non-Chromium-based browsers: In the first situation, we see that some of the fingerprinting lies are applied using JavaScript, which makes it easier to detect them.
For example, if we pretend to be Safari in our browser profile, it overrides the eval
function. The script takes care of overridden the toString
of eval
, but not the toString
of the toString
function itself. Thus, we can run the code snippet below to detect that it has been overridden.

Indeed, in the case of a genuine browser, the toString would indicate that the function is native:

Detecting Chromium-based browsers: We leverage the fact that Undetectable injects a JS program, even if it doesn’t apply fingerprint lies on Chromium-based browsers. Thus, similarly to what we did earlier, we override the document.getElementsByTagName
function to:
- Know if it is called;
- Throw and catch an exception to get more context about which function/script called it;
// Store the original function
const originalGetElementsByTagName = document.getElementsByTagName
// We override getElementsByTagName
document.getElementsByTagName = function(tagName) {
try {
// we throw an exception on purpose to get more context through the stack trace
null[0];
} catch (e) {
console.log('Stack trace:');
console.log(e.stack);
// If the stack trace contains the `ready` function and the `iframeCheck` function
// Then we know this is Undetectable
if (e.stack.includes('ready') && e.stack.includes('iframeCheck')) {
console.log('Undetectable detected!')
}
}
return originalGetElementsByTagName.call(this, tagName)
}
// We create and insert an iframe to trigger `getElementsByTagName` if
// the user is running Undetectable
const iframe = document.createElement('iframe')
document.body.appendChild(iframe)
When we execute the code in Undetectable, we see the following stack trace. It contains traces of functions defined by Undetectable such as ready and iframeCheck. Thus, we can detect the presence of the anti-detect Undetectable browser with high accuracy.

While we presented two specific approaches that can detect the Undetectable browser specifically, in general, it’s key to collect as many fingerprinting signals as possible and to correlate their values with each other to detect lies. Thus, we can catch anti-detect browsers using fingerprint inconsistencies even for situations when you don’t have a specific browser signature,
Conclusion
In this article, we did a deep dive into Undetectable, a popular anti-detect browser. Through reverse engineering, we discovered how it applies fingerprinting lies both at the JavaScript level for non-Chromium browsers and directly within Chromium for Chrome-based browsers. We also presented two reliable detection techniques that can identify when a user is running Undetectable, regardless of the browser profile they're trying to impersonate.
The key findings of our analysis include:
- For non-Chromium browsers (Safari, Firefox), Undetectable relies heavily on JavaScript injection to modify browser properties and behaviors, which leaves detectable traces like modified function toString representations.
- For Chromium-based browsers, modifications are made directly within the browser's core functionality through the chromium_framework binary, making them more difficult to detect through conventional means.
- Despite these sophisticated approaches, we found reliable detection methods that work across both scenarios - either by identifying JavaScript modifications or by detecting Undetectable's characteristic script injection patterns.
This research demonstrates that while anti-detect browsers are becoming increasingly sophisticated, they still leave detectable patterns that can be used to identify their presence. As these tools continue to evolve, we expect to see more advanced techniques for both fingerprint spoofing and detection methods.