fishhook 源碼學(xué)習(xí)

Mach-O

什么是Mach-O

Mach-OMach Object文件格式的縮寫,是用于 iOS 和 macOS 的可執(zhí)行文件,目標(biāo)代碼,動(dòng)態(tài)庫等等多種文件類型的的文件格式。

Mach-O文件格式

蘋果官方給了一張結(jié)構(gòu)圖:

我們編寫一個(gè)HelloWorld程序,將其編譯,然后通過MachOView來打開.out文件:

可以知道Mach-O由三部分組成:

  • Header:指明了CPU架構(gòu)、文件類型、Load Commands 個(gè)數(shù)等一些基本信息。
  • Load Commands:描述了怎樣加載每個(gè) Segment 的信息。在 Mach-O 文件中可以有多個(gè) Segment,每個(gè) Segment 可能包含零個(gè)、一個(gè)或多個(gè) Section。
  • Data:Segment 的具體數(shù)據(jù),包含了代碼和數(shù)據(jù)等。

Header

/*
 * The 32-bit mach header appears at the very beginning of the object file for
 * 32-bit architectures.
 */
struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};

/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures.
 */
struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};
  • magic:魔數(shù),0xfeedface是32位,0xcefaedfe是64位
  • cputype:CPU類型
  • cpusubtype:CPU具體類型
  • filetype:文件類型,例如可執(zhí)行文件、庫文件等
  • ncmds:Load Commands的數(shù)量
  • sizeofcmds:Load Commands的總大小
  • flags:標(biāo)志位,用于描述該文件的詳細(xì)加載信息
  • reserved:64位才有的保留字段,暫時(shí)沒用

對(duì)于上面的HelloWorld程序來說,它的Header信息如下:

這里有一點(diǎn)值得注意:上面的定義中,flags是一個(gè)int值,但是怎么能夠存儲(chǔ)這么多類型?其實(shí)巧妙運(yùn)用了宏定義值的特殊性,使得它們本身和它們和的組合具有唯一性,例如這里的flags值為00200085

#define MH_NOUNDEFS 0x1     /* the object file has no undefined
                       references */
#define MH_INCRLINK 0x2     /* the object file is the output of an
                       incremental link against a base file
                       and can't be link edited again */
#define MH_DYLDLINK 0x4     /* the object file is input for the
                       dynamic linker and can't be staticly
                       link edited again */

這里定義了0x1、0x2、0x4三個(gè)值,并且沒有定義0x5,flags最低位是5,那么表示同時(shí)有MH_NOUNDEFSMH_DYLDLINK兩個(gè)標(biāo)志位。

Load Commands

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};
  • cmd類型:指定command類型
  • cmdsize:表示command大小,用于計(jì)算到下一個(gè)command的偏移量
cmd 作用
LC_SEGMENT/LC_SEGMENT_64 將段內(nèi)數(shù)據(jù)加載映射到內(nèi)存中去
LC_SYMTAB 符號(hào)表信息
LC_DYSYMTAB 動(dòng)態(tài)符號(hào)表信息
LC_DYLD_INFO_ONLY 記錄地址重定向信息
LC_LOAD_DYLINKER 啟動(dòng)dyld
LC_UUID 唯一標(biāo)識(shí)符
LC_SOURCE_VERSION 源代碼版本
LC_MAIN 程序入口
LC_LOAD_DYLIB 加載動(dòng)態(tài)庫
LC_FUNCTION_STARTS 函數(shù)符號(hào)表
LC_DATA_IN_CODE Data注入代碼地址
LC_CODE_SIGNATURE 代碼簽名信息
  1. 程序在構(gòu)建時(shí)會(huì)指定加載的基地址,但是無法保證基地址的唯一性,也無法保證映像的地址區(qū)間不重疊。

  2. iOS采用了ASLR(Address space layout randomization)技術(shù),使得每個(gè)程序加載時(shí)的基地址隨機(jī)化。

這兩個(gè)原因?qū)е鲁绦蚣虞d到內(nèi)存時(shí),真實(shí)的基地址和構(gòu)建時(shí)指定的基地址是不同的,因此需要進(jìn)行地地址的重定向,LC_DYLD_INFO記錄的就是相關(guān)信息。

segment

首先看看segment的定義:

struct segment_command { /* for 32-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT */
    uint32_t    cmdsize;    /* includes sizeof section structs */
    char        segname[16];    /* segment name */
    uint32_t    vmaddr;     /* memory address of this segment */
    uint32_t    vmsize;     /* memory size of this segment */
    uint32_t    fileoff;    /* file offset of this segment */
    uint32_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 */
};
  • cmd:上面提到的Load Command類型
  • cmdsize:Load Command大小
  • segname[16]:段名稱
segname 含義
__PAGEZERO 可執(zhí)行文件捕獲空指針的段
__TEXT 代碼段和只讀數(shù)據(jù)
__DATA 全局變量和靜態(tài)變量
__LINKEDIT 包含動(dòng)態(tài)鏈接器所需的符號(hào)、字符串表等數(shù)據(jù)
  • vmaddr:段虛擬地址(未重定向),真實(shí)虛擬地址要加上ASLR的偏移量(隨機(jī)地址防御溢出攻擊)
  • vmsize:段的虛擬地址大小
  • fileoff:段在文件內(nèi)的地址偏移
  • filesize:段在文件內(nèi)的大小
  • nsects:段內(nèi)section數(shù)量
  • flags:標(biāo)志位,用于描述詳細(xì)信息

將segment內(nèi)容加載到內(nèi)存的過程,就是從文件偏移fileoff處,將大小為filesize的段,加載到虛擬機(jī)vmaddr處。

程序在構(gòu)建時(shí)的基地址,可以從第一個(gè)__TEXT代碼段中的vmaddr獲取。而真實(shí)的的基地址,就是header指針指向的地址。

section

section的定義:

struct section { /* for 32-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint32_t    addr;       /* memory address of this section */
    uint32_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) */
};
  • sectname:section名稱
  • segname:所屬的segment名稱
    (大寫的__TEXT代表segment,小寫的__text代表section
sectname 含義
__text 源代碼對(duì)應(yīng)的機(jī)器指令
__subs 樁代碼
__stub_helper 用于動(dòng)態(tài)鏈接,啟動(dòng)dyld
__cstring 硬編碼的C字符串
__la_symbol_ptr 延遲加載
__data 初始化的可變的變量
  • addr:section在內(nèi)存中的地址
  • size:section大小
  • offset:section在文件中的偏移
  • align:內(nèi)存對(duì)齊邊界
  • reloff:重定位入口在文件中的偏移(目前沒有找到實(shí)際用處)
  • nreloc:重定位入口數(shù)量
  • flags:標(biāo)志位,記錄type(互斥)和attributes (多個(gè)共存)
  • reserved:保留位,reserved1在下面非常有用

fishhook

什么是fishhook

fishhook 是一個(gè)由Facebook開源的框架,可以動(dòng)態(tài)修改鏈接 Mach-O 符號(hào)表。

demo

我們用這樣一段C代碼來演示:

#include <stdio.h>
#include <string.h>
#include "fishhook.h"

static int (*original_strlen)(const char *_s);

int new_strlen(const char *_s) {
    return 666;
}

int main(int argc, const char * argv[]) {

    struct rebinding strlen_rebinding = {
        "strlen",
        new_strlen,
        (void *)&original_strlen
    };
    rebind_symbols((struct rebinding[1]){strlen_rebinding}, 1);

    char *str = "Hello Fishhook!";
    printf("%d\n", strlen(str));
    return 0;
}

首先我們構(gòu)造了一個(gè)和原函數(shù)簽名相同的函數(shù)指針*original_strlen,然后重新實(shí)現(xiàn)了新的new_strlen函數(shù)。在main函數(shù)中,創(chuàng)建了一個(gè)rebinding結(jié)構(gòu)體,分別傳入了需要hook的函數(shù)名,新實(shí)現(xiàn)的函數(shù),與原函數(shù)簽名相同的函數(shù)指針。最后通過rebind_symbols進(jìn)行符號(hào)的重新綁定,運(yùn)行時(shí)就會(huì)輸出666。

源碼分析

結(jié)構(gòu)體定義

首先來看一看rebinding的定義:

struct rebinding {
  const char *name; // 需要hook的函數(shù)名
  void *replacement; // 新函數(shù)的實(shí)現(xiàn)
  void **replaced; // 指向“原函數(shù)”的函數(shù)指針
};

這里通過函數(shù)名和一個(gè)函數(shù)簽名,可以確定需要hook的是哪個(gè)函數(shù),然后用新函數(shù)代替它,儲(chǔ)存這些信息的數(shù)據(jù)結(jié)構(gòu)就是一個(gè)rebinding

struct rebindings_entry {
  struct rebinding *rebindings; // 數(shù)組實(shí)例
  size_t rebindings_nel; // 元素?cái)?shù)量
  struct rebindings_entry *next; // 鏈表索引
};

// 全局靜態(tài)變量,記錄表頭
static struct rebindings_entry *_rebindings_head;

一個(gè)rebindings_entry可以理解為一次hook時(shí)的信息入口,它存儲(chǔ)了一個(gè)rebinding數(shù)組、數(shù)組元素的數(shù)量和下一個(gè)節(jié)點(diǎn)。所以這里維護(hù)的是一個(gè)rebindings_entry鏈表,有多少次hook,鏈表就有多少個(gè)節(jié)點(diǎn)。

rebind_symbols

直接調(diào)用的函數(shù)實(shí)現(xiàn):

int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
  // prepend_rebindings方法完成了rebindings_entry鏈表的初始化
  int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
  // 返回-1則表示失敗
  if (retval < 0) {
    return retval;
  }
  // If this was the first call, register callback for image additions (which is also invoked for
  // existing images, otherwise, just run on existing images
  if (!_rebindings_head->next) {
    // 若第一次進(jìn)行方法替換,則將此方法注冊(cè)到dyld中去,之后的每次替換都會(huì)調(diào)用該方法
    _dyld_register_func_for_add_image(_rebind_symbols_for_image);
  } else {
    // 如果不是第一次替換符號(hào),則遍歷已經(jīng)加載的動(dòng)態(tài)庫
    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是使用fishhook的入口函數(shù),它有兩個(gè)作用,一是調(diào)用prepend_rebindings進(jìn)行數(shù)據(jù)結(jié)構(gòu)的初始化,二是注冊(cè)了_rebind_symbols_for_image函數(shù)并在新映像加載時(shí)回調(diào)。如果調(diào)用_dyld_register_func_for_add_image時(shí),系統(tǒng)已經(jīng)加載了某些映像,則會(huì)分別調(diào)用它們注冊(cè)的回調(diào)函數(shù)。也就是說,在加載、卸載映像,以及為映像注冊(cè)回調(diào)函數(shù)時(shí),回調(diào)函數(shù)都會(huì)被調(diào)用,所以這個(gè)函數(shù)通常被用來監(jiān)控映像和統(tǒng)計(jì)系統(tǒng)數(shù)據(jù)。

prepend_rebindings 初始化


/*
 該方法用于維護(hù)rebindings_entry
 struct rebindings_entry **rebindings_head -> static *_rebindings_head
 struct rebinding rebindings[] -> 傳入的方法符號(hào)數(shù)組
 size_t nel -> 數(shù)組的元素?cái)?shù)量
*/
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
                              struct rebinding rebindings[],
                              size_t nel) {
  // 聲明rebindings_entry指針,分配空間
  struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
  // 分配空間失敗
  if (!new_entry) {
    return -1;
  }
  // 為鏈表元素的rebindings分配空間
  new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel);
  // 分配空間失敗時(shí),釋放new_entry
  if (!new_entry->rebindings) {
    free(new_entry);
    return -1;
  }
  // 將傳入的rebindings數(shù)組,copy到new_entry->rebindings中
  memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
  // 為new_entry->rebindings_nel賦值
  new_entry->rebindings_nel = nel;
  // 為new_entry->next賦值,以維護(hù)反向鏈表結(jié)構(gòu)
  new_entry->next = *rebindings_head;
  // 將static的*rebindings_head指針指向表頭
  *rebindings_head = new_entry;
  return 0;
}

prepend_rebindings初始化了一個(gè)新的rebindings_entry節(jié)點(diǎn)并插入鏈表頭部。如果在一個(gè)程序中多次調(diào)用rebind_symbols來hook函數(shù),就有多個(gè)rebinding數(shù)組需要維護(hù),rebindings_entry維護(hù)的是一個(gè)反向鏈表,每個(gè)節(jié)點(diǎn)都維護(hù)一個(gè)rebinding數(shù)組,通過鏈表可以判斷是否是第一次hook。

_rebind_symbols_for_image 準(zhǔn)備基址

// 入口方法,目的是滿足回調(diào)函數(shù)的簽名格式;intprt_t是符合平臺(tái)標(biāo)準(zhǔn)字長(zhǎng)的整型指針
static void _rebind_symbols_for_image(const struct mach_header *header,
                                      intptr_t slide) {
    // 真正調(diào)用的函數(shù)
    rebind_symbols_for_image(_rebindings_head, header, slide);
}

