From e8126c68198c36f63dd8f1855aa81021f85b7dde Mon Sep 17 00:00:00 2001 From: daijro Date: Sun, 18 Aug 2024 02:55:48 -0500 Subject: [PATCH] Add WebRTC IP spoofing Implement WebRTC IP spoofing at the protocol level by modifying ICE candidates and SDP before they're sent. --- README.md | 27 ++++- patches/webrtc-ip-spoofing.patch | 191 +++++++++++++++++++++++++++++++ settings/camoufox.cfg | 2 + 3 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 patches/webrtc-ip-spoofing.patch diff --git a/README.md b/README.md index ec78d7a..90ab86a 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Camoufox aims to be a minimalistic browser for robust fingerprint injection & an - Patches to avoid bot detection ✅ - Custom Playwright Juggler implementation for the latest Firefox ✅ - Font spoofing & anti-fingerprinting ✅ +- WebRTC IP spoofing ✅ - Patches from LibreWolf & Ghostery to remove Mozilla services ✅ - Optimized for memory and speed ✅ - Stays up to date with the latest Firefox version 🕓 @@ -45,7 +46,7 @@ Camoufox is built on top of Firefox/Juggler instead of Chromium because: ## Fingerprint Injection -Camoufox overrides Javascript properties on the lowest level possible, allowing values to be spoofed across all scopes. +In Camoufox, data is intercepted at the C++ implementation level, making the changes undetectable through JavaScript inspection. To spoof fingerprint properties, pass a JSON containing properties to spoof: @@ -221,7 +222,24 @@ Camoufox can override the following network headers:
+ +WebRTC IP + +Camoufox implements WebRTC IP spoofing at the protocol level by modifying ICE candidates and SDP before they're sent. + +| Property | Status | Description | +| ----------- | ------ | ------------------- | +| webrtc:ipv4 | ✅ | IPv4 address to use | +| webrtc:ipv6 | ✅ | IPv6 address to use | + +**Notes:** + +- To completely disable WebRTC, set the `media.peerconnection.enabled` preference to `false`. + +
+ +
Addons @@ -281,6 +299,7 @@ Miscellaneous (WebGl spoofing, battery status, etc) - Battery API spoofing - Support for spoofing both inner and outer window viewport sizes - Network headers (Accept-Languages and User-Agent) are spoofed to match the navigator properties +- WebRTC IP spoofing at the protocol level - etc. #### Anti font fingerprinting @@ -321,7 +340,7 @@ Miscellaneous (WebGl spoofing, battery status, etc) ### Tests -Camoufox performs well against every major WAF I've tested. (Test sites from [Botright](https://github.com/Vinyzu/botright/?tab=readme-ov-file#browser-stealth)) +Camoufox performs well against every major WAF I've tested. (Original test sites from [Botright](https://github.com/Vinyzu/botright/?tab=readme-ov-file#browser-stealth)) | Test | Status | | -------------------------------------------------------------------------------------------------- | ------------------------------------------------- | @@ -340,6 +359,10 @@ Camoufox performs well against every major WAF I've tested. (Test sites from [Bo | **Cloudflare** | ✔️ | | ‣ [Turnstile](https://nopecha.com/demo/turnstile) | ✔️ | | ‣ [Interstitial](https://nopecha.com/demo/cloudflare) | ✔️ Unstable on Chrome fingerprints | +| **WebRTC IP Spoofing** | ✔️ | +| ‣ [Browserleaks WebRTC](https://browserleaks.net/webrtc) | ✔️ | +| ‣ [CreepJS WebRTC](https://abrahamjuliot.github.io/creepjs/) | ✔️ | +| ‣ [BrowserScan WebRTC](https://www.browserscan.net/webrtc) | ✔️ | | **Font Fingerprinting** | ✔️ | | ‣ [Browserleaks Fonts](https://browserleaks.net/fonts) | ✔️ | | ‣ [CreepJS TextMetrics](https://abrahamjuliot.github.io/creepjs/tests/fonts.html) | ✔️ | diff --git a/patches/webrtc-ip-spoofing.patch b/patches/webrtc-ip-spoofing.patch new file mode 100644 index 0000000..0d2aa53 --- /dev/null +++ b/patches/webrtc-ip-spoofing.patch @@ -0,0 +1,191 @@ +diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp +index 5b70a58104..579a620c48 100644 +--- a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp ++++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp +@@ -116,6 +116,8 @@ + + #include "mozilla/dom/BrowserChild.h" + #include "mozilla/net/WebrtcProxyConfig.h" ++#include "MaskConfig.hpp" ++#include "mozilla/RustRegex.h" + + #ifdef XP_WIN + // We need to undef the MS macro again in case the windows include file +@@ -1705,7 +1707,15 @@ PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) { + } + }; + +- mLocalRequestedSDP = aSDP; ++ // Sanitize the SDP to prevent IP leaks ++ std::string sanitizedSDP(aSDP); ++ nsresult sanitizeResult = SanitizeSDPForIPLeak(sanitizedSDP); ++ if (NS_FAILED(sanitizeResult)) { ++ CSFLogError(LOGTAG, "%s - Failed to sanitize SDP", __FUNCTION__); ++ return sanitizeResult; ++ } ++ ++ mLocalRequestedSDP = sanitizedSDP; + + SyncToJsep(); + +@@ -3275,12 +3285,22 @@ const std::string& PeerConnectionImpl::GetName() { + return mName; + } + +-void PeerConnectionImpl::CandidateReady(const std::string& candidate, ++void PeerConnectionImpl::CandidateReady(const std::string& candidate_, + const std::string& transportId, + const std::string& ufrag) { + STAMP_TIMECARD(mTimeCard, "Ice Candidate gathered"); + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + ++ const std::string& candidate = [&]() -> const std::string& { ++ if (ShouldSpoofCandidateIP()) { ++ static std::string spoofedCandidate; ++ spoofedCandidate = SpoofCandidateIP(candidate_); ++ return spoofedCandidate; ++ } else { ++ return candidate_; ++ } ++ }(); ++ + if (mForceIceTcp && std::string::npos != candidate.find(" UDP ")) { + CSFLogWarn(LOGTAG, "Blocking local UDP candidate: %s", candidate.c_str()); + STAMP_TIMECARD(mTimeCard, "UDP Ice Candidate blocked"); +@@ -3343,10 +3363,89 @@ void PeerConnectionImpl::SendLocalIceCandidateToContent( + uint16_t level, const std::string& mid, const std::string& candidate, + const std::string& ufrag) { + STAMP_TIMECARD(mTimeCard, "Send Ice Candidate to content"); +- JSErrorResult rv; +- mPCObserver->OnIceCandidate(level, ObString(mid.c_str()), +- ObString(candidate.c_str()), +- ObString(ufrag.c_str()), rv); ++ ++ // Check if IP spoofing is enabled ++ if (ShouldSpoofCandidateIP()) { ++ std::string spoofedCandidate = SpoofCandidateIP(candidate); ++ JSErrorResult rv; ++ mPCObserver->OnIceCandidate(level, ObString(mid.c_str()), ++ ObString(spoofedCandidate.c_str()), ++ ObString(ufrag.c_str()), rv); ++ } else { ++ JSErrorResult rv; ++ mPCObserver->OnIceCandidate(level, ObString(mid.c_str()), ++ ObString(candidate.c_str()), ++ ObString(ufrag.c_str()), rv); ++ } ++} ++ ++bool PeerConnectionImpl::ShouldSpoofCandidateIP() const { ++ // Checks if either webrtc:ipv4 or webrtc:ipv6 is set in the config ++ return MaskConfig::GetString("webrtc:ipv4").has_value() || ++ MaskConfig::GetString("webrtc:ipv6").has_value(); ++} ++ ++nsresult PeerConnectionImpl::SanitizeSDPForIPLeak(std::string& sdp) { ++ auto replaceIP = [&sdp](const char* pattern, const char* pref) { ++ if (auto value = MaskConfig::GetString(pref)) { ++ mozilla::RustRegex regex(pattern); ++ if (regex.IsValid()) { ++ std::string result; ++ auto iter = regex.IterMatches(sdp); ++ size_t lastEnd = 0; ++ while (auto match = iter.Next()) { ++ result.append(sdp, lastEnd, match->start - lastEnd); ++ result.append(value.value()); ++ lastEnd = match->end; ++ } ++ result.append(sdp, lastEnd); ++ sdp = std::move(result); ++ } ++ } ++ }; ++ ++ replaceIP("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b", "webrtc:ipv4"); ++ replaceIP("\\b(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}\\b", "webrtc:ipv6"); ++ ++ return NS_OK; ++} ++ ++std::string PeerConnectionImpl::SpoofCandidateIP(const std::string& candidate) { ++ CSFLogDebug(LOGTAG, "Original candidate: %s", candidate.c_str()); ++ ++ std::istringstream iss(candidate); ++ std::vector tokens{std::istream_iterator{iss}, ++ std::istream_iterator{}}; ++ ++ bool spoofed = false; ++ // Check for IPv4 or .local address ++ if (tokens.size() >= 8) { ++ if (tokens[4].find('.') != std::string::npos) { // IPv4 or .local ++ if (auto value = MaskConfig::GetString("webrtc:ipv4")) { ++ tokens[4] = value.value(); ++ spoofed = true; ++ } ++ } ++ // Check for IPv6 ++ else if (tokens[4].find(':') != std::string::npos) { ++ if (auto value = MaskConfig::GetString("webrtc:ipv6")) { ++ tokens[4] = value.value(); ++ spoofed = true; ++ } ++ } ++ } ++ ++ if (spoofed) { ++ std::ostringstream oss; ++ std::copy(tokens.begin(), tokens.end(), ++ std::ostream_iterator(oss, " ")); ++ std::string result = oss.str(); ++ CSFLogDebug(LOGTAG, "Spoofed candidate: %s", result.c_str()); ++ return result; ++ } ++ ++ CSFLogDebug(LOGTAG, "Candidate not spoofed: %s", candidate.c_str()); ++ return candidate; + } + + void PeerConnectionImpl::IceConnectionStateChange( +@@ -4446,8 +4545,10 @@ bool PeerConnectionImpl::GetPrefDefaultAddressOnly() const { + + uint64_t winId = mWindow->WindowID(); + +- bool default_address_only = Preferences::GetBool( +- "media.peerconnection.ice.default_address_only", false); ++ bool default_address_only = ++ Preferences::GetBool("media.peerconnection.ice.default_address_only", ++ false) || ++ ShouldSpoofCandidateIP(); + default_address_only |= + !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId); + return default_address_only; +diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.h b/dom/media/webrtc/jsapi/PeerConnectionImpl.h +index d7b54ad721..8d8e92d14b 100644 +--- a/dom/media/webrtc/jsapi/PeerConnectionImpl.h ++++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.h +@@ -624,8 +624,12 @@ class PeerConnectionImpl final + RefPtr GetMediaPipelineForTrack( + dom::MediaStreamTrack& aRecvTrack); + +- void CandidateReady(const std::string& candidate, ++ void CandidateReady(const std::string& candidate_, + const std::string& transportId, const std::string& ufrag); ++ ++ bool ShouldSpoofCandidateIP() const; ++ nsresult SanitizeSDPForIPLeak(std::string& sdp); ++ std::string SpoofCandidateIP(const std::string& candidate); + void SendLocalIceCandidateToContent(uint16_t level, const std::string& mid, + const std::string& candidate, + const std::string& ufrag); +diff --git a/dom/media/webrtc/jsapi/moz.build b/dom/media/webrtc/jsapi/moz.build +index 05920fc151..34dcf3b9a1 100644 +--- a/dom/media/webrtc/jsapi/moz.build ++++ b/dom/media/webrtc/jsapi/moz.build +@@ -62,3 +62,6 @@ EXPORTS.mozilla.dom += [ + ] + + FINAL_LIBRARY = "xul" ++ ++# DOM Mask ++LOCAL_INCLUDES += ["/dom/mask"] +\ No newline at end of file diff --git a/settings/camoufox.cfg b/settings/camoufox.cfg index 399ddb0..ee995e1 100644 --- a/settings/camoufox.cfg +++ b/settings/camoufox.cfg @@ -13,6 +13,8 @@ pref("browser.sessionstore.max_resumed_crashes", 0); pref("browser.sessionstore.restore_on_demand", false); pref("browser.sessionstore.restore_tabs_lazily", false); +pref("media.peerconnection.ice.no_host", true); + // Tweaks that undo Playwright: // Force enable content isolation (WAFs can detect this!)