( بِسْمِ اللَّـهِ الرَّحْمَـٰنِ الرَّحِيمِ )
CAUTION#FreePalestine
WARNINGThis is just for educational purposes.
Patch 9 — Replacing the memfd_create Name with "jit-cache"
Introduction
Frida’s JIT compiler (via Frida-Gum’s code generation pipeline) needs executable, writable memory regions to compile and store trampolines, inline hooks, and generated stubs at runtime. On Linux and Android, Frida allocates this memory using memfd_create — a Linux syscall that creates an anonymous, memory-backed file descriptor. Like all file descriptors, the one created by memfd_create appears in /proc/<pid>/fd/ as a symbolic link, and its name is visible in that symlink’s target string as /memfd:<name> (deleted).
Before this patch, the name argument passed to memfd_create is whatever the calling code supplies — in practice a Frida-branded string. The name appears verbatim in /proc/<pid>/fd/ and /proc/<pid>/maps, making it yet another static fingerprint readable from inside the process without any privileges.
This patch hardcodes "jit-cache" as the memfd_create name regardless of what the caller passes. jit-cache is the exact name the Android Runtime (ART) uses for its own JIT compilation memory regions — making Frida’s allocation indistinguishable from a legitimate ART artifact.
The Original Function / String
File: lib/base/linux.vala
Namespace: Frida
Function: memfd_create(string name, uint flags) — a private wrapper method
The function is a thin Vala wrapper around the raw Linux memfd_create(2) syscall:
private int memfd_create (string name, uint flags) {
return Linux.syscall (LinuxSyscall.MEMFD_CREATE, name, flags);
}It is called from the surrounding class whenever Frida needs to allocate anonymous executable memory. The name parameter is passed straight through to the kernel, which stores it internally and exposes it through the /proc filesystem.
How the kernel exposes memfd_create names:
When memfd_create("frida-<something>", MFD_CLOEXEC) is called (the exact name varies by Frida version — common examples include frida-jit-code or similar descriptive labels):
- The kernel creates an anonymous inode in
tmpfs. - The caller-supplied name is attached to that inode.
- A file descriptor is returned and appears in
/proc/<pid>/fd/<n>as a symlink pointing to/memfd:<name> (deleted). - Once the memory is mapped (via
mmapon the fd), the mapping appears in/proc/<pid>/mapswith the pathname/memfd:<name> (deleted).
Both /proc/<pid>/fd and /proc/<pid>/maps are readable by any thread in the process, and by a privileged companion process scanning from outside.
Why this is a detection surface:
- The string
frida-jit-code(or whatever variant Frida uses) is visible in/proc/<pid>/mapsfor as long as the JIT-compiled memory region is mapped — which is the entire duration of any active instrumentation session. - It is also visible in
/proc/<pid>/fdas a live file descriptor entry. - Neither of these paths requires the agent
.soto be on disk — they exist purely in kernel memory. All previous filename-based patches (Patch 2) cannot affect this surface. stringson the binary also finds the name literals passed tomemfd_createsince they are string arguments compiled into the Vala/C code.
Before Patching — Behavior on Android
memfd-backed mappings visible in /proc/<pid>/maps:
$ adb shell cat /proc/$(pidof com.example.targetapp)/maps | grep memfd
7a1c000000-7a1c100000 rwxp 00000000 00:05 131073 /memfd:frida-jit-code (deleted)
7a1c100000-7a1c200000 rwxp 00000000 00:05 131074 /memfd:frida-jit-code (deleted)The rwxp permissions (read, write, execute, private) are themselves a secondary heuristic — few legitimate Android libraries map RWX anonymous regions. But the frida-jit-code name removes all ambiguity.
memfd file descriptors visible in /proc/<pid>/fd:
$ adb shell ls -la /proc/$(pidof com.example.targetapp)/fd | grep memfd
lrwxrwxrwx 1 u0_a123 u0_a123 64 /proc/12345/fd/41 -> /memfd:frida-jit-code (deleted)
lrwxrwxrwx 1 u0_a123 u0_a123 64 /proc/12345/fd/42 -> /memfd:frida-jit-code (deleted)String visible in the compiled binary:
$ adb shell strings /data/local/tmp/frida-agent-64.so | grep "jit"
frida-jit-codeHow Apps Detect It
Method 1 — Scanning /proc/self/maps for memfd:frida entries (C++)
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
bool detectFridaMemfd() {
FILE *maps = fopen("/proc/self/maps", "r");
if (!maps) return false;
char line[512];
while (fgets(line, sizeof(line), maps)) {
// memfd entries appear as: address perms ... /memfd:<name> (deleted)
if (strstr(line, "/memfd:frida") != NULL) {
fclose(maps);
return true;
}
}
fclose(maps);
return false;
}Method 2 — Enumerating /proc/self/fd symlinks for memfd:frida (C++)
#include <dirent.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
bool detectFridaMemfdFd() {
DIR *fd_dir = opendir("/proc/self/fd");
if (!fd_dir) return false;
struct dirent *entry;
char target[256];
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, target, sizeof(target) - 1);
if (len <= 0) continue;
target[len] = '\0';
if (strstr(target, "/memfd:frida") != NULL) {
closedir(fd_dir);
return true;
}
}
closedir(fd_dir);
return false;
}Method 3 — Heuristic: RWX anonymous mappings combined with name check (C++)
Some detectors use a two-stage check: first flag any RWX memfd mapping (unusual in legitimate Android code), then confirm by name:
bool detectSuspiciousRwxMemfd() {
FILE *maps = fopen("/proc/self/maps", "r");
if (!maps) return false;
char line[512];
while (fgets(line, sizeof(line), maps)) {
// Check for rwxp (read+write+execute private) memfd mappings
// format: addr-addr rwxp offset dev inode pathname
if (strstr(line, "rwxp") == NULL) continue;
if (strstr(line, "/memfd:") == NULL) continue;
// Any non-ART memfd RWX mapping is suspicious
if (strstr(line, "/memfd:jit-cache") != NULL) continue; // legitimate ART
if (strstr(line, "/memfd:dalvik-") != NULL) continue; // legitimate ART
fclose(maps);
return true; // Unrecognised RWX memfd — likely Frida
}
fclose(maps);
return false;
}This third method is notably more robust — it does not depend on knowing Frida’s exact name, but relies instead on the absence from a whitelist of known legitimate names. This is exactly why the patch targets "jit-cache" specifically: it is already on that whitelist.
The Patch
What changed: The name parameter passed through to the kernel syscall is replaced with the hardcoded string "jit-cache":
--- a/lib/base/linux.vala
+++ b/lib/base/linux.vala
@@ -101,7 +101,7 @@ namespace Frida {
private int memfd_create (string name, uint flags) {
- return Linux.syscall (LinuxSyscall.MEMFD_CREATE, name, flags);
+ return Linux.syscall (LinuxSyscall.MEMFD_CREATE, "jit-cache", flags);
}
}Why "jit-cache" specifically:
The Android Runtime (ART) allocates its own JIT-compiled code regions using memfd_create with the name "jit-cache". On any Android device running ART (Android 5.0+), the target application’s process already has mappings named /memfd:jit-cache (deleted) in its maps file before Frida is ever injected. This means:
- Frida’s new
memfd:jit-cacheentries are camouflaged among pre-existing legitimate ART entries — a detector cannot distinguish them. - Any whitelist-based detector (Method 3 above) explicitly allows
jit-cachebecause it is a known ART artifact. - The name
jit-cacheappears in the binary as a string literal — innocuous and semantically appropriate for what the memory is actually used for (JIT-compiled code cache).
Why this does not break Frida’s functionality:
The name argument to memfd_create is purely cosmetic metadata attached to the kernel inode. The kernel does not use it for any access control, size limit, or behavioral decision. The file descriptor returned is functionally identical regardless of the name. All of Frida’s subsequent operations on the fd (mapping as executable memory, writing compiled stubs, etc.) work identically with "jit-cache" as the name.
After Patching — Behavior on Android
/proc/<pid>/maps — Frida’s JIT regions blend into ART’s existing entries:
$ adb shell cat /proc/$(pidof com.example.targetapp)/maps | grep memfd
7a0c000000-7a0c080000 rwxp 00000000 00:05 98304 /memfd:jit-cache (deleted)
7a1c000000-7a1c100000 rwxp 00000000 00:05 131073 /memfd:jit-cache (deleted)
7a1c100000-7a1c200000 rwxp 00000000 00:05 131074 /memfd:jit-cache (deleted)The first entry is ART’s own JIT cache. The second and third are Frida’s — but they are indistinguishable by name. A detector scanning for memfd:frida finds nothing. A whitelist detector sees only jit-cache entries, all of which it permits.
/proc/<pid>/fd — no frida in any symlink target:
$ adb shell ls -la /proc/$(pidof com.example.targetapp)/fd | grep memfd
lrwxrwxrwx ... /proc/12345/fd/38 -> /memfd:jit-cache (deleted)
lrwxrwxrwx ... /proc/12345/fd/41 -> /memfd:jit-cache (deleted)
lrwxrwxrwx ... /proc/12345/fd/42 -> /memfd:jit-cache (deleted)
$ adb shell ls -la /proc/$(pidof com.example.targetapp)/fd | grep "frida"
(no output)Static strings scan — frida-jit-code is gone:
$ adb shell strings /data/local/tmp/frida-agent-patched-64.so | grep "jit"
jit-cacheThe string jit-cache remains — but it is the ART-standard name, not a Frida-specific one. Any scanner that hits it will also hit every other legitimate Android process running ART, making it useless as a Frida-specific indicator.
Noob Section (In Simple Words)
The problem:
When Frida compiles hooks on the fly, it needs a chunk of memory to store the compiled code. On Android/Linux it creates this memory using a system call called memfd_create, which takes a name tag. Frida passes something like frida-jit-code as that name. The problem is that the name shows up in two places any code in the app can read: the process’s memory map (/proc/<pid>/maps) and its file descriptor list (/proc/<pid>/fd). So even if you renamed the .so file and cleaned up all the thread names, a security tool can still find /memfd:frida-jit-code sitting right there.
What this patch does:
It hardcodes the name to "jit-cache" instead of whatever Frida-branded name was being used. jit-cache is the exact same name that Android’s own runtime (ART) uses for its JIT-compiled code regions. Every Android app already has jit-cache entries in its memory map, so adding a few more looks completely normal. You can learn more about JIT Cache from here.
The result:
Security tools scanning /proc/<pid>/maps or /proc/<pid>/fd for anything containing “frida” find nothing. The Frida memory regions now blend in perfectly with the app’s own ART JIT cache entries. Even smarter detectors that use a whitelist of “known safe” names will let jit-cache through because it’s a standard Android artifact.
What about AOT? ummmmmm? AOT Cache?
