Introduction
Some time ago, I stumbled upon an interesting iOS banking application whose name shall not be disclosed (for the better). Unsurprisingly, the app immediately crashed on my checkra1n-jailbroken iPhone 6 on iOS 12.5.5. Because why would the developers of the banking app want reverse engineers to analyze their Holy Grail?
Curiously, I opened the decrypted app binary in IDA and searched for the usual culprits in these scenarios: “Cydia”, “Sileo”, “cycript”, “/bin/bash”, etc. To my surprise, IDA found nothing. This usually means:
a) (Important) strings like this in the binary are encrypted/obfuscated in a more or less sophisticated manner (ranging from simple XOR stuff to intricate shenanigans in the stack) and/or
b) The jailbreak detection is complex and does not rely on simple file-based checks with functions like fileExists
: fileExists
System Calls
A quick check confirmed my assumption: The strings are obfuscated and the jailbreak checks are pretty intricate. I’ve spent many hours in the IDA debugger and looked at confusing ARM assembly code. After some time, I noticed something peculiar: There are a lot of system calls (syscalls) in the app identified by SVC 0x80
instructions (hex: 0x011000D4 –> https://armconverter.com/?code=svc%20%230x80)
That’s when I discovered this amazing article on theiphonewiki.com:
The gist is the following: Syscall numbers on iOS go into the x16 register on ARM64. Then the SVC 0x80
instruction triggers a low-level CPU exception handler handling the syscall. Let me quote theiphonewiki: “This handler can check the syscall number to distinguish between POSIX calls (non-negative) and Mach traps (negative).” Yes, there are negative syscall numbers on iOS.
Given this new information, I lacked another crucial piece of information, however. Where do the syscall arguments go? The answer to this question is given in this amazing reply by s1guza on StackOverflow: StackOverflow Reply
x0 through x8 hold up to 9 arguments
stat64
There we go. Knowing this, I dug deeper into the binary and found the extremely convoluted, obfuscated function that apparently caused the crash. In there was a syscall with the number 0x152 (338) and two arguments that went into the x0
and x1
registers. The argument x0
was constructed in a really complex manner, but I didn’t care at all because dynamic analysis with LLDB and IDA showed the string that went into x0
: “/private/var/lib/apt”. Gotcha! Syscall #338 corresponds to stat64, by the way. Therefore, the banking app checks the existence of /private/var/lib/apt
.
There were several other checks with the same logic. Obviously, with the new knowledge, it was trivial to patch these syscall-based jailbreak checks. On that day, I noticed several other apps do the same: They devilishly check for a jailbreak with hidden system calls. That’s why I looked for some sort of syscall tracer for iOS on GitHub and found several repos:
frida-iOS-syscall-tracer
All of these tools, however, do not print out useful information (actual syscall name, arguments, etc.), so I decided to write my own Frida-based syscall tracer for iOS. Frida is a Swiss Army knife when it comes to reverse engineering and severely underrated in the RE community in my opinion. SSL pinning bypasses, root/jailbreak detection bypasses, and dumping are just a few examples of how Frida could be used: Frida Code Share
Check out the frida-iOS-syscall-tracer here: juliangrtz/frida-iOS-syscall-tracer