動態(tài)修改 C 語言函數(shù)的實現(xiàn)

關(guān)注倉庫,及時獲得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

Objective-C 作為基于 Runtime 的語言魁索,它有非常強大的動態(tài)特性搂鲫,可以在運行期間自省躯砰、進行方法調(diào)劑辉阶、為類增加屬性、修改消息轉(zhuǎn)發(fā)鏈路翔脱,在代碼運行期間通過 Runtime 幾乎可以修改 Objecitve-C 層的一切類奴拦、方法以及屬性。

真正絕對意義上的動態(tài)語言或者靜態(tài)語言是不存在的届吁。

C 語言往往會給我們留下不可修改的這一印象错妖;在之前的幾年時間里,筆者確實也是這么認為的疚沐,然而最近接觸到的 fishhook 使我對 C 語言的不可修改有了更加深刻的理解暂氯。

在文章中涉及到一個比較重要的概念,就是鏡像(image)亮蛔;在 Mach-O 文件系統(tǒng)中痴施,所有的可執(zhí)行文件、dylib 以及 Bundle 都是鏡像尔邓。

fishhook 簡介

到這里晾剖,我們該簡單介紹一下今天分享的 fishhook;fishhook 是一個由 facebook 開源的第三方框架梯嗽,其主要作用就是動態(tài)修改 C 語言函數(shù)實現(xiàn)齿尽。

這個框架的代碼其實非常的簡單,只包含兩個文件:fishhook.c 以及 fishhook.h灯节;兩個文件所有的代碼加起來也不超過 300 行循头。

不過它的實現(xiàn)原理是非常有意思并且精妙的,我們可以從 fishhook 提供的接口中入手炎疆。

從接口開始

fishhook 提供非常簡單的兩個接口以及一個結(jié)構(gòu)體:

struct rebinding {
    const char *name;
    void *replacement;
    void **replaced;
};

int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);

int rebind_symbols_image(void *header,
                         intptr_t slide,
                         struct rebinding rebindings[],
                         size_t rebindings_nel);

其中 rebind_symbols 接收一個 rebindings 數(shù)組卡骂,也就是重新綁定信息,還有就是 rebindings_nel形入,也就是 rebindings 的個數(shù)全跨。

使用 fishhook 修改 C 函數(shù)

使用 fishhook 修改 C 函數(shù)很容易,我們使用它提供的幾個范例來介紹它的使用方法亿遂。

這里要修改的是底層的 open 函數(shù)的實現(xiàn)浓若,首先在工程中引入 fishhook.h 頭文件渺杉,然后聲明一個與原函數(shù)簽名相同的函數(shù)指針:

#import "fishhook.h"

static int (*origianl_open)(const char *, int, ...);

然后重新實現(xiàn) new_open 函數(shù):

int new_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);
    }
}

這里調(diào)用的 original_open 其實相當于執(zhí)行原 open;最后挪钓,在 main 函數(shù)中使用 rebind_symbols 對符號進行重綁定:

// 初始化一個 rebinding 結(jié)構(gòu)體
struct rebinding open_rebinding = { "open", new_open, (void *)&original_open };

// 將結(jié)構(gòu)體包裝成數(shù)組是越,并傳入數(shù)組的大小,對原符號 open 進行重綁定
rebind_symbols((struct rebinding[1]){open_rebinding}, 1);

// 調(diào)用 open 函數(shù)
__unused int fd = open(argv[0], O_RDONLY); 

在對符號進行重綁定之后碌上,所有調(diào)用 open 函數(shù)的地方實際上都會執(zhí)行 new_open 的實現(xiàn)倚评,也就完成了對 open 的修改。

fishhook-result

程序運行之后打印了 Calling real open('/Users/apple/Library/Developer/Xcode/DerivedData/Demo-cdnoozusghmqtubdnbzedzdwaagp/Build/Products/Debug/Demo', 0) 說明我們的對 open 函數(shù)的修改達到了預(yù)期的效果馏予。

整個 main.m 文件中的代碼在文章的最后面 main.m

