1354 words
7 minutes
Patch 2 — Randomizing the `frida-agent-<arch>.so` Filename on Disk

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

CAUTION

#FreePalestine

WARNING

This is just for educational purposes.


Patch 2 — Randomizing the frida-agent-<arch>.so#

Introduction#

When Frida injects its agent into an Android process, it first has to write the agent shared library to a temporary location on the filesystem. In unpatched Frida the filename is always frida-agent-<arch>.so — a completely predictable, well-known string. This filename shows up in the device filesystem, in /proc/<pid>/maps for every instrumented process, and in /proc/<pid>/fd for processes that still hold the file descriptor open. It is one of the first things any Frida detector checks.

This patch, “Florida: frida_agent_so”, replaces the hardcoded frida-agent prefix with a freshly generated random UUID at session startup. Every time Frida runs, the agent is written under a different, unpredictable name — breaking every detector that relies on the filename as a static signature.


The Original Function / String#

File: src/linux/linux-host-session.vala
Namespace: Frida
Class: LinuxHostSession (agent setup block inside the session constructor / setup path)

When a Frida session starts on a Linux-based target (Android runs a Linux kernel), the host session code must make the compiled agent blobs available to the injector. It does this by constructing an AgentDescriptor — a structure that tells the injector the filename template to use when writing the agent .so to tmpdir on the device.

Before the patch, this looks like:

agent = new AgentDescriptor (PathTemplate ("frida-agent-<arch>.so"),
    new Bytes.static (blob32.data),
    new Bytes.static (blob64.data),
    new AgentResource[] {
        new AgentResource ("frida-agent-arm.so",   new Bytes.static (emulated_arm.data),   tempdir),
        new AgentResource ("frida-agent-arm64.so", new Bytes.static (emulated_arm64.data), tempdir),
    },
    AgentMode.INSTANCED,
    tempdir);

The PathTemplate string "frida-agent-<arch>.so" is the master filename template. <arch> is substituted at injection time with the appropriate architecture tag (e.g. 64, arm, arm64). The two AgentResource entries provide the emulated-architecture fallback blobs, each also carrying a hardcoded filename.

Why these filenames are a detection surface:

  • When the injector writes the agent to tempdir (typically somewhere under /data/local/tmp or a per-app files/ directory), the filename frida-agent-64.so appears on disk.
  • Once the library is dlopen-ed into the target process, the kernel records the full path in the target’s /proc/<pid>/maps entry. Any thread in that process — or a privileged watchdog process — can read /proc/<pid>/maps and search for the substring frida.
  • The filenames are constants baked into the Frida host binary; their predictability means a single strcmp / strstr is enough for detection. No heuristics required.

Before Patching — Behavior on Android#

Agent file written to the device temp directory:

$ adb shell ls /data/local/tmp/
frida-agent-64.so
re.frida.server

Library visible in the target process’s memory map:

$ adb shell cat /proc/$(pidof com.example.targetapp)/maps | grep frida
7a8f200000-7a8fa00000 r--p 00000000 fc:20 262144  /data/local/tmp/frida-agent-64.so
7a8fa00000-7a8fa01000 ---p 00000000 00:00 0
7a8fa01000-7a8fb40000 r-xp 00800000 fc:20 262144  /data/local/tmp/frida-agent-64.so
7a8fb40000-7a8fb80000 r--p 01140000 fc:20 262144  /data/local/tmp/frida-agent-64.so
7a8fb80000-7a8fb90000 rw-p 01180000 fc:20 262144  /data/local/tmp/frida-agent-64.so

Open file descriptor still referencing it:

$ adb shell ls -la /proc/$(pidof com.example.targetapp)/fd | grep frida
lrwxrwxrwx 1 u0_a123 u0_a123 64 2026-02-26 /proc/12345/fd/37 -> /data/local/tmp/frida-agent-64.so

All three surfaces — filesystem, maps, fd — expose the same literal string frida-agent.


How Apps Detect It#

