Cydia Substrate

From iPhone Development Wiki
Revision as of 06:29, 21 February 2013 by Dustin Howett (talk | contribs) (MobileSubstrate is 0.9.4001 now.)
Cydia Substrate
Cydia Package
Developer saurik
Package ID mobilesubstrate
Latest Version 0.9.4001


MobileSubstrate is the de facto framework that allows 3rd-party developers to provide run-time patches (“MobileSubstrate extensions”) to system functions, similar to Application Enhancer on the OS X.

MobileSubstrate 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:

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);

MSHookMessage() will replace the implementation of the Objective-C message -[class selector] 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 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 must be 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 the iPhoneOS by default a memory page cannot be simultaneously writable and executable, a kernel patch must be applied for MSHookFunction() to work.

As of the latest version of MobileSubstrate, MSHookMessage() also requires a kernel patch for supercall closures to hook all methods properly.

Example code

Using MSHookFunction:

static void (*original_CFShow)(CFTypeRef obj);  // a function pointer to store the original CFShow().
void replaced_CFShow(CFTypeRef obj) {           // our replacement of CFShow().
  printf("Calling original CFShow(%p)...", obj);
  original_CFShow(obj);                         // calls the original CFShow.
  printf(" done.\n");
}
...
// hook CFShow to our own implementation.
MSHookFunction(CFShow, replaced_CFShow, &original_CFShow);
// From now on any call to CFShow will pass through replaced_CFShow first.
...
CFShow(CFSTR("test"));

Using MSHookMessageEx:

static IMP original_UIView_setFrame_;
void replaced_UIView_setFrame_(UIView* self, SEL _cmd, CGRect frame) {  // Note the implicit self and _cmd parameters are needed explicitly here.
  CGRect originalFrame = self.frame;
  NSLog("Changing frame of %p from %@ to %@", self, NSStringFromCGRect(originalFrame), NSStringFromCGRect(frame));
  original_UIView_setFrame_(self, _cmd, frame);    // Remember to pass self and _cmd.
}
...
MSHookMessageEx([UIView class], @selector(setFrame:), (IMP)replaced_UIView_setFrame_, (IMP *)&original_UIView_setFrame_);
...
myView.frame = CGRectMake(0, 0, 100, 100);

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

MSHookMessageEx(objc_getMetaClass("UIView"), @selector(commitAnimations), replaced_UIView_commitAnimations, (IMP *)&original_UIView_commitAnimations);

Using MSHookFunction to hook private functions (in this case a C++ Method):

#define WebKit "/System/Library/PrivateFrameworks/WebKit.framework/WebKit"
#define WebCore "/System/Library/PrivateFrameworks/WebCore.framework/WebCore"

NSURLRequest* (*X_ZNK7WebCore15ResourceRequest12nsURLRequestEv)(void* something);

void (*X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE)(void* something, void* loader, unsigned long identifier,  void* request, const void** response);


MSHook(void, X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE, void* something, void* loader, unsigned long identifier,  void* request, const void** response) {
        
    NSURLRequest *nsRequest = X_ZNK7WebCore15ResourceRequest12nsURLRequestEv(request);
    //do something
    _X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE(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);
}

...

 dlopen(WebKit, RTLD_LAZY | RTLD_NOLOAD);
 dlopen(WebCore, RTLD_LAZY | RTLD_NOLOAD);

struct nlist nl[2];
        bzero(&nl, sizeof(struct nlist) * 2);
        nl[0].n_un.n_name = (char*)"__ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE";
        
        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 {
            struct nlist nlsucker[2];
            bzero(&nlsucker, sizeof(struct nlist) * 2);
            nlsucker[0].n_un.n_name = (char*)"__ZNK7WebCore15ResourceRequest12nsURLRequestEv";
            
            if (nlist(WebCore, nlsucker) < 0 || nlsucker[0].n_type == N_UNDF) {
                fprintf(stderr, "\n nlist(%s, %s) failed\n",
                        "WebCore",
                        nlsucker[0].n_un.n_name);
            }
            else {
                
                nlset(X_ZNK7WebCore15ResourceRequest12nsURLRequestEv, nlsucker, 0);    
            nlset(X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE, nl, 0);
            MSHookFunction(X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE,
MSHake(X_ZN20WebFrameLoaderClient23dispatchWillSendRequestEPN7WebCore14DocumentLoaderEmRNS0_15ResourceRequestERKNS0_16ResourceResponseE));
                }
        }

Because we want the pointer to a private symbol we have to use nlist.

MobileLoader

MobileLoader loads 3rd-party patching code into the running application.

MobileLoader will first load itself into the run application using DYLD_INSERT_LIBRARIES environment variable. Then it looks for all dynamic libraries in the directory /Library/MobileSubstrate/DynamicLibraries/, and dlopen them. An extension should use constructor code to perform any works, e.g.

...
// The attribute forces this function to be called on load.
__attribute__((constructor))
static void initialize() {
  NSLog(@"MyExt: Loaded");
  MSHookFunction(CFShow, replaced_CFShow, &original_CFShow);
}

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 foo.dylib, then the filter should be named foo.plist. 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.
Firmware CoreFoundation version
2.0 478.23
2.1 478.26.1
2.2 478.29
3.0 478.47.7
3.1 478.52
3.2 478.61
4.0 550.32
4.1 550.38.1
4.2 550.52
4.3 550.58.1
5.0 675.00
5.1 690.10
6.0 793.00
6.1 793.00
7.0 847.20
7.1 847.24
8.0 1140.10
8.1 1141.14
8.2 1142.16
8.3 1144.17
8.4 1145.15
9.0 1240.10
9.1 1241.11
9.2 1242.13
9.3 1280.38
10.0 1348.00
10.1 1348.00
10.2 1348.22
10.3 1349.56
11.0 1443.00
11.1 1445.32
11.2 1450.14
11.3 1452.23
11.4 1452.23
12.0 1556.00
12.1 1560.10
12.2 1570.15
12.3 1575.13
12.4 1575.17
12.5 1575.23
13.0 1665.15
13.1 1671.101
13.2 1673.126
13.3 1674.102
13.4 1675.129
13.5 1676.104
13.6 1677.104
13.7 1677.104
14.0 1751.108
14.1 1751.108
14.2 1770.106
14.3 1770.300
14.4 1774.101
14.5 1775.118
14.6 1776.103
14.7 1777.103
14.8 1778.101
15.0 1854
15.1 1855.105
15.2 1856.105
15.3 1856.105
15.4 1858.112
  • 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 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";
};


In addition, MobileLoader also hooks nlist() to improve its performance, and defines several signal handlers for safe mode.

For setuid apps, since all inserted environment variables are ignored, the developer of the App must explicitly dlopen("/Library/MobileSubstrate/MobileSubstrate.dylib") to let MobileLoader run.

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:

  • SIGTRAP
  • SIGABRT
  • SIGILL
  • SIGBUS
  • SIGSEGV
  • SIGSYS