fishhook 的原理以及實現(xiàn)

在介紹 fishhook 具體實現(xiàn)原理之前天梧,有幾個非常重要的知識需要我們了解,那就是 dyld吗蚌、動態(tài)鏈接以及 Mach-O 文件系統(tǒng)腿倚。

dyld 與動態(tài)鏈接

dyld 是 the dynamic link editor 的縮寫(筆者并不知道為什么要這么縮寫)。至于它的作用蚯妇,簡單一點說敷燎,就是負責將各種各樣程序需要的鏡像加載到程序運行的內(nèi)存空間中,這個過程發(fā)生的時間非常早 --- 在 objc 運行時初始化之前箩言。

在 dyld 加載鏡像時硬贯,會執(zhí)行注冊過的回調(diào)函數(shù);當然陨收,我們也可以使用下面的方法注冊自定義的回調(diào)函數(shù)饭豹,同時也會為所有已經(jīng)加載的鏡像執(zhí)行回調(diào)

extern void _dyld_register_func_for_add_image(
    void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)
);

對于每一個已經(jīng)存在的鏡像,當它被動態(tài)鏈接時务漩,都會執(zhí)行回調(diào) void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)拄衰,傳入文件的 mach_header 以及一個虛擬內(nèi)存地址 intptr_t

以一個最簡單的 Hello World 程序為例:

#include <stdio.h>

int main(int argc, const char * argv[]) {
    printf("Hello, World!\n");
    return 0;
}

代碼中只引用了一個 stdio 庫中的函數(shù) printf饵骨;我們?nèi)绻?Build 這段代碼翘悉,生成可執(zhí)行文件之后,使用下面的命令 nm

$ nm -nm HelloWorld

nm 命令可以查看可執(zhí)行文件中的符號(對 nm 不熟悉的讀者可以在終端中使用 man nm 查看手冊):

                 (undefined) external _printf (from libSystem)
                 (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100000f50 (__TEXT,__text) external _main

在可執(zhí)行文件中的符號列表中居触,_printf 這個符號是未定義(undefined)的妖混,換句話說,編譯器還不知道這個符號對應(yīng)什么東西轮洋。

但是制市,如果在文件中加入一個 C 函數(shù) hello_world

#include <stdio.h>

void hello_world() {
    printf("Hello, World!\n");
}

int main(int argc, const char * argv[]) {
    printf("Hello, World!\n");
    return 0;
}

在構(gòu)建之后,同樣使用 nm 查看其中的符號:

                 (undefined) external _printf (from libSystem)
                 (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100000f30 (__TEXT,__text) external _hello_world
0000000100000f50 (__TEXT,__text) external _main

我們的符號 _hello_world 并不是未定義的(undefined)弊予,它包含一個內(nèi)存地址以及 __TEXT 段祥楣。也就是說手寫的一些函數(shù),在編譯之后,其地址并不是未定義的荣堰,這一點對于之后分析 fishhook 有所幫助床未。

使用 nm 打印出的另一個符號 dyld_stub_binder 對應(yīng)另一個同名函數(shù)。dyld_stub_binder 會在目標符號(例如 printf)被調(diào)用時振坚,將其鏈接到指定的動態(tài)鏈接庫 libSystem,再執(zhí)行 printf 的實現(xiàn)(printf 符號位于 __DATA 端中的 lazy 符號表中):

fishhook-symbo

每一個鏡像中的 __DATA 端都包含兩個與動態(tài)鏈接有關(guān)的表斋扰,其中一個是 __nl_symbol_ptr渡八,另一個是 __la_symbol_ptr

  • __nl_symbol_ptr 中的 non-lazy 符號是在動態(tài)鏈接庫綁定的時候進行加載的
  • __la_symbol_ptr 中的符號會在該符號被第一次調(diào)用時,通過 dyld 中的 dyld_stub_binder 過程來進行加載
0000000100001010    dq  0x0000000100000f9c  ; XREF=0x1000002f8, imp___stubs__printf

地址 0x0000000100000f9c 就是 printf 函數(shù)打印字符串實現(xiàn)的位置:

fishbook-printf

在上述代碼調(diào)用 printf 時传货,由于符號是沒有被加載的屎鳍,就會通過 dyld_stub_binder 動態(tài)綁定符號。

Mach-O

由于文章中會涉及一些關(guān)于 Mach-O 文件格式的知識问裕,所以在這里會簡單介紹一下 Mach-O 文件格式的結(jié)構(gòu)逮壁。

每一個 Mach-O 文件都會被分為不同的 Segments,比如 __TEXT, __DATA, __LINKEDIT

fishhook-mach-o

這也就是 Mach-O 中的 segment_command(32 位與 64 位不同):

struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};

而每一個 segment_command 中又包含了不同的 section

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint64_t    addr;       /* memory address of this section */
    uint64_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* section alignment (power of 2) */
    uint32_t    reloff;     /* file offset of relocation entries */
    uint32_t    nreloc;     /* number of relocation entries */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};

