Skip to content

About

Anti-detect Chromium for linux/arm64: fingerprint-chromium carried from Chromium 144 to 153, plus the cross-compile recipe

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

fingerprint-chromium-arm64

Anti-detect Chromium for linux/arm64 — upstream fingerprint-chromium carried forward from Chromium 144 to 153, plus the recipe that cross-compiles it.

Upstream ships no arm64 build and no source newer than 144. This repo closes both gaps.


⚠️ Read these two caveats before you rely on this

1. The published binary has never been executed on real arm64 hardware by us. It was cross-compiled on an x86 host. We verified the architecture (ELF e_machine=183), that all 12 fingerprint switches are present, and that patch-specific symbols are compiled in — all with negative controls. We did not verify that it starts, because the build machine could not run aarch64 code and has since been terminated. A colleague independently ran it on a t4g.large and it worked, including the deterministic hardwareConcurrency check below — but treat first-run on any new host as unproven. scripts/verify-binary.sh says so in its own output.

2. Upstream source only exists through Chromium 144. adryfish publishes binaries first and patch source only at the next major release. The newest tag containing patches is 144.0.7559.132 (2026-02); newer tags contain only LICENSE/README. Everything here is carried forward from 144 by us; there is no upstream 153 patch set to compare against.


What is here

Path What it is
patches/ The 16 fingerprint patches + series (apply order)
rebased-sources/ The 8 Chromium files carrying the 153 rebase
scripts/build.sh The cross-compile recipe, stage by stage
scripts/applyrej.py Reject-hunk applier with a uniqueness guard
scripts/verify-binary.sh Static verification, every check with a negative control

Where the 153 work actually lives — read this before using patches/

Be precise about what each directory is, because the names invite the wrong assumption (they misled the author of this README first):

patches/ is essentially upstream's 144 patch set, NOT a 153 rebase. 15 of the 16 files are byte-identical to adryfish/fingerprint-chromium @ 144.0.7559.132, and series is identical too. Exactly one patch is modified: 013-canvas-toDataURL.patch, one line, because partition_alloc::MaxDirectMapped() no longer exists in 153 — MaxAllocationSize() replaces it.

rebased-sources/ is where the 153 work is. Applying patches/ to a 153 tree produces 16 rejected files / 21 failed hunks. applyrej.py resolves 11; the other 5 were resolved by hand, and those resolutions exist only as the files in rebased-sources/, never as patch files. Copy them over the tree rather than trying to re-derive them:

cp -R rebased-sources/. "$SRC/"

So: patches/ ≈ upstream, rebased-sources/ + the 013 one-liner + this recipe = the work product. A git log of patches/ will therefore look misleadingly quiet.

Binaries

Not in git — see Releases. The 2026-09-18 build:

ungoogled-chromium-153.0.8010.36-1-arm64_linux.tar.xz
  146,750,952 bytes
  sha256 9b7260125d808d132ccb60ba5f81393c62c8df17d84aa7d13c6f5e5a1b75cd90

Top-level directory inside the tarball is ungoogled-chromium-153.0.8010.36-1-arm64_linux/; the executable is chrome, alongside chromedriver, chrome_crashpad_handler and swiftshader libs (675 entries).

⚠️ Unpacking gotcha for install scripts: because the tarball has a versioned top-level directory, a script that extracts it and then looks for chrome at a fixed path will miss it, fall through to a find-based fallback, and may install the binary alone — without icudtl.dat, resources.pak, or v8_context_snapshot.bin. [ -x chrome ] then passes while Chromium cannot start. Move the whole directory; never just the binary. Note also the tarball is .xz, so tar -xzf (which forces gzip on GNU tar) is the wrong flag — use -xJf or plain -xf.

Sandboxing

This package ships no setuid chrome-sandbox helper (the packaging step omits it). On Ubuntu 24.04, unprivileged user namespaces are restricted by AppArmor, so the correct fix is an AppArmor profile granting userns to this binary — not --no-sandbox. Earlier notes of ours recommended --no-sandbox; that advice predates the AppArmor profile and is superseded. Use --no-sandbox only if you have no profile and accept the tradeoff.

Quick start

tar -xJf ungoogled-chromium-153.0.8010.36-1-arm64_linux.tar.xz
cd ungoogled-chromium-153.0.8010.36-1-arm64_linux
./chrome --headless=new --fingerprint=12345 \
  --dump-dom 'data:text/html,<script>document.write(navigator.hardwareConcurrency)</script>'
# expect 24

Verifying a switch actually works

Passing --fingerprint=12345 and not getting an error proves nothing: Chromium silently ignores every unknown switch, so a real flag and a made-up one behave identically. The usable criterion is a switch's own deterministic observable. 005-hardware-concurrency-fingerprint.patch computes ((seed % 13) + 4) * 2:

Flag navigator.hardwareConcurrency
--fingerprint=12345 24
--fingerprint=1 10
--fingerprint=42 14
(none) the machine's real core count

On a 2-core box, 2 vs 24 is unmistakable. Run scripts/verify-binary.sh <path> for the static checks.

Known leak: UA-CH architecture on arm64

Without --fingerprint-platform, navigator.userAgentData.architecture reports the real hardware (arm), while the UA string is the hardcoded Chromium constant X11; Linux x86_64. Real x86 says x86 on both; real arm says arm on both; only this build disagrees with itself — which is itself a signal.

On arm64, always pass --fingerprint-platform=linux so architecture is forced to match the UA. The patch assigns architecture only inside the HasSwitch(kFingerprintPlatform) branch, which is why the default leaks. The patch does not touch bitness at all.

