juliangrtz.me

frida-iOS-syscall-tracer

Thu Jan 11, 2024

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