通過(guò)lldb來(lái)說(shuō)符號(hào)表綁定與fishhook

fishhook棘利,facebook開(kāi)源的一個(gè)可以動(dòng)態(tài)綁定Mach-O符號(hào)表的庫(kù)。在程序啟動(dòng)時(shí)與運(yùn)行時(shí)會(huì)通過(guò)dyld來(lái)綁定符號(hào)表(這里有非懶加載與懶加載之分)唉地,而fishhook可以修改符號(hào)表的綁定。


驗(yàn)證

  • 非懶加載綁定
  • 懶加載綁定
非懶加載綁定

先來(lái)說(shuō)說(shuō)非懶加載綁定,我們最熟悉的objc_msgSend就是非懶加載綁定半夷。新建一個(gè)空的命令行項(xiàng)目:

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

NSLog(@"Hello, World!")處打上斷點(diǎn)婆廊,并在Debug Workflow中選中Always Show Disassembly,運(yùn)行程序后執(zhí)行如下操作:

image.png

可以看到slide的值為0巫橄,似乎命令行項(xiàng)目與iOS不同淘邻,并沒(méi)有使用ASLR技術(shù)。

image.png

通過(guò)MachOView可以看到湘换,objc_msgSend在MachO中的偏移量為0x2008宾舅,而由于slide為0,所以這里的虛擬地址0x100002008就是程序運(yùn)行后的真實(shí)地址彩倚,在控制臺(tái)中繼續(xù)執(zhí)行如下操作:

image.png
  1. 通過(guò)image lookup -a可以看到0x100002008地址對(duì)應(yīng)的確實(shí)是objc_msgSend筹我,并且得到函數(shù)指針0x00007fff6b167040,它存儲(chǔ)在Test.__DATA_CONST.__got
  2. 通過(guò)image lookup -n得到objc_msgSend所在動(dòng)態(tài)庫(kù)libobjc.A.dylib及偏移量0x0000000000006040
  3. 通過(guò)image list -o -f得到libobjc.A.dylib動(dòng)態(tài)庫(kù)首地址0x00007fff6b161000
  4. libobjc.A.dylib動(dòng)態(tài)庫(kù)首地址加上objc_msgSend偏移量得到objc_msgSend存儲(chǔ)地址0x00007fff6b167040

這與用image lookup -a命令看到的地址完全吻合帆离。

懶加載綁定
image.png

NSLog的偏移量為0x3000崎溃,同理,由于slide為0盯质,這里的虛擬地址0x100003000就是真實(shí)地址袁串。

image.png

可以看到NSLog符號(hào)存儲(chǔ)在Test.__DATA.__la_symbol_ptr中,但是這里得到的地址0x0000000100001eac與真實(shí)的NSLog存儲(chǔ)地址0x00007fff37702332并不相同呼巷,顯然在0x0000000100001eac處必定進(jìn)行了綁定操作囱修。

0x0000000100001eac處打上斷點(diǎn),繼續(xù)執(zhí)行程序王悍,得到如下所示匯編代碼:

image.png

這里會(huì)跳轉(zhuǎn)到0x100001e9c處破镰,繼續(xù)加斷點(diǎn)跟進(jìn):

image.png

這里調(diào)用了dyld_stub_binderNSLog的符號(hào)綁定就是通過(guò)這個(gè)函數(shù)完成的压储,繼續(xù)跟進(jìn)并在dyld_stub_binder對(duì)應(yīng)的匯編代碼末尾處打上斷點(diǎn)鲜漩,繼續(xù)運(yùn)行程序后做如下操作:

image.png
  1. 此時(shí)r11存儲(chǔ)的值為0x00007fff37702332
  2. 通過(guò)image lookup -a得到這個(gè)值就是NSLog的存儲(chǔ)地址
  3. 再次查看0x100003000處信息,此時(shí)已經(jīng)變成NSLog(之前為(void *)0x0000000100001eac

以上就是符號(hào)的非懶加載綁定與懶加載綁定集惋,順便說(shuō)一句孕似,懶加載綁定后已經(jīng)存在符號(hào)到函數(shù)的映射信息,再次調(diào)用相同函數(shù)時(shí)不存在這個(gè)綁定過(guò)程刮刑,而是直接調(diào)用喉祭。

fishhook

  • fishhook的簡(jiǎn)單使用
  • fishhook源碼解析
fishhook的簡(jiǎn)單使用

接著上文的NSLog繼續(xù)說(shuō):

void (*sys_log)(NSString *format, ...);

void my_log(NSString *format, ...) {
    sys_log([format stringByAppendingFormat:@"---hello fishhook---"]);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"before");
        
        struct rebinding context;
        context.name = "NSLog";
        context.replacement = &my_log;
        context.replaced = (void *)&sys_log;
        struct rebinding contexts[1] = {context};
        rebind_symbols(contexts, 1);
        
        NSLog(@"after");
    }
    return 0;
}

運(yùn)行程序后輸出如下:

image.png

