14-Hook原理(一)fishHook

前言

本篇文章開始給大家分享下Hook(鉤子)的原理吏廉,包括iOS系統(tǒng)原生的Method Swizzle,還有很有名的Hook第三方框架垮兑,例如fishHook抚芦、Cydia Substrate以及inlineHook等,然后會重點(diǎn)介紹下fishHook的底層處理流程洋丐,希望大家能夠跟著實(shí)操一遍先朦。

一霸妹、Hook概述

Hook中文譯為掛鉤鉤子粘姜。在iOS逆向中是指改變程序運(yùn)行流程的一種技術(shù)鬓照。通過Hook可以讓別人的程序執(zhí)行自己所寫的代碼。在逆向中經(jīng)常使用這種技術(shù)孤紧。只有了解其原理才能夠?qū)阂獯a進(jìn)行有效的防護(hù)豺裆。

比如很久之前的微信自動搶紅包插件??

1.1Hook的幾種方式

iOS中Hook技術(shù)的大致上分為5種:Method Swizzlefishhook号显、Cydia Substrate臭猜、libffiinlinehook押蚤。
1. Method Swizzle (OC)
利用OC的Runtime特性蔑歌,動態(tài)改變SEL(方法編號)IMP(方法實(shí)現(xiàn))的對應(yīng)關(guān)系,達(dá)到OC方法調(diào)用流程改變的目的?? (主要用于OC方法

可以將SEL 和 IMP 之間的關(guān)系理解為一本書的目錄揽碘。SEL 就像標(biāo)題次屠,IMP就像頁碼。他們是一一對應(yīng)的關(guān)系??

方法交換的實(shí)現(xiàn)方式
主要有3種??

  1. method_exchangeImplementations ?? 在分類中直接交換就可以了雳刺,如果不在分類劫灶,需要配合class_addMethod實(shí)現(xiàn)跳回到原方法
  2. class_replaceMethod ?? 直接替換原方法掖桦。
  3. method_setImplementation ?? 重新賦值原方法浑此,通過getImpsetImp配合。

具體使用案例可以參考我之前寫的文章 ?? 11-代碼注入(??注意:拉到最后面??)

2. fishhook
Facebook提供的一個(gè)動態(tài)修改鏈接MachO文件的工具滞详。利用MachO文件加載原理凛俱,通過修改懶加載非懶加載兩個(gè)表的指針,達(dá)到C函數(shù)(系統(tǒng)C函數(shù))HOOK的目的料饥。

fishhook官方鏈接

大概流程 ?? dyld 更新 Mach-O 二進(jìn)制的 __DATA segment__la_symbol_str 中的指針蒲犬,使用 rebind_symbol方法更新兩個(gè)符號位置來進(jìn)行符號的重新綁定。后面我會詳細(xì)的分析底層的流程岸啡。

3. Cydia Substrate
Cydia Substrate 原名為 Mobile Substrate 原叮,主要作用是針對OC方法C函數(shù)以及函數(shù)地址進(jìn)行HOOK操作。并不僅僅針對iOS而設(shè)計(jì)奋隶,安卓一樣可以用擂送。

Cydia Substrate官方鏈接

Cydia Substrate結(jié)構(gòu)
Cydia Substrate主要分為3部分:Mobile HookerMobileLoader唯欣、safe mode嘹吨。

  1. Mobile Hooker
    它定義了一系列的宏和函數(shù),底層調(diào)用objcruntimefishhook來替換系統(tǒng)或者目標(biāo)應(yīng)用的函數(shù)境氢。其中有兩個(gè)函數(shù):
  • MSHookMessageEx:主要作用于OC方法 MSHookMessageEx

    void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result) 
    
  • MSHookFunction :(inline hook)主要作用于CC++函數(shù) MSHookFunction蟀拷。 Logos語法的%hook就是對這個(gè)函數(shù)做了一層封裝。

    void MSHookFunction(voidfunction,void* replacement,void** p_original)
    
  1. MobileLoader
    MobileLoader用于加載第三方dylib在運(yùn)行的應(yīng)用程序萍聊。啟動時(shí)MobileLoader會根據(jù)規(guī)則把指定目錄的第三方的動態(tài)庫加載進(jìn)去问芬,第三方的動態(tài)庫也就是我們寫的破解程序。

  2. safe mode
    破解程序本質(zhì)是dylib寄生在別人進(jìn)程里寿桨。 系統(tǒng)進(jìn)程一旦出錯(cuò)此衅,可能導(dǎo)致整個(gè)進(jìn)程崩潰,崩潰后就會造成iOS癱瘓亭螟。所以CydiaSubstrate引入了安全模式炕柔,在安全模式下所有基于CydiaSubstratede的三方dylib都會被禁用,便于查錯(cuò)與修復(fù)媒佣。

4. libffi
基于libbfi動態(tài)調(diào)用C函數(shù)匕累。使用libffi中的ffi_closure_alloc構(gòu)造與原方法參數(shù)一致的"函數(shù)" (stingerIMP),以替換原方法函數(shù)指針默伍;此外欢嘿,生成了原方法和Block的調(diào)用的參數(shù)模板cifblockCif。方法調(diào)用時(shí)也糊,最終會調(diào)用到void _st_ffi_function(ffi_cif *cif, void *ret, void **args, void *userdata)炼蹦, 在該函數(shù)內(nèi),可獲取到方法調(diào)用的所有參數(shù)狸剃、返回值位置掐隐,主要通過ffi_call根據(jù)cif調(diào)用原方法實(shí)現(xiàn)和切面block