你只需要對這幾個概念有一個簡單的了解粮宛,知道它們有怎樣的包含關(guān)系窥淆,當文章中挑出這個名字時,對它不是一無所知就足夠了巍杈,這里并不會涉及太多相關(guān)的知識忧饭。

fishhook 的原理

到目前為止,我們對 dyld 以及 Mach-O 有了一個初步的了解筷畦,而 fishhook 使用了前面章節(jié)提到的 _dyld_register_func_for_add_image 注冊了一個回調(diào)词裤,在每次加載鏡像到程序中執(zhí)行回調(diào),動態(tài)修改 C 函數(shù)實現(xiàn)鳖宾。

在具體分析其源代碼之前吼砂,先為各位讀者詳細地介紹它的實現(xiàn)原理:

dyld 通過更新 Mach-O 二進制文件 __DATA 段中的一些指針來綁定 lazy 和 non-lazy 的符號;而 fishhook 先確定某一個符號在 __DATA 段中的位置鼎文,然后保存原符號對應(yīng)的函數(shù)指針渔肩,并使用新的函數(shù)指針覆蓋原有符號的函數(shù)指針,實現(xiàn)重綁定漂问。

整個過程可以用這么一張圖來表示:

fishhook-before-afte

原理看起來還是很簡單的赖瞒,其中最復(fù)雜的部分就是從二進制文件中尋找某個符號的位置,在 fishhook 的 README 中蚤假,有這樣一張圖:

fishhook-imp

這張圖初看很復(fù)雜栏饮,不過它演示的是尋找符號的過程,我們根據(jù)這張圖來分析一下這個過程:

  1. __DATA 段中的 lazy 符號指針表中查找某個符號磷仰,獲得這個符號的偏移量 1061袍嬉,然后在每一個 section_64 中查找 reserved1,通過這兩個值找到 Indirect Symbol Table 中符號對應(yīng)的條目
  2. 在 Indirect Symbol Table 找到符號表指針以及對應(yīng)的索引 16343 之后,就需要訪問符號表
  3. 然后通過符號表中的偏移量伺通,獲取字符串表中的符號 _close

fishhook 的實現(xiàn)

上面梳理了尋找符號的過程箍土,現(xiàn)在,我們終于要開始分析 fishhook 的源代碼罐监,看它是如何一步一步替換原有函數(shù)實現(xiàn)的吴藻。

對實現(xiàn)的分析會 rebind_symbols 函數(shù)為入口,首先看一下函數(shù)的調(diào)用棧:

int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
└── extern void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide));

static void _rebind_symbols_for_image(const struct mach_header *header, intptr_t slide)
└── static void rebind_symbols_for_image(struct rebindings_entry *rebindings, const struct mach_header *header, intptr_t slide)
    └── 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)

其實函數(shù)調(diào)用棧非常簡單弓柱,因為整個庫中也沒有幾個函數(shù)沟堡,rebind_symbols 作為接口,其主要作用就是注冊一個函數(shù)并在鏡像加載時回調(diào):