Building

See scripts/build.sh. Roughly: clone ungoogled-chromium-portablelinux at a tag that has arm64 assets → fetch the Chromium tarball → apply the 16 patches at --fuzz=0 → resolve rejects → ARCH=arm64 ./scripts/docker-build.sh → package.

Host: x86_64, 32+ vCPU, 32+ GB RAM, 300 GB disk. ~4–6 h on 64 vCPU; about $3–5 on spot. Do not build on an arm64 host: Google publishes no prebuilt Linux-aarch64 clang/rust, so a native build must build its own toolchain first. Cross-compiling is also what Google's own docs/linux/chromium_arm.md recommends. Do not build on an agent machine — t4g.large (2 vCPU / 8 GB) is far below the floor and will OOM.

The five files that needed hand work

11 of 16 rejects were resolved automatically by scripts/applyrej.py. The rest:

File Why
user_agent_metadata.cc, font_cache.cc #include block drift — neighbours added/removed upstream
static_bitmap_image.cc upstream renamed base::RandInt → base::RandIntInclusive
user_agent_utils.cc upstream deleted the user_agent_reduction parameter; 3 signatures changed
v8-runtime-agent-impl.cc see below

v8-runtime-agent-impl.cc is the interesting one, and it is the clearest example of why patches/ and rebased-sources/ disagree. Upstream's patch inserts a bare return; at the top of addBindings() and comments out the body. That fails to compile on 153, which enables -Wunreachable-code-aggressive (build/config/compiler/BUILD.gn:2197) and has added two locals to the function. The shipped form, visible only in rebased-sources/v8/src/inspector/v8-runtime-agent-impl.cc:1080, is:

if (true || !m_enabled) return;

Same behaviour, no warning. patches/001-disable-runtime.enable.patch still contains upstream's original return; — applying that patch alone will not build. Inherit an upstream patch's intent separately from its literal form.

Two compile failures that only appear late

Both surfaced after ~50,000 of 94,181 targets, i.e. hours in. Neither is arm64-specific; an x86 build of these patches on 153 hits both.

  1. FakeShadowRoot — 007-shadow-root.patch lands its .cc definition and .idl binding but not the .h declaration, because upstream moved the anchor from line 842 to 1132. The generated bindings then fail with no member named 'FakeShadowRoot' in 'blink::Element'. The declaration is present in rebased-sources/.../element.h next to OpenShadowRoot. Worth noting how this was diagnosed: a whole-tree grep for the declaration returned 0 with a positive control matching OpenShadowRoot — proving the hunk was lost, not silently applied somewhere wrong. Those two failure modes need different fixes, so distinguish them before editing.

  2. MaxDirectMapped() — removed in 153; MaxAllocationSize() is the replacement (partition_alloc_constants.h). This one is fixed in patches/013-canvas-toDataURL.patch, the single patch we modified.

Notes for whoever rebases to 154+

  • --fuzz=0 is mandatory. At the default fuzz=2 with free offsets, a dry run "succeeds" against almost any version and measures nothing.
  • Measure conflicts in the real stack, with ungoogled's ~109 patches applied first. Counting against pristine Chromium and subtracting a "baseline" produces a confident wrong number — we predicted 10 conflicting files that way and got 16 / 21 failed hunks in reality.
  • Don't trust a failure count without a positive control. GNU patch 3.x prints uppercase N out of M hunks FAILED; grepping the other case silently returns 0. If your reject-file count and your failed-hunk count disagree, suspect the counting, not the facts.
  • Don't grep the compile log for error: — several Chromium GN targets contain that substring (//chrome/browser/resources/webui_js_error:*) and match unconditionally. Use ninja's LINK line and the exit code.
  • grep -x returns 0 for strings embedded in blobs. A positive control returning 0 means your probe is broken, not that the string is absent.
  • getNoiseData and ShouldSkipClientRectsOffset resolve to 0 symbols — inlined / DCE'd, not missing. getNoiseData has no callers even in upstream 144.
  • The canvas switch is spelled fingerprinting-canvas-measuretext-noise (no hyphen between measure and text). The intuitive spelling greps to a false MISSING.
  • Not every portablelinux tag ships arm64 assets — 153.0.8010.47-1 does not, 153.0.8010.36-1 does. Check the release assets before choosing a tag.

Provenance of the vendored sources

rebased-sources/ was snapshotted from the build tree partway through the 2026-09-18 build, before two later fixes were applied on that machine. Both were reconciled when this repo was assembled:

  • element.h — the FakeShadowRoot declaration was added here from the build log, since the snapshot predates that fix. Inserted after the OpenShadowRoot declaration using a uniqueness-checked anchor, the same method applyrej.py uses.
  • html_canvas_element.cc — not vendored at all; its fix lives in patches/013-canvas-toDataURL.patch, so nothing was needed.

The build machine was a spot instance and is long terminated, so this repo — not that tree — is now the only copy. If you find a third divergence, the 2026-09-18 build log is the authority.

Provenance and licence

Built from three BSD-3-Clause projects:

fingerprint-chromium is not a hard fork of Chromium: it is ungoogled-chromium plus ~116–148 KB of patches appended to patches/series. We verified this — the 112 shared patch files are byte-identical between the two projects; the only difference in series is the 16 appended extra/fingerprint/* lines.

This repository is likewise BSD-3-Clause (see LICENSE). We are not affiliated with, nor endorsed by, any of the upstream projects.

About

Anti-detect Chromium for linux/arm64: fingerprint-chromium carried from Chromium 144 to 153, plus the cross-compile recipe

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages