phantom-frida
phantom-frida builds Android Frida Server and Gadget from source while
changing a targeted set of observable runtime identifiers. It is a builder and
verification harness, not a promise that every application-specific detection
method is defeated.
The current compatibility target is Frida 17.16.4 on Android. Other Frida versions are intentionally treated as unverified until their source contracts, full build, and rooted-device acceptance have been repeated.
Use this project only on applications and devices you own or are authorized to test.
What is verified
The repository separates three kinds of evidence:
- Unit and fixture tests validate input handling, exact Frida 17.16.4 source patch contracts, DEX rebuilding, artifact promotion, metadata, workflows, and failure behavior.
build.py --verifyrequires both Server and Gadget, strips the staged Gadget, then rejects known forbidden runtime markers before publishing either artifact.scripts/android_smoke.pyexercises a built artifact on one rooted device: authenticated abstract-UNIX transport, stock-client RPC, spawn, attach, Java bridge assertions,/procchecks, an external root memory scan, and a separately loaded Gadget.
A passing source test or byte scan is not equivalent to runtime stealth. Claims about Android behavior should include the generated, redacted smoke-test report.
Build with GitHub Actions
Run Build Custom Frida from the Actions tab. The reusable build workflow:
- validates every user-controlled build input;
- downloads Android NDK r29 from Google and checks its published checksum;
- clones a fresh upstream source tree instead of caching patched source;
- strips Gadget symbols and uploads only after the hard artifact and marker gates pass;
- includes
build-info.jsonandSHA256SUMSin one build artifact.
The weekly workflow resolves the latest release through the authenticated GitHub API, calls the same read-only build workflow, verifies the downloaded artifact, attests it, and grants release write permission only to the final job.
Local build
Requirements:
- Ubuntu 22.04 or newer (WSL is supported);
- Python 3.10 or newer;
- Git, curl, unzip, a C/C++ toolchain, JDK 17, and Node.js 18 or newer (CI uses Node.js 24.13.1);
- Android SDK platform and build-tools containing
android.jarand D8; - about 20 GB of free disk space.
If --ndk-path is omitted, the builder downloads Android NDK r29 under
build/. A verified pinned-input arm64 build is:
export ANDROID_SDK_ROOT=/path/to/Android/Sdk
python3 build.py \
--version 17.16.4 \
--name oemcodec \
--arch android-arm64 \
--port 27142 \
--extended \
--strict-wx \
--verify
Useful options:
--version, -v Exact Frida semantic version (required)
--name, -n Lowercase replacement name, 3-20 characters
--arch, -a One or more supported Android architectures
--port, -p Listening port; omitted keeps 27042
--extended, -e Apply the optional extended identifier transformations
--strict-wx Harden Frida-owned persistent anonymous RWX mappings on Android
--temp-fixes Apply opt-in, device-specific stability changes
--verify Reject known forbidden markers in final artifacts
--skip-build Patch source without compiling
--skip-clone Use an existing source tree in the work directory
--ndk-path Use an existing Android NDK r29 directory
Outputs and provenance
For the example above, output/ contains:
oemcodec-server-17.16.4-android-arm64
oemcodec-server-17.16.4-android-arm64.gz
oemcodec-gadget-17.16.4-android-arm64.so
oemcodec-gadget-17.16.4-android-arm64.so.gz
build-info.json
SHA256SUMS
build-info.json records the exact builder, Frida, and frida-core commits, NDK
version, UTC build time, architectures, name, port, strict W^X code-pool mode,
and workflow URL when built in Actions. Verify downloaded binary files before use:
cd output
sha256sum --check SHA256SUMS
python3 -m json.tool build-info.json >/dev/null
Public weekly releases also receive a GitHub build-provenance attestation.
SHA256SUMS identifies one output set; binary hashes are not expected to match
across different build hosts.
Stock-client compatibility
The builder preserves the D-Bus protocol interfaces under re.frida.*, the
/re/frida/GadgetSession path, public capital Frida JavaScript API strings,
and generated C ABI symbols required by stock clients. Renaming those values
would break the normal Frida client/server contract.
The D-Bus service identifier, helper JNI package, zymbiote socket prefix, selected process/library/path identifiers, selected thread names, and an optional custom port are separate implementation details that the builder can transform. The output verifier rejects this explicit marker set:
frida\0
frida-zymbiote
re/frida/HelperBackend
frida-server
frida-helper
frida-agent
frida-gadget
frida-eternal-agent
frida-generate-certificate
frida-main-loop
frida:rpc
FridaScriptEngine
GLib-GIO
GDBusProxy
GumScript
Frida/
gum-js-loop
gmain\0
gdbus\0
pool-frida
pool-spawner
jit-cache\0
The exact public API string Frida\0 and allowlisted re.frida.* protocol
identifiers remain intentionally preserved. The HTTP/Inspector prefix
Frida/ is a verifier failure and is renamed independently.
Rooted Android acceptance
Install the exact Python binding recorded in build-info.json, install the
pinned Java bridge dependency, and connect exactly one rooted Android device:
python3 -m pip install "frida==17.16.4" frida-tools
npm ci --ignore-scripts
python3 scripts/android_smoke.py \
--server output/oemcodec-server-17.16.4-android-arm64 \
--gadget output/oemcodec-gadget-17.16.4-android-arm64.so \
--name oemcodec \
--port 27142 \
--package com.example.app \
--ndk build/android-ndk-r29 \
--report android-smoke-report.json
The package must be an installed Java application you are authorized to test.
The harness compiles test_comprehensive.js with the explicit
frida-java-bridge required by Frida 17. Server and Gadget each listen on a
random authenticated abstract-UNIX socket; their host TCP ports exist only as
ADB forwards and no device TCP listener is exposed. The harness also compiles
an ABI-matched root probe that scans the mapped agent and Gadget images through
/proc/<pid>/mem, outside Frida's own process view. It cleans up its processes,
forwards, and remote test directory on exit. The /proc gate also rejects
legacy linjector file-descriptor names and a non-zero TracerPid.
Frida Gadget configuration must be named next to the library as
lib<name>-gadget.config.so; the harness generates and deploys this file.
Development checks
python -m pip install --requirement requirements-dev.txt
npm ci --ignore-scripts
python -m pytest
ruff check .
ruff format --check .
mypy build.py patches.py namegen.py scripts
node --check test_comprehensive.js
bash -n build-wsl.sh
go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
See CONTRIBUTING.md for the full evidence requirements.
Repository layout
build.py Clone, patch, compile, verify, and collect artifacts
patches.py Source and same-length binary transformations
namegen.py Seeded build-name and port generation
scripts/android_smoke.py Rooted Android Server/Gadget acceptance harness
test_comprehensive.js Structured Frida 17 Java bridge assertions
tests/ Unit, contract, fixture, and workflow tests
.github/workflows/ CI, CodeQL, reusable build, and release isolation
Known boundaries
- Only Frida 17.16.4 is the current verification target; support is not inferred for all 17.x or 16.x releases.
--temp-fixeschanges runtime behavior and remains opt-in.- Marker absence does not prove resistance to behavioral, integrity, timing, or application-specific detection.
- Stock-client compatibility preserves public ABI/protocol behavior. Active protocol probing and code-integrity checks may still identify instrumentation.
- This builder does not hide root, automated app launch, Interceptor code changes, user-script strings, or failures reported by remote attestation.
--strict-wxseparates the Android injector's executable code from writable data and stack pages, disables Gum's persistent RWX code pools, places GumJSNativeCallbackclosure code on dedicated RX pages while keeping libffi storage RW, and finalizes newly generated code as RX. If a matching remote libc is unavailable, the Android injector fails closed instead of using its upstream RWX fallback. It preserves pages that were already RWX and does not reject an explicit user-script request for RWX memory. A runtime/proc/mapssample does not prove that no transient RWX permission change occurs while executable code is being patched.- The external memory gate scans the mapped agent/Gadget images for its explicit marker set; it is not a general-purpose scan of every anonymous heap page.
- Frida 17 raw agents need explicit bridge imports or bundling. The harness uses
frida.Compiler; the Frida REPL and tracer provide their own bundled bridges.
Credits and licensing
The builder code is MIT licensed. Generated binaries retain upstream licensing; see THIRD_PARTY_NOTICES.md.