AOPStingerBlockHook就是使用libbfi做的钞馁。

5. inlinehook
Inline Hook 就是在運(yùn)行的流程中插入跳轉(zhuǎn)指令來搶奪運(yùn)行流程的一個(gè)方法虑省。大體分為三步??

  1. 原函數(shù)的前 N 個(gè)字節(jié)搬運(yùn)到 Hook 函數(shù)的前 N 個(gè)字節(jié);
  2. 然后將原函數(shù)的前 N 個(gè)字節(jié)填充跳轉(zhuǎn)Hook 函數(shù)的跳轉(zhuǎn)指令僧凰;
  3. Hook 函數(shù)末尾幾個(gè)字節(jié)填充跳轉(zhuǎn)回原函數(shù) +N 的跳轉(zhuǎn)指令探颈;

大致流程如下圖??


其中, Cydia Substrate框架中的MSHookFunction就是使用的inlinehook原理训措。

Dobby
Dobby(原名:HOOKZz)是一個(gè)全平臺inlineHook框架伪节,它用起來就和fishhook一樣光羞。
Dobby 通過 mmap 把整個(gè) Mach-O 文件映射到用戶的內(nèi)存空間,寫入完成保存本地怀大。所以 Dobby 并不是在原 Mach-O 上進(jìn)行操作纱兑,而是重新生成并替換

Dobby 是通過插入 __zDATA 段和 __zTEXT 段到 Mach-O 中化借。

  • __zDATA ?? 記錄 Hook 信息(Hook 數(shù)量潜慎、每個(gè) Hook 方法的地址)、每個(gè) Hook 方法的信息(函數(shù)地址屏鳍、跳轉(zhuǎn)指令地址、寫 Hook 函數(shù)的接口地址)局服、每個(gè) Hook 的接口(指針)钓瞭。
    *__zText ?? 記錄每個(gè) Hook 函數(shù)的跳轉(zhuǎn)指令。

Dobby gitHub鏈接

二淫奔、fishHook

2.1 fishhook的使用

首先我們看看fishhook是如何使用的 ?? 當(dāng)然看.h頭文件??

/*
 * A structure representing a particular intended rebinding from a symbol
 * name to its replacement
 */
struct rebinding {
  const char *name;//需要HOOK的函數(shù)名稱山涡,C字符串
  void *replacement;//新函數(shù)的地址
  void **replaced;//原始函數(shù)地址的指針!
};

