Skip to content

Commit

Permalink
Further improved WebGL spoofing beta.12
Browse files Browse the repository at this point in the history
- Added ability to spoof webgl2 supported extensions
- Added ability to block parameters that aren't defined in config
- Passing null in config will block the value
- Added more parameters to the demo site
  • Loading branch information
daijro committed Oct 15, 2024
1 parent 1532d7b commit 5bfc3ee
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 65 deletions.
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,27 +348,30 @@ Because I don't have a dataset of WebGL fingerprints to rotate against, WebGL fi
This repository includes a demo site (see [here](https://github.com/daijro/camoufox/blob/main/scripts/examples/webgl.html)) that prints your browser's WebGL parameters. You can use this site to generate WebGL fingerprints for Camoufox from other devices.
<img src="https://i.imgur.com/jwT5VqG.png" width="80%">
<img src="https://i.imgur.com/jwT5VqG.png">
### Properties
Camoufox supports spoofing WebGL parameters, supported extensions, context attributes, and shader precision formats.
**Note**: Do NOT randomly assign values to these properties. WAFs hash your WebGL fingerprint and compare it against a dataset. Randomly assigning values will lead to detection as an unknown device.
| Property | Description | Example |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| webgl:renderer | Spoofs the name of the unmasked WebGL renderer. | `"NVIDIA GeForce GTX 980, or similar"` |
| webgl:vendor | Spoofs the name of the unmasked WebGL vendor. | `"NVIDIA Corporation"` |
| webgl:supportedExtensions | An array of supported WebGL extensions ([full list](https://registry.khronos.org/webgl/extensions/)). | `["ANGLE_instanced_arrays", "EXT_color_buffer_float", "EXT_disjoint_timer_query", ...]` |
| webgl:contextAttributes | A dictionary of WebGL context attributes. | `{"alpha": true, "antialias": true, "depth": true, ...}` |
| webgl2:contextAttributes | The same as `webgl:contextAttributes`, but for WebGL2. | `{"alpha": true, "antialias": true, "depth": true, ...}` |
| webgl:parameters | A dictionary of WebGL parameters. Keys must be GL enums, and values are the values to spoof them as. | `{"2849": 1, "2884": false, "2928": [0, 1], ...}` |
| webgl2:parameters | The same as `webgl:parameters`, but for WebGL2. | `{"2849": 1, "2884": false, "2928": [0, 1], ...}` |
| webgl:shaderPrecisionFormats | A dictionary of WebGL shader precision formats. Keys are formatted as `"<shaderType>,<precisionType>"`. | `{"35633,36336": {"rangeMin": 127, "rangeMax": 127, "precision": 23}, ...}` |
| webgl2:shaderPrecisionFormats | The same as `webGL:shaderPrecisionFormats`, but for WebGL2. | `{"35633,36336": {"rangeMin": 127, "rangeMax": 127, "precision": 23}, ...}` |
| webgl:shaderPrecisionFormats:blockIfNotDefined | If set to `true`, only the shader percisions in `webgl:shaderPrecisionFormats` will be passed. Everything else will be blocked. | `true` |
| webgl2:shaderPrecisionFormats:blockIfNotDefined | If set to `true`, only the shader percisions in `webgl2:shaderPrecisionFormats` will be passed. Everything else will be blocked. | `true` |
| Property | Description | Example |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| webgl:renderer | Spoofs the name of the unmasked WebGL renderer. | `"NVIDIA GeForce GTX 980, or similar"` |
| webgl:vendor | Spoofs the name of the unmasked WebGL vendor. | `"NVIDIA Corporation"` |
| webgl:supportedExtensions | An array of supported WebGL extensions ([full list](https://registry.khronos.org/webgl/extensions/)). | `["ANGLE_instanced_arrays", "EXT_color_buffer_float", "EXT_disjoint_timer_query", ...]` |
| webgl2:supportedExtensions | The same as `webgl:supportedExtensions`, but for WebGL2. | `["ANGLE_instanced_arrays", "EXT_color_buffer_float", "EXT_disjoint_timer_query", ...]` |
| webgl:contextAttributes | A dictionary of WebGL context attributes. | `{"alpha": true, "antialias": true, "depth": true, ...}` |
| webgl2:contextAttributes | The same as `webgl:contextAttributes`, but for WebGL2. | `{"alpha": true, "antialias": true, "depth": true, ...}` |
| webgl:parameters | A dictionary of WebGL parameters. Keys must be GL enums, and values are the values to spoof them as. | `{"2849": 1, "2884": false, "2928": [0, 1], ...}` |
| webgl2:parameters | The same as `webgl:parameters`, but for WebGL2. | `{"2849": 1, "2884": false, "2928": [0, 1], ...}` |
| webgl:parameters:blockIfNotDefined | If set to `true`, only the parameters in `webgl:parameters` will be allowed. Can be dangerous if not used correctly. | `true`/`false` |
| webgl2:parameters:blockIfNotDefined | If set to `true`, only the parameters in `webgl2:parameters` will be allowed. Can be dangerous if not used correctly. | `true`/`false` |
| webgl:shaderPrecisionFormats | A dictionary of WebGL shader precision formats. Keys are formatted as `"<shaderType>,<precisionType>"`. | `{"35633,36336": {"rangeMin": 127, "rangeMax": 127, "precision": 23}, ...}` |
| webgl2:shaderPrecisionFormats | The same as `webGL:shaderPrecisionFormats`, but for WebGL2. | `{"35633,36336": {"rangeMin": 127, "rangeMax": 127, "precision": 23}, ...}` |
| webgl:shaderPrecisionFormats:blockIfNotDefined | If set to `true`, only the shader percisions in `webgl:shaderPrecisionFormats` will be allowed. | `true`/`false` |
| webgl2:shaderPrecisionFormats:blockIfNotDefined | If set to `true`, only the shader percisions in `webgl2:shaderPrecisionFormats` will be allowed. | `true`/`false` |
</details>
Expand Down
11 changes: 7 additions & 4 deletions additions/camoucfg/MaskConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Written by daijro.
#include <stdlib.h>
#include <stdio.h>
#include <variant>
#include <cstddef>

#ifdef _WIN32
# include <windows.h>
Expand Down Expand Up @@ -217,14 +218,15 @@ inline std::optional<T> GetAttribute(const std::string attrib, bool isWebGL2) {
return value.value().get<T>();
}

inline std::optional<std::variant<int64_t, bool, double, std::string>> GLParam(
uint32_t pname, bool isWebGL2) {
inline std::optional<
std::variant<int64_t, bool, double, std::string, std::nullptr_t>>
GLParam(uint32_t pname, bool isWebGL2) {
auto value =
MaskConfig::GetNested(isWebGL2 ? "webgl2:parameters" : "webgl:parameters",
std::to_string(pname));
if (!value) return std::nullopt;
auto data = value.value();
// cast the data and return
if (data.is_null()) return std::nullptr_t();
if (data.is_number_integer()) return data.get<int64_t>();
if (data.is_boolean()) return data.get<bool>();
if (data.is_number_float()) return data.get<double>();
Expand All @@ -249,7 +251,8 @@ inline std::vector<T> MParamGLVector(uint32_t pname,
bool isWebGL2) {
if (auto value = MaskConfig::GetNested(
isWebGL2 ? "webgl2:parameters" : "webgl:parameters",
std::to_string(pname)); value.has_value()) {
std::to_string(pname));
value.has_value()) {
if (value.value().is_array()) {
std::array<T, 4UL> result = value.value().get<std::array<T, 4UL>>();
return std::vector<T>(result.begin(), result.end());
Expand Down
61 changes: 46 additions & 15 deletions patches/webgl-spoofing.patch
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
index db60868f65..afed6eeb7c 100644
index db60868f65..7361f0fc9c 100644
--- a/dom/canvas/ClientWebGLContext.cpp
+++ b/dom/canvas/ClientWebGLContext.cpp
@@ -4,6 +4,8 @@
@@ -4,6 +4,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "ClientWebGLContext.h"
+#include "MaskConfig.hpp"
+#include <algorithm>
+#include <cstddef>

#include <bitset>

@@ -744,6 +746,13 @@ void ClientWebGLContext::SetDrawingBufferColorSpace(
@@ -744,6 +747,13 @@ void ClientWebGLContext::SetDrawingBufferColorSpace(
Run<RPROC(SetDrawingBufferColorSpace)>(*mDrawingBufferColorSpace);
}

Expand All @@ -25,7 +26,7 @@ index db60868f65..afed6eeb7c 100644
void ClientWebGLContext::GetContextAttributes(
dom::Nullable<dom::WebGLContextAttributes>& retval) {
retval.SetNull();
@@ -754,14 +763,38 @@ void ClientWebGLContext::GetContextAttributes(
@@ -754,14 +764,38 @@ void ClientWebGLContext::GetContextAttributes(

const auto& options = mNotLost->info.options;

Expand Down Expand Up @@ -72,7 +73,7 @@ index db60868f65..afed6eeb7c 100644
}

// -----------------------
@@ -979,18 +1012,28 @@ bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
@@ -979,18 +1013,28 @@ bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
std::unordered_map<GLenum, bool> webgl::MakeIsEnabledMap(const bool webgl2) {
auto ret = std::unordered_map<GLenum, bool>{};

Expand Down Expand Up @@ -111,11 +112,13 @@ index db60868f65..afed6eeb7c 100644
}

return ret;
@@ -2058,6 +2101,28 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2058,6 +2102,57 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
const auto& state = State();

// -
+ std::optional<std::variant<int64_t, bool, double, std::string>> data;
+ std::optional<
+ std::variant<int64_t, bool, double, std::string, std::nullptr_t>>
+ data;
+ data = MaskConfig::GLParam(pname, mIsWebGL2);
+
+ if (data.has_value()) {
Expand All @@ -136,11 +139,38 @@ index db60868f65..afed6eeb7c 100644
+ retval.set(StringValue(cx, std::get<std::string>(value), rv));
+ return;
+ }
+ if (std::holds_alternative<std::nullptr_t>(value)) {
+ retval.set(JS::NullValue());
+ return;
+ }
+ }
+ // If the value is not array (we will handle those later),
+ // then check if it should be blocked.
+ switch (pname) {
+ case LOCAL_GL_DEPTH_RANGE:
+ case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
+ case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
+ case LOCAL_GL_COLOR_CLEAR_VALUE:
+ case LOCAL_GL_BLEND_COLOR:
+ case LOCAL_GL_MAX_VIEWPORT_DIMS:
+ case LOCAL_GL_SCISSOR_BOX:
+ case LOCAL_GL_VIEWPORT:
+ case LOCAL_GL_COMPRESSED_TEXTURE_FORMATS:
+ case LOCAL_GL_COLOR_WRITEMASK:
+ case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
+ case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
+ break;
+ default:
+ if (MaskConfig::GetBool(mIsWebGL2 ? "webgl2:parameters:blockIfNotDefined"
+ : "webgl:parameters:blockIfNotDefined")) {
+ retval.set(JS::NullValue());
+ return;
+ }
+ }

const auto fnSetRetval_Buffer = [&](const GLenum target) {
const auto buffer = *MaybeFind(state.mBoundBufferByTarget, target);
@@ -2163,49 +2228,84 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2163,49 +2258,84 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,

// 2 floats
case LOCAL_GL_DEPTH_RANGE:
Expand Down Expand Up @@ -238,7 +268,7 @@ index db60868f65..afed6eeb7c 100644
return;
}

@@ -2385,6 +2485,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2385,6 +2515,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,

switch (pname) {
case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
Expand All @@ -249,7 +279,7 @@ index db60868f65..afed6eeb7c 100644
ret = GetUnmaskedRenderer();
if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
*ret = webgl::SanitizeRenderer(*ret);
@@ -2392,6 +2496,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2392,6 +2526,10 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
break;

case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
Expand All @@ -260,7 +290,7 @@ index db60868f65..afed6eeb7c 100644
ret = GetUnmaskedVendor();
break;

@@ -2482,7 +2590,9 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
@@ -2482,7 +2620,9 @@ void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
case LOCAL_GL_COLOR_WRITEMASK: {
const auto mask = uint8_t(*maybe);
const auto bs = std::bitset<4>(mask);
Expand All @@ -271,7 +301,7 @@ index db60868f65..afed6eeb7c 100644
JS::Rooted<JS::Value> arr(cx);
if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
rv = NS_ERROR_OUT_OF_MEMORY;
@@ -2865,6 +2975,24 @@ ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
@@ -2865,6 +3005,24 @@ ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
const GLenum precisiontype) {
if (IsContextLost()) return nullptr;
const auto info = [&]() {
Expand All @@ -296,7 +326,7 @@ index db60868f65..afed6eeb7c 100644
const auto& inProcess = mNotLost->inProcess;
if (inProcess) {
return inProcess->GetShaderPrecisionFormat(shadertype, precisiontype);
@@ -5822,6 +5950,17 @@ bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
@@ -5822,6 +5980,17 @@ bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
return false;
}

Expand All @@ -314,14 +344,15 @@ index db60868f65..afed6eeb7c 100644
const auto& limits = Limits();
return limits.supportedExtensions[ext];
}
@@ -5833,6 +5972,17 @@ void ClientWebGLContext::GetSupportedExtensions(
@@ -5833,6 +6002,18 @@ void ClientWebGLContext::GetSupportedExtensions(
if (!mNotLost) return;

auto& retarr = retval.SetValue();
+
+ // Implement separately to prevent O(n^2) timing
+ if (std::vector<std::string> maskValues =
+ MaskConfig::GetStringList("webgl:supportedExtensions");
+ MaskConfig::GetStringList(mIsWebGL2 ? "webgl2:supportedExtensions"
+ : "webgl:supportedExtensions");
+ !maskValues.empty()) {
+ for (const auto& ext : maskValues) {
+ retarr.AppendElement(NS_ConvertUTF8toUTF16(ext));
Expand Down
Loading

0 comments on commit 5bfc3ee

Please sign in to comment.