I've been teaching iOS Application Security and Auditing to pentesters and developers (secure programming guidelines) online / real world and one of the questions which always comes up is can Anti-Piracy measures work if implemented in the application? Pentesters want to know if they could run into problems with applications implementing runtime protections. Developers on the other hand, want to know if they can sleep well if they implement such protections.
The short answer is NO, if your code runs on a platform controlled by the attacker, and if he is skilled enough, he would eventually figure out how to subvert your protection. This is especially true for a Jailbroken device where the attacker can pretty much run anything.
I can already see pentesters smiling :) If you know how to do runtime analysis using Cycript and GDB, then you should be able to subvert most protections. However, as this is significantly different from other application pentests (web and network) and involves a component of reverse engineering an application on the ARM platform, this might get interesting and challenging!
This blog post is the first in the series I am planning to talk about the common techniques used by developers today to check for jailbreaking and how an attacker could subvert them.
In order to try things out and we need a sample application! :) I've created a simple AntiPiracyDemo Application for iOS which I use for my online iOS course. You can download the IPA here. Please note that this is a self-signed application and would require a Jailbroken device (iPhone / iPad) to run.
You can install the application using installipa as shown below:
The application has been tested on iOS 5.1.1 and 6.1.2. Once you run the application, you can confronted with a simple screen to check for Jailbroken state:
Clicking on the button, confirms this application is running on a Jailbroken phone. The developer of a real world application can now exit or send a report (privacy violation? :) ) back to his server to notify.
Objective: To bypass the is Jailbroken check implemented by the iOS Application
Step 1: Find the application PID and the directory in which it is installed. This is easy to do using "ps" along with a "grep" for the application name
Step 2: Go to the Application directory and locate the actually application binary
Step 3: Native iOS applications are written in Objective-C which is a dynamically typed language. This requires that all the class information be available at runtime and hence is embedded into the binary. We can extract this class information using a tool called class-dump-z as show below:
Step 4: View the class information file - there is a ton of information in there! :)
Step 5: We need to find the rootViewController for the current window. This can be done using a tool called Cycript, which uses Mobile Substrate to hook into any running application. We can find the current rootViewController as below:
Step 6: Lets go back to the out of class-dump-z in Step 4 and find the "@interface" section for AntiPiracyViewController
Step 7: We see a "checkPiracy" method and more interestingly - we see a method called "isJailbroken" which returns a BOOL and takes no inputs which probably means this checks for a jailbroken state.
We can use 2 different techniques to bypass this protection --
1. Runtime Modification using GDB
2. Method Swizzling using Cycript
Let's take up Runtime Modification using GDB first
Step 1: Attach GDB to AntiPiracyDemo using the PID
Step 2: Set a Breakpoint for the isJailbroken
Step 3: Continue running the application and then click on the "Am I Pirated" button to see if we hit the breakpoint
Step 4: Disassemble! :) Be Prepared for some unfamiliar looking symbols if ARM Assembly is not your thing :)
Step 5: iOS devices have an ARM based processor and what you are seeing is ARM Assembly. If you are from the x86 world then there is only one thing you need to keep in mind when working with ARM assembly - the arguments are passed via the registers R0, R1, R2, R3. More than 4 arguments are passed on the stack. Here is the ABI document if you are interested.
Step 6: In the disassembly in Step 4, you see a lot of "blx 0x98fe4 <dyld_stub_objc_msgSend>" BLX is "Branch with Link" which basically ends up calling objc_msgSend which has the following definition:
The above is the from Apple Developer site. Objc_msgSend is really the "carrier" of all messages inside an iOS application. Using the ABI we can conclude that:
- theReceiver would be pointed to by R0
- theSelector would be pointed to by R1
- First argument pointed to by R2
Step 7: We could set a breakpoint for objc_msgSend but I would prefer to add breakpoints in all locations its called to better illustrate the concept. So, let's set the breakpoints:
Step 8: Let's continue running the application and let's dump R0/R1 when we hit the Breakpoint 2. This will help us understand the receiver of the message and its respective selector
NSSString alloc is not interesting, Let's repeat the same for other breakpoints. Below is the output when we hit Breakpoints 4 and 5.
Breakpoint 4 tells us that the application is using NSFileManager and Breakpoint 5 tells us it is checking for "FileExistsAtPath:" for "/private/var/lib/apt" Very interesting!
APT is probably is one of the first binaries to be installed on a Jailbroken phone to manage packages from Cydia. Looks like the developer is checking for the presence of this binary.
Step 9: So where do we go from here? The return value is stored in R0 and if you check the documentation of NSFileManager FileExistsAtPath it returns a BOOL. This means "0" will be returned in case the device is NOT Jailbroken and "1" will be returned if it IS Jailbroken.
In our case, as our iPhone is jailbroken, it will return "1". We can verify this by setting a breakpoint in the next line of code and checking the value of R0 as below:
Step 10: The easiest way to subvert this mechanism would be to change the value of R0 from "1" to "0" so that it indicates to the application that the APT does not exists and hence the device is not Jailbroken. We can do this very easily:
Step 11: If we check the Application now - it happily tells us that "This iPhone is NOT Jailbroken"
Of course, we have not patched the check in the binary so you would need do this every time :) I will take up Application Patching in another blog post.
Now let us look at the other technique - Method Swizzling using Cycript
Step 1: Attack to the application using Cycript
The above command should give you a ton of output! You can clearly see isJailbroken is there in it as highlighted. What is really isa.messages?
If you look at the Objective-C runtime implementation, then isa is really a pointer to the class structure itself.
isa is never exposed to the programmer directly but with Cycript we are able to access it. Quoting from Apple's website:
If you’re a procedural programmer new to object-oriented concepts, it might help at first to think of an object as essentially a structure with functions associated with it. This notion is not too far off the reality, particularly in terms of runtime implementation.
Every Objective-C object hides a data structure whose first member—or instance variable—is the isa pointer. (Most remaining members are defined by the object’s class and superclasses.) The isa pointer, as the name suggests, points to the object’s class, which is an object in its own right (see Figure 2-1) and is compiled from the class definition. The class object maintains a dispatch table consisting essentially of pointers to the methods it implements; it also holds a pointer to its superclass, which has its own dispatch table and superclass pointer. Through this chain of references, an object has access to the method implementations of its class and all its superclasses (as well as all inherited public and protected instance variables). The isa pointer is critical to the message-dispatch mechanism and to the dynamism of Cocoa objects.
Please read the rest here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html
The last line of the above excerpt summarizes the importance of isa in message dispatching. Here is more information on it:
The key to messaging lies in the structures that the compiler builds for each class and object. Every class structure includes these two essential elements:
- A pointer to the superclass.
- A class dispatch table. This table has entries that associate method selectors with the class-specific addresses of the methods they identify.
Full Details here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html
Step 3: It's OK if the above does not make any sense :) but it's good to know what is really happening in the background. Now let's change the implementation of isJailbroken with Cycript
Step 4: Now when you try Clicking on "Am I Pirated?" you will always get a "NO" :) W00t :)
Hope you enjoyed this post. I will creating more posts on Bypassing more checks like Binary checks, Bundle and Hash checks etc. in coming posts. Stay tuned!
If you are interested in learning how to methodically understand many of the above concepts and test iOS applications with a blackbox approach, then please have a look at my SecurityTube iOS Security Expert (SISE) is an online course and certification which focuses on the iOS platform and application security. This course is ideal for pentesters, researchers and the casual iOS enthusiast who would like to dive deep and understand how to analyze and systematically audit applications on this platform using a variety of bleeding edge tools and techniques.