Mobile Development 22 min read

Understanding Hook Techniques and Fishhook Implementation on iOS

This article explains the fundamentals of hooking on iOS, covering Method Swizzle and Facebook's fishhook, detailing the Mach‑O file structure, PIC technique, and providing complete source code examples that demonstrate how to replace system functions such as NSLog at runtime.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Understanding Hook Techniques and Fishhook Implementation on iOS

In the context of iOS reverse engineering, Hook technology allows developers to alter a program's execution flow, enabling custom code to run inside another app; mastering Hook concepts is essential before measuring app startup performance.

Two primary Hook schemes are discussed: Method Swizzle , which leverages Objective‑C runtime to swap SEL and IMP mappings, and fishhook , a Facebook‑provided library that can intercept static C functions by modifying Mach‑O symbol tables.

fishhook works by exploiting the Mach‑O loader’s lazy and non‑lazy symbol pointer tables. When a Mach‑O binary is loaded, dyld creates placeholder pointers in the __DATA segment; fishhook rewrites these pointers to point to user‑defined implementations, effectively hooking C functions despite their static nature.

Below is the core fishhook source code that performs the rebinding of symbols. The code is reproduced verbatim and wrapped in a tag as required:

#import <dlfcn.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "fishhook.h"

static int (*orig_close)(int);
static int (*orig_open)(const char *, int, ...);

int my_close(int fd) {
    printf("Calling real close(%d)\n", fd);
    return orig_close(fd);
}

int my_open(const char *path, int oflag, ...) {
    va_list ap = {0};
    mode_t mode = 0;
    if ((oflag & O_CREAT) != 0) {
        // mode only applies to O_CREAT
        va_start(ap, oflag);
        mode = va_arg(ap, int);
        va_end(ap);
        printf("Calling real open('%s', %d, %d)\n", path, oflag, mode);
        return orig_open(path, oflag, mode);
    } else {
        printf("Calling real open('%s', %d)\n", path, oflag);
        return orig_open(path, oflag, mode);
    }
}

int main(int argc, char *argv[]) {
    @autoreleasepool {
        rebind_symbols((struct rebinding[2]){{"close", my_close, (void *)&orig_close}, {"open", my_open, (void *)&orig_open}}, 2);
        int fd = open(argv[0], O_RDONLY);
        uint32_t magic_number = 0;
        read(fd, &magic_number, 4);
        printf("Mach-O Magic Number: %x \n", magic_number);
        close(fd);
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

The library defines a linked list of rebinding structures, registers a callback with dyld, and walks through the Mach‑O load commands to locate the __LINKEDIT segment, symbol table, string table, and indirect symbol table. Functions such as prepend_rebindings , perform_rebinding_with_section , and rebind_symbols_for_image manipulate the pointer arrays ( __la_symbol_ptr and __nl_symbol_ptr ) to replace original implementations.

static int prepend_rebindings(struct rebindings_entry **rebindings_head,
                              struct rebinding rebindings[],
                              size_t nel) {
    struct rebindings_entry *new_entry = (struct rebindings_entry *)malloc(sizeof(struct rebindings_entry));
    if (!new_entry) {
        return -1;
    }
    new_entry->rebindings = (struct rebinding *)malloc(sizeof(struct rebinding) * nel);
    if (!new_entry->rebindings) {
        free(new_entry);
        return -1;
    }
    memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
    new_entry->rebindings_nel = nel;
    new_entry->next = *rebindings_head;
    *rebindings_head = new_entry;
    return 0;
}

static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
                                           section_t *section,
                                           intptr_t slide,
                                           nlist_t *symtab,
                                           char *strtab,
                                           uint32_t *indirect_symtab) {
    uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
    void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
    vm_prot_t oldProtection = VM_PROT_READ;
    if (isDataConst) {
        oldProtection = get_protection(rebindings);
        mprotect(indirect_symbol_bindings, section->size, PROT_READ | PROT_WRITE);
    }
    for (uint i = 0; i < section->size / sizeof(void *); i++) {
        uint32_t symtab_index = indirect_symbol_indices[i];
        if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
            symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
            continue;
        }
        uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
        char *symbol_name = strtab + strtab_offset;
        bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
        struct rebindings_entry *cur = rebindings;
        while (cur) {
            for (uint j = 0; j < cur->rebindings_nel; j++) {
                if (symbol_name_longer_than_1 &&
                    strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
                    if (cur->rebindings[j].replaced != NULL &&
                        indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
                        *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
                    }
                    indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
                    goto symbol_loop;
                }
            }
            cur = cur->next;
        }
    symbol_loop:;
    }
}

To illustrate practical usage, the article provides a concrete example that hooks the NSLog function. By defining a rebinding for NSLog , storing the original pointer, and supplying a custom implementation that appends a suffix, the author shows how the log output changes at runtime.

// rebinding structure definition
struct rebinding {
    const char *name;        // function name to hook
    void *replacement;       // pointer to new implementation
    void **replaced;         // pointer that will receive original function address
};

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"123");
    struct rebinding nslog;
    nslog.name = "NSLog";
    nslog.replacement = myNslog;
    nslog.replaced = (void *)&sys_nslog;
    struct rebinding rebs[1] = {nslog};
    rebind_symbols(rebs, 1);
}

static void (*sys_nslog)(NSString *format, ...);
void myNslog(NSString *format, ...) {
    format = [format stringByAppendingString:@"勾上了!\n"];
    sys_nslog(format);
}

Running the app shows that the original NSLog output is replaced by the custom message, confirming that fishhook successfully intercepts C‑level functions in an iOS binary. The author also demonstrates how to locate the function’s offset in the Mach‑O file using tools such as MachOView and how the lazy symbol pointer table maps to the actual implementation address.

In summary, the article provides a thorough walkthrough of Hook techniques, explains the underlying Mach‑O and PIC mechanisms that enable C‑function hooking, and supplies complete, ready‑to‑use source code for developers interested in iOS runtime manipulation and performance monitoring.

mobile developmentiOSMach-OReverse EngineeringHookFishhook
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.