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.
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.
| 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 |
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.
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).
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.
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.
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 24Passing --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.
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.
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.
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.
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.
-
FakeShadowRoot—007-shadow-root.patchlands its.ccdefinition and.idlbinding but not the.hdeclaration, because upstream moved the anchor from line 842 to 1132. The generated bindings then fail withno member named 'FakeShadowRoot' in 'blink::Element'. The declaration is present inrebased-sources/.../element.hnext toOpenShadowRoot. Worth noting how this was diagnosed: a whole-tree grep for the declaration returned 0 with a positive control matchingOpenShadowRoot— proving the hunk was lost, not silently applied somewhere wrong. Those two failure modes need different fixes, so distinguish them before editing. -
MaxDirectMapped()— removed in 153;MaxAllocationSize()is the replacement (partition_alloc_constants.h). This one is fixed inpatches/013-canvas-toDataURL.patch, the single patch we modified.
--fuzz=0is 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'sLINKline and the exit code. grep -xreturns 0 for strings embedded in blobs. A positive control returning 0 means your probe is broken, not that the string is absent.getNoiseDataandShouldSkipClientRectsOffsetresolve to 0 symbols — inlined / DCE'd, not missing.getNoiseDatahas 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-1does not,153.0.8010.36-1does. Check the release assets before choosing a tag.
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— theFakeShadowRootdeclaration was added here from the build log, since the snapshot predates that fix. Inserted after theOpenShadowRootdeclaration using a uniqueness-checked anchor, the same methodapplyrej.pyuses.html_canvas_element.cc— not vendored at all; its fix lives inpatches/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.
Built from three BSD-3-Clause projects:
- adryfish/fingerprint-chromium — the 16 fingerprint patches
- ungoogled-software/ungoogled-chromium
- ungoogled-chromium-portablelinux — the arm64 cross-compile support
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.