Cydia Substrate: Difference between revisions

From iPhone Development Wiki
m (→‎Filters: Should be exact.)
No edit summary
Line 1: Line 1:
{{Infobox Package
The NSAutoreleasePool class is a thin wrapper around the '''NSPushAutoreleasePool''' and '''NSPopAutoreleasePool''' functions.
|developer=saurik
|version=0.9.5001
|package=mobilesubstrate
}}
<small>'''Languages: English &bull; [[MobileSubstrate/fr|français]] &bull; [[MobileSubstrate/th|ไทย]]'''</small>
 
'''Cydia Substrate''' (formerly called '''MobileSubstrate''') is the ''de facto'' framework that allows 3rd-party developers to provide run-time patches (“Cydia Substrate extensions”) to system functions, similar to [http://web.archive.org/web/20021207193945/http://www.unsanity.com/haxies/ape/ Application Enhancer] on the OS X.
 
saurik has written a [http://www.cydiasubstrate.com/id/264d6581-a762-4343-9605-729ef12ff0af/ whole website of documentation for Substrate].
 
Cydia Substrate consists of 3 major components: MobileHooker, MobileLoader and safe mode.
 
== MobileHooker ==
 
MobileHooker is used to replace system functions. This process is known as hooking. There are 2 APIs that one would use:
 
<source lang="c">
IMP MSHookMessage(Class class, SEL selector, IMP replacement, const char* prefix); // prefix should be NULL.
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP *result);
void MSHookFunction(void* function, void* replacement, void** p_original);
</source>
 
MSHookMessage() will replace the implementation of the Objective-C message <tt>-[</tt>''class'' ''selector''<tt>]</tt> by ''replacement'', and return the original implementation. To hook a class method, provide the meta class retrieved from objc_getMetaClass in the MSHookeMessage(Ex) call and see example note below. This dynamic replacement is in fact a feature of Objective-C, and can be done using [http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html#//apple_ref/c/func/method_setImplementation method_setImplementation].
MSHookMessage() is not thread-safe and has been deprecated in favor of MSHookMessageEx()
 
MSHookFunction() is like MSHookMessage() but is for C/C++ functions. The replacement is done at assembly level. Conceptually, MSHookFunction() will write instructions that jumps to the replacement function, and allocate some bytes on a custom memory location, which has the original cut-out instructions and a jump to the rest of the hooked function. Since on iOS by default a memory page cannot be simultaneously writable and executable, a kernel patch must be applied for MSHookFunction() to work. (Any public jailbreak should have one.)
 
As of the latest version of MobileSubstrate, MSHookMessage() also requires a kernel patch for supercall closures to hook all methods properly.
 
It is also possible to get references to classes' ivars via [[Hooking Instance Variables | MSHookIvar]] when not possible via instance methods.
 
=== Example code ===
 
Using MSHookFunction:
 
<source lang="c">
MSHook(void, CFShow, CFTypeRef obj) {          // our replacement of CFShow().
  printf("Calling original CFShow(%p)...", obj);
  _CFShow(obj);                                // calls the original CFShow.
  printf(" done.\n");
}
...
// hook CFShow to our own implementation.
MSHookFunction(CFShow, MSHake(CFShow));
// From now on any call to CFShow will pass through our CFShow replacement first.
...
CFShow(CFSTR("test"));
</source>
 
Using MSHookMessageEx:


<source lang="objc">
<source lang="objc">
static IMP original_UIView_setFrame_;
#ifdef __cplusplus
void replaced_UIView_setFrame_(UIView* self, SEL _cmd, CGRect frame) { // Note the implicit self and _cmd parameters are needed explicitly here.
extern "C" {
  CGRect originalFrame = self.frame;
#endif
  NSLog("Changing frame of %p from %@ to %@", self, NSStringFromCGRect(originalFrame), NSStringFromCGRect(frame));
void *NSPushAutoreleasePool(NSUInteger capacity);
  original_UIView_setFrame_(self, _cmd, frame);   // Remember to pass self and _cmd.
void NSPopAutoreleasePool(void* token);
#ifdef __cplusplus
}
}
...
#endif
MSHookMessageEx([UIView class], @selector(setFrame:), (IMP)replaced_UIView_setFrame_, (IMP *)&original_UIView_setFrame_);
...
myView.frame = CGRectMake(0, 0, 100, 100);
</source>
</source>


Note that if you are hooking a class method, you have to put a meta-class in the ''class'' argument, e.g.
Example:


<source lang="objc">
<source lang="objc">
MSHookMessageEx(objc_getMetaClass("UIView"), @selector(commitAnimations), replaced_UIView_commitAnimations, (IMP *)&original_UIView_commitAnimations);
static void MyMethod()
</source>
 
Using MSHookFunction to hook private functions (in this case a C++ Method):
 
<source lang="c">
#define PRIVATE_FRAMEWORKS "/System/Library/PrivateFrameworks"
#define WEBKIT PRIVATE_FRAMEWORKS"/WebKit.framework/WebKit"
#define WEBKIT_FUNC_NAME "__ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE"
#define WEBCORE PRIVATE_FRAMEWORKS"/WebCore.framework/WebCore"
#define WEBCORE_FUNC_NAME "__ZNK7WebCore15ResourceRequest12nsURLRequestEv"
 
NSURLRequest* (*webcore_func)(void* something);
 
void (*webkit_func)(void* something, void* loader, unsigned long identifier,  void* request, const void** response);
 
 
MSHook(void, webkit_func, void* something, void* loader, unsigned long identifier,  void* request, const void** response) {
       
    NSURLRequest *nsRequest = webcore_func(request);
    //do something
    _webkit_func(something, loader, identifier, request, response);
}
 
template <typename Type_>
static void nlset(Type_ &function, struct nlist *nl, size_t index) {
    struct nlist &name(nl[index]);
    uintptr_t value(name.n_value);
    if ((name.n_desc & N_ARM_THUMB_DEF) != 0)
        value |= 0x00000001;
    function = reinterpret_cast<Type_>(value);
}
 
//later, in your %ctor
 
dlopen(WEBKIT, RTLD_LAZY | RTLD_NOLOAD);
struct nlist nl[2];
bzero(&nl, sizeof(struct nlist) * 2);
nl[0].n_un.n_name = (char*)WEBKIT_FUNC_NAME;
 
dlopen(WEBCORE, RTLD_LAZY | RTLD_NOLOAD);
struct nlist nl2[2];
bzero(&nl2, sizeof(struct nlist) * 2);
nl2[0].n_un.n_name = (char*)WEBCORE_FUNC_NAME;
if(nlist(WEBKIT, nl) < 0 || nl[0].n_type == N_UNDF)
{
    fprintf(stderr, "\n nlist(%s, %s) failed\n", "WebKit", nl[0].n_un.n_name);
}
else if (nlist(WEBCORE, nl2) < 0 || nl2[0].n_type == N_UNDF)
{
{
     fprintf(stderr, "\n nlist(%s, %s) failed\n", "WebCore", nl2[0].n_un.n_name);
     void *pool = NSPushAutoreleasePool(0);
}
     [[[NSObject alloc] init] autorelease];
else
    NSPopAutoreleasePool(pool);
{
     nlset(webcore_func, nl2, 0);
    nlset(webkit_func, nl, 0);
    MSHookFunction(webkit_func, MSHake(webkit_func));
}
</source>
 
Because we want the pointer to a private symbol we have to use nlist. However you should not be doing it this way, as it is not portable to other platforms and later versions of iOS. [http://www.cydiasubstrate.com/api/c/MSGetImageByName MSGetImageByName] and [http://www.cydiasubstrate.com/api/c/MSFindSymbol/ MSFindSymbol] provide the same functionality and are portable.
 
== MobileLoader ==
 
MobileLoader loads 3rd-party patching code into the running application.
 
MobileLoader will first load itself into the run application using [http://koichitamura.blogspot.com/2008/11/hooking-library-calls-on-mac.html <tt>DYLD_INSERT_LIBRARIES</tt>] environment variable. Then it looks for all dynamic libraries in the directory <tt>/Library/MobileSubstrate/DynamicLibraries/</tt>, and dlopen them. An extension should use constructor code to perform any works, e.g.
<source lang="objc">
...
// The attribute forces this function to be called on load.
__attribute__((constructor))
static void initialize() {
  NSLog(@"MyExt: Loaded");
  MSHookFunction(CFShow, replaced_CFShow, &original_CFShow);
}
}
</source>
</source>


=== Filters ===
The "capacity" argument of NSPushAutoreleasePool only serves as a hint. It is unused in the current implementation.
 
Developers may add filters to restrict whether the extension should be loaded or not. Filters are implemented as plist that lives beside the dylib. If the dylib is named <tt>foo.dylib</tt>, then the filter should be named <tt>foo.plist</tt>. The filter should be a dictionary with key '''Filter''', which is another dictionaries that can contain these keys:
* '''CoreFoundationVersion''' (array): The extension is loaded only if the version of [[CoreFoundation.framework]] is above the specified values. Currently, only the first 2 values are checked.
{{CoreFoundation Version Table|center=1}}
* '''Bundles''' (array): The extension is loaded only if the bundle-ID of the running application matches the list.
* '''Classes''' (array): The extension is loaded only if the one of the specified objective-C classes is implemented in the application.
* '''Executables''' (array): The extension is loaded only if one of the executable names matches the running application. This is required to hook things that have no other identifiable characteristics.
 
For example, to restrict the extension only load in {{applink|SpringBoard}}, the plist would look like
Filter = {
  Bundles = (com.apple.springboard);
};
 
You can also use this method to restrict the extension to only load into applications that link to a specific bundle, such as UIKit. For example:
Filter = {
  Bundles = (com.apple.UIKit);
};
 
 
You can use CoreFoundationVersion key and specify lower- and upper-bounds. When two values are in the array, the first is treated as greater-than-or-equal-to rule, while the second is a less-than rule. The following example shows loading restricted to firmwares from 4.0 to 4.3 only:
 
Filter = {
  CoreFoundationVersion = (550.32, 675.00);
};
 
In general the rule is that if you are using more than one filter (example: Executables and Bundle) all filters have to match. You can change that by using Mode = "Any".
 
Filter = {
  Executables = ("mediaserverd");
  Bundles = ( "com.apple.MobileSMS", "net.whatsapp.WhatsApp" );
  Mode = "Any";
};
 
As of iOS 9.0, the filter plist ''must'' exist. Dylibs without a corresponding plist will not be loaded. To replicate the previous effect of no filter plist causing the dylib to be loaded into all processes, set your filter to the bundle <code>com.apple.Security</code>.
 
For setuid apps, since almost all environment variables are discarded, the developer of the app must explicitly perform <code>dlopen("/Library/MobileSubstrate/MobileSubstrate.dylib", RTLD_LAZY)</code> within <code>main()</code> to let MobileLoader run. This is not recommended, since most extensions do not expect to be running within a root process, and can have unexpected behavior (such as writing a file with permissions that would disallow it from being read by non-root processes). It would be a better choice to design the app to run as mobile, with a helper process performing root operations.
 
In addition, MobileLoader also hooks <tt>nlist()</tt> to improve its performance, and defines several signal handlers for safe mode.
 
== Safe mode ==
 
When a extension crashed the SpringBoard, MobileLoader will catch that and put the device into safe mode. In safe mode all 3rd-party extensions will be disabled.
 
The following signals will invoke safe mode:
* SIGABRT
* SIGILL
* SIGBUS
* SIGSEGV
* SIGSYS
 
== External links ==
 
* Safemode source: http://gitweb.saurik.com/safemode-ios.git (git://git.saurik.com/safemode-ios.git)


{{Navbox HookingLibs}}
{{occlass|library=Foundation.framework}}
{{Navbox Library}}
[[Category:Directories in /Library]]

Revision as of 09:40, 2 February 2017

The NSAutoreleasePool class is a thin wrapper around the NSPushAutoreleasePool and NSPopAutoreleasePool functions.

#ifdef __cplusplus
extern "C" {
#endif
void *NSPushAutoreleasePool(NSUInteger capacity);
void NSPopAutoreleasePool(void* token);
#ifdef __cplusplus
}
#endif

Example:

static void MyMethod()
{
    void *pool = NSPushAutoreleasePool(0);
    [[[NSObject alloc] init] autorelease];
    NSPopAutoreleasePool(pool);
}

The "capacity" argument of NSPushAutoreleasePool only serves as a hint. It is unused in the current implementation.