PLT hook笔记

1. hook技术概述   

     hook技术是一种拦截用户函数调用的技术。经过hook技术能够实现统计用户对某些函数的调用次数,对函数注入新的功能的目标。在Linux平台,Hook技术能够分红用户和内核两个层面,每一个类比中都存在不一样的hook技术。本文主要介绍针对动态连接技术的PLT hook。html

2. 代码实例

    首先咱们先用一个实例来向你们展现一下PLT hook的效果。代码的功能是验证用户在命令行输入的密码,hook的目标是strcmp函数,经过将strcmp函数的返回值置为0,达到不管用户输入任何密码,即便是错误的,都返回验证经过的提示。 
    先编写passwd.c。该代码的做用是调用strcmp函数并判断用户输入的密码是否正确,并打印相应的提示。文件中只有一个函数就是check_is_authenticated。

  其次编写咱们的main.c 该函数实现了将要替换strcmp函数的my_strcmp,和使hook生效的hook函数。代码的具体细节linux

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <string.h>
 5 #include <inttypes.h>
 6 #include <execinfo.h>
 7 #include <sys/user.h>
 8 #include <sys/mman.h>
 9 #include "passwd.h"
10 
11 #define PAGE_SHIFT  12
12 #define PAGE_SIZE  (_AC(1,UL) << PAGE_SHIFT)
13 #define PAGE_MASK  (~(PAGE_SIZE-1))
14 
15 #define __AC(X,Y) (X##Y)
16 #define _AC(X,Y) __AC(X,Y)
17 
18 #define PAGE_START(addr) ((addr) & PAGE_MASK)
19 #define PAGE_END(addr)   (PAGE_START(addr) + PAGE_SIZE)
20 
21 int my_strcmp(const char *s1, const char *s2) 
22 {
23     return 0;
24 }
25 
26 uintptr_t get_base_addr(char *libname) 
27 {
28     FILE *fp;
29     char line[1024];
30     //char base_addr[1024];
31     uintptr_t base_addr = 0;
32 
33     if (NULL == (fp = fopen("/proc/self/maps", "r"))) {
34         perror("open err");
35         return -1;
36     }
37 
38     while (NULL != fgets(line, sizeof(line), fp)) {
39         if (NULL != strstr(line, libname)) {
40             sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr);
41             printf("line2:%s, base_addr:%"PRIxPTR"\n", line, base_addr);
42             break;
43         } 
44     }
45     fclose(fp);
46 
47     return base_addr;
48 }
49 
50 void hook() {
51     uintptr_t base_addr;
52     uintptr_t addr;
53     //1. get the base addr of libpasswd.so
54     base_addr = get_base_addr("libpasswd");
55     if (0 == base_addr) return;
56     addr = base_addr + 0x201020; 
57     //2. add the write permisson
58     mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ|PROT_WRITE);
59     //3. replace our hook func my_strcmp
60     *(void **)addr = my_strcmp; 
61     //4. clear the cache
62     __builtin___clear_cache((void *)PAGE_START(addr),
63             (void *)PAGE_END(addr));
64 }
65 
66 int main() 
67 {
68     hook();
69     check_is_authenticated("abcd");
70     return 0;
71 }
  接下来咱们将passwd.c编译成动态库libpasswd.so,main.c调用libpasswd.so中的check_is_authenticated函数。咱们是用下列gcc命令将passwd.c编译为动态库,其中-fpic是生成位置无关代码,-shared表示咱们生成的是一个动态库,不须要main函数的参与。

  在将libpasswd.so放置到/usr/lib/目录中以后,使用如下命令编译main.c缓存

  运行以后咱们 能够看到验证结果始终是正确的,说明咱们对于libpasswd.so中的strcmp函数的Hook已经生效。函数

怎么样,是否是很神奇呀!下面咱们就来看看plt hook究竟是怎么实现的。post

