1518 words
8 minutes
Patch 10 — Disabling Frida's Own Anti-Anti-Frida Logic

( بِسْمِ اللَّـهِ الرَّحْمَـٰنِ الرَّحِيمِ )

CAUTION

#FreePalestine

WARNING

This is just for educational purposes.


Patch 10 — Wiring anti-anti-frida.py into the Build Pipeline#

Introduction#

Patches 3 through 7 built up a powerful post-processing script — src/anti-anti-frida.py — that renames ELF symbols, reverses .rodata string literals, and randomizes thread names in the compiled frida-agent.so. But a script that must be run manually after every build is fragile: it can be forgotten, skipped, or applied against the wrong build artifact. It also creates a gap where intermediate build outputs contain unobfuscated agent binaries that could be captured by an automated CI system or build cache.

Patch 10 closes that gap. It modifies src/embed-agent.py — the Python script that the Frida build system runs to embed the compiled agent blobs into the final binary — to automatically invoke anti-anti-frida.py against each freshly compiled agent .so before it is embedded. From this point forward, every build of Frida produces an obfuscated agent with no manual intervention required.


The Original Function / Script#

File: src/embed-agent.py
Role: The Frida build system calls this script as a build step to take the compiled frida-agent-<arch>.so binaries, optionally compress or transform them, and embed them as binary blobs into a generated C source file or object that gets linked into frida-core.

The relevant section of the original script handles each agent flavor (architecture variant) in a loop. For each flavor it either copies the agent to an embedded path or writes an empty placeholder:

if agent.exists():
    shutil.copy(agent, embedded_agent)
else:
    embedded_agent.write_bytes(b"")
## ← anti-anti-frida.py was NOT called here; embedding happened immediately
embedded_assets += [embedded_agent]

The agent is passed to the embedder in its raw, unobfuscated state.

Why the integration point matters:

embed-agent.py runs at a specific moment in the build graph: after the agent .so has been compiled and linked by the native toolchain, but before it is compressed and embedded as a blob into frida-core. This is the only window where:

  1. The agent exists as a standalone ELF file on disk (accessible to Python’s os.system).
  2. The agent has not yet been wrapped, compressed, or split into data blobs (so LIEF can still parse and write it as a normal ELF).
  3. All the obfuscation performed by anti-anti-frida.py is on the final linked binary — not an intermediate object — so no subsequent linker pass can re-introduce the stripped strings.

Invoking the script anywhere else in the build (e.g. before linking, or after embedding) would either process incomplete artifacts or operate on data that is no longer a parseable ELF.


Before Patching — Behavior on Android#

Without this patch, the build pipeline produces an unobfuscated agent unless the developer manually runs anti-anti-frida.py against the output. If they forget:

Unobfuscated agent embedded in a build that skipped the manual script:

$ adb shell strings /data/local/tmp/frida-agent-64.so | grep -E "frida_agent_main|gum-js-loop|gmain|FridaScriptEngine"
frida_agent_main
gum-js-loop
gmain
FridaScriptEngine
GumScript
GDBusProxy
GLib-GIO

All signatures are present. Every detection layer from Patches 1–9 is circumvented only if the developer remembers to invoke the script. One forgotten manual step undoes all the obfuscation work.

Build log without the integration — anti-anti-frida never fires:

[1234/1235] COPY frida-agent-64.so → _frida_core_embedded/frida-agent-64.so
[1235/1235] LINK frida-core.a
Build finished. No anti-anti-frida output.

How Apps Detect It#

The detection angle for this patch is slightly different from the others. The risk is not a new artifact — it is the failure to apply all previous patches reliably. A detector that finds any of the artifacts from Patches 1–9 in a supposedly-patched build knows the pipeline was broken. This patch prevents that failure mode entirely rather than eliminating a new artifact.

That said, build pipeline integrity can itself be probed:

Method 1 — CI/build artifact scanning (infrastructure-level)#

Automated build systems and security scanners that intercept build artifacts can detect whether a given Frida build was post-processed:

#!/bin/bash
## Build artifact integrity check — run in CI after build
AGENT="./build/_frida_embedded/frida-agent-64.so"

SIGNATURES=(
    "frida_agent_main"
    "gum-js-loop"
    "gmain"
    "FridaScriptEngine"
    "GumScript"
    "frida:rpc"
)

for sig in "${SIGNATURES[@]}"; do
    if strings "$AGENT" | grep -q "$sig"; then
        echo "[FAIL] Unobfuscated signature found: $sig"
        exit 1
    fi
done
echo "[PASS] No known Frida signatures found in agent binary."

If this check runs against a build where anti-anti-frida.py was not integrated, it will fail with every signature on the list. With Patch 10, it passes automatically on every build.

Method 2 — Runtime detection still applies as a secondary check#

Even with the build pipeline integrated, a runtime detector on the device is the final line of defense. The previous patches individually ensure each artifact is eliminated — Patch 10 is what guarantees they are all applied consistently without human error.


The Patch#

What changed: Eight lines are inserted into src/embed-agent.py immediately after the agent file is written to its embedded path and before it is added to embedded_assets:

--- a/src/embed-agent.py
+++ b/src/embed-agent.py
@@ -78,6 +78,14 @@ def main(argv):
             if agent.exists():
                 shutil.copy(agent, embedded_agent)
             else:
                 embedded_agent.write_bytes(b"")
