A General Approach to Bypassing Frida Detection
About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.
This research is published for educational and defensive purposes. The target sample is anonymized throughout.
A General Approach to Bypassing Frida Detection
Foreword
Anti-detection is best approached from the exit path. When a detector sees an abnormal environment, it tries to kill the process one way or another. Those exits fall roughly into two buckets: libc-based and svc-based.
- libc:
exit,kill,_exit, and friends. You can neutralize these by callingFCAnd.anti.anti_debug();directly (add any functions I missed). - svc: anything that goes through
syscall.
At the libc level, Frida can hook the relevant functions and print a stack trace to locate the detector. svc can’t be hooked directly with Frida, so you need the general approach below.
The scripts in this article mostly come from: https://github.com/deathmemory/FridaContainer
Scanning for svc
People have shared ways to detect svc calls using Frida’s Stalker, and that’s one option — but Stalker’s stability has always been a problem. So here I use IDA Python + Frida instead.
The core idea: implement the “locate where a .so calls svc” step in IDA Python. Straight to the IDAPython code:
from idautils import *
from idc import *
import ida_idp
ida_idp.get_idp_name()
# the svc byte sequence to search for
# arm
# search_bytes = b"\x00\x00\x00\xef"
# arm64
search_bytes = b"\x01\x00\x00\xd4"
result = []
# iterate over the executable segments of the binary
seg = ida_segment.get_segm_by_name(".text")
# search for the byte sequence in each segment
for address in range(seg.start_ea, seg.end_ea):
if get_bytes(address, len(search_bytes)) == search_bytes:
print("Match found at address: 0x%x" % address)
result.append(address)
print([hex(n) for n in result])
You can load this as python/android/idaSearchSvc.py in IDA and run it. It prints every svc call site in the .so, for example:
['0x4826c', '0x487bc', '0x48dc4', '0x496d4', '0x49880', '0x499d0', '0x4b200', '0x4bf40', '0x51578', '0x51598', '0x516fc', '0x51984', '0x519bc', '0x51a34', '0x51b24', '0x51b9c', '0x51e98']
Locating the detector with Frida
Hook all the svc_address_list entries above, or just call the FCAnd wrapper:
let svc_address_list = ['0x4826c', '0x487bc', '0x48dc4', '0x496d4', '0x49880', '0x499d0', '0x4b200', '0x4bf40', '0x51578', '0x51598', '0x516fc', '0x51984', '0x519bc', '0x51a34', '0x51b24', '0x51b9c', '0x51e98'];
FCAnd.watch_svc_address_list(mod.base, svc_address_list);
Then launch the target process with frida in spawn mode and every svc call’s stack trace gets printed. To hook in time, start hooking after the target .so loads:
FCAnd.whenSoLoad("libshell-super.com.wm.xxxxxx.so", (mod) => {
let svc_address_list = ['0x4826c', '0x487bc', '0x48dc4', '0x496d4', '0x49880', '0x499d0', '0x4b200', '0x4bf40', '0x51578', '0x51598', '0x516fc', '0x51984', '0x519bc', '0x51a34', '0x51b24', '0x51b9c', '0x51e98'];
FCAnd.watch_svc_address_list(mod.base, svc_address_list);
// DMLog.i("dd", "so load base: " + mod.base);
});
The stack traces look like this:
......
[INFO][05/10/2024, 07:14:18 PM][PID:9502][Thread-134][9551][showNativeStacks]: Backtrace:
0x6e86545b10 libshell-super.com.xxxxxx.so!0x51b10
0x6e86545580 libshell-super.com.xxxxxx.so!0x51580
0x7187d9075c libc.so!_ZL15__pthread_startPv+0x44
0x7187d30154 libc.so!__start_thread+0x44
[INFO][05/10/2024, 07:14:18 PM][PID:9502][Thread-135][9551][showNativeStacks]: Backtrace:
0x6e86545b10 libshell-super.com.xxxxxx.so!0x51b10
0x6e86545580 libshell-super.com.xxxxxx.so!0x51580
0x7187d9075c libc.so!_ZL15__pthread_startPv+0x44
0x7187d30154 libc.so!__start_thread+0x44
[INFO][05/10/2024, 07:14:18 PM][PID:9502][Thread-136][9551][showNativeStacks]: Backtrace:
0x6e864ede0c
0x6e86545580 libshell-super.com.xxxxxx.so!0x51580
0x7187d9075c libc.so!_ZL15__pthread_startPv+0x44
0x7187d30154 libc.so!__start_thread+0x44
[INFO][05/10/2024, 07:14:18 PM][PID:9502][Thread-137][9551][showNativeStacks]: Backtrace:
0x6e86545b10 libshell-super.com.xxxxxx.so!0x51b10
0x6e86545580 libshell-super.com.xxxxxx.so!0x51580
0x7187d9075c libc.so!_ZL15__pthread_startPv+0x44
0x7187d30154 libc.so!__start_thread+0x44
......
Skim through and the most frequent offenders are basically 0x51b10 → 0x51580.
Next, open IDA at those addresses, look at the detection logic, and you can figure out what it checks and how to bypass it.