/*
 * 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);

很簡單唆迁,只提供了一個(gè)結(jié)構(gòu)體rebinding兩個(gè)函數(shù)鸭丛。

rebinding
struct rebinding {
  const char *name;//需要HOOK的函數(shù)名稱,C字符串
  void *replacement;//新函數(shù)的地址
  void **replaced;//原始函數(shù)地址的指針唐责!
};
  • name ?? 要HOOK的函數(shù)名稱鳞溉,C字符串。
  • replacement ?? 新函數(shù)的地址鼠哥。(函數(shù)指針熟菲,也就是函數(shù)名稱)。
  • replaced ?? 原始函數(shù)地址的指針朴恳。(二級指針)抄罕。
2個(gè)函數(shù)
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);

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

  • header ?? image的Header
  • slide ?? ASLR
  • rebindings[] ?? 存放rebinding結(jié)構(gòu)體的數(shù)組(可以同時(shí)交換多個(gè)函數(shù))
  • rebindings_nel ?? rebindings數(shù)組的長度

示例演示

示例一:HOOK NSLog

現(xiàn)在我們使用fishHook hook一下系統(tǒng)的NSLog函數(shù),代碼??

- (void)hook_NSLog {
    struct rebinding rebindNSLog;
    rebindNSLog.name = "NSLog";
    rebindNSLog.replacement = LG_NSLog;
    rebindNSLog.replaced = (void *)&sys_NSLog;
    
    struct rebinding rebinds[] = {rebindNSLog};
    
    rebind_symbols(rebinds, 1);
}

//原函數(shù)于颖,函數(shù)指針
static void (*sys_NSLog)(NSString *format, ...);

//新函數(shù)
void LG_NSLog(NSString *format, ...) {
    format = [format stringByAppendingFormat:@"被 Hook了4艋摺!森渐!"];
    //調(diào)用系統(tǒng)NSLog
    sys_NSLog(format);
}

調(diào)用代碼??

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"hello");
}

run??

此時(shí)就已經(jīng)Hook住NSLog做入,走到了LG_NSLog中。
Hook代碼調(diào)用完畢同衣,sys_NSLog保存系統(tǒng)NSLog原地址母蛛,NSLog就指向LG_NSLog

示例二:HOOK 自定義C函數(shù)

接下來我們來Hook一下自定義的C函數(shù)??

void func(const char * str) {
    NSLog(@"%s",str);
}

- (void)hook_func {
    struct rebinding rebindFunc;
    rebindFunc.name = "func";
    rebindFunc.replacement = LG_func;
    rebindFunc.replaced = (void *)&original_func;
    
    struct rebinding rebinds[] = {rebindFunc};
    
    rebind_symbols(rebinds, 1);
}
//原函數(shù),函數(shù)指針
static void (*original_func)(const char * str);

//新函數(shù)
void LG_func(const char * str) {
    NSLog(@"Hook func");
    original_func(str);
}

調(diào)用代碼??

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self hook_func];
    func("hello");
}

運(yùn)行??

我們發(fā)現(xiàn)毯焕,此時(shí)是沒有Hook到func函數(shù)的。由此得出??

自定義的函數(shù)fishhook 不能hook修肠,系統(tǒng)的函數(shù) fishhook 可以hook秫逝。

2.2 fishhook原理

fishhook可以HOOK C函數(shù)恕出,但是我們知道函數(shù)是靜態(tài)的,也就是說在編譯的時(shí)候就確定了實(shí)現(xiàn)地址违帆,這也是C函數(shù)只寫函數(shù)聲明浙巫,在調(diào)用時(shí)會報(bào)錯(cuò)的原因。那么為什么fishhook還能夠改變C函數(shù)的調(diào)用呢刷后?是否像Method Swizzle一樣改變了函數(shù)實(shí)現(xiàn)的地址的畴?帶著這些問題,我們繼續(xù)往下看尝胆。

首先我們得弄清楚 ?? 系統(tǒng)函數(shù)本地函數(shù)有什么區(qū)別丧裁?

2.2.1 符號 & 符號綁定 & 符號表 & 重綁定符號

NSLog函數(shù)的地址在編譯的那一刻,我們的App程序并不知道NSLog函數(shù)實(shí)現(xiàn)的真實(shí)地址 ?? 因?yàn)?code>NSLog在Foundation框架中含衔,在運(yùn)行時(shí)NSLog函數(shù)的實(shí)現(xiàn)地址在 共享緩存 中煎娇。只有系統(tǒng)的dyld知道這個(gè)真實(shí)地址。

LLVM編譯器生成MachO文件時(shí)贪染,我們知道MachO中分為Text(只讀)Data(可讀可寫)缓呛,如果先空著系統(tǒng)函數(shù)的地址,等運(yùn)行起來替換系統(tǒng)函數(shù)的地址杭隙,顯然這種方式行不通哟绊,因?yàn)槟悴恢酪斩嗌倏臻g,而且也比較浪費(fèi)空間痰憎。

可行的方案 ?? 在Data段放一個(gè) 占位符(8字節(jié))匿情,讓代碼編譯的時(shí)候直接bl 占位符。在運(yùn)行的時(shí)候(即dyld加載應(yīng)用的時(shí)候)信殊,將Data段的地址修改為NSLog真實(shí)地址炬称,代碼bl 占位符此時(shí)不變 ,這樣就能保證運(yùn)行NSLog時(shí)執(zhí)行的是真實(shí)的實(shí)現(xiàn)代碼涡拘。這個(gè)技術(shù)就叫做 PIC(position independent code) 位置無關(guān)代碼玲躯。(當(dāng)然實(shí)際的實(shí)現(xiàn)并不是這么簡單)

  • 占位符 就叫做 符號
  • dylddata段符號進(jìn)行修改的這個(gè)過程叫做 符號綁定
  • 一個(gè)又一個(gè)的符號放在一起形成了一個(gè)列表,叫做 符號表

所以鳄乏,外部的C函數(shù)是通過符號地址跷车, 那么,我們就有機(jī)會動態(tài)的Hook外部C函數(shù)橱野。OC的Method Swizzle是修改SEL與IMP對應(yīng)的關(guān)系朽缴,對于符號, 當(dāng)然也能修改符號所對應(yīng)的地址水援。這個(gè)動作叫做 重新綁定符號表密强。這也就是fishhook hook的原理茅郎。

2.2.2 示例驗(yàn)證

首先在Hook NSLog前后分別調(diào)用NSLog??

    NSLog(@"Hook 前");
    [self hook_NSLog];
    NSLog(@"Hook 后");

接著編譯,查看Mach-O的懶加載和非懶加載符號表??

我們在懶加載表中找到NSLog或渤,說明NSLog是懶加載符號 ?? 只有調(diào)用的時(shí)候才去綁定系冗。

在MachO中可以看到_NSLog的Data(值)10000064ECoffset值0x8010薪鹦。

綁定前的地址

然后我們在 NSLog(@"Hook 前");打上斷點(diǎn)掌敬,lldb調(diào)試如下??

我們通過image list指令,查看程序的起始地址是0x0000000100624000池磁,其中ASLR的值是0x624000奔害。接著我們打開匯編調(diào)試??

然后進(jìn)入NSLog??

最終,我們得到NSLog在內(nèi)存中的地址是0x00000001043464ec地熄。

回到Mach-O中华临,NSLog的Data值是0x10000064EC + ASLR值0x4340000 = 0x00000001043464ec。那么我們由此可以推斷出??

Mach-O中記錄的NSLog的Data值是沒有ASLR(虛擬地址偏移)的离斩。

綁定后的地址

繼續(xù)運(yùn)行斷點(diǎn)到綁定后的NSLog银舱,同理瘪匿,查看地址??

程序起始地址0x0000000104340000 + NSLog的偏移地址0x8010得到了NSLog的真實(shí)地址跛梗,然后通過lldb的x指令查看起始的8字節(jié)中存儲的值是地址0x0104345650,再通過dis -s查看改地址對應(yīng)的匯編代碼棋弥,發(fā)現(xiàn)就是LG_NSLog方法核偿。由此可見??

懶加載符號表里面綁定的地址已經(jīng)改變了。

2.3 符號綁定過程

接下來顽染,我們來分析一下漾岳,上面懶加載符號表中,綁定的地址發(fā)生變化的過程粉寞,也就是符號綁定的過程尼荆。

  • iOS中函數(shù)名、變量名唧垦、方法名捅儒、編譯完成后會生成一張符號表
  • 符號有2種類型 ?? 內(nèi)部符號 & 外部符號

2.3.1 內(nèi)部符號:內(nèi)部函數(shù),方法名稱

ViewDidLoad振亮。內(nèi)部符號又分為??

  1. 本地符號 ?? 自己內(nèi)部使用的
  2. 全局符號 ?? 外部也可以使用
示例演示

新建工程symbolTest巧还,定義一個(gè)全局函數(shù)代碼??

//全局符號,可以暴露給外界
void test(){
    
}

本地函數(shù)??

//本地符號 作用域相當(dāng)于本文件
static void test1(){
    NSLog(@"test1");
}

??注意:App在上架時(shí)會去符號坊秸,去的是本地符號麸祷。

我們可以通過dump指令查看Mach-O中的所有符號??

objdump --macho -t xxx(你的MachO文件名稱)

再使用MachOView查看??

符號表Symbols包含所有的符號 ?? 本地符號,全局符號褒搔,間接符號阶牍。

2.3.2 外部符號(間接符號表)

MachO文件中調(diào)用外部方法名稱喷面,如NSLog,LLVM編譯時(shí)期并不知道外部(MachO文件以外)方法的地址荸恕。

間接符號有個(gè)專門的符號表Indirect Symbols乖酬,用到的外部符號例如NSStringFromClass,編譯時(shí)會生成一個(gè)符號??

2.3.3 符號綁定過程

接著回到正題融求,看看符號綁定過程咬像。首先,有以下代碼??

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"外部函數(shù)第一次調(diào)用");
    NSLog(@"外部函數(shù)第二次調(diào)用");
}

斷點(diǎn)斷到第一個(gè)NSLog生宛,查看匯編??

可以看到兩次調(diào)用NSLog是同一個(gè)地址0x102c06524县昂,并且通過image list得到程序的起始地址是0x0000000102c00000,那么0x1049ee524 - 0x00000001049e8000 = 0x6524陷舅,而0x6524是??

0x6524在MachO的Symbol Stubs中倒彰。這個(gè)就是NSLog的樁(外部符號的樁),值為1F2003D510D7005800021FD6(是代碼)莱睁,這個(gè)代碼是??

查看第一句匯編的地址0x1049ee524存儲的值??

就是NSLog樁的值4洹!仰剿!

繼續(xù)執(zhí)行完NSLog代碼??

上圖可知创淡,執(zhí)行完NSLog匯編后,通過讀取x16寄存器的值(即返回值)可知??

執(zhí)行Symbol Stubs樁中的代碼來找到Symbol Stubs符號中的代碼南吮。

至此琳彩,我們定位到了地址00000001000065CC,而65CC地址在__stub_helper中??

其中部凑,綠框中執(zhí)行的b 0x1000065b4就是符號綁定的過程露乏,我們繼續(xù)執(zhí)行匯編代碼??

上圖的第一句匯編在MachO中其實(shí)對應(yīng)的就是adr x17,12204,因?yàn)?code>0x1049ee5b4 - 0x00000001049e8000(程序起始地址) = 0x65B4涂邀。

繼續(xù)執(zhí)行瘟仿,進(jìn)去??

而MachO中的dyld_stub_binder是??

和上面的匯編就一一對應(yīng)上了!1让恪劳较!????????????
實(shí)際上執(zhí)行的是dyld_stub_binder,綜上可以得出結(jié)論??

懶加載符號表里面的初始值都是執(zhí)行符號綁定的函數(shù)敷搪。

但是dyld_stub_binder也是外部符號兴想,那接下來的問題就是 ?? 怎么找到dyld_stub_binder這個(gè)符號呢?

繼續(xù)執(zhí)行匯編代碼赡勘,走到0x1049ee5c8: br x16這句??

上圖我們通過lldb指令讀取x16寄存器的地址是0x0000000181041474嫂便,該地址正是dyld_stub_binder的實(shí)現(xiàn)地址,那么接下來就是該值是如何計(jì)算出的呢闸与?依舊看MachO??

這個(gè)符號在非懶加載表中(一運(yùn)行就綁定)??

綜上所述毙替,第一次符號綁定的過程??

  1. 程序一運(yùn)行岸售,先綁定No-Lazy Symbol Pointers表中dyld_stub_binder函數(shù)的值。
  2. 調(diào)用NSLog時(shí)厂画,先找Symbol Stubs樁凸丸,執(zhí)行樁中的代碼,樁中的代碼是對應(yīng)找到懶加載符號表中的代碼去執(zhí)行袱院。
  3. 懶加載符號表中的初始值是本地的源代碼屎慢,這個(gè)代碼去NoLazy表中找綁定函數(shù)地址
  4. 最后就執(zhí)行dyld_stub_binder函數(shù)進(jìn)行符號綁定忽洛。
繼續(xù)執(zhí)行NSLog

第2次執(zhí)行NSLog的時(shí)候腻惠,通過樁直接跳到了真實(shí)地址,因?yàn)榉柋碇幸呀?jīng)保存了地址執(zhí)行代碼欲虚。

小結(jié)

符號綁定的整個(gè)流程圖如下??


  1. 外部函數(shù)調(diào)用時(shí)執(zhí)行樁(__TEXT,__stubs)中的代碼
  2. 樁中的代碼去懶加載符號表(__DATA,__la_symbo_ptrl)中找地址執(zhí)行
    • 綁定過 ?? 要么直接調(diào)用綁定的函數(shù)地址
    • 未綁定 ??去__TEXT,__stubhelper中找綁定函數(shù)dyld_stub_binder進(jìn)行綁定集灌。
    • 懶加載符號表中默認(rèn)保存的是尋找binder的代碼
  3. 懶加載中的代碼去__TEXT,__stubhelper中執(zhí)行綁定代碼(binder函數(shù))。
  4. dyld_stub_binder非懶加載符號表中(__DATA._got)复哆,程序運(yùn)行就綁定好了欣喧。

2.4 通過符號找字符串

我們使用fishhook的時(shí)候我們是通過rebindNSLog.name = "NSLog";來hook NSLog。那么fishhook通過NSLog字符串怎么找到的NSLog函數(shù)符號的呢梯找?

根據(jù)上面分析的符號綁定過程唆阿,我們知道,在綁定的時(shí)候是去Lazy Symbol中去找的NSLog對應(yīng)的綁定代碼??

0x00008008這個(gè)地址初肉,在Lazy Symbol中NSLog排在第一個(gè)酷鸦。在Indirect Symbols間接符號表中可以看到順序Lazy Symbols相同??

所以反過來饰躲,要找Lazy Symbols中的符號牙咏,只要找到Indirect Symbols中對應(yīng)的索引值就可以了,那么接下來就是確定索引值了嘹裂。
我們注意到妄壶,在上圖的間接符號表中,NSLog對應(yīng)的Data值是000000BD(十六進(jìn)制)寄狼,轉(zhuǎn)換成十進(jìn)制是189丁寄,這個(gè)189就是代表著NSLog總符號表(Symbols)中的角標(biāo)??

注意到Data中保存的是000000D4(十六機(jī)制),這是NSLogString Table中偏移值??

通過偏移值計(jì)算得到0xD334泊愧,就找到了_NSLog(長度+首地址)伊磺。

??注意:.表示分隔符,函數(shù)名前面有_

至此删咱,我們就從Lazy Symbols -> Indirect Symbols -> Symbols - > String Table通過符號找到了字符串屑埋。fishhook找符號的過程就是這么處理的,通過遍歷所有符號和要hook的數(shù)組中的字符串做對比痰滋。

fishhookgitHub網(wǎng)址中有一張圖說明這個(gè)關(guān)系??

上圖是通過符號查找close字符串的過程??

  1. Lazy Symbol Pointer Table中close index為1061
  2. Indirect Symbol Table 1061 對應(yīng)的角標(biāo)為0X00003fd7(十進(jìn)制16343)
  3. Symbol Table找角標(biāo)16343對應(yīng)的字符串表中的偏移值70026
  4. String Table中找首地址+偏移值(70026)就找到了close字符串

反過來摘能,那么通過字符串找符號過程??

  1. String Table中找到字符串续崖,計(jì)算偏移值
  2. 通過偏移值Symbols中找到角標(biāo)
  3. 通過角標(biāo)Indirect Symbols中找到對應(yīng)的符號,也能取到這個(gè)符號的index
  4. 通過找到的indexLazy Symbols中找到對應(yīng)index符號团搞。

2.5 去掉符號 & 恢復(fù)符號

符號本身在MachO文件中严望,占用包體積大小 ,在我們分析別人的App時(shí)符號是去掉的逻恐。

2.5.1 去掉符號

  • 對于App來說像吻,會去掉所有符號(間接符號除外)
  • 對于動態(tài)庫來說要保留全局符號(外部要調(diào)用)
脫符號的設(shè)置

去掉符號在Build setting中設(shè)置??

Strip Style說明??

  • All Symbols去掉所有符號間接除外
  • Non-Global Symbols去掉除全局符號外的符號
  • Debugging Symbols去掉調(diào)試符號

??注意:Deployment Postprocessing ?? 設(shè)置為YES則在編譯階段去符號,否則在打包階段去符號复隆。

All Symbols

設(shè)置Deployment PostprocessingYES萧豆,Strip StyleAll Symbols,然后編譯昏名,打開包所在的位置??

查看多了一個(gè).bcsymbolmap文件涮雷,這個(gè)文件就是bitcode。接著我們查看MachO文件中Symbols總符號表??

上圖中轻局,我們看到NSLog的Value段存儲的地址是0000000000000000洪鸭,value為函數(shù)的實(shí)現(xiàn)地址(imp),所以代碼中打斷點(diǎn)就斷不住了??

直接跑完了仑扑。要斷住NSLog就要打符號斷點(diǎn)??

再運(yùn)行??

bt指令查看調(diào)用棧览爵,發(fā)現(xiàn)??

frame #0: 0x0000000182762ba8 Foundation`NSLog
frame #1: 0x0000000104e51fc4 symbolTest`___lldb_unnamed_symbol2$$symbolTest + 72

說明自定義的方法test1unnamed,這個(gè)很明顯就是去掉符號的镇饮。這種情況下就不好分析代碼了蜓竹。

之前學(xué)習(xí)匯編的時(shí)候,可知道储藐,oc方法調(diào)用則直接讀取x0俱济,x1就能獲取self和cmd,例如??

接著钙勃,我們可以下地址斷點(diǎn)蛛碌,再通過image list指令,結(jié)合ASLR值辖源,計(jì)算出偏移值??

后面蔚携,就能ASLR+偏移值直接下斷點(diǎn),找到方法的imp地址克饶,這就是動態(tài)調(diào)試酝蜒。

2.5.2 恢復(fù)符號

動態(tài)調(diào)試下斷點(diǎn),使用起來還是比較麻煩矾湃,需要計(jì)算亡脑,如果能恢復(fù)符號的話就方便很多了。

我明知道,在上面的例子中去掉所有符號Symbol Table中只有間接符號了远豺,雖然符號表中沒有了奈偏,但是類列表方法列表中依然存在。

這也就為我們提供了恢復(fù)Symbol Table的機(jī)會躯护。

恢復(fù)指令

可以通過restore-symbol工具恢復(fù)符號(只能恢復(fù)oc的惊来,runtime機(jī)制導(dǎo)致)??

./restore-symbol 原始Macho文件 -o 恢復(fù)后文件

查看恢復(fù)后的machO??

這個(gè)時(shí)候就可以重簽名后進(jìn)行動態(tài)調(diào)試了。

restore-symbol工具鏈接

restore-symbol

2.6 fishhook源碼解析

最后棺滞,也是本篇文章的重點(diǎn)裁蚁,就是fishhook源碼解析,廢話不多說继准,直接上源碼枉证。

2.6.1 rebind_symbols

//第一次是拿dyld的回調(diào),之后是手動拿到所有image去調(diào)用移必。這里因?yàn)闆]有指定image所以需要拿到所有的室谚。
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
    //prepend_rebindings的函數(shù)會將整個(gè) rebindings 數(shù)組添加到 _rebindings_head 這個(gè)鏈表的頭部
    //Fishhook采用鏈表的方式來存儲每一次調(diào)用rebind_symbols傳入的參數(shù),每次調(diào)用崔泵,就會在鏈表的頭部插入一個(gè)節(jié)點(diǎn)秒赤,鏈表的頭部是:_rebindings_head
    int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
    //根據(jù)上面的prepend_rebinding來做判斷,如果小于0的話憎瘸,直接返回一個(gè)錯(cuò)誤碼回去
    if (retval < 0) {
    return retval;
  }
  //根據(jù)_rebindings_head->next是否為空判斷是不是第一次調(diào)用入篮。
  if (!_rebindings_head->next) {
      //第一次調(diào)用的話,調(diào)用_dyld_register_func_for_add_image注冊監(jiān)聽方法.
      //已經(jīng)被dyld加載的image會立刻進(jìn)入回調(diào)幌甘。之后的image會在dyld裝載的時(shí)候觸發(fā)回調(diào)潮售。這里相當(dāng)于注冊了一個(gè)回調(diào)到 _rebind_symbols_for_image 函數(shù)。
    _dyld_register_func_for_add_image(_rebind_symbols_for_image);
  } else {
    //不是第一次調(diào)用锅风,遍歷已經(jīng)加載的image酥诽,進(jìn)行的hook
    uint32_t c = _dyld_image_count();//這個(gè)相當(dāng)于 image list count
    for (uint32_t i = 0; i < c; i++) {
        //遍歷重新綁定image header  aslr
      _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
    }
  }
  return retval;
}
  1. 首先通過prepend_rebindings函數(shù)生成鏈表,存放所有要Hook的函數(shù)遏弱。
  2. 根據(jù)_rebindings_head->next是否為空判斷是不是第一次調(diào)用盆均,第一次調(diào)用走系統(tǒng)的回調(diào)塞弊,第二次則自己獲取所有的image list進(jìn)行遍歷漱逸。
  3. 最后都會走_rebind_symbols_for_image函數(shù)。
rebindings_entry鏈表

其中游沿,_rebindings_head是指向鏈表rebindings_entry結(jié)構(gòu)體的指針??

struct rebindings_entry {
  struct rebinding *rebindings; // HOOK的相關(guān)信息
  size_t rebindings_nel; // 所占空間大小
  struct rebindings_entry *next; // 鏈表的next指針
};

static struct rebindings_entry *_rebindings_head;
rebind_symbols_image
int rebind_symbols_image(void *header,
                         intptr_t slide,
                         struct rebinding rebindings[],
                         size_t rebindings_nel) {
    struct rebindings_entry *rebindings_head = NULL;
    int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
    rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);
    if (rebindings_head) {
      free(rebindings_head->rebindings);
    }
    free(rebindings_head);
    return retval;
}

rebind_symbols_image流程比rebind_symbols簡單很多饰抒,直接調(diào)用rebind_symbols_for_image,因?yàn)橹付?code>void *header诀黍,不需要遍歷所有的image袋坑。

2.6.2 _rebind_symbols_for_image

//兩個(gè)參數(shù) header  和 ASLR
static void _rebind_symbols_for_image(const struct mach_header *header,
                                      intptr_t slide) {
    //_rebindings_head 參數(shù)是要交換的數(shù)據(jù),head的頭
    rebind_symbols_for_image(_rebindings_head, header, slide);
}

直接調(diào)用rebind_symbols_for_image眯勾,傳遞了head鏈表地址枣宫。

2.6.4 rebind_symbols_for_image

//回調(diào)的最終就是這個(gè)函數(shù)婆誓! 三個(gè)參數(shù):要交換的數(shù)組  、 image的頭 也颤、 ASLR的偏移
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
                                     const struct mach_header *header,
                                     intptr_t slide) {
    
    /*dladdr() 可確定指定的address 是否位于構(gòu)成進(jìn)程的進(jìn)址空間的其中一個(gè)加載模塊(可執(zhí)行庫或共享庫)內(nèi)洋幻,如果某個(gè)地址位于在其上面映射加載模塊的基址和為該加載模塊映射的最高虛擬地址之間(包括兩端),則認(rèn)為該地址在加載模塊的范圍內(nèi)翅娶。如果某個(gè)加載模塊符合這個(gè)條件文留,則會搜索其動態(tài)符號表,以查找與指定的address 最接近的符號竭沫。最接近的符號是指其值等于燥翅,或最為接近但小于指定的address 的符號。
     */
    /*
     如果指定的address 不在其中一個(gè)加載模塊的范圍內(nèi)蜕提,則返回0 森书;且不修改Dl_info 結(jié)構(gòu)的內(nèi)容。否則谎势,將返回一個(gè)非零值拄氯,同時(shí)設(shè)置Dl_info 結(jié)構(gòu)的字段。
     如果在包含address 的加載模塊內(nèi)它浅,找不到其值小于或等于address 的符號译柏,則dli_sname 、dli_saddr 和dli_size字段將設(shè)置為0 姐霍; dli_bind 字段設(shè)置為STB_LOCAL 鄙麦, dli_type 字段設(shè)置為STT_NOTYPE 。
     */
  //    typedef struct dl_info {
  //            const char      *dli_fname; //image 鏡像路徑
  //            void            *dli_fbase; //鏡像基地址
  //            const char      *dli_sname; //函數(shù)名字
  //            void            *dli_saddr; //函數(shù)地址
  //    } Dl_info;
  Dl_info info;