+            import os
+            custom_script=str(output_dir)+"/../../../../frida/subprojects/frida-core/src/anti-anti-frida.py"
+            return_code = os.system("python3 "+custom_script+" "+str(priv_dir / f"frida-agent-{flavor}.so"))
+            if return_code == 0:
+                print("anti-anti-frida finished")
+            else:
+                print("anti-anti-frida error. Code:", return_code)
+            
             embedded_assets += [embedded_agent]

Breaking down the path construction:

custom_script = str(output_dir) + "/../../../../frida/subprojects/frida-core/src/anti-anti-frida.py"

output_dir is a pathlib.Path passed into embed-agent.py by the build system, pointing to the Meson build output directory structure. The relative traversal ../../../../frida/ walks back up from the per-arch build output directory to the Frida source tree root, then down into frida-core/src/ where anti-anti-frida.py lives. This path is specific to the Frida+Florida build directory layout used by the Florida project.

The agent path passed to the script:

str(priv_dir / f"frida-agent-{flavor}.so")

priv_dir is the private output directory for the current architecture flavor. flavor is the arch string (e.g. "64", "arm", "arm64"), so the argument becomes something like:

/build/frida-android-arm64/frida-agent-64.so

This is the same file that was just written by shutil.copy, so it is the final linked agent for this architecture — exactly what the post-processing script expects.

Error handling:

os.system() returns the exit code of the subprocess. If anti-anti-frida.py exits with a non-zero code (LIEF parse failure, file not found, etc.), the build prints a warning but does not abort. This is a deliberate choice: a post-processing failure should not break the entire build. However the printed error message "anti-anti-frida error. Code: <N>" makes the failure visible in the build log.

Why os.system rather than subprocess.run:

This is pragmatic build-script code, not production Python. os.system is simpler, inherits the current shell environment (important for sed availability in the PATH used by anti-anti-frida.py), and is sufficient for a fire-and-forget post-processing invocation where the output is printed to stdout and the exit code is the only return value needed.

Execution order within embed-agent.py:

for each architecture flavor:
    1. Resolve agent .so path
    2. Copy/write agent to embedded_agent path   ← shutil.copy
    3. Run anti-anti-frida.py on the copy        ← [NEW — this patch]
    4. Add the (now obfuscated) copy to assets   ← embedded_assets += [embedded_agent]
    5. Compress / embed into C blob

Step 3 modifies the file in-place (LIEF’s binary.write(input_file) and sed -b -i both operate in-place). By the time step 4 runs, all obfuscation has been applied and the file that gets embedded is the clean version.


After Patching — Behavior on Android#

Build log — anti-anti-frida.py fires automatically for each arch:

[1231/1235] COPY frida-agent-arm.so → _embedded/frida-agent-arm.so
[*] Patch frida-agent: .../frida-agent-arm.so
[*] Patch `frida` to `TzaRc`
[*] Patching section name=.rodata offset=0x18f320 orig:FridaScriptEngine new:enigEnpircSadirF
[*] Patching section name=.rodata offset=0x18f500 orig:GLib-GIO new:OIG-biLG
[*] Patching section name=.rodata offset=0x18f680 orig:GDBusProxy new:yxorPsuBDG
[*] Patching section name=.rodata offset=0x18f720 orig:GumScript new:tpircSmuG
[*] Patch `gum-js-loop` to `xKbNmPqRjLa`
[*] Patch `gmain` to `WrXcV`
[*] Patch `gdbus` to `BnZpQ`
[*] Patch Finish
anti-anti-frida finished
[1232/1235] COPY frida-agent-64.so → _embedded/frida-agent-64.so
[*] Patch frida-agent: .../frida-agent-64.so
...
anti-anti-frida finished
[1235/1235] LINK frida-core.a

Every agent flavor is obfuscated before embedding — no manual step required.

Verification on the deployed device:

$ adb shell strings /data/local/tmp/frida-agent-64.so | grep -E "frida_agent_main|gum-js-loop|gmain|FridaScriptEngine|GumScript"
(no output)

$ adb shell nm -D /data/local/tmp/frida-agent-64.so | grep -i frida
(no output)

$ adb shell ps -T -p $(pidof com.example.targetapp) | grep -E "gum-js-loop|gmain|gdbus|pool-frida"
(no output)

CI artifact scan — all signatures pass:

$ ./check-agent-signatures.sh ./build/_frida_embedded/frida-agent-64.so
[PASS] No known Frida signatures found in agent binary.

With Patch 10 in place, the entire obfuscation pipeline from Patches 3–9 runs automatically on every build invocation. The developer never needs to remember to run a post-processing step, and the embedded agent that ships in every Frida build is consistently scrubbed of all known static and dynamic fingerprints.


Noob Section (In Simple Words)#

The problem:

Patches 3 through 9 created a powerful cleanup script (anti-anti-frida.py) that scrubs all Frida fingerprints from the compiled agent. But up until now, someone had to remember to run that script manually after every build. If they forgot even once, the entire build would ship with all the original Frida signatures intact, and all the previous patches would be useless for that build.

What this patch does:

It plugs anti-anti-frida.py directly into Frida’s build system. Specifically, it hooks into embed-agent.py, the script that packages the compiled agent into the final Frida binary. Right after the agent .so is copied and right before it gets packaged, the cleanup script runs automatically. Every architecture variant (arm, arm64, 64-bit, etc.) gets processed.

The result:

Every single build now produces a clean, obfuscated agent with zero manual effort. There’s no way to accidentally ship an unpatched binary. The build log shows the cleanup script firing for each architecture, and if anything goes wrong it prints an error so the developer knows. It turns a fragile “remember to run this” step into a reliable, automated part of the pipeline.