int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
    int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
    if (retval < 0) return retval;

    if (!_rebindings_head->next) {
        _dyld_register_func_for_add_image(_rebind_symbols_for_image);
    } else {
        uint32_t c = _dyld_image_count();
        for (uint32_t i = 0; i < c; i++) {
            _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
        }
    }
    return retval;
}

rebind_symbols 最開始執(zhí)行時矢空,會先調(diào)用一個 prepend_rebindings 的函數(shù)航罗,將整個 rebindings 數(shù)組添加到 _rebindings_head 這個私有數(shù)據(jù)結(jié)構(gòu)的頭部:

static int prepend_rebindings(struct rebindings_entry **rebindings_head,
                              struct rebinding rebindings[],
                              size_t nel) {
    struct rebindings_entry *new_entry = malloc(sizeof(struct rebindings_entry));
    if (!new_entry) {
        return -1;
    }
    new_entry->rebindings = 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;
}

也就是說每次調(diào)用的 rebind_symbols 方法傳入的 rebindings 數(shù)組以及數(shù)組的長度都會以 rebindings_entry 的形式添加到 _rebindings_head 這個私有鏈表的首部:

struct rebindings_entry {
    struct rebinding *rebindings;
    size_t rebindings_nel;
    struct rebindings_entry *next;
};

static struct rebindings_entry *_rebindings_head;

這樣可以通過判斷 _rebindings_head->next 的值來判斷是否為第一次調(diào)用,然后使用 _dyld_register_func_for_add_image_rebind_symbols_for_image 注冊為回調(diào)或者為所有存在的鏡像單獨調(diào)用 _rebind_symbols_for_image

static void _rebind_symbols_for_image(const struct mach_header *header, intptr_t slide) {
    rebind_symbols_for_image(_rebindings_head, header, slide);
}

_rebind_symbols_for_image 只是對另一個名字非常相似的函數(shù) rebind_symbols_for_image 的封裝屁药,從這個函數(shù)開始粥血,就到了重綁定符號的過程;不過由于這個方法的實現(xiàn)比較長酿箭,具體分析會分成三個部分并省略一些不影響理解的代碼:

static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
                                     const struct mach_header *header,
                                     intptr_t slide) {
    segment_command_t *cur_seg_cmd;
    segment_command_t *linkedit_segment = NULL;
    struct symtab_command* symtab_cmd = NULL;
    struct dysymtab_command* dysymtab_cmd = NULL;

    uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
    for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
        cur_seg_cmd = (segment_command_t *)cur;
        if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
            if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
                linkedit_segment = cur_seg_cmd;
            }
        } else if (cur_seg_cmd->cmd == LC_SYMTAB) {
            symtab_cmd = (struct symtab_command*)cur_seg_cmd;
        } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
            dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
        }
    }

    ...
}

這部分的代碼主要功能是從鏡像中查找 linkedit_segment symtab_commanddysymtab_command复亏;在開始查找之前,要先跳過 mach_header_t 長度的位置七问,然后將當前指針強轉(zhuǎn)成 segment_command_t蜓耻,通過對比 cmd 的值,來找到需要的 segment_command_t械巡。

在查找了幾個關(guān)鍵的 segment 之后刹淌,我們可以根據(jù)幾個 segment 獲取對應(yīng)表的內(nèi)存地址:

static void rebind_symbols_for_image(struct rebindings_entry *rebindings, const struct mach_header *header, intptr_t slide) {
    ...

    uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
    nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
    char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);

    uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
    
    ...
}

linkedit_segment 結(jié)構(gòu)體中獲得其虛擬地址以及文件偏移量,然后通過一下公式來計算當前 __LINKEDIT 段的位置:

slide + vmaffr - fileoff

