( بِسْمِ اللَّـهِ الرَّحْمَـٰنِ الرَّحِيمِ )
CAUTION#FreePalestine
WARNINGThis is just for educational purposes.
Patch 7 — Script Overhaul: Expanded Charset, .rodata String Reversal, and gdbus Thread
Introduction
Patches 3 through 5 built up src/anti-anti-frida.py incrementally — each adding one new obfuscation step. Patch 7 is a consolidating overhaul of the entire script. It introduces four distinct improvements in one commit:
- Expanded random charset — the character pool for random replacements is upgraded from limited lowercase or uppercase sets to the full mixed-case alphanumeric alphabet, making generated names harder to characterize by case alone.
.rodatasection string patching — a new LIEF-based loop scans the.rodatasection of the compiled agent for four additional Frida-branded string literals (FridaScriptEngine,GLib-GIO,GDBusProxy,GumScript) and reverses their bytes in-place, destroying the readable form without changing the string length.gdbusthread name randomization — a fourthsed -b -ireplacement is added, covering GLib’s D-Bus background thread namegdbus.- Colored console output — a
log_color()helper wraps all status messages in ANSI red-on-black formatting for build-log visibility.
The Original Functions / Strings
This patch targets four categories of artifacts simultaneously.
A — Expanded random charset
Previous patches used:
"ABCDEFGHIJKLMNO"(15 uppercase chars) for symbol names"abcdefghijklmn"(14 lowercase chars) for thread names
Both pools are small and their case restriction means the generated names have a detectable statistical property: all-uppercase or all-lowercase 5-character strings are unusual in legitimate native library symbol tables. This patch unifies both to:
random_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"52 characters, mixed case. random.sample(random_charset, 5) now produces names like xKbQm, TzaRc, nLpWj — indistinguishable in case distribution from real symbol names in any native library.
B — .rodata string literals: FridaScriptEngine, GLib-GIO, GDBusProxy, GumScript
File location: .rodata section of frida-agent.so
These four strings are SDK-level identifiers baked into Frida’s source:
| String | Origin / Purpose |
|---|---|
FridaScriptEngine | Frida’s JS engine class/type name used in log messages and GObject type registration |
GLib-GIO | GLib’s GIO subsystem identifier, appears in GLib error domain strings and D-Bus log output |
GDBusProxy | GLib D-Bus proxy class name, emitted in GLib warning/error messages during IPC operations |
GumScript | Frida-Gum’s script class name, appears in GumJS debug output and error messages |
None of these are ELF symbol names — they are plain C string literals stored in .rodata. LIEF’s .symbols iterator does not reach them — only direct section scanning can find and patch them. Because they contain either Frida, Gum, or well-known GLib class names associated exclusively with Frida’s dependency stack, they are valid static signatures.
Why reversal instead of random replacement:
Unlike symbol names (which are null-terminated and have no length constraint enforced by the ELF spec) and thread names (which sed handles at exact known lengths), these .rodata strings can appear adjacent to other data with no padding. Replacing them with a different string of the same length preserves binary layout integrity. Reversal is the simplest same- length transformation: FridaScriptEngine → enigEnpircSadirF. It is deterministic, requires no random state, and produces a string that will not match any forward-scanning signature.
C — gdbus thread name
GLib’s D-Bus implementation creates a background thread named gdbus when any D-Bus connection is initialised. Frida uses GLib’s D-Bus stack internally for its own IPC. The gdbus thread therefore appears in /proc/<pid>/task/<tid>/comm alongside gum-js-loop and gmain whenever the agent is active. It was not covered by earlier patches.
Why it is a detection surface:
gdbus is a known GLib thread name. While it can appear in legitimate Android apps that use GLib (uncommon), its simultaneous presence with gum-js-loop or gmain in the same process is a high-confidence Frida indicator.
Before Patching — Behavior on Android
All four detectable strings present in .rodata:
$ adb shell strings /data/local/tmp/frida-agent-64.so | grep -E "FridaScriptEngine|GLib-GIO|GDBusProxy|GumScript"
FridaScriptEngine
GLib-GIO
GDBusProxy
GumScriptAll four Frida-specific thread names visible at runtime:
$ adb shell ps -T -p $(pidof com.example.targetapp) | grep -E "gum-js-loop|gmain|gdbus"
u0_a123 12345 12351 ... gum-js-loop
u0_a123 12345 12352 ... gmain
u0_a123 12345 12353 ... gdbus/proc task comm files:
android:/ # cat /proc/12345/task/12353/comm
gdbusHow Apps Detect It
Method 1 — Extended static string scan (shell / Python)
Detectors that already scan frida-agent.so for the strings from earlier patches maintain a list of additional secondary signatures:
import subprocess
SIGNATURES = [
# Primary (covered by earlier patches)
"frida:rpc", "frida_agent_main", "gum-js-loop", "gmain",
# Secondary — caught by this patch
"FridaScriptEngine", "GLib-GIO", "GDBusProxy", "GumScript", "gdbus",
]
def scan_binary(path: str) -> list[str]:
result = subprocess.run(["strings", path], capture_output=True, text=True)
found = []
for sig in SIGNATURES:
if sig in result.stdout:
found.append(sig)
return found
hits = scan_binary("/data/local/tmp/frida-agent-64.so")
if hits:
print(f"[!] Frida signatures found: {hits}")Method 2 — GObject type name probe (C++)
FridaScriptEngine and GumScript are registered as GObject type names inside the agent. A detector that has already identified the agent’s base address in memory can search for these strings in .rodata directly:
#include <string.h>
// Called after finding frida-agent's load base (e.g. from /proc/self/maps by UUID name)
bool detectFridaGObjectTypes(const uint8_t *rodata_base, size_t rodata_size) {
const char *targets[] = {
"FridaScriptEngine",
"GumScript",
"GDBusProxy",
"GLib-GIO",
NULL
};
for (int i = 0; targets[i] != NULL; i++) {
size_t len = strlen(targets[i]);
for (size_t off = 0; off + len < rodata_size; off++) {
if (memcmp(rodata_base + off, targets[i], len) == 0) {
return true;
}
}
}
return false;
}Method 3 — Combined thread name set detection including gdbus (C++)
static const char *FRIDA_THREADS[] = {
"gum-js-loop",
"gmain",
"gdbus", // newly added in this patch's threat model
NULL
};
bool detectAllFridaThreads() {
DIR *task_dir = opendir("/proc/self/task");
if (!task_dir) return false;
struct dirent *entry;
while ((entry = readdir(task_dir)) != NULL) {
if (entry->d_name[0] == '.') continue;
char comm_path[64], comm[32] = {0};
snprintf(comm_path, sizeof(comm_path), "/proc/self/task/%s/comm", entry->d_name);
FILE *f = fopen(comm_path, "r");
if (!f) continue;
fgets(comm, sizeof(comm), f);
fclose(f);
comm[strcspn(comm, "\n")] = '\0';
for (int i = 0; FRIDA_THREADS[i]; i++) {
if (strcmp(comm, FRIDA_THREADS[i]) == 0) {
closedir(task_dir);
return true;
}
}
}
closedir(task_dir);
return false;
}The Patch
Full diff
--- a/src/anti-anti-frida.py
+++ b/src/anti-anti-frida.py
@@ -2,36 +2,59 @@ import lief
import sys
import random
import os
-
+
+def log_color(msg):
+ print(f"\033[1;31;40m{msg}\033[0m")
+
if __name__ == "__main__":
input_file = sys.argv[1]
- print(f"[*] Patch frida-agent: {input_file}")
- random_name = "".join(random.sample("ABCDEFGHIJKLMNO", 5))
- print(f"[*] Patch `frida` to `{random_name}``")
-
+ random_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ log_color(f"[*] Patch frida-agent: {input_file}")
binary = lief.parse(input_file)
-
+
if not binary:
+ log_color(f"[*] Not elf, exit")
exit()
+
+ random_name = "".join(random.sample(random_charset, 5))
+ log_color(f"[*] Patch `frida` to `{random_name}`")
for symbol in binary.symbols:
if symbol.name == "frida_agent_main":
symbol.name = "main"
-
+
if "frida" in symbol.name:
symbol.name = symbol.name.replace("frida", random_name)
-
+
if "FRIDA" in symbol.name:
symbol.name = symbol.name.replace("FRIDA", random_name)
-
+
+ all_patch_string = ["FridaScriptEngine", "GLib-GIO", "GDBusProxy", "GumScript"]
+ for section in binary.sections:
+ if section.name != ".rodata":
+ continue
+ for patch_str in all_patch_string:
+ addr_all = section.search_all(patch_str)
+ for addr in addr_all:
+ patch = [ord(n) for n in list(patch_str)[::-1]]
+ log_color(f"[*] Patching section name={section.name} offset={hex(section.file_offset + addr)} orig:{patch_str} new:{''.join(list(patch_str)[::-1])}")
+ binary.patch_address(section.file_offset + addr, patch)
+
binary.write(input_file)
-
- # gum-js-loop thread
- random_name = "".join(random.sample("abcdefghijklmn", 11))
- print(f"[*] Patch `gum-js-loop` to `{random_name}`")
+
+ # thread_gum_js_loop
+ random_name = "".join(random.sample(random_charset, 11))
+ log_color(f"[*] Patch `gum-js-loop` to `{random_name}`")
os.system(f"sed -b -i s/gum-js-loop/{random_name}/g {input_file}")
-
- # gmain thread
- random_name = "".join(random.sample("abcdefghijklmn", 5))
- print(f"[*] Patch `gmain` to `{random_name}`")
- os.system(f"sed -b -i s/gmain/{random_name}/g {input_file}")
+
+ # thread_gmain
+ random_name = "".join(random.sample(random_charset, 5))
+ log_color(f"[*] Patch `gmain` to `{random_name}`")
+ os.system(f"sed -b -i s/gmain/{random_name}/g {input_file}")
+
+ # thread_gdbus
+ random_name = "".join(random.sample(random_charset, 5))
+ log_color(f"[*] Patch `gdbus` to `{random_name}`")
+ os.system(f"sed -b -i s/gdbus/{random_name}/g {input_file}")
+
+ log_color(f"[*] Patch Finish")Breaking down the .rodata reversal logic
all_patch_string = ["FridaScriptEngine", "GLib-GIO", "GDBusProxy", "GumScript"]
for section in binary.sections:
if section.name != ".rodata":
continue
for patch_str in all_patch_string:
addr_all = section.search_all(patch_str) # find all byte offsets within section
for addr in addr_all:
patch = [ord(n) for n in list(patch_str)[::-1]] # reverse → list of ints
binary.patch_address(section.file_offset + addr, patch)Step by step for FridaScriptEngine (17 bytes):
Original bytes: F r i d a S c r i p t E n g i n e
46 72 69 64 61 53 63 72 69 70 74 45 6e 67 69 6e 65
Reversed bytes: e n i g n E t p i r c S a d i r F
65 6e 69 67 6e 45 74 70 69 72 63 53 61 64 69 72 46
Result string: "enigEnpircSadirF"The byte count is identical (17 bytes in, 17 bytes out), so no adjacent data is disturbed. The reversed string is nonsense to any forward-scanning string tool.
Why not use random bytes? Reversal guarantees the same length with zero risk of accidentally generating a valid keyword or partial match. It also means the operation is fully reproducible from the original string if needed during debugging — no random state needs to be saved.
Complete state of anti-anti-frida.py after this patch:
import lief, sys, random, os
def log_color(msg):
print(f"\033[1;31;40m{msg}\033[0m")
if __name__ == "__main__":
input_file = sys.argv[1]
random_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
log_color(f"[*] Patch frida-agent: {input_file}")
binary = lief.parse(input_file)
if not binary:
log_color(f"[*] Not elf, exit")
exit()
random_name = "".join(random.sample(random_charset, 5))
log_color(f"[*] Patch `frida` to `{random_name}`")
for symbol in binary.symbols:
if symbol.name == "frida_agent_main":
symbol.name = "main"
if "frida" in symbol.name:
symbol.name = symbol.name.replace("frida", random_name)
if "FRIDA" in symbol.name:
symbol.name = symbol.name.replace("FRIDA", random_name)
all_patch_string = ["FridaScriptEngine", "GLib-GIO", "GDBusProxy", "GumScript"]
for section in binary.sections:
if section.name != ".rodata":
continue
for patch_str in all_patch_string:
addr_all = section.search_all(patch_str)
for addr in addr_all:
patch = [ord(n) for n in list(patch_str)[::-1]]
log_color(f"[*] Patching section name={section.name} offset={hex(section.file_offset + addr)} orig:{patch_str} new:{''.join(list(patch_str)[::-1])}")
binary.patch_address(section.file_offset + addr, patch)
binary.write(input_file)
# thread_gum_js_loop
random_name = "".join(random.sample(random_charset, 11))
log_color(f"[*] Patch `gum-js-loop` to `{random_name}`")
os.system(f"sed -b -i s/gum-js-loop/{random_name}/g {input_file}")
# thread_gmain
random_name = "".join(random.sample(random_charset, 5))
log_color(f"[*] Patch `gmain` to `{random_name}`")
os.system(f"sed -b -i s/gmain/{random_name}/g {input_file}")
# thread_gdbus
random_name = "".join(random.sample(random_charset, 5))
log_color(f"[*] Patch `gdbus` to `{random_name}`")
os.system(f"sed -b -i s/gdbus/{random_name}/g {input_file}")
log_color(f"[*] Patch Finish")After Patching — Behavior on Android
Static string scan — all four .rodata strings gone:
$ adb shell strings /data/local/tmp/frida-agent-patched-64.so | grep -E "FridaScriptEngine|GLib-GIO|GDBusProxy|GumScript"
(no output)
$ adb shell strings /data/local/tmp/frida-agent-patched-64.so | grep -E "enigEn|OIG-biLG|yxorPsuBDG|tpircSmuG"
enigEnpircSadirF
OIG-biLG
yxorPsuBDG
tpircSmuGThe reversed strings are present at the same file offsets — nothing shifted — but are completely opaque to any forward-scanning string matcher.
Thread listing — all three Frida threads now have random names:
$ adb shell ps -T -p $(pidof com.example.targetapp) | grep -E "gum-js-loop|gmain|gdbus"
(no output)
$ adb shell ps -T -p $(pidof com.example.targetapp)
USER PID TID PPID VSZ RSS WCHAN PC S NAME
u0_a123 12345 12345 1234 2.1G 45M futex_wait 0 S com.example.targetapp
u0_a123 12345 12346 1234 2.1G 45M futex_wait 0 S Jit thread pool
u0_a123 12345 12347 1234 2.1G 45M futex_wait 0 S RenderThread
u0_a123 12345 12351 1234 2.1G 45M futex_wait 0 S xKbQmTzaRc1
u0_a123 12345 12352 1234 2.1G 45M futex_wait 0 S nLpWj
u0_a123 12345 12353 1234 2.1G 45M futex_wait 0 S jRmNbBuild-time console output (colored red in terminal):
[*] Patch frida-agent: ./out/frida-agent-64.so
[*] Patch `frida` to `xKbQm`
[*] Patching section name=.rodata offset=0x1a3f20 orig:FridaScriptEngine new:enigEnpircSadirF
[*] Patching section name=.rodata offset=0x1a4100 orig:GLib-GIO new:OIG-biLG
[*] Patching section name=.rodata offset=0x1a4280 orig:GDBusProxy new:yxorPsuBDG
[*] Patching section name=.rodata offset=0x1a4350 orig:GumScript new:tpircSmuG
[*] Patch `gum-js-loop` to `xKbQmTzaRc1`
[*] Patch `gmain` to `nLpWj`
[*] Patch `gdbus` to `jRmNb`
[*] Patch FinishThe combined effect across all patches so far is that frida-agent.so no longer contains any of its original identifying strings — neither in the symbol table, nor in .rodata, nor as live thread names in the kernel’s task list.
Noob Section (In Simple Words)
The problem:
Even after earlier patches cleaned up the function names and thread names, the Frida agent library still had four readable strings buried in its data section: FridaScriptEngine, GLib-GIO, GDBusProxy, and GumScript. These are internal class/type names that Frida and its libraries use, and any scanner searching the file for Frida-related keywords would find them. On top of that, there was a third thread name (gdbus) that hadn’t been randomized yet, and the random character pools used in earlier patches were too small and predictable (all-uppercase or all-lowercase).
What this patch does:
Three improvements in one go. First, the character pool for generating random names is expanded to the full alphabet (uppercase + lowercase, 52 characters) so generated names look more natural. Second, the four data-section strings are reversed in place, so FridaScriptEngine becomes enigEnpircSadirF, GDBusProxy becomes yxorPsuBDG, and so on. Reversing keeps them the same length so nothing in the file shifts. Third, the gdbus thread name gets the same random-replacement treatment that gum-js-loop and gmain already got.
The result:
A scanner searching for any of those four strings finds nothing readable. The reversed versions are gibberish to any forward-scanning tool. The gdbus thread now has a random name too, and all random names use a much larger character set that blends in better. At this point, the agent binary has no Frida-related text left in its function list, data section, or thread names.