3. PLT hook原理

  说了这么久,PLT hook究竟是怎么实现的呢?到底什么是PLT呢?下面我就带你们了解如下到底什么是PLT。
    在了解PLT以前咱们须要先了解下什么是共享库和动态连接技术。静态连接技术,是以一种将多个可连接目标文件连接为一个独立的可执行文件的过程。在连接的过程当中,链接器会将静态库中的函数完整的复制到可执行文件的文本段中。在一个运行较多进程的系统中,这种连接方式对于内存消耗是不可小觑的。以下图所示是两种不一样的链接方式生成的可执行文件的大小,能够看到两者相差极大。为了解决这个问题,共享库诞生了, 共享库是一个目标模块或者说目标文件,在运行或者加载时,能够加载到内存的任意位置,并和内存中的程序连接起来,这个过程是一个叫作动态连接器的组件完成的。在完成连接的过程当中,连接器仅仅复制一些重定位和符号表信息到可执行文件中,大大减少了可执行文件的大小。

 

   PLT(Procedure Linkage Table)全程过程连接表,主要用于协助程序完成延迟加载的功能,假设程序调用一个动态库中的函数,由于动态库能够被加载到内存中的任意位置,所以咱们没法去预测这个函数的运行时地址。正常的作法是为该函数调用生成一个重定位记录,而后动态连接器在程序加载的时候去解析它。可是这并不符合位置无关代码的作法,由于须要连接器修改调用模块的文本段。GNU使用延迟加载的技术去解决这个问题,把对函数地址的解析延迟到了对于函数的实际调用的时刻。使用这种技术,在第一次函数调用时的开销较大,可是在以后的调用中只会花费一条指令和一个间接的内存引用。
   延迟调用的完成须要PLT和GOT(Global Offset Table)全局偏移量表协做完成。若是一个目标模块调用任何在共享库中的函数,那么它就有本身的GOT和PLT。GOT是数据段的一部分,PLT是代码段的一部分。下面咱们来看看PLT和GOT表中的内容:
  PLT。PLT表中的每一项是一个16字节代码。PLT[0]是一个特殊条目,它跳转到动态连接器中。PLT[1]是系统启动函数的条目。从第三项开始是用户调用的动态库函数的条目。GOT。GOT中每一个表项是8字节的地址类型数据。和PLT相似,GOT[0]和GOT[1]也是特殊条目,包含动态连接器在解析函数地址时会使用的信息。GOT[2]是动态连接器在ld-linux.so模块中的入口点。其他的每个条目对应一个被调用的函数,其地址在须要时进行解析。每一个条目都有一个对应的PLT条目。下面咱们仍是以一个实例来解释一下延迟调用的过程。
  下列代码是一个动态库libpasswd.so中调用strcmp函数的过程,从下列代码,咱们能够看到对于strcmp函数的调用,实际上调用的是PLT中的strcmp条目。strcmp条目其实是三条汇编代码,让咱们这些汇编语句都作了什么。在560行,使用jmpq指令跳转到GOT中strcmp对应的条目,GOT初始时都指向它对应的PLT条目的第二条指令,在这里也就是"pushq $0x0"。在把strcmp的ID(0x0)压入栈中后,使用jmpq指令跳转到PLT[0], 前面说过PLT[0]存储的是动态连接器相关的条目,先使用pushq命令将GOT[1]中存储的动态链接器的一个参数压栈("pushq 0x200ab2(%rip)")。值得注意的是这里的0x200ab2是一个常量,这里用到了位置无关代码(PIC)的相关知识,再也不赘述。而后使用jmpq指令,经过GOT[2]中存储的动态连接器的地址间接跳转到动态连接器中,这里要注意代码中"*"的意义,"*"表示获取地址对应的内存,相似指针变量的解引用运算符。在跳转到动态连接器以后,链接器经过咱们刚刚压入栈中的两个值(被调用函数的ID, 和GOT[1])来肯定strcmp函数的运行时地址,并用这个地址重写strcmp对应的GOT条目,在将控制传递给strcmp函数。

  以上就是第一次调用strcmp函数的流程,能够看出开销仍是不小的。在后续的调用中,首先仍是跳转到strcmp的plt条目中,而后执行jmpq *0x200aaa(%rip)指令,不一样的是这是strcmp对应的GOT条目已经被写入了strcmp的运行时地址,因此这条jmp指令直接将程序的执行跳转到strcmp函数中。那么是否是只要在strcmp对应的GOT条目中写入咱们本身的函数的地址,就能够将控制跳转到本身实现的函数中了嘛。本着这样的思路咱们来看看开始时的代码。ui

4. 代码分析

代码分红两部分,一个是被hook的动态库libpasswd.so和调用被hook动态库的文件main.c。这里要强调的是咱们拦截的是动态库中的函数。下面我来说解一下核心的hook函数。
  1.  第一步咱们要获取libpasswd在进程中的首地址,时刻记住咱们拦截的是动态库中的函数,未来要替换的GOT[strcmp]也属于libpasswd。
  2.  第二步就是获取libpasswd中调用strcmp对应的GOT条目的地址,为何是addr = base_addr + 0x201020呢?咱们回到上文的libpasswd的plt段,能够看到strcmp的plt条目的第一条指令已经写出了相应的GOT条目的地址就是0x201020。又由于咱们拦截的动态库的strcmp函数,因此必须加上libpasswd在main中的首地址。spa

 

    3.  由于咱们要写入目标进程的数据段,因此必须给相应的页增长写权限,这里使用mprotect函数来调整相应页的权限。命令行

         4.  第四步就是将相应的GOT条目的内容替换为咱们的函数的地址。这一句值得说道说道,*(void **)addr = my_strcmp;你们以为这句和 (void *)addr = my_strcmp有什么区别呢?毕竟对*(void **) = (void *)看上去也是正确的啊!起初我也有这样的疑惑,可是仔细想一想,其实并非这样,咱们要记住GOT表中的每一项都存储的是一个地址,同时addr存储的是相应的GOT条目的地址,所以咱们必须先将addr强转为一个指向指针类型的指针。再使用星号解引用这样获取的就是GOT条目中存储的内容。这里须要理解的是指针类型其实和int char double是同样的也是一种数据类型。将上面的代码转换为如下形式也许更好理解:*( (void *) *addr) = my_strcmp。
 
 1 void hook() {
 2     uintptr_t base_addr;
 3     uintptr_t addr;
 4     //1. get the base addr of libpasswd.so
 5     base_addr = get_base_addr("libpasswd");
 6     if (0 == base_addr) return;
 7     addr = base_addr + 0x201020; 
 8     //2. add the write permisson
 9     mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ|PROT_WRITE);
10     //3. replace our hook func my_strcmp
11     *(void **)addr = my_strcmp; 
12     //4. clear the cache
13     __builtin___clear_cache((void *)PAGE_START(addr),
14             (void *)PAGE_END(addr));
15 }
        5. 最后一步咱们须要清除硬件缓存,由于GOT表项的内容极可能已经绕过内存被缓存到了硬件缓存中,可能会致使hook失败。
  以上就是我了解到的PLT hook的所有内容,这里咱们还只是hook本身的进程中调用的动态库的函数。对于其余进程的函数由于涉及到读取/proc/map文件和修改页权限,所以必需要root权限才可以运行。具体如何实如今下一篇文章中,咱们再进行探讨。

参考

3. 深刻理解计算机系统 本文关于连接知识的来源
相关文章
相关标签/搜索