類似地讥耗,在 symtab_command 中獲取符號表偏移量和字符串表偏移量有勾,從 dysymtab_command 中獲取間接符號表(indirect symbol table)偏移量,就能夠獲得符號表古程、字符串表以及間接符號表的引用了蔼卡。

  • 間接符號表中的元素都是 uint32_t *,指針的值是對應(yīng)條目 n_list 在符號表中的位置

  • 符號表中的元素都是 nlist_t 結(jié)構(gòu)體挣磨,其中包含了當前符號在字符串表中的下標

    struct nlist_64 {
        union {
            uint32_t  n_strx; /* index into the string table */
        } n_un;
        uint8_t n_type;        /* type flag, see below */
        uint8_t n_sect;        /* section number or NO_SECT */
        uint16_t n_desc;       /* see <mach-o/stab.h> */
        uint64_t n_value;      /* value of this symbol (or stab offset) */
    };
    
  • 字符串表中的元素是 char 字符

該函數(shù)的最后一部分就開啟了遍歷模式雇逞,查找整個鏡像中的 SECTION_TYPES_LAZY_SYMBOL_POINTERS 或者 S_NON_LAZY_SYMBOL_POINTERS 的 section,然后調(diào)用下一個函數(shù) perform_rebinding_with_section 來對 section 中的符號進行處理:

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);
    for (uint i = 0; i < section->size / sizeof(void *); i++) {
        uint32_t symtab_index = indirect_symbol_indices[i];
        uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
        char *symbol_name = strtab + strtab_offset;

        struct rebindings_entry *cur = rebindings;
        while (cur) {
            for (uint j = 0; j < cur->rebindings_nel; j++) {
                if (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:;
    }
}

該函數(shù)的實現(xiàn)的核心內(nèi)容就是將符號表中的 symbol_namerebinding 中的名字 name 進行比較茁裙,如果出現(xiàn)了匹配塘砸,就會將原函數(shù)的實現(xiàn)傳入 origian_open 函數(shù)指針的地址,并使用新的函數(shù)實現(xiàn) new_open 代替原實現(xiàn):

if (cur->rebindings[j].replaced != NULL &&
    indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
    *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; // 將原函數(shù)的實現(xiàn)傳入 original_open 函數(shù)指針的地址
}
indirect_symbol_bindings[i] = cur->rebindings[j].replacement; // 使用新的函數(shù)實現(xiàn) new_open 替換原實現(xiàn)

如果你理解了上面的實現(xiàn)代碼晤锥,該函數(shù)的其它代碼就很好理解了:

  1. 通過 indirect_symtab + section->reserved1 獲取 indirect_symbol_indices *掉蔬,也就是符號表的數(shù)組
  2. 通過 (void **)((uintptr_t)slide + section->addr) 獲取函數(shù)指針列表 indirect_symbol_bindings
  3. 遍歷符號表數(shù)組 indirect_symbol_indices * 中的所有符號表中廊宪,獲取其中的符號表索引 symtab_index
  4. 通過符號表索引 symtab_index 獲取符號表中某一個 n_list 結(jié)構(gòu)體,得到字符串表中的索引 symtab[symtab_index].n_un.n_strx
  5. 最后在字符串表中獲得符號的名字 char *symbol_name

到這里比較前的準備工作就完成了女轿,剩下的代碼會遍歷整個 rebindings_entry 數(shù)組箭启,在其中查找匹配的符號,完成函數(shù)實現(xiàn)的替換:

while (cur) {
    for (uint j = 0; j < cur->rebindings_nel; j++) {
        if (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;
}

在之后對某一函數(shù)的調(diào)用(例如 open)蛉迹,當查找其函數(shù)實現(xiàn)時傅寡,都會查找到 new_open 的函數(shù)指針;在 new_open 調(diào)用 origianl_open 時婿禽,同樣也會執(zhí)行原有的函數(shù)實現(xiàn)赏僧,因為我們通過 *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i] 將原函數(shù)實現(xiàn)綁定到了新的函數(shù)指針上。

實驗

fishhook 在 dyld 加載鏡像時扭倾,插入了一個回調(diào)函數(shù),交換了原有函數(shù)的實現(xiàn)挽绩;但是 fishhook 能否修改非動態(tài)鏈接庫膛壹,比如開發(fā)人員自己手寫的函數(shù)呢?我們可以做一個非常簡單的小實驗唉堪,下面是我們的 main.m 文件:

#import <Foundation/Foundation.h>
#import "fishhook.h"

void hello() {
    printf("hello\n");
}

static void (*original_hello)();

void new_hello() {
    printf("New_hello\n");
    original_hello();
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        struct rebinding open_rebinding = { "hello", new_hello, (void *)&original_hello };
        rebind_symbols((struct rebinding[1]){open_rebinding}, 1);
        hello();
    }
    return 0;
}

這里的函數(shù)實現(xiàn)非常的簡單模聋,相信也不需要筆者過多解釋了,我們直接運行這份代碼:

fishhook-hello

代碼中只打印了 hello唠亚,說明 fishhook 對這種手寫的函數(shù)是沒有作用的链方,如果在下面這里打一個斷點:

fishhook-hello-breakpoint

代碼并不會進這里,因為 hello 這個函數(shù)是包含在當前鏡像的灶搜,它只是從其它代碼地址跳轉(zhuǎn)到了當前函數(shù)實現(xiàn)祟蚀,這與我們調(diào)用外部庫時有很大的不同,當調(diào)用外部庫時割卖,我們需要 dyld 解決函數(shù)地址的問題前酿,但是函數(shù)在當前鏡像中卻并不需要 issue #25

小結(jié)

fishhook 的實現(xiàn)非常的巧妙鹏溯,但是它的使用也有一定的局限性罢维,在接觸到 fishhook 之前,從沒有想到過可以通過一種方式修改 C 函數(shù)的實現(xiàn)丙挽。

Reference

其它

main.m

#import <Foundation/Foundation.h>

#import "fishhook.h"

static int (*original_open)(const char *, int, ...);

int new_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 original_open(path, oflag, mode);
    } else {
        printf("Calling real open('%s', %d)\n", path, oflag);
        return original_open(path, oflag, mode);
    }
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        struct rebinding open_rebinding = { "open", new_open, (void *)&original_open };
        rebind_symbols((struct rebinding[1]){open_rebinding}, 1);
        __unused int fd = open(argv[0], O_RDONLY);
    }
    return 0;
}

