Xiangxiang's Personal Site

Machine Learning & Security Engineer
生命不息,折腾不止,留下一点活着的记录.

View on GitHub
13 September 2025

How to develop an iOS tweak

by xiangxiang

Method Swizzling & fishhook & theos

0x00 Env Settings

theos

bash -c "$(px curl -fsSL https://raw.githubusercontent.com/theos/theos/master/bin/install-theos)"
$THEOS/bin/install-sdk iPhoneOS15.6

$THEOS/bin/nic.pl
# for TrollFools tweak, choose iphone/library

make
cp .theos/obj/debug/tweak.dylib /mnt/e/iOS-security/

FINALPACKAGE=1 make
cp .theos/obj/tweak.dylib /mnt/e/iOS-security/

VSCode

sudo apt-get install bear
make clean; bear -- make

dump IPA header

python -m venv ~/python-venv/iOS
source ~/python-venv/iOS/bin/activate
python -m pip install setuptools
python -m pip install --upgrade k2l
ktool dump --out ./headers --headers BINARY

0x01 Method Swizzling

1.1 Core Components of the Objective-C Runtime

Method swizzling directly manipulates this dispatch table

Class
    Dispatch Table
+---------------------+
|  SEL    |   IMP     |
|---------------------|
| method1    ptr_1 ---|---> actual method1 code
| method2    ptr_2    |
| ...                 | 
|                     |
|  init      ptr_init |
+---------------------+

Method
+---------------------------+
| method1(SEL) | (ptr1)IMP  |
|              |            |
+---------------------------+

1.2 Method Swizzling for tweak

No access to private class inside the target app(ld error)

NSString *originClassName;
NSString *originMethodName
Class originClass = NSClassFromString(originClassName);
SEL swizzledSel = NSSelectorFromString(newMethodName);

class_addMethod(originalClass, swizzledSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

Superclass

// Attempt to add the swizzled method to the class.
// If the original method is implemented in a superclass, this will add it to the current class.
BOOL didAddMethod = class_addMethod(cls,
                                    originalSelector,
                                    method_getImplementation(swizzledMethod),
                                    method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
    // If the method was added successfully, it means the original method was in a superclass.
    // We now need to replace the implementation of our swizzled method with the original (superclass's) implementation.
    class_replaceMethod(cls,
                        swizzledSelector,
                        method_getImplementation(originalMethod),
                        method_getTypeEncoding(originalMethod));
} else {
    // If the method already existed in this class, we can just exchange the implementations.
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

Class method

0x02 Fishhook

/*
 * A structure representing a particular intended rebinding from a symbol
 * name to its replacement
 */
struct rebinding {
  const char *name;  // 需要hook的函数名称(C字符串)
  void *replacement; // 新函数地址
  void **replaced;   // 原始函数地址的指针
};

/*
 * For each rebinding in rebindings, rebinds references to external, indirect
 * symbols with the specified name to instead point at replacement for each
 * image in the calling process as well as for all future images that are loaded
 * by the process. If rebind_functions is called more than once, the symbols to
 * rebind are added to the existing list of rebindings, and if a given symbol
 * is rebound more than once, the later rebinding will take precedence.
 */
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);

/*
 * Rebinds as above, but only in the specified image. The header should point
 * to the mach-o header, the slide should be the slide offset. Others as above.
 */
FISHHOOK_VISIBILITY
int rebind_symbols_image(void *header,
                         intptr_t slide,
                         struct rebinding rebindings[],
                         size_t rebindings_nel);
static int (*orig_close)(int);
int my_close(int fd) {
  return orig_close(fd);
}

struct rebinding close_rebinding;
close_rebinding.name = "close";
close_rebinding.replacement = my_close;
close_rebinding.replaced = (void *) &orig_close; // 注意为了修改我们需要传指针

rebind_symbols(struct rebinding[1]{close_rebinding}, 1);

原理

void hookXXXX(const char *libName, const char *funcName,
              void *replaceFun, void **origFun) {
    void *libHandle = RTLD_DEFAULT;
    if (libName) {
        libHandle = dlopen(libName, RTLD_NOW);
        if (!libHandle) {
            libHandle = RTLD_DEFAULT;
        }
    }
    void *pFunc = dlsym(libHandle, funcName);
    if (!pFunc) {
        NSLog(@"hook failed to find function %s", funcName);
        return;
    }
    uint32_t *pIns =
    (uint32_t *)ptrauth_strip(pFunc, ptrauth_key_function_pointer);
    NSLog(@"hook %s resolved pFunc -> %p -> %p", funcName, pFunc, pIns);
    if (origFun)
        *origFun = pFunc;
    if (rebind_symbols(
                       (struct rebinding[1]), 1) <
        0) {
        NSLog(@"Failed to do fishhook for %s!", funcName);
    }
}
tags: iOS security