|
|
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 • [[MobileSubstrate/fr|français]] • [[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]]
| |