- Detection
1.1 Common Frida Detection
- File / port
– Look for “frida” files in /data/local/tmp
– Check if TCP 27042 is open (default frida-server port) - Dual-process
– Enumerate /proc/<pid> - Memory introspection
– Scan /proc/<pid><pid> - D-Bus probe
– Android normally does not use D-Bus; send a D-Bus ping to every port—if any replies “reject”, frida-server is likely running
1.2 Self-implemented Detection (svc / syscall)
Key idea: avoid libc and instead use raw syscalls or inline assembly to bypass Frida hooks that target libc.
- Direct syscall wrappers
#include <sys/syscall.h>
int my_open(const char *p, int f) { return syscall(SYS_open, p, f); }
ssize_t my_read(int fd, void *b, size_t c) { return syscall(SYS_read, fd, b, c); }
int my_close(int fd) { return syscall(SYS_close, fd); }
- Hardened syscall wrapper
ssize_t secure_read(int fd, void *buf, size_t cnt) {
if (syscall(SYS_gettid) != syscall(SYS_getpid)) // extra thread ⇒ debugger?
return -1;
ssize_t n = syscall(SYS_read, fd, buf, cnt);
/* additional integrity / XOR tricks here */
return n;
}
- Runtime-generated syscall stub
typedef long (*sc_fn)(long,...);
sc_fn gen_write(void) {
unsigned char code[] = {
0x48,0xc7,0xc0,0x01,0x00,0x00,0x00, /* mov rax,1 (write) */
0x0f,0x05, /* syscall */
0xc3 /* ret */
};
void *m = mmap(NULL, 4096, PROT_R|PROT_W|PROT_X, MAP_ANON|MAP_PRIVATE, -1, 0);
memcpy(m, code, sizeof(code));
return (sc_fn)m;
}
- Pure assembly (ARM64 example)
asm
.global my_write
my_write:
mov x8, #64 // SYS_write
svc #0
ret
- Practical detection snippet
JNIEXPORT jboolean JNICALL
Java_com_example_SecurityCheck_detectFrida(JNIEnv *env, jobject thiz) {
char line[256];
int fd = syscall(SYS_open, "/proc/self/maps", O_RDONLY);
if (fd != -1) {
while (syscall(SYS_read, fd, line, sizeof(line)) > 0) {
if (strstr(line, "frida") || strstr(line, "gum-js-loop")) {
syscall(SYS_close, fd);
return JNI_TRUE;
}
}
syscall(SYS_close, fd);
}
return JNI_FALSE;
}
- Bypass
2.1 Bypassing Common Checks — One-Shot Script
Hook libc’s strstr / strcmp, force any match against Frida keywords to return 0 (not found).
JavaScript
function replace_str() {
['strstr', 'strcmp'].forEach(fn => {
const addr = Module.findExportByName('libc.so', fn);
Interceptor.attach(addr, {
onEnter(args) {
const s2 = args[1].readCString();
if (s2 && /tmp|frida|gum-js-loop|gmain|gdbus|pool-frida|linjector/i.test(s2))
this.hook = true;
},
onLeave(retval) {
if (this.hook) retval.replace(0);
}
});
});
}
replace_str();
2.2 Bypassing Self-Implemented Checks
Since self-implemented detection uses raw svc instructions, we can scan executable memory for svc opcodes and then hook them with Frida’s Memory API.
Example: locate and hook ARM64 SYS_open
JavaScript
function hookSysOpen() {
const SYS_OPEN = 56; // ARM64
const SVC_HEX = "01 00 00 D4"; // svc #0
Process.enumerateRanges('r-x').forEach(r => {
if (!r.file || !r.file.path.endsWith('.so')) return;
Memory.scan(r.base, r.size, SVC_HEX, {
onMatch(addr) {
const sysno = addr.sub(4).readU32() & 0xFFFF;
if (sysno === SYS_OPEN) {
Interceptor.attach(addr, {
onEnter(args) {
const fn = args[0].readUtf8String();
console.log('[SYS_open]', fn);
}
});
}
}
});
});
}
hookSysOpen();
Inside onEnter
you can decide whether to tamper with the return value to bypass the check.
TL;DR
Detector side: files, ports, maps, threads, D-Bus (common); svc / syscall / assembly / runtime machine code (self-implemented).
Bypasser side: one-shot script kills common checks; signature scanning + instruction-level hook defeats self-implemented checks.