Much has been said about Apple’s closed approach to selling devices and running an app platform.But what few know is that behind closed doors there’s a massive ecosystem of libraries and hardware features waiting to be unlocked by developers.All of the APIs Apple uses internally to build their services,applications,and widgets are available once the locks are broken via a process called jailbreaking.Most of them are written in Objective-C,a dynamic language that provides very rich introspection capabilities and has a culture of self-describing code.Further tearing down barriers,most people install something called CydiaSubstrate shortly after jailbreaking,which allows running custom code inside every existing process on the device.This is very powerful—not only have we broken out of the walled garden into the rest of the forest,all of the footpaths are already labeled.Building code that targets jailbroken iOS devices involves unique ways of inspecting APIs,injecting code into processes,and writing code that modifies existing classes and finalized behaviors of the system.
The APIs implemented on iOS can be divided into four categories:framework-level Objective-C APIs,app-level Objective-C classes,C-accessible APIs and JavaScript-accessible APIs.Objective-C frameworks are the most easily accessible.Normally the structure of a component is only accessible to the programmer and those the source code or documentation have been made available to,but compiled Objective-C binaries include method tables describing all of the classes,protocols,methods and instance variables contained in the binary.An entire family of“class-dump”tools exists to take these method tables and convert them to header-like output for easy consumption by adventurous programmers.Calling these APIs is as simple as adding the generated headers to your project and linking with the framework or library.The second category of app internal classes may be inspected via the same tools,but are not linkable via standard tools.To get to those classes,one has to have code injected into the app in question and use the Objective-C runtime function objc_getClass to get a reference to the class;from there,one can call APIs via the headers generated by the tool.Inspecting C-level functions are more difficult.No information about what the parameters or data structures are baked into the binaries,only the names of exported functions.The developer tools that ship with OS X come with a disassembler named“otool”which can dump the instructions used to implement the code in the device.Paired with knowledge of ARM assembly,the type information can be reconstructed by hand with much effort.This is much more cumbersome than with Objective-C code.Luckily,some of the components implemented in C are shared with OS X and have headers available in the OS X SDK,or are available as open-source from Apple.JavaScript-level APIs are most often facades over Objective-C level APIs to make additional functionality accessible to web pages hosted inside the iTunes,App Store,iCloud and iAd sections of the operating system.
Putting the APIs one has uncovered to use often requires having code run inside the process where their implementations are present.This can be done using the DYLD_INSERT_LIBRARIES environment variable on systems that use dyld,but this facility offers very few provisions for crash protection and can easily leave a device in a state where a restore is necessary.Instead,the gold standard on iOS devices is a system known as Cydia Substrate,a package that standardizes process injection and offers safety features to limit the damage testing new code can do.Once Cydia Substrate is installed,one needs only to drop a dynamic library compiled for the device in/Library/MobileSubstrate/DynamicLibraries,and substrate will load it automatically in every process on the device.Filtering to only a specific process can be achieved by dropping a property list of the same name alongside it with details on which process or grouping of processes to filter to.Once inside,one can register for events,call system APIs and perform any of the same behaviors that the process normally could.This applies to apps that come preinstalled on the device,apps available from the App Store,the window manager known as SpringBoard,UI services that apps can make use of such as the mail composer,and background services such as the media decoder daemon.It is important to note that any state that the injected code has will be unique to the process it’s injected into and to share state mandates use inter-process communication techniques such as sockets,fifos,mach ports and shared memory.
Modifying existing code is where it really starts to get powerful and allows tweaking existing functionality of the device in simple or even radical ways.Because Objective-C method lookup is all done at runtime and the runtime offers APIs to modify methods and classes,it is really straightforward to replace the implementations of existing methods with new ones that add new functionality,suppress the original behavior or both.This is known as method hooking and in Objective-C is done through a complicated dance of calls to the class_addMethod,class_getInstanceMethod,method_getImplementation and method_setImplementation runtime functions.This is very unwieldy;tools to automate this have been built.The simplest is Cydia Substrate’s own MSHookMessage function.It takes a class,the name of the method you want to replace,the new implementation,and gives back the original implementation of the function so that the replacement can perform the original behavior if necessary.This has been further automated in the Logos Objective-C preprocessor tool,which introduces syntax specifically for method hooking and is what most tweaks are now written in.Writing Logos code is as simple as writing what would normally be an Objective-C method implementation,and sticking it inside of a%hook ClassName...%end block instead of an@implementation ClassName...%end block,and calling%orig()instead of[super...].Simple tweaks to how the system behaves can often done by replacing a single method with a different implementation,but complicated adjustments often require assembling numerous method hooks.Since most of iOS is implemented in Objective-C,the vast majority of tweaks need only these building blocks to apply the modifications they require.For the lower levels of the system that are written in C,a more complicate hooking approach is required.The lowest level and most compatible way of doing so is to simply rewrite the assembly instructions of the victim function.This is very dangerous and does not compose well when many developers are modifying the same parts of the system.Again,CydiaSubstrate introduces an API to automate this in form of MSHookFunction.Just like MSHookMessage,one needs only to pass in the target function,new replacement implementation function,and it applies the hook and returns the old implementation that the new replacement can call if necessary.With the tools the community has made available,the details of the very complex mechanics of hooking have been abstracted and simplified to the point where they’re hidden from view and a developer can concentrate on what new features they’re adding.
Combining these techniques unique to the jailbreak scene,with those present in the standard iOS and OS X development communities yields a very flexible and powerful tool chest for building features and experiences that the world hasn’t seen yet.
(关于苹果的封闭性可谓路人皆知。但其实这扇紧闭的大门背后有不计其数的宝藏在等待着开发者们让它们重见天日——这些宝藏就是苹果内部使用的所有API,而让它们重见天日的过程就是越狱。这些API中的大多数都是使用Objective-C——一门动态性强、语义清晰的语言——编写的。越狱之后,为了进一步扩展iDevice的功能,绝大多数人都会安装CydiaSubstrate,它使我们能够在其他进程中运行自己编写的代码。它的意义非比寻常——我们不但破除了苹果的限制,还能够以其人之道,还治其人之身!越狱iOS开发与普通的App Store开发不尽相同,它需要你去摸索API的用法,把代码注入别的进程,修改现有的类,最终达到改变系统行为的目的。
iOS调用的API可以分为4类:framework使用的Objective-C API、App使用的Objective-C API、C API和JavaScript API,其中framework层面的API是最容易使用的。一般情况下,一个程序的设计、代码、文档只为少数人所有,但经过编译的Objective-C二进制文件中含有所有的类、协议、方法和实例变量信息,“class-dump”大家族的工具可以把这些信息导出来,形成头文件,供那些好奇的开发者研究并调用。App使用的API可以通过同样的方式导出,但不能直接调用。这时,可以把代码注入对应的进程里,然后调用Objective-C的运行时函数objc_getClass来获取一个类的对象,进而调用类方法。C API就要复杂一点,没有class-dump这样的工具把C函数的参数和数据结构给导出来,能找到的只有导出函数的函数名;但是,利用OSX自带的反汇编工具otool,结合一些ARM汇编语言的知识,我们可以手动分析,还原C函数的原貌,这个过程比分析Objective-C方法要复杂得多。幸运的是,iOS中的C实现与OSX是部分相通的,有一些C函数的头文件可以从OSX的SDK中找到参考,还有一些是开源的。JavaScript API通常只是Objective-C API的封装,供iTunes、App Store、iCloud和iAd等应用中的网页调用。
通过逆向工程得知的API通常需要注入对应的进程才能调用,因为只有这些进程中含有API的实现。进程注入可以通过DYLD_INSERT_LIBRARIES方式实现,但这种方式提供的防崩溃保护不够强,很容易导致设备白苹果,只有重刷系统才能解决。更好的替代方案是CydiaSubstrate,它为进程注入制定了一套标准,并引入了更加安全的防崩溃保护机制。安装CydiaSubstrate之后,只需要把一个dylib放到/Library/MobileSubstrate/DynamicLibraries下即可,CydiaSubstrate会自动在每个进程启动时尝试把这个dylib加载进去;我们可以进一步通过编写一个plist指定dylib加载的对象。一旦我们的代码得到注入,就可以监听事件,调用内部API,做这个进程本身能够做的任何事。这种注入方式可以用在任何进程上:系统App、App Store App、iOS上的窗口管理器SpringBoard、UI服务(如MailComposer)和守护进程(如mediaserverd)。值得一提的是,我们的代码只在注入的进程内部生效,如果要使其跨进程作用,则需要用到sockets、fifos、mach ports和shared memory等技术。
从我们能够通过简单方便的CydiaSubstrate更改现有的代码开始,我们能做的事就多了起来。因为Objective-C方法调用都是在运行时决定的,而Objective-C的运行时函数提供了修改类和方法的API,所以这个过程就比较直观了,大体是通过class_addMethod、class_getInstanceMethod、method_getImplementation和method_setImplementation这4个运行时函数来实现hook的功能。这个过程并不困难,但比较繁复,许多工具就是为了自动化这个过程而存在的,其中比较简单的是CydiaSubstrate提供的MSHookMessage函数,它有4个参数,分别是:一个类、一个你要hook的方法名、一个新的实现和一个原始实现。现在越狱社区又出现了一种更简单的Logos预处理工具,它引入了专为hook设计的语法,因此在广大越狱开发者中流行了起来。Logos语法与Objective-C语法十分类似,把@implementation ClassName...%end的首尾替换掉,改成%hook ClassName...%end,把[super...]替换为%orig就行了。简单地更改系统功能往往只需要hook一两个独立函数,把它们的实现改掉就可以了;但多数情况下我们需要组合hook好几个函数并协调它们之间的调用关系。因为iOS的大部分功能是用Objective-C写的,所以大多数tweak仅用上面提到的语法就够了,但一旦涉及了更底层的C函数,hook的方法就要复杂许多,一般需要重写一段ARM汇编指令,当然这种方法的危险系数极高,不建议一般开发者尝试。好在CydiaSubstrate也提供了一个简单的API,即MSHookFunction,调用者只需要把需要hook的函数及其新的实现作为参数传进去就可以了。越狱社区提供的这些工具把hook操作复杂的一面给抽象化了,封装成几个简单的接口供开发者使用,从而使我们能够把精力集中在tweak的编写上。
把这些越狱社区独有的技术同App Store开发的普遍性技术结合起来,我们得到了一套用法灵活、功能强大的工具集,利用这套工具能够打造的功能也将是前所未有的。)
Ryan Petrich
牧“码”人,Activator之父