//這個(gè)dladdr函數(shù)就是在程序里面找header
  if (dladdr(header, &info) == 0) {
    return;
  }
    //下面就是定義好幾個(gè)變量镊折,準(zhǔn)備從MachO里面去找胯府!
  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的大小,找loadCommand
  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;
    }
  }
   //如果剛才獲取的恨胚,有一項(xiàng)為空就直接返回
  if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
      !dysymtab_cmd->nindirectsyms) {
    return;
  }

  // Find base symbol/string table addresses
//鏈接時(shí)程序的基址 = __LINKEDIT.VM_Address -__LINKEDIT.File_Offset + silde的改變值
  uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
//    printf("地址:%p\n",linkedit_base);
    //符號表的地址 = 基址 + 符號表偏移量
  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)
    //動態(tài)符號表地址 = 基址 + 動態(tài)符號表偏移量
  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) {
        //尋找到data段
      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);
        }
      }
    }
  }
}

核心的步驟有??

  • 根據(jù)linkedit偏移值分別找到符號表的地址字符串表的地址以及間接符號表地址骂因。
  • 遍歷load commandsdata段找到懶加載符號表非懶加載符號表
  • 找到表的同時(shí)就直接調(diào)用perform_rebinding_with_section進(jìn)行hook替換函數(shù)符號赃泡。

