From iPhone Development Wiki
Jump to: navigation, search

Cydia Package
Developer Lars Fröder (opa334)
Package ID com.opa334.libundirect
Latest Version 1.1.2

libundirect is a patchfinding and rebinding 64-bit library that provides directives to hook Objective-C direct methods. With the feature called objc_direct introduced in Xcode 12 and iOS 14.0, these direct methods are stripped when compiled - being unexported C functions and thus their signature and name are missing.

As a patchfinder, libundirect_find() can find the address of the function to hook by providing unique bytes within the function and the name of the binary or library that the function resides. You need to perform reverse engineering on the binary that you are working with in order to determine the bytes to use. See libundirect's note on patchfinding or johnzaro's guide to using libundirect for more details.

As a rebinder, libundirect_rebind() can re-add Objective-C method signature to the function that you hook. This provides backward compatibility for earlier iOS versions that the signature is still intact - and that you have to import <libundirect/libundirect_hookoverwrite.h> to make MSHookMessageEx() aware of direct methods and turn itself into MSHookFunction (because it is just a C-like function now).

How to use this library

Headers are available from libundirect's GitHub project (the latest one) or from Theos' headers project. The library from opa334's repository can be installed to $THEOS/vendor/lib by running script inside libundirect repository.

Include directive(s)

#import <libundirect/libundirect.h>
#import <libundirect/libundirect_dynamic.h> // if you don't want to link directly against libundirect, import this one instead
#import <libundirect/libundirect_hookoverwrite.h> // if you want backward compatibility


Add to your Makefile:

  • XXX_LIBRARIES = undirect


Add to your package's control file:

  • com.opa334.libundirect (>= 1.1.1) to the Depends field.


Hooking (and rebinding) a direct Objective-C method (MobileSafari)

We will use the code in SafariPlus by opa334 as an example.

#import <libundirect/libundirect.h>
#import <libundirect/libundirect_hookoverwrite.h>
#import <version.h>

%hook BrowserController

- (void)tabControllerDocumentCountDidChange:(TabController *)tabController {



// In your constructor:

if (IS_IOS_OR_NEWER(iOS_14_0)) {
    Class class_browserController = NSClassFromString(@"BrowserController");

    // -[BrowserController tabControllerDocumentCountDidChange:]
    // arm64 memory of function: [600100B4 F44FBEA9]
    // arm64e memory of function: [A00100B4 7F2303D5 F44FBEA9]
    // criteria: called by -[BrowserController _updateSceneTitle] (14.2: sub_100041e5c, references string @"Private Browsing")
    // 14.2: sub_10005bef8, arm64e: sub_10005E824

    #if __arm64e__
        void* tabControllerDocumentCountDidChange = libundirect_find(@"MobileSafari", (unsigned char[]){0xA0, 0x01, 0x00, 0xB4, 0x7F, 0x23, 0x03, 0xD5, 0xF4, 0x4F, 0xBE, 0xA9}, 12, 0);
        void* tabControllerDocumentCountDidChange = libundirect_find(@"MobileSafari", (unsigned char[]){0x60, 0x01, 0x00, 0xB4, 0xF4, 0x4F, 0xBE, 0xA9}, 8, 0);

    libundirect_rebind(tabControllerDocumentCountDidChange, class_browserController, @selector(tabControllerDocumentCountDidChange:), "[email protected]:@");


Hooking an unnamed C function (libicucore)

We will use the code in EmojiAttributes by PoomSmart as an example.

#import <libundirect/libundirect.h>


%group getUnicodeProperties

%hookf(uint32_t, u_getUnicodeProperties, UChar32 c, int32_t column) {
    if (column >= propsVectorsColumns)
        return 0;
    uint16_t vecIndex = UTRIE2_GET16(&propsVectorsTrie, c);
    return propsVectors[vecIndex + column];


// In your constructor:

    // Memory of function (iOS 13.5): 31C083FE 020F8F8D 00000055 4889E581 FFFFD700 00770789 F8C1E805 EB4A81FF FFFF0000 771731C0 81FF00DC 0000B940 0100000F 4DC889F8 C1E805EB 29B8D813 000081FF FFFF1000 773289F8 C1E80B48 8D0D30CC 1B000FB7 8C414010 000089F8 C1E80583 E03F01C8 89C0488D 0D15CC1B 000FB704 4183E71F 488D0487 488D0D03 CC1B000F B7044148 63CE4801 C1488D05 D2B11A00 8B04885D C3
    // Memory of function (iOS 12.4): 554889E5 31C083FE 020F8F8A 00000081 FFFFD700 00770789 F8C1E805 EB4C81FF FFFF0000 771731C0 81FF00DC 0000B940 0100000F 4DC889F8 C1E805EB 2B81FFFF FF100076 07B8D413 0000EB32 89F8C1E8 0B488D0D CFE61B00 0FB78C41 40100000 89F8C1E8 0583E03F 01C889C0 488D0DB4 E61B000F B7044183 E71F488D 0487488D 0DA2E61B 000FB704 414863CE 4801C148 8D0571D3 1A008B04 885DC3
    // Unique bytes (iOS 13.5): E03F01C8 89C0488D 0D15CC1B (offset: 100)
    // Unique bytes (iOS 12.4): 0583E03F 01C889C0 488D0DB4 (offset: 100)
    // Starting byte (iOS 13.5): 0x31
    // Starting byte (iOS 12.4): 0x55
    void *rp = libundirect_find(@"libicucore.A.dylib", (unsigned char[]){0xE0, 0x3F, 0x01, 0xC8, 0x89, 0xC0, 0x48, 0x8D, 0x0D, 0x15, 0xCC, 0x1B}, 12, 0x31);
    if (rp == NULL)
        rp = libundirect_find(@"libicucore.A.dylib", (unsigned char[]){0x05, 0x83, 0xE0, 0x3F, 0x01, 0xC8, 0x89, 0xC0, 0x48, 0x8D, 0x0D, 0xB4}, 12, 0x55);
    // Memory of function: 3F080071 6D000054 00008052 C0035FD6 087C0B53 1F690071 68000054 087C0513 08000014 087C1053 68020035 08809B52 1F00086B 08288052 08B19F1A 0815800B 090D00B0 29110F91 28D96878 09100012 2809080B 090D00B0 29110F91 28D96878 0801010B 890C00B0 29A12791 20D968B8 C0035FD6 1F410071 69000054 08548252 F5FFFF17 087C0B13 090D00B0 29110F91 28C5288B 08816079 0A280553 08412A8B 28796878 EAFFFF17
    // Unique bytes: 3F080071 6D000054 00008052 C0035FD6 (offset: 0)
    // Starting byte: 0x3F
    void *rp = libundirect_find(@"libicucore.A.dylib", (unsigned char[]){0x3F, 0x08, 0x00, 0x71, 0x6D, 0x00, 0x00, 0x54, 0x00, 0x00, 0x80, 0x52, 0xC0, 0x03, 0x5F, 0xD6}, 16, 0x3F);

%init(getUnicodeProperties, u_getUnicodeProperties = (void *)rp);

Project Examples

Project Author
SafariPlus opa334
SafariTabs14 udevsharold
SafariBlocker p2kdev
EmojiAttributes PoomSmart

External Links