調(diào)用_dyld_register_func_for_add_image進(jìn)行注冊(cè)時(shí),需要滿足特定的回調(diào)函數(shù)簽名格式。

// 準(zhǔn)備基址
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
                                     const struct mach_header *header,
                                     intptr_t slide) {
  Dl_info info;
  if (dladdr(header, &info) == 0) {
    return;
  }

  // 聲明保留查找量:
  segment_command_t *cur_seg_cmd;
  segment_command_t *linkedit_segment = NULL;
  struct symtab_command* symtab_cmd = NULL;
  struct dysymtab_command* dysymtab_cmd = NULL;

  // header = 0x100000000 -> 二進(jìn)制文件基址默認(rèn)偏移
  // sizeof(mach_header_t) = 0x20 -> Mach-O Header 部分
  // 初始化游標(biāo),跳過Mach-O Header
  uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
  // 第一次遍歷Load Command,目的是找到上面的查找量,cur每次偏移Load Command的大小
  // 可以計(jì)算出 Base Address、Symbol Table、Dynamic Symbol 和 String Table
  for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
    // 獲得當(dāng)前的Load Command
    cur_seg_cmd = (segment_command_t *)cur;
    if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
        // Load Command的類型是LC_SEGMENT
      if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
        // 找到SEG_LINKEDIT,即Load Command的name為"__LINKEDIT"
        linkedit_segment = cur_seg_cmd;
      }
    } else if (cur_seg_cmd->cmd == LC_SYMTAB) {
      // Load Command的類型是LC_SYMTAB
      symtab_cmd = (struct symtab_command*)cur_seg_cmd;
    } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
      // Load Command的類型是LC_DYSYMTAB
      dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
    }
  }

  // 判空容錯(cuò)
  if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
      !dysymtab_cmd->nindirectsyms) {
    return;
  }

  // Find base symbol/string table addresses
  // base = segment真實(shí)地址(已計(jì)算ASLR偏移) - segment偏移地址
  // segment真實(shí)地址(已計(jì)算ASLR偏移) = segment虛擬地址(未計(jì)算ASLR偏移) + ASLR偏移量
  // base = ALSR偏移量 + segment虛擬地址 - segment偏移地址
  uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
  // symtab首地址 = base + symtab_cmd的symoff偏移地址
  // 注意:LC_SYMTAB和LC_DYSYMTAB的中所記錄的Offset都是基于__LINKEDIT段的
  nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
  // strtab首地址 = base + symtab_cmd的stroff偏移地址
  char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);

  // Get indirect symbol table (array of uint32_t indices into symbol table)
  // indirect_symtab首地址 = base + dysymtab_cmd的indirectsymoff偏移地址
  uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);

  // 游標(biāo)歸零復(fù)用
  cur = (uintptr_t)header + sizeof(mach_header_t);
  // 第二次遍歷,目的是找到LC_SEGMENT(__DATA)中 __nl_symbol_ptr和__la_symbol_ptr這兩個(gè)section
  // 可以確定lazy binding指針表和non lazy binding指針表在Dynamic Symbol中對(duì)應(yīng)的位置
  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) {
      // Load Command的類型是LC_SEGMENT
      if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
          strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
        // 過濾name不是SEG_DATA或者SEG_DATA_CONST的segment
        continue;
      }
      // 遍歷該segment下的section
      for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
        // 獲取section
        section_t *sect =
          (section_t *)(cur + sizeof(segment_command_t)) + j;
        // 和SECTION_TYPE取與,只保留后兩位,可判斷TYPE
        if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
          // 如果section類型為S_LAZY_SYMBOL_POINTERS,則進(jìn)行rebingding重寫操作
          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
        }
        if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
          // 如果section類型為S_NON_LAZY_SYMBOL_POINTERS
          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
        }
      }
    }
  }
}

這里對(duì)Load Commands進(jìn)行了兩次遍歷,第一次遍歷根據(jù)cmd找到了三個(gè)需要的segment:LC_SEGMENT__LINKEDIT、LC_SYMTAB、LC_DYSYMTAB,為第二次遍歷做好了地址計(jì)算的準(zhǔn)備;第二次遍歷找到了SECTION_TYPES_LAZY_SYMBOL_POINTERSS_NON_LAZY_SYMBOL_POINTERS的section,并調(diào)用perform_rebinding_with_section對(duì)section中的符號(hào)進(jìn)行處理。