2.6.5 perform_rebinding_with_section

//rebindings:要hook的函數(shù)鏈表寒波,可以理解為數(shù)組
//section:懶加載/非懶加載符號表地址
//slide:ASLR
//symtab:符號表地址
//strtab:字符串標(biāo)地址
//indirect_symtab:動態(tài)(間接)符號表地址
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) {
  //nl_symbol_ptr和la_symbol_ptrsection中的reserved1字段指明對應(yīng)的indirect symbol table起始的index。也就是第幾個(gè)這里是和間接符號表中相對應(yīng)的
  //這里就拿到了index
  uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
  //slide+section->addr 就是符號對應(yīng)的存放函數(shù)實(shí)現(xiàn)的數(shù)組也就是我相應(yīng)的__nl_symbol_ptr和__la_symbol_ptr相應(yīng)的函數(shù)指針都在這里面了升熊,所以可以去尋找到函數(shù)的地址俄烁。
  //indirect_symbol_bindings中是數(shù)組,數(shù)組中是函數(shù)指針级野。相當(dāng)于lazy和non-lazy中的data
  void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
  //遍歷section里面的每一個(gè)符號(懶加載/非懶加載)
  for (uint i = 0; i < section->size / sizeof(void *); i++) {
    //找到符號在Indrect Symbol Table表中的值
    //讀取indirect table中的數(shù)據(jù)
    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;
    }
      //以symtab_index作為下標(biāo)页屠,訪問symbol table,拿到string table 的偏移值
      uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
      //獲取到symbol_name 首地址 + 偏移值。(char* 字符的地址)
      char *symbol_name = strtab + strtab_offset;
      //判斷是否函數(shù)的名稱是否有兩個(gè)字符辰企,因?yàn)楹瘮?shù)前面有個(gè)_风纠,所以方法的名稱最少要1個(gè)
      bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
      //遍歷最初的鏈表,來判斷名字進(jìn)行hook
      struct rebindings_entry *cur = rebindings;
      while (cur) {
          for (uint j = 0; j < cur->rebindings_nel; j++) {
              //這里if的條件就是判斷從symbol_name[1]兩個(gè)函數(shù)的名字是否都是一致的牢贸,以及判斷字符長度是否大于1
              if (symbol_name_longer_than_1 &&
                  strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
                  //判斷replaced的地址不為NULL  要替換的自己實(shí)現(xiàn)的方法和rebindings[j].replacement的方法不一致议忽。
                  if (cur->rebindings[j].replaced != NULL &&
                      indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
                      //讓rebindings[j].replaced保存indirect_symbol_bindings[i]的函數(shù)地址,相當(dāng)于將原函數(shù)地址給到你定義的指針的指針十减。
                      *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
                  }
                  //替換內(nèi)容為自己自定義函數(shù)地址栈幸,這里就相當(dāng)于替換了內(nèi)存中的地址,下次樁直接找到lazy/non-lazy表的時(shí)候直接就走這個(gè)替換的地址了帮辟。
                  indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
                  //替換完成跳轉(zhuǎn)外層循環(huán)速址,到(懶加載/非懶加載)數(shù)組的下一個(gè)數(shù)據(jù)。
                  goto symbol_loop;
              }
          }
      //沒有找到就找自己要替換的函數(shù)數(shù)組的下一個(gè)函數(shù)由驹。
      cur = cur->next;
    }
    symbol_loop:;
  }
}

