InsecuriOS Labs - Jailbreak Detection (Objc Implementation)
This is a challenge about Jailbreak detection.
Initial Analysis
Challenge uses the isJailbrokenWithCompletion: method in JailbreakObjcChecker:
The verification logic performs multiple checks and combines results:
What it does:
Calls multiple jailbreak detection methods:
checkURLSchemes: Checks for Cydia/Sileo URL schemescheckSuspiciousFiles: Checks for jailbreak-related filescheckWritableDirectories: Checks if system directories are writablecheckSymbolicLinks: Checks for suspicious symbolic linkscheckOpenSystemFiles: Checks if system files can be openedcheckForJailbreakTweaks: Checks for loaded jailbreak tweaks
Combines all results using OR operations into w8 register
If any check returns 1, the final result is 1 (jailbroken)
Passes the boolean result to completion callback
LLDB Solution (Step by Step)
1. Start debugserver on device
# ssh into your device and run:
./debugserver 0.0.0.0:1337 -w "InsecuriOS Labs"
# on your mac terminal connect LLDB:
lldb
(lldb) process connect connect://<iPhone_IP>:1337
# example: process connect connect://192.168.1.100:1337
2. Find the jailbreak detection method
Using LLDB image lookup
(lldb) image lookup -r -n isJail
# look for JailbreakObjcChecker.isJailbrokenWithCompletion
# example output:
# Address: InsecuriOS Labs[0x0000000102778a08] (InsecuriOS Labs.__TEXT.__text + XXXXX)
# Summary: InsecuriOS Labs`+[JailbreakObjcChecker isJailbrokenWithCompletion:]
Important: Note the address in brackets [0x0000000102778a08], this is the file offset.
3. Calculate ASLR and set breakpoint
ASLR (Address Space Layout Randomization) means the binary is loaded at a random address each time. The runtime address needs to be calculated.
# step 1: get the ASLR slide
(lldb) p/x (uintptr_t)&_mh_execute_header - 0x100000000
# example output: (uintptr_t) 0x0000000000594000
# step 2: calculate runtime address (file_offset + ASLR slide = runtime_address)
# example: 0x0000000102778a08 + 0x0000000000594000 = 0x0000000102d0ca08
# ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
# file_offset ASLR slide
(lldb) p/x 0x0000000102778a08 + 0x0000000000594000
# Output: (long) 0x0000000102d0ca08
# step 3: set breakpoint at the calculated address
(lldb) br s -a 0x0000000102d0ca08
# example output:
# Breakpoint 1: where = InsecuriOS Labs`+[JailbreakObjcChecker isJailbrokenWithCompletion:], address = 0x0000000102d0ca08
The breakpoint summary should show isJailbrokenWithCompletion: method.
4. Inspect the method
Now it's necessary to understand what this method does.
Continue app execution
(lldb) c
Go to the app and trigger the jailbreak check.
The debugger will stop at isJailbrokenWithCompletion:. Now inspect it:
(lldb) disas
The output will show assembly code. Look for:
0x102778a34 <+44>: bl 0x10288c300 ; objc_msgSend$checkURLSchemes
0x102778a40 <+56>: bl 0x10288c2c0 ; objc_msgSend$checkSuspiciousFiles
0x102778a4c <+68>: bl 0x10288c320 ; objc_msgSend$checkWritableDirectories
0x102778a58 <+80>: bl 0x10288c2e0 ; objc_msgSend$checkSymbolicLinks
0x102778a64 <+92>: bl 0x10288c2a0 ; objc_msgSend$checkOpenSystemFiles
0x102778a70 <+104>: bl 0x10288c280 ; objc_msgSend$checkForJailbreakTweaks
These are the 6 jailbreak detection checks being called.
After the checks, the results are combined:
0x102778a74 <+108>: orr w8, w0, w25 ; w8 = tweaks OR openSystemFiles
0x102778a78 <+112>: orr w9, w24, w23 ; w9 = symlinks OR writableDirs
0x102778a7c <+116>: orr w8, w8, w9 ; w8 = w8 OR w9
0x102778a80 <+120>: orr w9, w22, w21 ; w9 = suspiciousFiles OR urlSchemes
0x102778a84 <+124>: orr w8, w8, w9 ; w8 = all checks combined
0x102778a8c <+132>: and w1, w8, #0x1 ; w1 = w8 & 1 (boolean for callback)
The and w1, w8, #0x1 instruction is the key point - this is where the final boolean is prepared for the completion callback.
5. Set breakpoint before the callback
# set breakpoint at the 'and' instruction (before callback)
(lldb) br s -a 0x102778a8c
# example output:
# Breakpoint 2: address = 0x102778a8c
6. Add commands to breakpoint
# add commands to automatically bypass the check
(lldb) breakpoint command add 2
# enter the following commands:
register write w8 0
continue
DONE
Explanation:
register write w8 0: Sets w8 to 0, soand w1, w8, #0x1results inw1 = 0(not jailbroken)continue: Continues executionDONE: Finishes command entry
Why w8?
Each check returns its result in x0, which gets saved to separate registers (x21-x25). After all checks complete, the results are combined using OR operations into w8. If any check detected jailbreak (returned 1), w8 will be 1.
By zeroing w8 before the and instruction, we ensure w1 = 0, and the completion callback receives false (not jailbroken).
7. Continue execution and trigger the bypass
(lldb) c
Now go to the app and trigger the jailbreak check again.
What will happen:
- Breakpoint 1 (
isJailbrokenWithCompletion:) will be hit - typecto continue - Breakpoint 2 (before callback) will be hit
- The automated commands will execute (
register write w8 0+continue) - The completion callback receives
false
8. Result
The app will continue executing and the jailbreak check will return false (not jailbroken).
Understanding the Assembly Flow
Register usage during checks
| Check Method | Return Register | Saved To |
|---|---|---|
| checkURLSchemes | x0 | x21 (w21) |
| checkSuspiciousFiles | x0 | x22 (w22) |
| checkWritableDirectories | x0 | x23 (w23) |
| checkSymbolicLinks | x0 | x24 (w24) |
| checkOpenSystemFiles | x0 | x25 (w25) |
| checkForJailbreakTweaks | x0 | stays in w0 |
OR operations flow
w8 = w0 OR w25 (tweaks OR openSystemFiles)
w9 = w24 OR w23 (symlinks OR writableDirs)
w8 = w8 OR w9 (combine)
w9 = w22 OR w21 (suspiciousFiles OR urlSchemes)
w8 = w8 OR w9 (final result: all checks combined)
Key insight
The and wX, wY, #0x1 pattern at the end of boolean checks is a common target for bypasses. It's the "funnel" where all verification logic converges before returning/calling callback.
LLDB Commands Reference
Short commands used
| Short command | Full Command | Description |
|---|---|---|
b <name> | breakpoint set -n <name> | Set a breakpoint on function name |
br s -a <addr> | breakpoint set -a <addr> | Set a breakpoint at memory address |
br com a <num> | breakpoint command add <num> | Add automated commands to breakpoint |
c | continue | Continue program execution |
disas | disassemble | Disassemble current function |
p/x <expr> | print/x <expr> | Print expression in hexadecimal |
re read <reg> | register read <reg> | Read specific register values |
re write <reg> <val> | register write <reg> <val> | Write value to register |
Other commands
| Command | Description |
|---|---|
image lookup -r -n <pattern> | Search for symbols by regex pattern |
breakpoint list | List all breakpoints |
breakpoint delete <num> | Delete specific breakpoint |