perform_rebinding_with_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) {
  // 在Indirect Symbol Table中獲取符號(hào)表數(shù)組,利用了reserved1
  uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
  // 獲取函數(shù)指針列表 __DATA.__nl_symbol_ptr(或__la_symbol_ptr) section
  void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
  // 遍歷section
  for (uint i = 0; i < section->size / sizeof(void *); i++) {
    // 通過偏移“索引”獲取Indirect Address的Value
    uint32_t symtab_index = indirect_symbol_indices[i];
    // 過濾INDIRECT_SYMBOL_ABS和INDIRECT_SYMBOL_LOCAL
    if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
        symtab_index == (INDIRECT_SYMBOL_LOCAL   | INDIRECT_SYMBOL_ABS)) {
      continue;
    }
    // 獲取符號(hào)名在符號(hào)表的偏移地址
    uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
    // 獲取符號(hào)名
    char *symbol_name = strtab + strtab_offset;
    // 符號(hào)名長(zhǎng)度小于1時(shí)過濾
    bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
    // 取出rebindings_entry鏈表頭部
    struct rebindings_entry *cur = rebindings;
    while (cur) {
      // 遍歷rebindings_entry中的rebindings鏈表,匹配符號(hào)名和方法名
      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) {
            // 記錄跳轉(zhuǎn)地址
            *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
          }
          // 重寫跳轉(zhuǎn)地址
          indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
          goto symbol_loop;
        }
      }
      cur = cur->next;
    }
  symbol_loop:;
  }
}

這個(gè)查找匹配的過程描述起來比較繞口,首先通過符號(hào)在__la_symbol_prt的index,加上Load Command中__la_symbol_prt的保留信息reserved1,得到了Indirect Symbols中位于index + reversed1 的數(shù)據(jù)index2,然后在Symbol Table中index2的位置拿到偏移地址offset,最后拿到String Table中offset處的數(shù)據(jù),這個(gè)數(shù)據(jù)就是函數(shù)名。如果函數(shù)名匹配,則更改__la_symbol_ptr表中的函數(shù)地址,完成hook。

總結(jié)

程序啟動(dòng)時(shí),會(huì)鏈接很多動(dòng)態(tài)庫,函數(shù)的調(diào)用就是通過指令跳轉(zhuǎn)到函數(shù)對(duì)應(yīng)的內(nèi)存地址。因?yàn)閯?dòng)態(tài)庫是運(yùn)行后開始鏈接,所以程序并不知道函數(shù)在哪里,所以這些函數(shù)放在__DATA,__la_symbol_prt表中。

例如我們現(xiàn)在要調(diào)用printf函數(shù),表中相應(yīng)內(nèi)容并不會(huì)直接指向printf,而是指向了dyld_stub_binder,它的作用就是計(jì)算真正的printf地址,并且將表中的指針指向修改,這樣下次就可以直接調(diào)用printf了,這就是懶加載。

而fishhook做的工作,就是在dyld綁定了地址之后再次做一個(gè)重綁定,200行左右的代碼,只有一行是在修改函數(shù)指針,最復(fù)雜的邏輯主要是在計(jì)算地址和匹配字符串。hook的局限性,就是只能修改__la_symbol_ptr表中的函數(shù)指向,也就是不能hook靜態(tài)庫中的函數(shù)和自定義的函數(shù)。安全方面,我們可以通過替換函數(shù)的地址是否在映像內(nèi),來判斷是否是惡意程序注入的hook。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 引用 Mac OS X ABI Mach-O File Format Reference 前言 fishhook ...
    取水閱讀 1,536評(píng)論 1 4
  • 原文地址:https://niyaoyao.github.io/2016/07/29/Objective-C-Ru...
    mengjz閱讀 591評(píng)論 0 0
  • 關(guān)鍵時(shí)刻,第一時(shí)間送達(dá)! 問題種類 時(shí)間復(fù)雜度 在集合里數(shù)據(jù)量小的情況下時(shí)間復(fù)雜度對(duì)于性能的影響看起來微乎其微。但...
    C9090閱讀 1,024評(píng)論 0 1
  • 13. Hook原理介紹 13.1 Objective-C消息傳遞(Messaging) 對(duì)于C/C++這類靜態(tài)語...
    Flonger閱讀 1,535評(píng)論 0 3
  • 前言 fishhook是fackbook開源的一個(gè)用來hook c函數(shù)的庫。在iOS開發(fā)中我們一般都是對(duì)OC方法進(jìn)...
    初心丶可曾記閱讀 1,633評(píng)論 6 4

友情鏈接更多精彩內(nèi)容