Detection routines targeting the agent filename operate at the filesystem level and at the /proc virtual filesystem level. Both are accessible from unprivileged app code on Android.

Method 1 — Scanning /proc/self/maps for the filename (C/C++)#

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

bool detectFridaAgentInMaps() {
    FILE *maps = fopen("/proc/self/maps", "r");
    if (!maps) return false;

    char line[512];
    while (fgets(line, sizeof(line), maps)) {
        // The maps line format is:
        // address perms offset dev inode pathname
        if (strstr(line, "frida-agent") != NULL) {
            fclose(maps);
            return true;  // frida-agent-<arch>.so found in memory map
        }
    }
    fclose(maps);
    return false;
}

Method 2 — Directory scan of known temp paths (Java)#

import java.io.File;

public class FridaFilesystemDetector {

    private static final String[] SCAN_DIRS = {
        "/data/local/tmp",
        "/data/local/tmp/re.frida.server",
        "/sdcard",
    };

    private static final String FRIDA_AGENT_PREFIX = "frida-agent";

    public static boolean isFridaAgentOnDisk() {
        for (String dirPath : SCAN_DIRS) {
            File dir = new File(dirPath);
            if (!dir.exists() || !dir.isDirectory()) continue;

            String[] files = dir.list();
            if (files == null) continue;

            for (String filename : files) {
                if (filename.startsWith(FRIDA_AGENT_PREFIX)) {
                    return true;
                }
            }
        }
        return false;
    }
}
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>

bool detectFridaAgentFd() {
    DIR *fd_dir = opendir("/proc/self/fd");
    if (!fd_dir) return false;

    struct dirent *entry;
    char link_target[512];

    while ((entry = readdir(fd_dir)) != NULL) {
        char fd_path[64];
        snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%s", entry->d_name);

        ssize_t len = readlink(fd_path, link_target, sizeof(link_target) - 1);
        if (len <= 0) continue;

        link_target[len] = '\0';
        if (strstr(link_target, "frida-agent") != NULL) {
            closedir(fd_dir);
            return true;
        }
    }
    closedir(fd_dir);
    return false;
}

All three methods rely on the same invariant: the filename contains the predictable prefix frida-agent. Remove that invariant and all three methods fail simultaneously.


The Patch#

What changed:

A single random UUID string is generated once at the point the AgentDescriptor is constructed, and used as the filename prefix for all agent resources in that session.

// Before
agent = new AgentDescriptor (PathTemplate ("frida-agent-<arch>.so"),
    ...
    new AgentResource[] {
        new AgentResource ("frida-agent-arm.so",   ...),
        new AgentResource ("frida-agent-arm64.so", ...),
    }, ...);

// After
var random_prefix = GLib.Uuid.string_random();
agent = new AgentDescriptor (PathTemplate (random_prefix + "-<arch>.so"),
    ...
    new AgentResource[] {
        new AgentResource (random_prefix + "-arm.so",   ...),
        new AgentResource (random_prefix + "-arm64.so", ...),
    }, ...);

Full diff:

--- a/src/linux/linux-host-session.vala
+++ b/src/linux/linux-host-session.vala
@@ -128,12 +128,13 @@ namespace Frida {
        var blob64        = Frida.Data.Agent.get_frida_agent_64_so_blob ();
        var emulated_arm  = Frida.Data.Agent.get_frida_agent_arm_so_blob ();
        var emulated_arm64= Frida.Data.Agent.get_frida_agent_arm64_so_blob ();
-       agent = new AgentDescriptor (PathTemplate ("frida-agent-<arch>.so"),
+       var random_prefix = GLib.Uuid.string_random();
+       agent = new AgentDescriptor (PathTemplate (random_prefix + "-<arch>.so"),
            new Bytes.static (blob32.data),
            new Bytes.static (blob64.data),
            new AgentResource[] {
-               new AgentResource ("frida-agent-arm.so",   new Bytes.static (emulated_arm.data),   tempdir),
-               new AgentResource ("frida-agent-arm64.so", new Bytes.static (emulated_arm64.data), tempdir),
+               new AgentResource (random_prefix + "-arm.so",   new Bytes.static (emulated_arm.data),   tempdir),
+               new AgentResource (random_prefix + "-arm64.so", new Bytes.static (emulated_arm64.data), tempdir),
            },
            AgentMode.INSTANCED,
            tempdir);

What GLib.Uuid.string_random() produces:

It produces a standard RFC-4122 v4 random UUID in lowercase hyphenated form, e.g.:

3f2504e0-4f89-11d3-9a0c-0305e82c3301

This becomes the agent filename on disk:

3f2504e0-4f89-11d3-9a0c-0305e82c3301-64.so

The UUID is regenerated on every Frida server startup, so no two sessions produce the same filename. There is no persistent association between the UUID and the word “frida” anywhere — a UUID-named .so in a temp directory is indistinguishable from any other dynamically generated temporary file.

Why this does not break Frida’s functionality:

The AgentDescriptor is the single source of truth for the filename throughout the injection pipeline. Both the file-write path and the dlopen path consult the same descriptor, so the injector writes the file under the UUID name and opens it under the same UUID name. Nothing in the injection flow assumes the prefix is frida-agent; the <arch> substitution in PathTemplate still works the same way, just against a different prefix.


After Patching — Behavior on Android#

Agent file on disk — UUID prefix, no frida anywhere:

$ adb shell ls /data/local/tmp/
3f2504e0-4f89-11d3-9a0c-0305e82c3301-64.so
re.frida.server

/proc/<pid>/maps — no frida string in any map entry:

$ adb shell cat /proc/$(pidof com.example.targetapp)/maps | grep frida
(no output)

$ adb shell cat /proc/$(pidof com.example.targetapp)/maps | grep "\.so" | tail -5
7a8f200000-7a8fa00000 r--p 00000000 fc:20 262144  /data/local/tmp/3f2504e0-4f89-11d3-9a0c-0305e82c3301-64.so
7a8fa00000-7a8fa01000 ---p 00000000 00:00 0
7a8fa01000-7a8fb40000 r-xp 00800000 fc:20 262144  /data/local/tmp/3f2504e0-4f89-11d3-9a0c-0305e82c3301-64.so
7a8fb40000-7a8fb80000 r--p 01140000 fc:20 262144  /data/local/tmp/3f2504e0-4f89-11d3-9a0c-0305e82c3301-64.so
7a8fb80000-7a8fb90000 rw-p 01180000 fc:20 262144  /data/local/tmp/3f2504e0-4f89-11d3-9a0c-0305e82c3301-64.so

/proc/<pid>/fd — no frida in any symlink target:

$ adb shell ls -la /proc/$(pidof com.example.targetapp)/fd | grep frida
(no output)

All three detection layers — filesystem scan, maps scan, fd scan — now return clean results. The agent is present and active in the process, but its filename carries no static signature that any known Frida detector matches against.


Noob Section (In Simple Words)#

The problem:

When Frida injects into an app, it drops a file on the phone called frida-agent-64.so. That filename shows up in three places: on disk, in the process’s loaded-library list (/proc/<pid>/maps), and in its open file handles (/proc/<pid>/fd). Any security check that simply searches for the word frida in any of those three places catches it instantly.

What this patch does:

Instead of naming the file frida-agent-64.so, the patch generates a random UUID (a long random string like 3f2504e0-4f89-11d3-9a0c-0305e82c3301) every time Frida starts and uses that as the filename. So the file on disk becomes something like 3f2504e0-4f89-11d3-9a0c-0305e82c3301-64.so, which has zero connection to the word “frida.” A new random name is picked on every session, so it’s different every time.

The result:

All three detection spots (filesystem, maps, file descriptors) no longer contain anything with “frida” in it. The file just looks like any other randomly named temporary .so file. Security tools scanning for the frida-agent filename prefix find nothing.