核心步驟??

  1. 首先通過懶加載/非懶加載符號表間接符號表找到所有的index角標(biāo)芍锚,reserved1確認(rèn)了懶加載和非懶加載符號在間接符號表中的index值。
  1. 懶加載/非懶加載符號表Data值放入indirect_symbol_bindings數(shù)組中蔓榄。
  1. 遍歷懶加載/非懶加載符號表??
  • 讀取indirect_symbol_indices找到符號在Indrect Symbol Table表中的值放入symtab_index并炮。
  • symtab_index作為下標(biāo),訪問symbol table甥郑,拿到string tablestrtab_offset偏移值逃魄。
  • 根據(jù)strtab_offset偏移值獲取字符地址symbol_name字符名。
  • 循環(huán)遍歷rebindings鏈表(即自定義的Hook數(shù)據(jù))
  • 判斷&symbol_name[1]rebindings[j].name兩個(gè)函數(shù)的名字是否都是一致的澜搅,以及判斷字符長度是否大于1心步驟??伍俘。
  • 相同 ?? 先保存(replaced時(shí),沒有replaced則不保存)原地址到自定義函數(shù)指針勉躺。并且用要Hook的目標(biāo)函數(shù)replacement替換indirect_symbol_bindings癌瘾,這里就完成了Hook