關(guān)注倉庫肺孵,及時獲得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

原文鏈接: http://draveness.me/fishhook/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颜阐,隨后出現(xiàn)的幾起案子平窘,更是在濱河造成了極大的恐慌,老刑警劉巖瞬浓,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件初婆,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機磅叛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門屑咳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弊琴,你說我怎么就攤上這事兆龙。” “怎么了敲董?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵紫皇,是天一觀的道長。 經(jīng)常有香客問我,道長票摇,這世上最難降的妖魔是什么尼荆? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮铃剔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘查刻。我一直安慰自己键兜,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布穗泵。 她就那樣靜靜地躺著普气,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佃延。 梳的紋絲不亂的頭發(fā)上现诀,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音苇侵,去河邊找鬼赶盔。 笑死,一個胖子當著我的面吹牛榆浓,可吹牛的內(nèi)容都是我干的于未。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼陡鹃,長吁一口氣:“原來是場噩夢啊……” “哼烘浦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起萍鲸,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤闷叉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后脊阴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體握侧,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡蚯瞧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了品擎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片埋合。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖萄传,靈堂內(nèi)的尸體忽然破棺而出甚颂,到底是詐尸還是另有隱情,我是刑警寧澤秀菱,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布振诬,位于F島的核電站,受9級特大地震影響衍菱,放射性物質(zhì)發(fā)生泄漏赶么。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一脊串、第九天 我趴在偏房一處隱蔽的房頂上張望禽绪。 院中可真熱鬧,春花似錦洪规、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至从橘,卻和暖如春念赶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恰力。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工叉谜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人踩萎。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓停局,卻偏偏與公主長得像,于是被迫代替她去往敵國和親香府。 傳聞我的和親對象是個殘疾皇子董栽,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容