Say the detection function is sub_515C4. Step into it and see what it checks. I won’t go into the details here — just the conclusion. This function mainly checks:
- Thread names in
/proc/self/task/%s/status readlinkfeature names in/proc/self/fd/%s- Frida feature names in
/proc/self/maps
Nothing exotic. But here’s the weird part: my Frida build was already feature-stripped, so all of these should have been bypassed — yet it still detected the environment and killed the process. I went back and re-confirmed this was definitely the right detection point. So which check in sub_515C4 actually fired?
Pinpointing the exact check
Now that sub_515C4 is confirmed and it only touches those few files, the question is what’s actually wrong. I first hooked snprint with Frida to filter its path concatenation, and found that what matters is /proc/self/task/%s/status. Its detection boils down to three feature strings:
gum-js-loopgmainpool-frida

The first two are definitely replaced when compiling a feature-stripped Frida. The only suspect is pool-frida — but I couldn’t find that exact string in the Frida source or the generated binary. Not convinced, I turned to Frida’s Stalker to verify whether it really is the trigger.
Start following on sub_515C4’s onEnter, and unfollow on exit to minimize overhead and instability:
Interceptor.attach(base.add(0x515C4), {
onEnter: function (args) {
DMLog.i("hook_515C4", "onEnter");
// FCAnd.showNativeStacks(this.context);
function logX3(context: any) {
console.log('x3:', context.x3.readInt());
}
Stalker.follow(this.tid, {
events: {
call: false,
ret: false,
exec: true,
block: false,
compile: false
},
transform: function (iterator: StalkerArm64Iterator) {
var ins = iterator.next();
if (ins == null) return;
if (ins.address.sub(base) > ptr(0x00000000000515C4)
&& ins.address.sub(base) < ptr(0x000000000005207C)) {
// if (ins.address.sub(base).compare(ptr(0x52074)) == 0) {
// // console.log(`==== 51afc fd: `);
// // iterator.putCallout(logX3);
// DMLog.i("0x52074", "sleep");
// Thread.sleep(9999);
// }
console.log(`${ins.address.sub(base)} ${ins}`);
}
iterator.keep();
while (iterator.next() != null) iterator.keep();
}
});
},
onLeave: function (retval) {
DMLog.i("hook_515C4", "onLeave1111======: " + retval);
if (retval.toInt32() == 1) {
DMLog.i("hook_515C4", "sleep x3: " + (this.context as Arm64CpuContext).x3);
Thread.sleep(9999);
}
retval.replace(ptr(0));
Stalker.unfollow(this.tid);
}
})
The trace output:
Cross-reference with the key branch addresses from IDA. We already suspected the
pool-fridacheck, so once the trace is out, you don’t need to read it line by line — just search whether the addresses involved in thepool-fridalogic got printed.
......
0x302666770 cbz x0, #0x71d69bb788
0x302666774 ldaxr x21, [x0]
0x302666780 stlxr w8, x22, [x0]
0x302666780 stlxr w8, x22, [x0]
0x302666774 ldaxr x21, [x0]
0x302666788 ldp x20, x19, [sp, #0x20]
0x30266484c add x0, x19, #0x18
0x3026c8d20 adrp x16, #0x71d6a22000
0x3026c51a8 movi v0.2d, #0000000000000000
0x3026c51b8 strh wzr, [x0]
0x302664860 b #0x71d69b9868
0x302664868 mov x0, x19
0x51a20 cbz x0, #0x6ed43a7014
0x51a24 mov x27, x0
0x51a38 mov x0, x27
0x4440 adrp x16, #0x6ed43c6000
0x302664878 stp x29, x30, [sp, #-0x30]!
0x30266488c add x19, x0, #0x18
0x3026c8740 adrp x16, #0x71d6a22000
0x3026c5238 stp x29, x30, [sp, #-0x10]!
0x3026c5250 and x8, x8, #0x2000
0x3026c5264 stxrh w10, w9, [x0]
0x3026c5264 stxrh w10, w9, [x0]
0x3026c5258 ldaxrh w10, [x0]
0x3026c526c mov w0, wzr
0x30266489c ldr x8, [x20, #8]
0x3026648cc add x21, x20, #0x40
0x3026af860 mov x8, #0x3d
0x3026af874 ret
0x3026648e4 cmn w0, #1
0x302664900 cmp w0, #1
0x302664908 mov w8, w0
0x3026648a8 ldrh w9, [x21, #0x10]
0x302664918 mov x0, x19
0x3026c8780 adrp x16, #0x71d6a22000
0x3026c5740 stp x29, x30, [sp, #-0x30]!
0x3026c5764 orr w9, w8, #2
0x3026c5764 orr w9, w8, #2
0x3026c5774 cmp w10, w9
0x3026c586c mov w0, wzr
0x302664920 mov x0, x21
0x51a40 cbz x0, #0x6ed43a700c
0x51a44 movi v0.2d, #0000000000000000
0x51ac0 add x9, x9, #1
0x51ab0 ldrb w10, [x8]
0x51acc b #0x6ed43a6a38
0x3026c5250 and x8, x8, #0x2000
0x3026648a4 ldr x21, [x20, #0x10]
0x51ad0 adrp x9, #0x6ed43af000
0x51aec add x9, x9, #1
0x51adc ldrb w10, [x8]
0x51af8 b #0x6ed43a6a38
0x51afc adrp x2, #0x6ed43af000
0x4020 adrp x16, #0x6ed43c6000
0x3026bee88 stp x29, x30, [sp, #-0x20]!
0x3026bef00 add x10, sp, #0x118
0x3026a03d0 stp x29, x30, [sp, #-0x60]!
0x3026c8550 adrp x16, #0x71d6a22000
0x302661ff0 mrs x8, tpidr_el0
0x3026a040c movi v2.2d, #0000000000000000
0x3026a047c ldr w9, [x8, #0x30]
0x3026a0498 mov w9, #-1
0x3026a048c ldr x8, [x19, #0x18]
0x3026a04cc mov w8, #0x1a
0x3026a05cc add x9, x22, #0x18
0x3026a0688 cmp w8, #0x25
0x3026a0690 add x9, x9, #1
0x3026a069c add x10, x28, x9
0x3026a06a4 subs x9, x10, x28
0x3026a06ac sub w11, w20, w26
0x3026a06b8 ldr x23, [sp, #0x90]
0x3026a06e4 mov w15, wzr
0x3026a0720 adrp x11, #0x71d698d000
0x3026a0e4c mov w21, w15
0x3026a0e58 ldr x8, [sp, #0x1a0]
0x3026a1340 ldr x8, [sp, #0x50]
0x3026a134c ldr x10, [sp, #0x50]
0x3026a1360 ldr x9, [sp, #0x20]
0x3026a14e4 ldr x8, [x8]
0x3026a1548 str w27, [sp, #0xb4]
0x3026a1568 mov x0, x25
0x3026c8580 adrp x16, #0x71d6a22000
0x30265e870 and x4, x0, #0xfff
0x30265e880 ldp x2, x3, [x0]
0x30265e8a4 csel x4, x4, x5, lo
0x3026a1570 mov x26, x0
0x3026a157c str x20, [sp, #0xc0]
0x3026a1cbc ldrb w9, [sp, #0x1fc]
0x3026a1cfc ldr w8, [sp, #0xcc]
0x3026a1e14 mov w21, w24
0x3026a1e94 ldrb w8, [sp, #0x1ad]
0x3026a1f08 cmp w20, #0x80
0x3026a2094 cmp w27, #1
0x3026a2008 ldr w22, [sp, #0xb4]
0x3026a2010 ldr x8, [sp, #0x1c0]
0x3026a2074 ldr w26, [sp, #0xac]
0x3026a29ec ldr w21, [sp, #0xcc]
0x3026a2af0 cmp w21, w24
0x3026a2b08 ldr x9, [sp, #0x1c0]
0x3026a2b14 add x1, sp, #0x1b0
0x3026ab9b8 stp x29, x30, [sp, #-0x60]!
0x3026ab9dc ldr w8, [x0, #0x10]
0x3026ab9ec ldr x9, [x19, #0x18]
0x3026aba1c ldr x9, [x20]
0x3026aba2c tbnz w8, #0, #0x71d6a00c04
0x3026aba30 mov w25, #0x4200
0x3026aba8c cbnz x21, #0x71d6a00aa0
0x3026abaa0 ldp w23, w8, [x19, #0xc]
0x3026abb14 tbnz w8, #9, #0x71d6a00a38
0x3026aba38 cmp x21, w23, sxtw
0x3026c8500 adrp x16, #0x71d6a22000
0x30265e000 sub x5, x0, x1
0x30265de90 prfm pldl1keep, [x1]
0x30265dee0 cmp x2, #8
0x30265dee8 ldr x6, [x1]
0x3026aba54 ldr w8, [x19, #0xc]
0x3026aba90 ldr x21, [x26, #8]
0x3026aba9c ldur x22, [x26, #-0x10]
0x30265df00 tbz w2, #2, #0x71d69b2f18
0x30265df04 ldr w6, [x1]
0x3026abbfc mov w0, wzr
0x3026abd50 ldp x20, x19, [sp, #0x50]
0x3026a2b20 mov w14, #0x10
0x3026a2b30 str wzr, [sp, #0x1b8]
0x3026a067c mov x9, xzr
0x3026a06d8 b #0x71d69f7d58
0x3026a2d58 cbz x9, #0x71d69f7d74
0x3026a2d5c add x1, sp, #0x1b0
0x3026a2d68 str xzr, [sp, #0x1c0]
0x3026a2d74 str wzr, [sp, #0x1b8]
0x3026c8850 adrp x16, #0x71d6a22000
0x30264fb08 stp x29, x30, [sp, #-0x10]!
0x30264fb44 cbnz x8, #0x71d69a4b50
0x30264fb48 ldp x29, x30, [sp], #0x10
0x302658580 mov x1, x0
0x3026585a0 sub sp, sp, #0x40
0x3026585d0 adrp x8, #0x71d6a22000
0x3026585dc cbnz x22, #0x71d69ad608
0x3026585e0 b #0x71d69ad6a4
0x3026586a4 ldp x20, x19, [sp, #0x30]
0x3026a2d90 ldr x0, [sp, #0xb8]
0x3026a2d9c ldr x0, [sp, #0x1a0]
0x3026a2dbc ldr x8, [x25, #0x28]
0x3026a2dcc mov w0, w22
0x3026bef9c ldr x8, [sp, #0x118]
0x3026befb4 add sp, sp, #0x230
0x51b10 mov w8, #0x38
0x51b2c add x8, sp, #0x370
0x51b60 strb wzr, [x8], #1
0x51b6c strb wzr, [x8], #1
0x51b78 strb wzr, [x8], #1
0x51b54 strb wzr, [x8], #1
0x51b84 mov x10, xzr
0x51ba8 ldrb w8, [sp, #0x270]
0x51bb4 strb w8, [x23, x10]
0x51b8c mov w8, #0x3f
0x51bc4 ldr x8, [sp, #0x20]
0x51bd0 ldurb w10, [x8, #-5]
0x51bdc cbnz w10, #0x6ed43a6bcc
0x51bcc add x8, x8, #1
0x51be0 b #0x6ed43a6c60
0x51c60 ldr x8, [sp, #0x18]
0x51c6c ldurb w10, [x8, #-2]
0x51c78 cbnz w10, #0x6ed43a6c68
0x51c68 add x8, x8, #1
0x51c7c b #0x6ed43a6cb4
0x51cb4 ldr x8, [sp, #0x10]
0x51cc0 ldurb w10, [x8, #-4]
0x51ccc cbnz w10, #0x6ed43a6cbc
0x51cbc add x8, x8, #1
0x51cd0 b #0x6ed43a6a2c
0x51a2c mov w8, #0x39
0x51be4 ldurb w10, [x8, #-4]
0x51c80 ldurb w10, [x8, #-1]
0x51cd4 ldurb w10, [x8, #-3]
0x51ce0 ldurb w10, [x8, #-2]
0x51cec ldurb w10, [x8, #-1]
0x51cf8 ldrb w10, [x8]
0x51d04 ldrb w10, [x8, #1]
0x51d10 ldrb w10, [x8, #2]
0x51d1c ldrb w10, [x8, #3]
0x51d28 ldrb w10, [x8, #4]
0x51d34 ldrb w10, [x8, #5]
0x51d40 b #0x6ed43a7070
0x52070 ldr x9, [sp, #0x38]
0x52040 ldr x8, [x9, #0x28]
0x52050 add sp, sp, #0x580
0x305eb050c ldr x17, #0x71da205518
0x30476a14c sub sp, sp, #0x10
[INFO][05/07/2024, 07:52:08 PM][PID:18959][Thread-17][19001][hook_515C4]: onLeave1111======: 0x1
Now I have the function’s execution path. The first key point: 0x51afc.

This confirms it goes through the status-related check.
The second key point: 0x51D34.

This confirms it’s checking pool-frida. That’s the one. But the string wasn’t found in the source or the generated binary earlier — which means the string must be concatenated at runtime.
In the end it turned out to be pool-%s — concatenated into pool-frida.
By the way, the pool-frida thread only exists at process startup as a temporary thread; once the Frida environment is ready, the thread exits.
Now that we know the problem, the fix is trivial: replace that string in our Frida anti-detection script.
Back to happily hooking away. :)
Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).
Get in touch