Because I use Shadowrocket daily, every time I open a certain car rental app, it prompts that the phone has a proxy enabled, and it crashes directly on a jailbroken device. How can I bypass the appâs proxy and jailbreak detection?
Difficulty Using Shadowrocket
â â âââ
Tools and Environment: Shadowrocket
- Jailbroken iOS 14.4
- frida-ios-dump
- frida
- frida-trace
- IDA 7.7
Shadowrocket IDA Reverse Analysis
By dumping the appâs maco file, we can directly analyze it in IDA. Since the app performs jailbreak detection, we first search for functions related to âjailâ.

There are indeed functions for jailbreak detection, which determine if the device is jailbroken by checking for the existence of specific files and directories. âCydia.appâ is a common app store on jailbroken devices, while â/bin/bashâ and â/usr/sbin/sshdâ indicate that the device has full file system access.

The detection process is implemented using the NSFileManagerâs fileExistsAtPath: method. If any of the files or directories are detected, the function returns 1, indicating the device is jailbroken. If none are found, the function returns 0, indicating the device is not jailbroken.
âUsing frida-trace to Hook Jailbreak Functions with Shadowrocketâ
At this point, you might think, why not just use frida to hook the jailbreak detection function +[_priv_NBSProbe isJailBreak]
to bypass it?
Initially, I thought the same, so I used frida-trace to hook it.
frida-trace -U -f com.szzc.szzc -m "+[_priv_NBSProbe isJailBreak]"
Modify the hook code as follows, replacing the function return value with 0 to bypass the detection.
{ onEnter(log, args, state) { log(`Entering +[_priv_NBSProbe isJailBreak] detection function`); log(args[0]); }, onLeave(log, retval, state) { log('Exiting +[_priv_NBSProbe isJailBreak] detection function'); retval.replace(0); } }

The program exits before entering this function, indicating that the app has other detection functions that execute and exit before this one.
Locate Program Exit Stack
How to locate the stack when the program exits? You can use frida-trace to hook the systemâs exit or abort functions.
frida-trace -U -i "exit" -i "abort" -f com.szzc.szzc
Add the following exit js code:
{ onEnter(log, args, state) { log(`exit(status=${args[0]})`); log('exit() called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); }, onLeave(log, retval, state) { } }

frida prints the stack when the program exits, showing it exits at offset address 0x275d4b8.
IDA Analysis of Program Exit Stack
Here is an explanation of the three addresses printed by frida.
0x1047994b8 WCCApp!0x275d4b8 (0x10275d4b8)
- 0x1047994b8 is the actual memory address of the program in memory. Due to Address Space Layout Randomization (ASLR), the addresses seen during runtime are offsets relative to the programâs base address, which changes with each run.
- 0x275d4b8 is the offset address of the function relative to the base address.
- 0x10275d4b8 is the address of the function in IDA, as IDAâs default base address is 0x100000000, so 0x10275d4b8 = 0x100000000 + 0x275d4b8
In IDA, go to the address 0x10275d4b8.

It is not the code logic for determining exceptions. Go to the previous stack layer at 0x100007464.

There are dozens of judgment logics here, including jailbreak and proxy detection, suggesting that all detections are integrated and executed sequentially. From the strings, it can be inferred that the app integrates the iJiami SDK.
frida Pitfalls
So, should we directly hook the address 0x7464?
frida -U -l wcc.js -f com.szzc.szzc
wcc.js code is as follows:
var baseAddr = Module.findBaseAddress('WCCApp'); var offsetAddr = 0x7464 // 0x100007464 var targetAddr = baseAddr.add(offsetAddr); console.log("WCCApp base address: " + baseAddr); console.log("Target function address: " + targetAddr); Interceptor.attach(targetAddr, { onEnter: function(args) { console.log("Function hook successful!"); console.log(' called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); this.skip = true; }, onLeave: function(retval) { console.log("Function execution finished."); } });

Why doesnât frida print the stack information when executed?

frida needs to hook the offset address of the function name and then add it to the base address, not the internal offset address of the function.

Why doesnât hooking with the offset address provided by frida work?
Check the cross-references of the function.


I suspect the upper-level function calls use asynchronous methods.
Cross-Reference to Locate Outer Function Calls

Further up, IDA can identify this as an objc method.

Bypass Jailbreak Detection
At this point, the approach is clear. Use frida to hook this function and replace sub_100007008 with an empty function. Rewrite the frida script as wcc_jail.js
var baseAddr = Module.findBaseAddress('WCCApp'); console.log("WCCApp base address: " + baseAddr); //0x7008 is the offset address of the sub_100007008 jailbreak detection function var targetFunctionAddr = baseAddr.add(0x7008); console.log("Target function address: " + targetFunctionAddr); const targetFunction = new NativeFunction(targetFunctionAddr, 'void', []); // Replace sub_100007008 with an empty function Interceptor.replace(targetFunctionAddr, new NativeCallback(function () { console.log("Skip the execution of sub_100007008"); }, 'void', [])); //0x8A24 is the offset address of -[RootViewController viewDidLoad] const targetFunctionAddrRootVC = baseAddr.add(0x8A24); Interceptor.attach(targetFunctionAddrRootVC, { onEnter: function (args) { console.log('Entering -RootViewController viewDidLoad!'); }, onLeave: function (retval) { console.log('Leaving -RootViewController viewDidLoad!'); } });
Execute the command as follows:
frida -U -l wcc_jail.js -f com.szzc.szzc