總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饵溅,一起剝皮案震驚了整個(gè)濱河市妨退,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜕企,老刑警劉巖咬荷,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異糖赔,居然都是意外死亡萍丐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門放典,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事奋构】怯埃” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵弥臼,是天一觀的道長宴咧。 經(jīng)常有香客問我,道長径缅,這世上最難降的妖魔是什么掺栅? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮纳猪,結(jié)果婚禮上氧卧,老公的妹妹穿的比我還像新娘。我一直安慰自己氏堤,他們只是感情好沙绝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鼠锈,像睡著了一般闪檬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上购笆,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天粗悯,我揣著相機(jī)與錄音,去河邊找鬼同欠。 笑死为黎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的行您。 我是一名探鬼主播铭乾,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼娃循!你這毒婦竟也來了炕檩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤捌斧,失蹤者是張志新(化名)和其女友劉穎笛质,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捞蚂,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妇押,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了姓迅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敲霍。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俊马,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肩杈,到底是詐尸還是另有隱情柴我,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布扩然,位于F島的核電站艘儒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏夫偶。R本人自食惡果不足惜界睁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望兵拢。 院中可真熱鬧翻斟,春花似錦、人聲如沸卵佛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽截汪。三九已至疾牲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間衙解,已是汗流浹背阳柔。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工归园, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔑祟,地道東北人河闰。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓扳炬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親班挖。 傳聞我的和親對象是個(gè)殘疾皇子偿衰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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

  • 《iOS底層原理文章匯總》[http://www.reibang.com/p/15af435341ce]上一篇文...
    一畝三分甜閱讀 1,388評論 2 2
  • HOOK概述 HOOK低匙,中文譯為“掛鉤”或“鉤子”一汽。在iOS逆向中是指改變程序運(yùn)行流程的一種技術(shù)避消。通過HOOK可以...
    帥駝駝閱讀 1,108評論 0 3
  • 一、Hook概述 HOOK中文譯為掛鉤或鉤子召夹。在iOS逆向中是指改變程序運(yùn)行流程的一種技術(shù)岩喷。通過hook可以讓別人...
    HotPotCat閱讀 4,422評論 1 12
  • 1.Fishhook hook原理 ??在一節(jié)筆記中我們已經(jīng)掌握了fishhook的基本使用,也詳細(xì)探討了dyld...
    _從今以后_閱讀 667評論 0 0
  • 注入小結(jié) 通過之前的學(xué)習(xí)监憎,我們知道了利用動態(tài)庫注入的兩種方式: 注入 App 后纱意,使得 項(xiàng)目和動態(tài)庫產(chǎn)生關(guān)聯(lián)關(guān)系。...
    Superman168閱讀 14,167評論 4 5