commit e4e0d80a1c823a2284047b9047e47ec3f7bffa86 Author: Greg Hughes Date: Fri Sep 16 15:06:56 2011 +0100 Initial commit diff --git a/Entitlements.plist b/Entitlements.plist new file mode 100644 index 0000000..929c4e9 --- /dev/null +++ b/Entitlements.plist @@ -0,0 +1,8 @@ + + + + + get-task-allow + + + \ No newline at end of file diff --git a/Info.plist b/Info.plist new file mode 100644 index 0000000..13168c4 --- /dev/null +++ b/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleName + demo + CFBundleSupportedPlatforms + + iPhoneOS + + CFBundleExecutable + demo + CFBundleIdentifier + demo + CFBundleResourceSpecification + ResourceRules.plist + LSRequiresIPhoneOS + + CFBundleDisplayName + demo + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e03ea70 --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +fruitstrap is available under the provisions of the GNU General Public License, +version 3 (or later), available here: http://www.gnu.org/licenses/gpl-3.0.html \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bd6beb4 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +IOS_CC = /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc + +all: demo.app fruitstrap + +demo.app: demo Info.plist + mkdir -p demo.app + cp demo demo.app/ + cp Info.plist ResourceRules.plist demo.app/ + codesign -f -s "iPhone Developer" --entitlements Entitlements.plist demo.app + +demo: demo.c + $(IOS_CC) -arch armv7 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -framework CoreFoundation -o demo demo.c + +fruitstrap: fruitstrap.c + gcc -o fruitstrap -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks fruitstrap.c + +install: all + ./fruitstrap demo.app + +debug: all + ./fruitstrap -d demo.app + +clean: + rm -rf *.app demo fruitstrap \ No newline at end of file diff --git a/MobileDevice.h b/MobileDevice.h new file mode 100644 index 0000000..1a39b09 --- /dev/null +++ b/MobileDevice.h @@ -0,0 +1,489 @@ +/* ---------------------------------------------------------------------------- + * MobileDevice.h - interface to MobileDevice.framework + * $LastChangedDate: 2007-07-09 18:59:29 -0700 (Mon, 09 Jul 2007) $ + * + * Copied from http://iphonesvn.halifrag.com/svn/iPhone/ + * With modifications from Allen Porter and Scott Turner + * + * ------------------------------------------------------------------------- */ + +#ifndef MOBILEDEVICE_H +#define MOBILEDEVICE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(WIN32) +#include +typedef unsigned int mach_error_t; +#elif defined(__APPLE__) +#include +#include +#endif + +/* Error codes */ +#define MDERR_APPLE_MOBILE (err_system(0x3a)) +#define MDERR_IPHONE (err_sub(0)) + +/* Apple Mobile (AM*) errors */ +#define MDERR_OK ERR_SUCCESS +#define MDERR_SYSCALL (ERR_MOBILE_DEVICE | 0x01) +#define MDERR_OUT_OF_MEMORY (ERR_MOBILE_DEVICE | 0x03) +#define MDERR_QUERY_FAILED (ERR_MOBILE_DEVICE | 0x04) +#define MDERR_INVALID_ARGUMENT (ERR_MOBILE_DEVICE | 0x0b) +#define MDERR_DICT_NOT_LOADED (ERR_MOBILE_DEVICE | 0x25) + +/* Apple File Connection (AFC*) errors */ +#define MDERR_AFC_OUT_OF_MEMORY 0x03 + +/* USBMux errors */ +#define MDERR_USBMUX_ARG_NULL 0x16 +#define MDERR_USBMUX_FAILED 0xffffffff + +/* Messages passed to device notification callbacks: passed as part of + * am_device_notification_callback_info. */ +#define ADNCI_MSG_CONNECTED 1 +#define ADNCI_MSG_DISCONNECTED 2 +#define ADNCI_MSG_UNKNOWN 3 + +#define AMD_IPHONE_PRODUCT_ID 0x1290 +#define AMD_IPHONE_SERIAL "3391002d9c804d105e2c8c7d94fc35b6f3d214a3" + +/* Services, found in /System/Library/Lockdown/Services.plist */ +#define AMSVC_AFC CFSTR("com.apple.afc") +#define AMSVC_BACKUP CFSTR("com.apple.mobilebackup") +#define AMSVC_CRASH_REPORT_COPY CFSTR("com.apple.crashreportcopy") +#define AMSVC_DEBUG_IMAGE_MOUNT CFSTR("com.apple.mobile.debug_image_mount") +#define AMSVC_NOTIFICATION_PROXY CFSTR("com.apple.mobile.notification_proxy") +#define AMSVC_PURPLE_TEST CFSTR("com.apple.purpletestr") +#define AMSVC_SOFTWARE_UPDATE CFSTR("com.apple.mobile.software_update") +#define AMSVC_SYNC CFSTR("com.apple.mobilesync") +#define AMSVC_SCREENSHOT CFSTR("com.apple.screenshotr") +#define AMSVC_SYSLOG_RELAY CFSTR("com.apple.syslog_relay") +#define AMSVC_SYSTEM_PROFILER CFSTR("com.apple.mobile.system_profiler") + +typedef unsigned int afc_error_t; +typedef unsigned int usbmux_error_t; +typedef unsigned int service_conn_t; + +struct am_recovery_device; + +typedef struct am_device_notification_callback_info { + struct am_device *dev; /* 0 device */ + unsigned int msg; /* 4 one of ADNCI_MSG_* */ +} __attribute__ ((packed)) am_device_notification_callback_info; + +/* The type of the device restore notification callback functions. + * TODO: change to correct type. */ +typedef void (*am_restore_device_notification_callback)(struct + am_recovery_device *); + +/* This is a CoreFoundation object of class AMRecoveryModeDevice. */ +typedef struct am_recovery_device { + unsigned char unknown0[8]; /* 0 */ + am_restore_device_notification_callback callback; /* 8 */ + void *user_info; /* 12 */ + unsigned char unknown1[12]; /* 16 */ + unsigned int readwrite_pipe; /* 28 */ + unsigned char read_pipe; /* 32 */ + unsigned char write_ctrl_pipe; /* 33 */ + unsigned char read_unknown_pipe; /* 34 */ + unsigned char write_file_pipe; /* 35 */ + unsigned char write_input_pipe; /* 36 */ +} __attribute__ ((packed)) am_recovery_device; + +/* A CoreFoundation object of class AMRestoreModeDevice. */ +typedef struct am_restore_device { + unsigned char unknown[32]; + int port; +} __attribute__ ((packed)) am_restore_device; + +/* The type of the device notification callback function. */ +typedef void(*am_device_notification_callback)(struct + am_device_notification_callback_info *, void* arg); + +/* The type of the _AMDDeviceAttached function. + * TODO: change to correct type. */ +typedef void *amd_device_attached_callback; + + +typedef struct am_device { + unsigned char unknown0[16]; /* 0 - zero */ + unsigned int device_id; /* 16 */ + unsigned int product_id; /* 20 - set to AMD_IPHONE_PRODUCT_ID */ + char *serial; /* 24 - set to AMD_IPHONE_SERIAL */ + unsigned int unknown1; /* 28 */ + unsigned char unknown2[4]; /* 32 */ + unsigned int lockdown_conn; /* 36 */ + unsigned char unknown3[8]; /* 40 */ +} __attribute__ ((packed)) am_device; + +typedef struct am_device_notification { + unsigned int unknown0; /* 0 */ + unsigned int unknown1; /* 4 */ + unsigned int unknown2; /* 8 */ + am_device_notification_callback callback; /* 12 */ + unsigned int unknown3; /* 16 */ +} __attribute__ ((packed)) am_device_notification; + +typedef struct afc_connection { + unsigned int handle; /* 0 */ + unsigned int unknown0; /* 4 */ + unsigned char unknown1; /* 8 */ + unsigned char padding[3]; /* 9 */ + unsigned int unknown2; /* 12 */ + unsigned int unknown3; /* 16 */ + unsigned int unknown4; /* 20 */ + unsigned int fs_block_size; /* 24 */ + unsigned int sock_block_size; /* 28: always 0x3c */ + unsigned int io_timeout; /* 32: from AFCConnectionOpen, usu. 0 */ + void *afc_lock; /* 36 */ + unsigned int context; /* 40 */ +} __attribute__ ((packed)) afc_connection; + +typedef struct afc_directory { + unsigned char unknown[0]; /* size unknown */ +} __attribute__ ((packed)) afc_directory; + +typedef struct afc_dictionary { + unsigned char unknown[0]; /* size unknown */ +} __attribute__ ((packed)) afc_dictionary; + +typedef unsigned long long afc_file_ref; + +typedef struct usbmux_listener_1 { /* offset value in iTunes */ + unsigned int unknown0; /* 0 1 */ + unsigned char *unknown1; /* 4 ptr, maybe device? */ + amd_device_attached_callback callback; /* 8 _AMDDeviceAttached */ + unsigned int unknown3; /* 12 */ + unsigned int unknown4; /* 16 */ + unsigned int unknown5; /* 20 */ +} __attribute__ ((packed)) usbmux_listener_1; + +typedef struct usbmux_listener_2 { + unsigned char unknown0[4144]; +} __attribute__ ((packed)) usbmux_listener_2; + +typedef struct am_bootloader_control_packet { + unsigned char opcode; /* 0 */ + unsigned char length; /* 1 */ + unsigned char magic[2]; /* 2: 0x34, 0x12 */ + unsigned char payload[0]; /* 4 */ +} __attribute__ ((packed)) am_bootloader_control_packet; + +/* ---------------------------------------------------------------------------- + * Public routines + * ------------------------------------------------------------------------- */ + +void AMDSetLogLevel(int level); + +/* Registers a notification with the current run loop. The callback gets + * copied into the notification struct, as well as being registered with the + * current run loop. dn_unknown3 gets copied into unknown3 in the same. + * (Maybe dn_unknown3 is a user info parameter that gets passed as an arg to + * the callback?) unused0 and unused1 are both 0 when iTunes calls this. + * In iTunes the callback is located from $3db78e-$3dbbaf. + * + * Returns: + * MDERR_OK if successful + * MDERR_SYSCALL if CFRunLoopAddSource() failed + * MDERR_OUT_OF_MEMORY if we ran out of memory + */ + +mach_error_t AMDeviceNotificationSubscribe(am_device_notification_callback + callback, unsigned int unused0, unsigned int unused1, void* //unsigned int + dn_unknown3, struct am_device_notification **notification); + +/* Connects to the iPhone. Pass in the am_device structure that the + * notification callback will give to you. + * + * Returns: + * MDERR_OK if successfully connected + * MDERR_SYSCALL if setsockopt() failed + * MDERR_QUERY_FAILED if the daemon query failed + * MDERR_INVALID_ARGUMENT if USBMuxConnectByPort returned 0xffffffff + */ + +mach_error_t AMDeviceConnect(struct am_device *device); + +/* Calls PairingRecordPath() on the given device, than tests whether the path + * which that function returns exists. During the initial connect, the path + * returned by that function is '/', and so this returns 1. + * + * Returns: + * 0 if the path did not exist + * 1 if it did + */ + +int AMDeviceIsPaired(struct am_device *device); + +/* iTunes calls this function immediately after testing whether the device is + * paired. It creates a pairing file and establishes a Lockdown connection. + * + * Returns: + * MDERR_OK if successful + * MDERR_INVALID_ARGUMENT if the supplied device is null + * MDERR_DICT_NOT_LOADED if the load_dict() call failed + */ + +mach_error_t AMDeviceValidatePairing(struct am_device *device); + +/* Creates a Lockdown session and adjusts the device structure appropriately + * to indicate that the session has been started. iTunes calls this function + * after validating pairing. + * + * Returns: + * MDERR_OK if successful + * MDERR_INVALID_ARGUMENT if the Lockdown conn has not been established + * MDERR_DICT_NOT_LOADED if the load_dict() call failed + */ + +mach_error_t AMDeviceStartSession(struct am_device *device); + +/* Starts a service and returns a handle that can be used in order to further + * access the service. You should stop the session and disconnect before using + * the service. iTunes calls this function after starting a session. It starts + * the service and the SSL connection. unknown may safely be + * NULL (it is when iTunes calls this), but if it is not, then it will be + * filled upon function exit. service_name should be one of the AMSVC_* + * constants. If the service is AFC (AMSVC_AFC), then the handle is the handle + * that will be used for further AFC* calls. + * + * Returns: + * MDERR_OK if successful + * MDERR_SYSCALL if the setsockopt() call failed + * MDERR_INVALID_ARGUMENT if the Lockdown conn has not been established + */ + +mach_error_t AMDeviceStartService(struct am_device *device, CFStringRef + service_name, service_conn_t *handle, unsigned int * + unknown); + +mach_error_t AMDeviceStartHouseArrestService(struct am_device *device, CFStringRef identifier, void *unknown, service_conn_t *handle, unsigned int *what); + +/* Stops a session. You should do this before accessing services. + * + * Returns: + * MDERR_OK if successful + * MDERR_INVALID_ARGUMENT if the Lockdown conn has not been established + */ + +mach_error_t AMDeviceStopSession(struct am_device *device); + +/* Opens an Apple File Connection. You must start the appropriate service + * first with AMDeviceStartService(). In iTunes, io_timeout is 0. + * + * Returns: + * MDERR_OK if successful + * MDERR_AFC_OUT_OF_MEMORY if malloc() failed + */ + +afc_error_t AFCConnectionOpen(service_conn_t handle, unsigned int io_timeout, + struct afc_connection **conn); + +/* Pass in a pointer to an afc_device_info structure. It will be filled. */ +afc_error_t AFCDeviceInfoOpen(afc_connection *conn, struct + afc_dictionary **info); + +/* Turns debug mode on if the environment variable AFCDEBUG is set to a numeric + * value, or if the file '/AFCDEBUG' is present and contains a value. */ +void AFCPlatformInit(); + +/* Opens a directory on the iPhone. Pass in a pointer in dir to be filled in. + * Note that this normally only accesses the iTunes sandbox/partition as the + * root, which is /var/root/Media. Pathnames are specified with '/' delimiters + * as in Unix style. + * + * Returns: + * MDERR_OK if successful + */ + +afc_error_t AFCDirectoryOpen(afc_connection *conn, const char *path, + struct afc_directory **dir); + +/* Acquires the next entry in a directory previously opened with + * AFCDirectoryOpen(). When dirent is filled with a NULL value, then the end + * of the directory has been reached. '.' and '..' will be returned as the + * first two entries in each directory except the root; you may want to skip + * over them. + * + * Returns: + * MDERR_OK if successful, even if no entries remain + */ + +afc_error_t AFCDirectoryRead(afc_connection *conn/*unsigned int unused*/, struct afc_directory *dir, + char **dirent); + +afc_error_t AFCDirectoryClose(afc_connection *conn, struct afc_directory *dir); +afc_error_t AFCDirectoryCreate(afc_connection *conn, const char *dirname); +afc_error_t AFCRemovePath(afc_connection *conn, const char *dirname); +afc_error_t AFCRenamePath(afc_connection *conn, const char *from, const char *to); +afc_error_t AFCLinkPath(afc_connection *conn, long long int linktype, const char *target, const char *linkname); + +/* Returns the context field of the given AFC connection. */ +unsigned int AFCConnectionGetContext(afc_connection *conn); + +/* Returns the fs_block_size field of the given AFC connection. */ +unsigned int AFCConnectionGetFSBlockSize(afc_connection *conn); + +/* Returns the io_timeout field of the given AFC connection. In iTunes this is + * 0. */ +unsigned int AFCConnectionGetIOTimeout(afc_connection *conn); + +/* Returns the sock_block_size field of the given AFC connection. */ +unsigned int AFCConnectionGetSocketBlockSize(afc_connection *conn); + +/* Closes the given AFC connection. */ +afc_error_t AFCConnectionClose(afc_connection *conn); + +/* Registers for device notifications related to the restore process. unknown0 + * is zero when iTunes calls this. In iTunes, + * the callbacks are located at: + * 1: $3ac68e-$3ac6b1, calls $3ac542(unknown1, arg, 0) + * 2: $3ac66a-$3ac68d, calls $3ac542(unknown1, 0, arg) + * 3: $3ac762-$3ac785, calls $3ac6b2(unknown1, arg, 0) + * 4: $3ac73e-$3ac761, calls $3ac6b2(unknown1, 0, arg) + */ + +unsigned int AMRestoreRegisterForDeviceNotifications( + am_restore_device_notification_callback dfu_connect_callback, + am_restore_device_notification_callback recovery_connect_callback, + am_restore_device_notification_callback dfu_disconnect_callback, + am_restore_device_notification_callback recovery_disconnect_callback, + unsigned int unknown0, + void *user_info); + +/* Causes the restore functions to spit out (unhelpful) progress messages to + * the file specified by the given path. iTunes always calls this right before + * restoring with a path of + * "$HOME/Library/Logs/iPhone Updater Logs/iPhoneUpdater X.log", where X is an + * unused number. + */ + +unsigned int AMRestoreEnableFileLogging(char *path); + +/* Initializes a new option dictionary to default values. Pass the constant + * kCFAllocatorDefault as the allocator. The option dictionary looks as + * follows: + * { + * NORImageType => 'production', + * AutoBootDelay => 0, + * KernelCacheType => 'Release', + * UpdateBaseband => true, + * DFUFileType => 'RELEASE', + * SystemImageType => 'User', + * CreateFilesystemPartitions => true, + * FlashNOR => true, + * RestoreBootArgs => 'rd=md0 nand-enable-reformat=1 -progress' + * BootImageType => 'User' + * } + * + * Returns: + * the option dictionary if successful + * NULL if out of memory + */ + +CFMutableDictionaryRef AMRestoreCreateDefaultOptions(CFAllocatorRef allocator); + +/* ---------------------------------------------------------------------------- + * Less-documented public routines + * ------------------------------------------------------------------------- */ + +/* mode 2 = read, mode 3 = write */ +afc_error_t AFCFileRefOpen(afc_connection *conn, const char *path, + unsigned long long mode, afc_file_ref *ref); +afc_error_t AFCFileRefSeek(afc_connection *conn, afc_file_ref ref, + unsigned long long offset1, unsigned long long offset2); +afc_error_t AFCFileRefRead(afc_connection *conn, afc_file_ref ref, + void *buf, unsigned int *len); +afc_error_t AFCFileRefSetFileSize(afc_connection *conn, afc_file_ref ref, + unsigned long long offset); +afc_error_t AFCFileRefWrite(afc_connection *conn, afc_file_ref ref, + const void *buf, unsigned int len); +afc_error_t AFCFileRefClose(afc_connection *conn, afc_file_ref ref); + +afc_error_t AFCFileInfoOpen(afc_connection *conn, const char *path, struct + afc_dictionary **info); +afc_error_t AFCKeyValueRead(struct afc_dictionary *dict, char **key, char ** + val); +afc_error_t AFCKeyValueClose(struct afc_dictionary *dict); + +unsigned int AMRestorePerformRecoveryModeRestore(struct am_recovery_device * + rdev, CFDictionaryRef opts, void *callback, void *user_info); +unsigned int AMRestorePerformRestoreModeRestore(struct am_restore_device * + rdev, CFDictionaryRef opts, void *callback, void *user_info); + +struct am_restore_device *AMRestoreModeDeviceCreate(unsigned int unknown0, + unsigned int connection_id, unsigned int unknown1); + +unsigned int AMRestoreCreatePathsForBundle(CFStringRef restore_bundle_path, + CFStringRef kernel_cache_type, CFStringRef boot_image_type, unsigned int + unknown0, CFStringRef *firmware_dir_path, CFStringRef * + kernelcache_restore_path, unsigned int unknown1, CFStringRef * + ramdisk_path); + +unsigned int AMDeviceGetConnectionID(struct am_device *device); +mach_error_t AMDeviceEnterRecovery(struct am_device *device); +mach_error_t AMDeviceDisconnect(struct am_device *device); +mach_error_t AMDeviceRetain(struct am_device *device); +mach_error_t AMDeviceRelease(struct am_device *device); +CFStringRef AMDeviceCopyValue(struct am_device *device, unsigned int, CFStringRef cfstring); +CFStringRef AMDeviceCopyDeviceIdentifier(struct am_device *device); + +typedef void (*notify_callback)(CFStringRef notification, void *data); + +mach_error_t AMDPostNotification(service_conn_t socket, CFStringRef notification, CFStringRef userinfo); +mach_error_t AMDObserveNotification(void *socket, CFStringRef notification); +mach_error_t AMDListenForNotifications(void *socket, notify_callback cb, void *data); +mach_error_t AMDShutdownNotificationProxy(void *socket); + +/*edits by geohot*/ +mach_error_t AMDeviceDeactivate(struct am_device *device); +mach_error_t AMDeviceActivate(struct am_device *device, CFMutableDictionaryRef); +/*end*/ + +void *AMDeviceSerialize(struct am_device *device); +void AMDAddLogFileDescriptor(int fd); +//kern_return_t AMDeviceSendMessage(service_conn_t socket, void *unused, CFPropertyListRef plist); +//kern_return_t AMDeviceReceiveMessage(service_conn_t socket, CFDictionaryRef options, CFPropertyListRef * result); + +/* ---------------------------------------------------------------------------- + * Semi-private routines + * ------------------------------------------------------------------------- */ + +/* Pass in a usbmux_listener_1 structure and a usbmux_listener_2 structure + * pointer, which will be filled with the resulting usbmux_listener_2. + * + * Returns: + * MDERR_OK if completed successfully + * MDERR_USBMUX_ARG_NULL if one of the arguments was NULL + * MDERR_USBMUX_FAILED if the listener was not created successfully + */ + +usbmux_error_t USBMuxListenerCreate(struct usbmux_listener_1 *esi_fp8, struct + usbmux_listener_2 **eax_fp12); + +/* ---------------------------------------------------------------------------- + * Less-documented semi-private routines + * ------------------------------------------------------------------------- */ + +usbmux_error_t USBMuxListenerHandleData(void *); + +/* ---------------------------------------------------------------------------- + * Private routines - here be dragons + * ------------------------------------------------------------------------- */ + +/* AMRestorePerformRestoreModeRestore() calls this function with a dictionary + * in order to perform certain special restore operations + * (RESTORED_OPERATION_*). It is thought that this function might enable + * significant access to the phone. */ + +typedef unsigned int (*t_performOperation)(struct am_restore_device *rdev, + CFDictionaryRef op); // __attribute__ ((regparm(2))); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..c56cc5e --- /dev/null +++ b/README @@ -0,0 +1,25 @@ +fruitstrap +========== +Install and debug iPhone apps without Xcode. Designed to work on unjailbroken devices. + +## Requirements + +* Mac OS X. Tested on Snow Leopard only. +* You need to have a valid iPhone development certificate installed. +* Xcode must be installed, along with the SDK for your iOS version. + +## Usage +* `fruitstrap [-d] ` + +* Optional `-d` flag launches a remote GDB session after the app has been installed. +* `` must be an iPhone application bundle, *not* an IPSW. + +## Demo + +* The included demo.app represents the minimum required to get code running on iOS. +* `make install` will install demo.app to the device. +* `make debug` will install demo.app and launch a GDB session. + +## Notes + +* With some modifications, it may be possible to use this without Xcode installed; however, you would need a copy of the relevant DeveloperDiskImage.dmg (included with Xcode). GDB would also run slower as symbols would be downloaded from the device on-the-fly. \ No newline at end of file diff --git a/ResourceRules.plist b/ResourceRules.plist new file mode 100644 index 0000000..e7ec329 --- /dev/null +++ b/ResourceRules.plist @@ -0,0 +1,25 @@ + + + + + rules + + .* + + Info.plist + + omit + + weight + 10 + + ResourceRules.plist + + omit + + weight + 100 + + + + diff --git a/demo.c b/demo.c new file mode 100644 index 0000000..1a263e5 --- /dev/null +++ b/demo.c @@ -0,0 +1,5 @@ +#include + +int main() { + return 0; +} \ No newline at end of file diff --git a/fruitstrap.c b/fruitstrap.c new file mode 100644 index 0000000..d85c43e --- /dev/null +++ b/fruitstrap.c @@ -0,0 +1,365 @@ +//TODO: find mystery segfault in write_gdb_prep_cmds -> CFURLCreateCopyDeletingLastPathComponent(NULL, disk_app_url) +//TODO: don't copy/mount DeveloperDiskImage.dmg if it's already done - Xcode checks this somehow + +#import +#include +#include +#include +#include +#include +#include "MobileDevice.h" + +#define FDVENDOR_PATH "/tmp/fruitstrap-remote-debugserver" +#define PREP_CMDS_PATH "/tmp/fruitstrap-gdb-prep-cmds" +#define GDB_SHELL "/Developer/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin --arch armv7 -q -x " PREP_CMDS_PATH + +// approximation of what Xcode does: +#define GDB_PREP_CMDS CFSTR("set mi-show-protections off\n\ + set auto-raise-load-levels 1\n\ + set shlib-path-substitutions /usr \"{ds_path}/Symbols/usr\" /System \"{ds_path}/Symbols/System\" \"{device_container}\" \"{disk_container}\" \"/private{device_container}\" \"{disk_container}\" /Developer \"{ds_path}/Symbols/Developer\"\n\ + set remote max-packet-size 1024\n\ + set sharedlibrary check-uuids on\n\ + set env NSUnbufferedIO YES\n\ + set minimal-signal-handling 1\n\ + set sharedlibrary load-rules \\\".*\\\" \\\".*\\\" container\n\ + set inferior-auto-start-dyld 0\n\ + file \"{disk_app}\"\n\ + set remote executable-directory {device_app}\n\ + set remote noack-mode 1\n\ + set trust-readonly-sections 1\n\ + target remote-mobile " FDVENDOR_PATH "\n\ + mem 0x1000 0x3fffffff cache\n\ + mem 0x40000000 0xffffffff none\n\ + mem 0x00000000 0x0fff none\n\ + run\n\ + set minimal-signal-handling 0\n\ + set inferior-auto-start-cfm off\n\ + set sharedLibrary load-rules dyld \".*libobjc.*\" all dyld \".*CoreFoundation.*\" all dyld \".*Foundation.*\" all dyld \".*libSystem.*\" all dyld \".*AppKit.*\" all dyld \".*PBGDBIntrospectionSupport.*\" all dyld \".*/usr/lib/dyld.*\" all dyld \".*CarbonDataFormatters.*\" all dyld \".*libauto.*\" all dyld \".*CFDataFormatters.*\" all dyld \"/System/Library/Frameworks\\\\\\\\|/System/Library/PrivateFrameworks\\\\\\\\|/usr/lib\" extern dyld \".*\" all exec \".*\" all\n\ + sharedlibrary apply-load-rules all\n\ + set inferior-auto-start-dyld 1") + +typedef struct am_device * AMDeviceRef; +int AMDeviceSecureTransferPath(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); +int AMDeviceSecureInstallApplication(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); +int AMDeviceMountImage(AMDeviceRef device, CFStringRef image, CFDictionaryRef options, void *callback, int cbarg); +int AMDeviceLookupApplications(AMDeviceRef device, int zero, CFDictionaryRef* result); + +bool found_device = false, debug = false; +char *app_path = NULL; +CFStringRef last_path = NULL; +service_conn_t gdbfd; + +CFStringRef copy_device_support_path(AMDeviceRef device) { + CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); + CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); + CFStringRef path_with_build = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), version, build); + CFStringRef path_without_build = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@"), version); + + CFRelease(version); + CFRelease(build); + + // they tack the build number on for beta builds + if (access(CFStringGetCStringPtr(path_with_build, kCFStringEncodingMacRoman), F_OK) == 0) { + CFRelease(path_without_build); + return path_with_build; + } else if (access(CFStringGetCStringPtr(path_without_build, kCFStringEncodingMacRoman), F_OK) == 0) { + CFRelease(path_with_build); + return path_without_build; + } else { + printf("[ !! ] Unable to locate DeviceSupport directory. Is Xcode installed?\n"); + exit(1); + } +} + +void mount_callback(CFDictionaryRef dict, int arg) { + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + + if (CFEqual(status, CFSTR("LookingUpImage"))) { + printf("[ 0%%] Looking up developer disk image\n"); + } else if (CFEqual(status, CFSTR("CopyingImage"))) { + printf("[ 30%%] Copying DeveloperDiskImage.dmg to device\n"); + } else if (CFEqual(status, CFSTR("MountingImage"))) { + printf("[ 90%%] Mounting developer disk image\n"); + } +} + +void mount_developer_image(AMDeviceRef device) { + CFStringRef ds_path = copy_device_support_path(device); + CFStringRef image_path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/DeveloperDiskImage.dmg"), ds_path); + CFStringRef sig_path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/DeveloperDiskImage.dmg.signature"), ds_path); + CFRelease(ds_path); + + FILE* sig = fopen(CFStringGetCStringPtr(sig_path, kCFStringEncodingMacRoman), "rb"); + void *sig_buf = malloc(128); + assert(fread(sig_buf, 1, 128, sig) == 128); + fclose(sig); + CFDataRef sig_data = CFDataCreateWithBytesNoCopy(NULL, sig_buf, 128, NULL); + CFRelease(sig_path); + + CFTypeRef keys[] = { CFSTR("ImageSignature"), CFSTR("ImageType") }; + CFTypeRef values[] = { sig_data, CFSTR("Developer") }; + CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease(sig_data); + + int result = AMDeviceMountImage(device, image_path, options, &mount_callback, 0); + if (result == 0 || result == 0xe8000076 /* already mounted */) { + printf("[ 95%%] Developer disk image mounted successfully\n"); + } else { + printf("[ !! ] Unable to mount developer disk image. (%x)\n", result); + exit(1); + } + + CFRelease(image_path); + CFRelease(options); +} + +void transfer_callback(CFDictionaryRef dict, int arg) { + int percent; + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); + + if (CFEqual(status, CFSTR("CopyingFile"))) { + CFStringRef path = CFDictionaryGetValue(dict, CFSTR("Path")); + + if ((last_path == NULL || !CFEqual(path, last_path)) && !CFStringHasSuffix(path, CFSTR(".ipa"))) { + printf("[%3d%%] Copying %s to device\n", percent / 2, CFStringGetCStringPtr(path, kCFStringEncodingMacRoman)); + } + + if (last_path != NULL) { + CFRelease(last_path); + } + last_path = CFStringCreateCopy(NULL, path); + } +} + +void install_callback(CFDictionaryRef dict, int arg) { + int percent; + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); + + printf("[%3d%%] %s\n", (percent / 2) + 50, CFStringGetCStringPtr(status, kCFStringEncodingMacRoman)); +} + +void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { + CFSocketNativeHandle socket = (CFSocketNativeHandle)(*((CFSocketNativeHandle *)data)); + + struct msghdr message; + struct iovec iov[1]; + struct cmsghdr *control_message = NULL; + char ctrl_buf[CMSG_SPACE(sizeof(int))]; + char dummy_data[1]; + + memset(&message, 0, sizeof(struct msghdr)); + memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); + + dummy_data[0] = ' '; + iov[0].iov_base = dummy_data; + iov[0].iov_len = sizeof(dummy_data); + + message.msg_name = NULL; + message.msg_namelen = 0; + message.msg_iov = iov; + message.msg_iovlen = 1; + message.msg_controllen = CMSG_SPACE(sizeof(int)); + message.msg_control = ctrl_buf; + + control_message = CMSG_FIRSTHDR(&message); + control_message->cmsg_level = SOL_SOCKET; + control_message->cmsg_type = SCM_RIGHTS; + control_message->cmsg_len = CMSG_LEN(sizeof(int)); + + *((int *) CMSG_DATA(control_message)) = gdbfd; + + sendmsg(socket, &message, 0); + CFSocketInvalidate(s); + CFRelease(s); +} + +CFURLRef copy_device_app_url(AMDeviceRef device, CFStringRef identifier) { + CFDictionaryRef result; + assert(AMDeviceLookupApplications(device, 0, &result) == 0); + + CFDictionaryRef app_dict = CFDictionaryGetValue(result, identifier); + assert(app_dict != NULL); + + CFStringRef app_path = CFDictionaryGetValue(app_dict, CFSTR("Path")); + assert(app_path != NULL); + + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, app_path, kCFURLPOSIXPathStyle, true); + CFRelease(result); + return url; +} + +CFStringRef copy_disk_app_identifier(CFURLRef disk_app_url) { + CFURLRef plist_url = CFURLCreateCopyAppendingPathComponent(NULL, disk_app_url, CFSTR("Info.plist"), false); + CFReadStreamRef plist_stream = CFReadStreamCreateWithFile(NULL, plist_url); + CFReadStreamOpen(plist_stream); + CFPropertyListRef plist = CFPropertyListCreateWithStream(NULL, plist_stream, 0, kCFPropertyListImmutable, NULL, NULL); + CFStringRef bundle_identifier = CFRetain(CFDictionaryGetValue(plist, CFSTR("CFBundleIdentifier"))); + CFReadStreamClose(plist_stream); + + CFRelease(plist_url); + CFRelease(plist_stream); + CFRelease(plist); + + return bundle_identifier; +} + +void write_gdb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { + CFMutableStringRef cmds = CFStringCreateMutableCopy(NULL, 0, GDB_PREP_CMDS); + CFRange range = { 0, CFStringGetLength(cmds) }; + + CFStringRef ds_path = copy_device_support_path(device); + CFStringFindAndReplace(cmds, CFSTR("{ds_path}"), ds_path, range, 0); + + CFStringRef bundle_identifier = copy_disk_app_identifier(disk_app_url); + CFURLRef device_app_url = copy_device_app_url(device, bundle_identifier); + CFStringRef device_app_path = CFURLCopyFileSystemPath(device_app_url, kCFURLPOSIXPathStyle); + CFStringFindAndReplace(cmds, CFSTR("{device_app}"), device_app_path, range, 0); + + CFStringRef disk_app_path = CFURLCopyFileSystemPath(disk_app_url, kCFURLPOSIXPathStyle); + CFStringFindAndReplace(cmds, CFSTR("{disk_app}"), disk_app_path, range, 0); + + CFURLRef device_container_url = CFURLCreateCopyDeletingLastPathComponent(NULL, device_app_url); + CFStringRef device_container_path = CFURLCopyFileSystemPath(device_container_url, kCFURLPOSIXPathStyle); + CFMutableStringRef dcp_noprivate = CFStringCreateMutableCopy(NULL, 0, device_container_path); + CFStringFindAndReplace(dcp_noprivate, CFSTR("/private/var/"), CFSTR("/var/"), range, 0); + CFStringFindAndReplace(cmds, CFSTR("{device_container}"), dcp_noprivate, range, 0); + + CFURLRef disk_container_url = CFURLCreateCopyDeletingLastPathComponent(NULL, disk_app_url); + CFStringRef disk_container_path = CFURLCopyFileSystemPath(disk_container_url, kCFURLPOSIXPathStyle); + CFStringFindAndReplace(cmds, CFSTR("{disk_container}"), disk_container_path, range, 0); + + CFDataRef cmds_data = CFStringCreateExternalRepresentation(NULL, cmds, kCFStringEncodingASCII, 0); + FILE *out = fopen(PREP_CMDS_PATH, "w"); + fwrite(CFDataGetBytePtr(cmds_data), CFDataGetLength(cmds_data), 1, out); + fclose(out); + + CFRelease(cmds); + CFRelease(ds_path); + CFRelease(bundle_identifier); + CFRelease(device_app_url); + CFRelease(device_app_path); + CFRelease(disk_app_path); + CFRelease(device_container_url); + CFRelease(device_container_path); + CFRelease(dcp_noprivate); + CFRelease(disk_container_url); + CFRelease(disk_container_path); + CFRelease(cmds_data); +} + +void start_remote_debug_server(AMDeviceRef device) { + assert(AMDeviceStartService(device, CFSTR("com.apple.debugserver"), &gdbfd, NULL) == 0); + + CFSocketRef fdvendor = CFSocketCreate(NULL, AF_UNIX, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, NULL); + + int yes = 1; + setsockopt(CFSocketGetNative(fdvendor), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + struct sockaddr_un address; + memset(&address, 0, sizeof(address)); + address.sun_family = AF_UNIX; + strcpy(address.sun_path, FDVENDOR_PATH); + CFDataRef address_data = CFDataCreate(NULL, (const UInt8 *)&address, sizeof(address)); + + unlink(FDVENDOR_PATH); + + CFSocketSetAddress(fdvendor, address_data); + CFRelease(address_data); + CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, fdvendor, 0), kCFRunLoopCommonModes); +} + +void handle_device(AMDeviceRef device) { + if (found_device) return; // handle one device only + found_device = true; + CFRetain(device); // don't know if this is necessary? + printf("[ 0%%] Found device, beginning install\n"); + + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + assert(AMDeviceValidatePairing(device) == 0); + assert(AMDeviceStartSession(device) == 0); + + CFStringRef path = CFStringCreateWithCString(NULL, app_path, kCFStringEncodingASCII); + CFURLRef relative_url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, false); + CFURLRef url = CFURLCopyAbsoluteURL(relative_url); + + CFRelease(path); + CFRelease(relative_url); + + CFStringRef keys[] = { CFSTR("PackageType") }; + CFStringRef values[] = { CFSTR("Developer") }; + CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + mach_error_t transfer_error = AMDeviceSecureTransferPath(0, device, url, options, &transfer_callback, 0); + if (transfer_error) { + printf("[ !! ] Unable to transfer package to device. (%x)\n", transfer_error); + exit(1); + } + + mach_error_t install_error = AMDeviceSecureInstallApplication(0, device, url, options, &install_callback, 0); + if (install_error) { + printf("[ !! ] Unable to install package. (%x)\n", install_error); + exit(1); + } + + CFRelease(options); + printf("[100%%] Installed package %s\n", app_path); + + if (!debug) exit(0); // no debug phase + + printf("------ Debug phase ------\n"); + + mount_developer_image(device); // put debugserver on the device + start_remote_debug_server(device); // start debugserver + write_gdb_prep_cmds(device, url); // dump the necessary gdb commands into a file + + CFRelease(url); + + printf("[100%%] Connecting to remote debug server\n"); + printf("-------------------------\n"); + + pid_t parent = getpid(); + int pid = fork(); + if (pid == 0) { + system(GDB_SHELL); // launch gdb + kill(parent, SIGTERM); // "No. I am your father." + _exit(0); + } +} + +void device_callback(struct am_device_notification_callback_info *info, void *arg) { + switch (info->msg) { + case ADNCI_MSG_CONNECTED: + handle_device(info->dev); + default: + break; + } +} + +int main(int argc, char *argv[]) { + if (argc < 2 || argc > 3) { + printf("usage: %s [-d] \n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "-d") == 0) { + assert(argc == 3); + debug = true; + app_path = argv[2]; + printf("------ Install phase ------\n"); + } else { + assert(argc == 2); + app_path = argv[1]; + } + + assert(access(app_path, F_OK) == 0); + + AMDSetLogLevel(5); // otherwise syslog gets flooded with crap + printf("[....] Waiting for iOS device to be connected\n"); + + struct am_device_notification *notify; + AMDeviceNotificationSubscribe(&device_callback, 0, 0, NULL, ¬ify); + CFRunLoopRun(); +} \ No newline at end of file