Class-dump by Steve Nygard [1] is a command-line utility for examining the Objective-C runtime information stored in Mach-O files. It generates declarations for the classes, categories and protocols.
Class-dump-dyld by Elias Limneos [2] allows dumping and retrieving symbols directly from the shared cache, eliminating the need to extract the files first. It can generate header files from app binaries, libraries, frameworks, bundles or the whole dyld_shared_cache. Is is also possible to Mass-dump the whole dyld_shared_cache or directories recursively.
MachoOView [3] is a useful visual Mach-O file browser that also allows for in-file editing of ARM binaries.
In the iOS world, jailbreaking means disabling Apple's code code signing mechanisms so that apps not signed by Apple can be run. If you're planning to do any form of dynamic security testing on an iOS device, you'll have a much easier time on a jailbroken device, as most useful testing tools are only available outside the app store.
Developing a jailbreak for any given version of iOS is not an easy endeavor. As a security tester, you'll most likely want to use publicly available jailbreak tools (don't worry, we're all script kiddies in some areas). Even so, we recommend studying the techniques used to jailbreak various versions of iOS in the past - you'll encounter many highly interesting exploits and learn a lot about the internals of the OS. For example, Pangu9 for iOS 9.x exploited at least five vulnerabilities, including a use-after-free bug in the kernel (CVE-2015-6794) and an arbitrary file system access vulnerability in the Photos app (CVE-2015-7037) [3].
In jailbreak lingo, we talk about tethered and untethered jailbreaking methods. In the "tethered" scenario, the jailbreak doesn't persist throughout reboots, so the device must be connected (tethered) to a computer during every reboot to re-apply it. "Untethered" jailbreaks need only be applied once, making them the most popular choice for end users.
(... TODO: Jailbreaking How-to ...)
Some typical Jailbreak detection techniques:
Check for the existence of files, such as:
/Library/MobileSubstrate/MobileSubstrate.dylib
/Applications/Cydia.app
/Library/MobileSubstrate/MobileSubstrate.dylib
/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist
Write a file to the /private/ directory:
NSError *error;
NSString *stringToBeWritten = @"This is a test.";
[stringToBeWritten writeToFile:@"/private/jailbreak.txt" atomically:YES
encoding:NSUTF8StringEncoding error:&error];
if(error==nil){
//Device is jailbroken
return YES;
} else {
//Device is not jailbroken
[[NSFileManager defaultManager] removeItemAtPath:@"/private/jailbreak.txt" error:nil];
}
Attempt to open a Cydia URL:
if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]){
References:
#import <substrate.h>
#define PT_DENY_ATTACH 31
static int (*_my_ptrace)(int request, pid_t pid, caddr_t addr, int data);
static int $_my_ptrace(int request, pid_t pid, caddr_t addr, int data) {
if (request == PT_DENY_ATTACH) {
request = -1;
}
return _ptraceHook(request,pid,addr,data);
}
%ctor {
MSHookFunction((void *)MSFindSymbol(NULL,"_ptrace"), (void *)$ptraceHook, (void **)&_ptraceHook);
}
Cycript is traditionally used in the iOS world. It also runs standalone on Android, however without injection support. It is based on a Java VM that can be injected into a running process using Cydia Substrate. The user then communicates with process through the Cycript console interface.
Cycript injects a JavaScriptCore VM into the running process. Users can then manipulate the process using JavaScript with some syntax extensions through the Cycript Console.
*(Todo - use cases and example for Cycript)
- Obtain references to existing objects
- Instantiate objects from classes
- Hooking native functions
- Hooking objective-C methods
- etc.* http://www.cycript.org/manual/
Cycript tricks:
http://iphonedevwiki.net/index.php/Cycript_Tricks
(---TODO---)
import frida
import sys
try:
session = frida.get_usb_device().attach("Target Process")
except frida.ProcessNotFoundError:
print "Failed to attach to the target process. Did you launch the app?"
sys.exit(0);
script = session.create_script("""
// Handle fork() based check
var fork = Module.findExportByName("libsystem_c.dylib", "fork");
Interceptor.replace(fork, new NativeCallback(function () {
send("Intercepted call to fork().");
return -1;
}, 'int', []));
var system = Module.findExportByName("libsystem_c.dylib", "system");
Interceptor.replace(system, new NativeCallback(function () {
send("Intercepted call to system().");
return 0;
}, 'int', []));
// Intercept checks for Cydia URL handler
var canOpenURL = ObjC.classes.UIApplication["- canOpenURL:"];
Interceptor.attach(canOpenURL.implementation, {
onEnter: function(args) {
var url = ObjC.Object(args[2]);
send("[UIApplication canOpenURL:] " + path.toString());
},
onLeave: function(retval) {
send ("canOpenURL returned: " + retval);
}
});
// Intercept file existence checks via [NSFileManager fileExistsAtPath:]
var fileExistsAtPath = ObjC.classes.NSFileManager["- fileExistsAtPath:"];
var hideFile = 0;
Interceptor.attach(fileExistsAtPath.implementation, {
onEnter: function(args) {
var path = ObjC.Object(args[2]);
// send("[NSFileManager fileExistsAtPath:] " + path.toString());
if (path.toString() == "/Applications/Cydia.app" || path.toString() == "/bin/bash") {
hideFile = 1;
}
},
onLeave: function(retval) {
if (hideFile) {
send("Hiding jailbreak file...");MM
retval.replace(0);
hideFile = 0;
}
// send("fileExistsAtPath returned: " + retval);
}
});
/* If the above doesn't work, you might want to hook low level file APIs as well
var openat = Module.findExportByName("libsystem_c.dylib", "openat");
var stat = Module.findExportByName("libsystem_c.dylib", "stat");
var fopen = Module.findExportByName("libsystem_c.dylib", "fopen");
var open = Module.findExportByName("libsystem_c.dylib", "open");
var faccesset = Module.findExportByName("libsystem_kernel.dylib", "faccessat");
*/
""")
def on_message(message, data):
if 'payload' in message:
print(message['payload'])
script.on('message', on_message)
script.load()
sys.stdin.read()
For iOS, distributed application packages are usually stored in an IPA format which an archive file containing application bundles which contain executable binary, resource files, support files and application properties. But when an application is released to the App Store, application's binary will be encrypted by Apple's FairPlay (DRM). Therefore, to perform a static analysis, a binary of an application needs to be decrypted first.
In order to analyze the iOS application from App Store, Tester need to decrypt the application which can be automatically conducted using “dumpdecrypted” tool developed by Stefan Esser.
To use “dumpdecrypted”, connect to the iOS device using SSH and set the DYLD_INSERT_LIBRARIES environment variable when executing the target binary:
ssh root@<ip of idevice>
iPod:root# DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Applications/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Example.app/Example
The decrypted binary is saved in the current working directory.
iOS ships with a console app, debugserver, that allows for remote debugging using gdb or lldb. By default however, debugserver cannot be used to attach to arbitrary processes (it is usually only used for debugging self-developed apps deployed with XCode). To enable debugging of third-part apps, the task_for_pid entitlement must be added to the debugserver executable. An easy way to do this is adding the entitlement to the debugserver binary shipped with XCode [5].
To obtain the executable mount the following DMG image:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/ DeviceSupport/<target-iOS-version//DeveloperDiskImage.dmg
You’ll find the debugserver executable in the /usr/bin/ directory on the mounted volume - copy it to a temporary directory. Then, create a file called entitlements.plist with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/ PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.springboard.debugapplications</key>
<true/>
<key>run-unsigned-code</key>
<true/>
<key>get-task-allow</key>
<true/>
<key>task_for_pid-allow</key>
<true/>
</dict>
</plist>
And apply the entitlement with codesign:
codesign -s - --entitlements entitlements.plist -f debugserver
Copy the modified binary to any directory on the test device (note: The following examples use usbmuxd to forward a local port through USB).
$ ./tcprelay.py -t 22:2222
$ scp -P2222 debugserver root@localhost:/tmp/
You can now attach debugserver to any process running on the device.
VP-iPhone-18:/tmp root# ./debugserver *:1234 -a 2670
debugserver-@(#)PROGRAM:debugserver PROJECT:debugserver-320.2.89
for armv7.
Attaching to process 2670...
- [1] Class-dump - http://stevenygard.com/projects/class-dump/
- [2] Class-dump-dyld - https://github.com/limneos/classdump-dyld/
- [3] MachOView - https://sourceforge.net/projects/machoview/
- [3] Jailbreak Exploits on the iPhone Dev Wiki - https://www.theiphonewiki.com/wiki/Jailbreak_Exploits#Pangu9_.289.0_.2F_9.0.1_.2F_9.0.2.29)
- [4] Stack Overflow - http://stackoverflow.com/questions/413242/how-do-i-detect-that-an-ios-app-is-running-on-a-jailbroken-phone
- [5] Debug Server on the iPhone Dev Wiki - http://iphonedevwiki.net/index.php/Debugserver