Neso Lab’s AppMinder project is another attempt at providing jailbreak detection for enterprise iOS applications (and perhaps AppStore apps). It provides three variants of jailbreak detections codes, each with an increasing level of self integrity checks and code obfuscation, and optionally including anti-debugging checks. It generates a piece of code with a function containing inline assembly code which can be inserted into the app’s source code and called where jailbreak detection needs to be implemented. The generated code is metamorphic with a random function name. Metamorphism is achieved by a combination of register interchanges, instruction reordering, inserting dead code such as unused push
, pop
and cmp
instructions.
The code can be made a little more readable using cat jbdetect.c | tr ';' '\n'
.
The code makes use of svc 0x80
instruction throughout to invoke system calls. svc 0x80
is the ARM counterpart of Intel’s int 0x80
instruction, used to invoke system calls. In iOS, which is a derivative of Darwin, the system call number is passed in r12 and the arguments in r0-r3. The return value is optionally returned in r0. svc
also goes by the name of swi
or software interrupt.
Debugger detection
Process tracing utilities and debuggers make use of ptrace
system call to trace a running process. If the ‘Anti-debugging option’ was selected, the code tries to detect and deny any debugging attempts. It does this by invoking ptrace
(system call 26) on the current process with an argument of 31, i.e. PT_DENY_ATTACH
. Quoting from the man page:
The process is killed if a debugger is detected. Additionally, debuggers are denied to trace the process in any future requests. gdb shows a “Operation not permitted” message if it tries to attach to a process which has called ptrace
with PT_DENY_ATTACH
.
A complete list of system calls can be found in sys/syscall.h.
Jailbreak Detection
The code detects jailbreaks using fork
. On non-jailbroken iOS, the sandbox restricts the use of fork
. Calling fork
fails with a return value of 1. However, on a jailbroken iOS, fork
succeeds and spawns a new process. The code terminates the process using exit(1)
if fork
does not return 1.
The code does not do anything new, and does not even conceal its presence. It can be easily identified in memory by simply looking for svc 0x80
instructions, which assemble to 80 00 00 EF
. Given the fact that jailbreak detection is inherently self defeating, it becomes trivial to bypass the above checks.