Bypassing the New iQIYI libmsaoaidsec.so 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.
Bypassing the New iQIYI libmsaoaidsec.so Frida Detection
1. Environment
- Phone: Google Pixel 6, AOSP Android 13
- CPU architecture: Arm64
- Frida: 16.1.10
- iQIYI: 15.2.5
2. The problem
I’ve been digging into some of iQIYI’s internals lately. The moment I attach Frida, I hit an anti-Frida check — running frida -l index.js -U -f com.qiyi.video makes the process restart. Below is my analysis of that detection and how I bypassed it with a hook.
3. Analysis
Hook do_dlopen to see which .so is loading when the crash happens. First, resolve do_dlopen’s relative offset in linker64.
Hook script:
function hookDlopen() {
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
let android_dlopen_ext = linker64_base_addr.add(offset)
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){
this.name = args[0].readCString()
Log.log(`dlopen onEnter ${this.name}`)
}, onLeave: function(retval){
Log.log(`dlopen onLeave name: ${this.name}`)
if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) {
let JNI_OnLoad = Module.getExportByName(this.name, 'JNI_OnLoad')
Log.log(`dlopen onLeave JNI_OnLoad: ${JNI_OnLoad}`)
}
}
})
}
Running frida -l index.js -U -f com.qiyi.video

The last .so loaded is libmsaoaidsec.so, and onLeave never fires — so the crash lives inside libmsaoaidsec.so, the check runs before JNI_OnLoad, and it’s almost certainly in an init_array constructor.
Since the process dies the moment Frida attaches, let’s figure out which syscall exits it. strace will show the syscalls, but we need the process to sleep 10 seconds before libmsaoaidsec.so loads so we have time to attach strace.
function hookDlopen() {
// get libc's sleep function
let sleep: Function = new NativeFunction(Module.getExportByName('libc.so', 'sleep'), 'uint', ['uint'])
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
let android_dlopen_ext = linker64_base_addr.add(offset)
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){
this.name = args[0].readCString()
Log.log(`dlopen onEnter ${this.name}`)
if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) {
sleep(10) // sleep 10s to leave time to attach strace
}
}, onLeave: function(retval){
Log.log(`dlopen onLeave name: ${this.name}`)
}
})
}
Re-run frida -l index.js -U -f com.qiyi.video

Grab the pid and immediately run strace -e trace=process -i -f -p 15534; after 10 seconds:

Look at this line — [pid 15576] [000000751926c008] exit_group(0 <unfinished ...>. Thread 15576 calls exit_group at address 0x751926c008. Checking proc/15534/maps, libc.so spans 0x7509b9c000 - 0x7509c70000, so 0x751926c008 is clearly not inside libc.so — the exit_group code was dynamically freed.
Dynamically freed code must touch memory somehow. Using the same trick, let’s strace the memory-related syscalls.
strace -e trace=process,memory -i -f -p 25947

Note this line: [pid 25990] [0000007509c48ab8] mmap(NULL, 28, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x751926c000
The mmap call returns 0x751926c000 — which matches the address right before exit_group (000000751926c008).
The mmap address tells us the call came from inside libc.so, so let’s hook mmap and dump the call stack.
function hook_mmap() {
const mmap = Module.getExportByName("libc.so", "mmap");
Interceptor.attach(mmap, {
onEnter: function (args) {
let length = args[1].toString(16)
if (parseInt(length, 16) == 28) {
Log.log('backtrace:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
}
}
})
}
function hookDlopen() {
let sleep: Function = new NativeFunction(Module.getExportByName('libc.so', 'sleep'), 'uint', ['uint'])
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
let android_dlopen_ext = linker64_base_addr.add(offset)
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){
this.name = args[0].readCString()
Log.log(`dlopen onEnter ${this.name}`)
if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) {
// sleep(10)
hook_mmap()
}
}, onLeave: function(retval) {
Log.log(`dlopen onLeave name: ${this.name}`)
}
})
}
The call stack:

Open libmsaoaidsec.so in IDA and look at the function at 0x2358c

The C pseudocode confirms it — the code is being freed and then executed:

IDA shows the function at 0x1bdcc, void sub_1B924(), is where the detection logic lives. I won’t rehash the detection itself here — worth exploring on your own.

4. The solution
The fix is to replace that void function void sub_1B924().
Since the detection runs inside an .init_* function, we first need the right hooking moment. Looking at AOSP source, linker executes init_array functions through call_constructors:
void soinfo::call_constructors() {
if (constructors_called) {
return;
}
constructors_called = true;
if (!is_main_executable() && preinit_array_ != nullptr) {
// The GNU dynamic linker silently ignores these, but we warn the developer.
PRINT("\"%s\": ignoring %zd-entry DT_PREINIT_ARRAY in shared library!",
get_realpath(), preinit_array_count_);
}
get_children().for_each([] (soinfo* si) {
si->call_constructors();
});
TRACE("\"%s\": calling constructors", get_realpath());
// DT_INIT should be called before DT_INIT_ARRAY if both are present.
call_function("DT_INIT", init_func_);
call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false);
}
So hook call_constructors directly and replace sub_1b924 in onEnter.
Resolve call_constructors’s offset in linker64.
5. Final code
function hookDlopen() {
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
let android_dlopen_ext = linker64_base_addr.add(offset)
if (android_dlopen_ext != null) {
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){
this.name = args[0].readCString()
if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) {
hook_linker_call_constructors()
}
}, onLeave: function(retval){
Log.log(`dlopen onLeave name: ${this.name}`)
if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) {
let JNI_OnLoad = Module.getExportByName(this.name, 'JNI_OnLoad')
Log.log(`dlopen onLeave JNI_OnLoad: ${JNI_OnLoad}`)
}
}
})
}
}
function hook_linker_call_constructors() {
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x521f0 // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset)
let listener = Interceptor.attach(call_constructors, {
onEnter: function (args) {
Log.log('hook_linker_call_constructors onEnter')
let secmodule = Process.findModuleByName("libmsaoaidsec.so")
if (secmodule != null) {
hook_sub_1b924(secmodule)
listener.detach()
}
}
})
}
function hook_sub_1b924(secmodule) {
Interceptor.replace(secmodule.base.add(0x1b924), new NativeCallback(function () {
Log.log(`hook_sub_1b924 >>>>>>>>>>>>>>>>> replace`)
}, 'void', []));
}
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