首页
社区
课程
招聘
探究FishHook底层原理实现
发表于: 2020-9-22 16:29 65189

探究FishHook底层原理实现

2020-9-22 16:29
65189

探究FishHook底层原理实现

看雪新人第一次发帖,大家多多指教。
之前只知道Fishhook是Facebook家的通过修改懒加载表(GOT表)的函数的指向达到hook的框架,但是没有真正的探究过代码。动态调试的一下Fishhook探究一下原理。

一. Fishhook hook的实现思路

修改外部引用的函数在(懒)加载表中指向的目标函数的地址,修改为自定义代码的地址
实现上,主要分两步走:
1.通过MachO文件的Load Commands段中定义的各个段中的偏移计算出,Dynamic Symbol Table(Indirect Symbol),Symbol Table (Symbols)和String Table这三个段的起始地址。

 

2.遍历Lazy Symbol pointers中的所有函数定义,从中查找需要hook的函数(通过函数名的字符串比较,这也是为什么需要String Table段,查找字符串的过程见下面的备注1)。如果是需要被修改的函数,那么修改此函数在(懒)加载表(GOT表)中指向的地址为自己代码实现的地址以达到Hook。

二. Fishhook第一步

计算3个段的起始地址:
Fishhook计算3个段的起始地址。
wLRdjs.png
这里先贴上MachO文件的Load Commands段示意图:
wLouI1.png
从上向下分析fish的逻辑:

1
2
3
4
5
6
7
// 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);

首先计算了_LinkEdit的起始地址,因为LC_SYMTAB和LC_DYSYMTAB的Offset都是依据_LinkEdit的起始地址的。
然后根据_linkedit的起始地址,计算这3个段的起始地址。

 

首先,Load Command中的显示:
1.linkedit_segment->vmaddr = 0x100010000
2.linkedit_segment->fileoff = 0x10000

 

调试时,lldb中执行image list -o -f查看内存当中的基地址:
slide = 0x2cb0000

 

接下面是动态调试的计算值:
2.1> 根据代码计算:linkedit_base = (0x4510000 + 0x100010000 - 0x10000) = 4367384576
动态调试时,内存中的值也为 4367384576:
wLozQO.png

 

2.2> symtab_cmd->symoff = 67160
根据代码计算:symtab = (4367384576+67160).toString(16) = 0x104520658
动态调试,Xcode动态调试如上图,显示的symtab也为:0x104520658
wLoXJx.png

 

2.3>
symtab_cmd->stroff = 70632
根据代码计算:strtab =(4367384576 + 70632).toString(16) = 0x1045213e8
动态调试,Xcode动态调试如上图,显示的strtab = "";

 

2.4>
dysymtab_cmd->indirectsymoff = 70424
根据代码计算:indirectsymoff = (4367384576 + 70424) = 0x104521318
wLojW6.png

 

这样,就计算到了Symbols Table,Indirect Symbols,Strings三个表的起始地址。这样准备工作就做好了,下面开始进入真正的got表hook。

三.Fishhook第二步

根据上图贴的代码,计算3个值后,开始遍历Header,从Load Command中寻找_DATA下的S_LAZY_SYMBOL_POINTERS和S_NON_LAZY_SYMBOL_POINTERS,即懒加载表和非懒加载段,这两个段中保存这函数调用时的真正指向,最终的目的也是修改这两个段中想要hook的函数的指向。

 

找到后,根据代码,进入关键的perform_rebinding_with_section函数。将计算的3个函数的段起始地址,ALSR地址,(懒)加载表的地址,传入的想要hook的函数rebingings链表传给这个函数,

 

开始从Indirect Symbols中查找想要hook的函数(通过函数名比较),如果找到了,那么就修改对应的在(懒)加载表中符号的指向。

 

这是perform_rebinding_with_section函数的实现:
wLjIln.png

 

首先计算了indirect_symbol_bindings的地址,这是(懒)符号表在内存中的起始地址,计算方法是偏移量+(懒)加载表中的addr,这里动态调试查看:
indirect_symbol_bindings =(0x4510000 + 4295000064)= 0x104518000,这个值就是(懒)加载表中在内存中的基地址。
wLzJLq.png

 

