美文网首页
ELF文件的native hook:实现

ELF文件的native hook:实现

作者: 拉丁吴 | 来源:发表于2024-04-11 14:55 被阅读0次

    在上一篇文章中,我们解释了操作系统的虚拟内存的概念和实现原理,以及基于此的native hook的实现原理,接下来我们要讲的就是native hook的一种实现:plt/got hook。

    构建工程

    // lib.h
    #ifndef LIB_H
    #define LIB_H
    
    
    void start();
    
    #endif
    
    
    
    /********************************************************/
    
    // lib.c
    
    #define _GNU_SOURCE
    #include<stdio.h>
    #include "lib.h"
    
    int global_value = 17;
    int global_value2 = 0xffeebbaa;
    void sayWords(){
    printf("hello owrld from C \n");
    printf("number: %d  %d \n",global_value,global_value2);
    }
    
     void start(){
        sayWords();
     }
    
    

    我们把lib.c的逻辑视作程序的主要逻辑,把它编译成动态链接库:

    gcc lib.c -shared -fPIC -o lib_test.so // 输出lib_test.so
    

    然后创建main.c,作为可执行程序

    #define _GNU_SOURCE
    #include<stdio.h>
    #include "lib.h"
    
         int main(){
            start();
            return 0;
         }
             
    

    然后编译链接main文件:

    gcc main.c -L. -l_test -o main.o
    
    

    -l_test会自动补全为lib_test,此时查看main依赖的so库

    $ ldd main.o
    
        linux-vdso.so.1 (0x00007ffe853a7000)
        lib_test.so => not found
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fce61a00000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fce61d7f000)
    
    

    如果显示lib_test.so not found ,需要把该so放入特定的目录下(/usr/lib),或者把当前目录设置到LD_LIBRARY_PATH中,建议直接设置环境变量

    $ open ~/.bashrc // 打开bashrc文件
    
    
    // 文件中添加存放lib_test.so的路径(绝对路径)
    export LD_LIBRARY_PATH="/home/your_path:$LD_LIBRARY_PATH"
    
    
    $ source ~/.bashrc
    

    之后,在命令窗口中重新查看mian的so库依赖,然后应该会正常打印lib_test.so的路径了。然后可以执行一下./main.o程序,看是否符合预期。

    至此,我们构建了一个正常的可执行程序,它依赖了一个我们自己的lib_test.so库还有一些其他的系统so库,

    如何hook

    此时我们想要hook一下lib_test.so库中使用的printf函数,根据前一篇文章梳理的原理和过程,我们需要找到该so库的重定位表,这个表中指向了需要重定位的外部导入符号的地址,里面就有printf的项。我们使用readelf查看一下

    
    $ readelf -r lib_test.so
    
    Relocation section '.rela.plt' at offset 0x5e0 contains 3 entries:
        Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
    0000000000004018  0000000200000007 R_X86_64_JUMP_SLOT     0000000000000000 puts@GLIBC_2.2.5 + 0
    0000000000004020  0000000300000007 R_X86_64_JUMP_SLOT     0000000000000000 printf@GLIBC_2.2.5 + 0
    0000000000004028  0000000800000007 R_X86_64_JUMP_SLOT     0000000000001159 sayWords + 0
    
    

    可以看到printf的重定位之后的真实地址记录在偏移为0x4020的地址处,也就是GOT(全局偏移表)中,lib_test.so中所有调用了printf的函数最终都会走向0x4020处。

    所以我们要把自己编写的myPrintf函数的首地址填入到该偏移地址处,但是这个记录的偏移只是在ELF文件中的偏移地址,并不是运行时的真实地址,需要等ELF文件被映射到虚拟内存空间中某个地址上之后,才知道它的真实地址。所以我们还要找到lib_test.so在运行时的基地址(载入的起始地址),然后加上我们找到的偏移地址才是真实地址。

    查找运行时库的运行地址我们依然使用C基础库中的dl_iterate_phdr函数,同时需要考虑对运行时的某段地址进行写入操作时 会碰到权限问题,修改内存的读写权限需要使用mprotect这个系统调用,这个我我们都直接参照网络上的用法。

    我们补全main.c的hook代码:

    
    #define _GNU_SOURCE
    #include<stdio.h>
    #include "lib.h"
    #include <stdarg.h>
    #include <link.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <inttypes.h>
    
       
    #ifndef PAGE_SIZE
    #define PAGE_SIZE 4096
    #define PAGE_MASK (~(PAGE_SIZE - 1))
    #endif
    
    #define PAGE_START(addr) ((addr) & PAGE_MASK)
    #define PAGE_END(addr)   (PAGE_START(addr) + PAGE_SIZE)
            // 我们用来替换printf的hook函数
        int myPrintf(const char *format, ...){
            puts("hook printf start =============================");
            va_list args;
                va_start(args, format);
                
                int result = printf(format,args);
                va_end(args);
                puts("hook printf end  =============================");
                    return result;
                
        }
           static int
           callback(struct dl_phdr_info *info, size_t size, void *data)
           {
               char *type;
               int p_type;
    
               printf("Name: \"%s\" (%d segments)\n", info->dlpi_name,
                          info->dlpi_phnum);
              if(strstr(info->dlpi_name,"lib_test") != NULL){ // 找到lib_test.so这个运行库
                      // 先打印一下
                printf("this so  %s  %14p \n",info->dlpi_name,info->dlpi_addr); 
                    // info->dlpi_addr就是程序的基地址,加上偏移0x4020就是运行时的地址
                uintptr_t addr = info->dlpi_addr+0x4020;
                    // 设置写入权限
                 mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
                     // 把addr地址转换为一个void二级指针,并把addr地址上的内容修改为myPrintf函数地址
                 *(void **)addr = myPrintf;
                 __builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
              }
          //printf("base_addr: %14p  \n",info->dlpi_addr);
    
    
               return 0;
           }
    
    
        void hookPrintf(){
            // 遍历共享对象列表,从中获取lib_test.so的地址
            dl_iterate_phdr(callback, NULL);
        }
            
         int main(){
             // 程序运行到main时,程序依赖的so都已经加载到内存中了,
             //因此我们可以开始查找lib_test.so的运行时内存基地址
            hookPrintf(); 
            start();
            return 0;
         }
    
    

    通过上面的逻辑代码,我们可以实现对位于基地址+偏移地址的函数进行修改.

    然后重新编译链接main文件:

    gcc main.c -L. -l_test -o main.o
    
    

    接着运行main.o程序,会发现lib_test.so中使用的函数printf被替换成了myPrintf.

    hook导出表函数

    我们前面讲的hook lib_test.so中使用的printf函数,实际上定义在libc.so中,因此对于lib_test.so而言,printf函数是一个导入函数。那么定义在lib_test.so内部的函数是否也可以被hook呢?答案是显然的。

    而且其基本原理与hook导入函数是一致的,我们在lib_test.so中定义了sayWords函数,因此我们尝试hook它。

    首先我们需要知道该函数所在的重定位地址的偏移,通过readelf 读取重定位表可知:

    
    $ readelf -r lib_test.so
    
    Relocation section '.rela.plt' at offset 0x5e0 contains 3 entries:
        Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
    0000000000004018  0000000200000007 R_X86_64_JUMP_SLOT     0000000000000000 puts@GLIBC_2.2.5 + 0
    0000000000004020  0000000300000007 R_X86_64_JUMP_SLOT     0000000000000000 printf@GLIBC_2.2.5 + 0
    0000000000004028  0000000800000007 R_X86_64_JUMP_SLOT     0000000000001159 sayWords + 0
    

    sayWords在偏移地址为0x4028处,那么同样的,我们找到lib_test.so在运行时的基地址,然后加上这个偏移地址就得到了GOT中记录sayWords函数地址的表项处,然后把我们预先写好的替代函数放到该地址处即可替换掉对sayWords函数的调用。

    #define _GNU_SOURCE
    #include<stdio.h>
    #include "lib.h"
    #include <stdarg.h>
    #include <link.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <inttypes.h>
    
       
    #ifndef PAGE_SIZE
    #define PAGE_SIZE 4096
    #define PAGE_MASK (~(PAGE_SIZE - 1))
    #endif
    
    #define PAGE_START(addr) ((addr) & PAGE_MASK)
    #define PAGE_END(addr)   (PAGE_START(addr) + PAGE_SIZE)
    
    void mySayWords(){
    printf("hook say Words ======================= \n");
    
    }
    
           static int
           callback(struct dl_phdr_info *info, size_t size, void *data)
           {
               char *type;
               int p_type;
    
               printf("Name: \"%s\" (%d segments)\n", info->dlpi_name,
                          info->dlpi_phnum);
              if(strstr(info->dlpi_name,"lib_test") != NULL){
                printf("this is we want %s  %14p \n",info->dlpi_name,info->dlpi_addr);
                uintptr_t addr = info->dlpi_addr+0x4028;
                 mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
                void **temp = (void **)addr; // 使用void二级指针来修改似乎更加方便
                *temp = mySayWords;
                 __builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
              }
          //printf("base_addr: %14p  \n",info->dlpi_addr);
    
    
               return 0;
           }
    
    
        void hookPrintf(){
            dl_iterate_phdr(callback, NULL);
        }
    
         int main(){
            hookPrintf();
            start();
            return 0;
         }
    
    

    在编译运行后过后,我们就可以看到hook后的结果了。

    相关文章

      网友评论

          本文标题:ELF文件的native hook:实现

          本文链接:https://www.haomeiwen.com/subject/jtzcxjtx.html