顯然已經(jīng)成功的hook了NSLog函數(shù),下面來(lái)看fishhook是怎么做到的雷绢。

rebind_symbols(contexts, 1)NSLog(@"after")處打上斷點(diǎn)泛烙,運(yùn)行程序并作如下操作:

image.png

由于在此之前已經(jīng)調(diào)用過(guò)NSLog,所以懶加載符號(hào)綁定已經(jīng)完成翘紊,此時(shí)0x100003000對(duì)應(yīng)的就是NSLog的函數(shù)指針蔽氨。繼續(xù)運(yùn)行程序執(zhí)行rebind_symbols,在此查看0x100003000,此時(shí)此時(shí)綁定的地址變成了自定義的my_log函數(shù)鹉究,所以當(dāng)再次調(diào)用NSLog時(shí)中捆,通過(guò)符號(hào)表找到的是my_log,從而實(shí)現(xiàn)對(duì)NSLog的hook

fishhook源碼解析

先了解一下數(shù)據(jù)結(jié)構(gòu):

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

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

static struct rebindings_entry *_rebindings_head;

接著看Demo中調(diào)用的關(guān)鍵函數(shù)rebind_symbols

rebind_symbols
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 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) {
    _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;
}

這里首先調(diào)用了prepend_rebindings坊饶,這個(gè)函數(shù)是整個(gè)數(shù)據(jù)結(jié)構(gòu)的關(guān)鍵:

prepend_rebindings
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;
}

這里生成一個(gè)新的結(jié)構(gòu)體指針new_entry泄伪,將rebindingsnel扔到結(jié)構(gòu)體對(duì)應(yīng)的成員變量中,并將next指針指向上文提到的_rebindings_head處匿级,而此時(shí)的_rebindings_head值為NULL蟋滴,最后又將new_entry賦值給_rebindings_head钾挟。

顯然慎王,如果rebind_symbols函數(shù)調(diào)用多次,最終_rebindings_head是個(gè)單鏈表破讨,prepend_rebindings函數(shù)的作用就是將新的結(jié)構(gòu)體指針添加到鏈表頭部孤页,而這個(gè)單鏈表的尾部是NULL尔苦。

當(dāng)然,這里涉及到一些內(nèi)存申請(qǐng)操作行施,如果申請(qǐng)失敗返回-1允坚。

繼續(xù)看prepend_rebindings函數(shù)之后的代碼:

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

如果_rebindings_head->nextNULL也就是說(shuō)rebind_symbols函數(shù)是首次調(diào)用,此時(shí)注冊(cè)image的回調(diào)函數(shù)_rebind_symbols_for_image

_dyld_register_func_for_add_image

這個(gè)函數(shù)是dyld提供給我們的鏡像加載回調(diào)函數(shù)蛾号,對(duì)應(yīng)注釋如下:

/*
* The following functions allow you to install callbacks which will be called
* by dyld whenever an image is loaded or unloaded. During a call to _dyld_register_func_for_add_image()
* the callback func is called for every existing image. Later, it is called as each new image
* is loaded and bound (but initializers not yet run). The callback registered with
* _dyld_register_func_for_remove_image() is called after any terminators in an image are run
* and before the image is un-memory-mapped.
*/

顯然稠项,在調(diào)用_dyld_register_func_for_add_image及有新鏡像加載時(shí),這個(gè)注冊(cè)的回調(diào)函數(shù)會(huì)被每個(gè)已加載的鏡像回調(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);
}

回調(diào)函數(shù)直接調(diào)用rebind_symbols_for_image

rebind_symbols_for_image

這是fishhook的核心函數(shù):

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;

  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;
    }
  }

  if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
      !dysymtab_cmd->nindirectsyms) {
    return;
  }

  ...
}

先通過(guò)dladdr函數(shù)查看是否可以加載到mach_header *對(duì)應(yīng)的info展运,如果加載不到,直接return精刷。

這里說(shuō)一下Dl_info的數(shù)據(jù)結(jié)構(gòu):

typedef struct dl_info {
        const char      *dli_fname;     /* Pathname of shared object */
        void            *dli_fbase;     /* Base address of shared object */
        const char      *dli_sname;     /* Name of nearest symbol */
        void            *dli_saddr;     /* Address of nearest symbol */
} Dl_info;

比如拗胜,Demo對(duì)應(yīng)的info信息如下:

image.png

接著定義了4個(gè)結(jié)構(gòu)體指針,以64位架構(gòu)為例:

  1. segment_command_t就是segment_command_64
  2. symtab_commandLoad CommandsLC_SYMTAB對(duì)應(yīng)的結(jié)構(gòu)體
  3. dysymtab_commandLoad CommandsLC_DYSYMTAB對(duì)應(yīng)的結(jié)構(gòu)體

然后跳過(guò)MachO Header怒允,開(kāi)始遍歷Load Commands埂软,將對(duì)應(yīng)的LC分別放到linkedit_segmentsymtab_cmd误算、dysymtab_cmd這三個(gè)結(jié)構(gòu)體指針中仰美。