下面进入循环,循环传入的(懒)加载表,这里也相当于循环Indirect Symbols,因为这两个表中的函数的index是相同的,这里详细见下面备注2。
对于(懒)加载表中的定义的每一个函数,得到这个函数在Indirect Symbols的data值(uint32_t symtab_index = indirect_symbol_indices[i];),然后再通过data找到symbol table中的该函数在字符串表中的偏移量(uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;),最终找到在字符串表中找到该函数的名称(char *symbol_name = strtab + strtab_offset;)。

 

程序接下来,开始循环想要hook的函数的链表,如果链表中函数名和当前遍历的函数的名称相同(strcmp() == 0),那么就修改此函数在got表中的指向为传入的被修改的函数的地址(indirect_symbol_bindings[i] = cur->rebindings[j].replacement;):
wOFnje.md.png

 

这里拿一个循环进行动态调试观察过程。例如当i = 0时:
symtab_index = 181,indirect_symbol_indices[i] =181;
代码中计算:uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
这里动态调试即为:strtab_offset = symtab[181].n_un.n_strx;(Symbol Table中各项的定义见下面备注3)
wOkTds.png
根据图中显示,strtab_offset为0x22E,为558。
那么String table的起始地址+558即为这个函数的函数名。

四.fishhook前后got表变化

通过前面的分析,知道替换got表即为indirect_symbol_bindings[i] = cur->rebindings[j].replacement;这行代码,那么在此前后断点,就知道got表想要被hook的函数前后究竟有什么变化,这里以hook NSlog举例:

 

这是我的调用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
 
    NSLog(@"lalala111");
 
    startHook();
 
    char *s = "lalala";
    NSLog(@"len : %ld",strlen(s));
}
 
 
void startHook() {
    rebind_symbols((struct rebinding[1]){{"NSLog",myNSLog,NULL}},1);
}

GOT表中NSlog指向的地址为:
ASLR + (懒)加载表里的偏移量,这里为:
0x1003f8000+ 0xc000 = 0x100404000。0x100404000存储着NSlog的函数指针。*(0x100404000)存储着NSlog的代码。
wOECng.png

 

0x100404000的值为:
wOEEhq.png
0x100404000存储着0x01a97ff7f0,寻这个地址的值,发现是NSLog实现。
disassemble --start-address 0x01a97ff7f0
wOEQHJ.png

 

接着执行indirect_symbol_bindings[i] = cur->rebindings[j].replacement;代码,在这之后,got表即被修改了指向,下面验证一下:

 

执行后,再查看0x100404000存储的值:
wOEyCt.png
0x100404000存储的值为0x01003fd5ec,*(0x01003fd5ec)可以找到改变后的"NSlog"(自定义的myNSlog):
wOEWDg.png

备注1

通过遍历(懒)加载表中定义的函数,找到函数的函数名的逻辑如下(上面第三节的最后也动态调试了)。遍历懒加载表中的函数,找到Indirect symbol表中该函数的index,通过该index找到symbol表中该函数的定义,symbol表中定义了函数在字符串表中函数的偏移,通过偏移找到字符串表中该函数的名称。
wOEH2V.md.png

备注2

遍历懒符号表和遍历Inderect表使用了相同的index,因为两个函数的定义的index相同,下面可以验证一下:
下面是Lazy Symbol Pointers的定义:
wOVRRx.png
下面是Indirect Symbols中定义的函数:
wOZwtA.png
可以看到,顺序都为NSlog,NSStringFromClass.....

备注3

Symbol table中符号表的定义如下,其中n_strx即为该函数在字符串表中的偏移量。
wOn9Te.png

 

参考链接:
1.段的地址计算过程
2.字符串寻址过程


[课程]Linux pwn 探索篇!

最后于 2020-9-22 16:32 被LeoW丨编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (6)
雪    币: 2074
活跃值: (2548)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
2
很详细,好文,来个精华
2020-9-23 01:37
0
雪    币: 19878
活跃值: (4912)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢楼主分享
2020-9-23 06:56
0
雪    币: 436
活跃值: (486)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
谢谢大家的鼓励
2020-9-23 14:41
0
雪    币: 5482
活跃值: (3272)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
5
精华精华
2020-9-24 13:40
0
雪    币: 3700
活跃值: (3817)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
感谢分享!
2020-9-24 14:41
0
雪    币: 0
活跃值: (37)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
fishhook 检测: https://github.com/545958498/IOSDetect
2020-9-29 12:45
0
游客
登录 | 注册 方可回帖
返回
//