完成循環(huán)后對(duì)結(jié)構(gòu)體指針判空,如果有滿足為空條件的儿礼,直接return。

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

  // Find base symbol/string table addresses
  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);

  // Get indirect symbol table (array of uint32_t indices into symbol table)
  uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);

  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_DATA) != 0 &&
          strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
        continue;
      }
      for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
        section_t *sect =
          (section_t *)(cur + sizeof(segment_command_t)) + j;
        if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
        }
        if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
        }
      }
    }
  }
}
  1. 獲取當(dāng)前鏡像SEG_LINKEDIT經(jīng)ASLR偏移后的首地址
  2. 獲取符號(hào)表地址
  3. 獲取字符串表地址
  4. 獲取動(dòng)態(tài)符號(hào)表地址
  5. 再次遍歷Load Commands找到__DATA__DATA_CONST中對(duì)應(yīng)的__nl_symbol_ptr__la_symbol_ptr進(jìn)行重新綁定

perform_rebinding_with_section的功能就是庆寺,遍歷當(dāng)前符號(hào)表是否有與鏈表中每個(gè)結(jié)構(gòu)體指針對(duì)應(yīng)的name字段匹配蚊夫,如果有則重新綁定。至此懦尝。fishhook的rebind就完成了知纷。

再回到rebind_symbols壤圃,來(lái)看對(duì)應(yīng)的else部分代碼:

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 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) {
    _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;
}

上文說(shuō)過(guò),_dyld_register_func_for_add_image只有在調(diào)用或者有新鏡像加載時(shí)才會(huì)調(diào)用注冊(cè)的回調(diào)函數(shù)琅轧。對(duì)于同一個(gè)鏡像來(lái)說(shuō)伍绳,當(dāng)rebind_symbols被多次調(diào)用時(shí),注冊(cè)的回調(diào)函數(shù)_rebind_symbols_for_image已經(jīng)不會(huì)再被調(diào)用乍桂,此時(shí)先獲取當(dāng)前已加載鏡像冲杀,然后逐個(gè)手動(dòng)調(diào)用_rebind_symbols_for_image即可。


Have fun!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末睹酌,一起剝皮案震驚了整個(gè)濱河市权谁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌憋沿,老刑警劉巖旺芽,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異辐啄,居然都是意外死亡采章,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門壶辜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)共缕,“玉大人,你說(shuō)我怎么就攤上這事士复⊥脊龋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵阱洪,是天一觀的道長(zhǎng)便贵。 經(jīng)常有香客問(wèn)我,道長(zhǎng)冗荸,這世上最難降的妖魔是什么承璃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蚌本,結(jié)果婚禮上盔粹,老公的妹妹穿的比我還像新娘。我一直安慰自己程癌,他們只是感情好舷嗡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著嵌莉,像睡著了一般进萄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天中鼠,我揣著相機(jī)與錄音可婶,去河邊找鬼。 笑死援雇,一個(gè)胖子當(dāng)著我的面吹牛矛渴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惫搏,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼具温,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了晶府?” 一聲冷哼從身側(cè)響起桂躏,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎川陆,沒(méi)想到半個(gè)月后剂习,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡较沪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年鳞绕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尸曼。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡们何,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出控轿,到底是詐尸還是另有隱情冤竹,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布茬射,位于F島的核電站鹦蠕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏在抛。R本人自食惡果不足惜钟病,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刚梭。 院中可真熱鬧肠阱,春花似錦、人聲如沸朴读。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)磨德。三九已至缘回,卻和暖如春吆视,著一層夾襖步出監(jiān)牢的瞬間典挑,已是汗流浹背酥宴。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留您觉,地道東北人拙寡。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像琳水,于是被迫代替她去往敵國(guó)和親肆糕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • 這是我在簡(jiǎn)書(shū)的第32篇文章 文/微辣 01 今天是繼六周肚皮舞課之后在孝,最震撼的一天诚啃。因?yàn)椋覀冇瓉?lái)了肚皮舞進(jìn)修班體...
    四間房閱讀 399評(píng)論 0 1
  • 每天都要看到你 在我的世界里看到你 這樣我才會(huì)發(fā)現(xiàn)生活的意義 每天都要看到你 在我們的小小世界里 看著就滿心歡喜
    王不煩閱讀 195評(píng)論 0 0
  • 本文準(zhǔn)備講解1個(gè)簡(jiǎn)單的算法編程問(wèn)題私沮, 這個(gè)算法編程問(wèn)題來(lái)自LintCode平臺(tái)始赎。不了解.LintCode平臺(tái)的讀者...
    billliu_0d62閱讀 137評(píng)論 2 0
  • 電影于2016年年初開(kāi)拍,因一些原因仔燕,劇本做了大的變動(dòng)造垛,于近期開(kāi)拍。 之前拍攝導(dǎo)演拍攝組演員晰搀,各界朋友都付出了很多...
    龔雪麗閱讀 1,378評(píng)論 0 2