Linux下Hook方式汇总
treebacker 二进制安全 19079浏览 · 2019-12-22 01:10

Linux下Hook方式汇总

  • #### 导语

    • Linux下的Hook方式,从ring3到ring0,姿势很多,但其实是互通的,这里总结自己实现的几个。

      /******    Ring3    ******/
      LD_PRELOAD劫持.so
      ptrace API调试技术Hook
      PLT劫持
      
      /******* Ring 0 *******/
      针对系统调用的hook
          --首先获得sys_call_table
      利用sys函数的嵌套实现hook调用的子函数
      修改系统调用的前几个字节为jmp之类的指令(内联
    • 网上很多教程是针对Linux2.6左右的,很多方法需要自己重新摸索,记录踩坑。

      • 注: 以下所有代码在Linux 5.0.3. x86_64内核调试通过。
  • #### LD_PRELOAD劫持.so

    • LD_PRELOAD是一个Linux下的动态链接的程序的环境变量,几乎我们用到的函数实现来自于glibc,.so文件是glibc编译得到的库,类似于Win下的DLL。而LD_PRELOAD变量优先于相关配置指定链接到哪个.so文件。

    • 一旦我们可以控制该变量,也就可以决定程序调用函数时会做什么。

    • 实例展示

      • 目标文件target.c

        #include <stdio.h>
        #include <string.h>
        int main(int argc, char *argv[])
        {
            if( strcmp(argv[1], "password") )
            {
                printf("Incorrect password\n");
            }
            else
            {
                printf("Correct password\n");
            }
            return 0;
        }
        //gcc target.c -o target
        
      • 伪造的.so文件preload.c

        #include <stdio.h>
        #include <string.h>
        #include <dlfcn.h>
        
        typedef int(*Strcmp)(const char*, const char*);
        
        int strcmp(const char* s1, const char* s2)
        {
          static void* handle = NULL;
          static Strcmp org_strcmp = NULL;
        
          if(!handle)
          {   //解析得到真实的strcmp函数
              handle = dlopen("libc.so.6", RTLD_LAZY);
              org_strcmp = (Strcmp)dlsym(handle, "strcmp");
          }
            //做我们想做的
          printf("Hacked by way of ld_preload\n\n\n");
            //完成真实地功能
          return org_strcmp(s1, s2);  
        }
        //gcc -fPIC preload.c -shared -o preload.so -ldl
        
    • 这种方式比较简单,前提是程序不是静态链接的。(不会再解析.so,程序已经包含了库函数的实现)。

    • 防护方案

      • 关闭LD_PRELOAD
  • #### ptrace API调试技术Hook

    • ptrace是很多Linux平台下调试器实现的基础,包括syscall跟踪程序strace。

    • ptrace可以实现调试程序、跟踪;但是一个进程只能被一个进程跟踪。所以无法在gdb或者其他程序调试的时候去ptrace一个程序,同样也无法在ptrace一个进程的时候,再去gdb调试。后者经常作为一个简单的反调试手段。

    • 而且Linux下的攻防中,ptrace也由于自己的特殊性,常常是必争之地。

    • 总体思路

      ptrace attach目标进程
      保存rip
      控制跳转到mmap分配一段rwx内存
      将一段机器码copy进去
      控制跳转到机器码(可以以bin文件的形式)
      恢复执行。
    • 简单示例代码

      • 首先需要知道一些函数在目标进程的地址,下面是已知pid获取libc基地址(读取/proc/pid/maps),和函数地址(dlsym)
      size_t getLibcbase(int pid)
      {
          size_t libcAddr;
          char* buf;
          char* end;
          char* mapfile[0x18];
          sprintf(mapfile, Mapfile, pid);
          FILE* fd = fopen(mapfile, "r");
          if(!fd)
          {
              printf("open maps error!");
              exit(1);
          }
          //search the libc-.....
          buf = (char*) malloc(0x100);
          do{
              fgets(buf, 0x100, fd);
          } while(!strstr(buf, "libc-"));
          end = strchr(buf, '-');
          libcAddr = strtol(buf, &end, 16);
          printf("The process %d's libcbase is: 0x%lx\n", pid, libcAddr);
          fclose(fd);
          return libcAddr;
      }
      size_t getFuncAddr(int pid, char* funcName)
      {
          size_t funcAddr;
          char* buf;
          char* end;
          char* mapfile[0x18];
          sprintf(mapfile, Mapfile, pid);
      
          //get function offset from self process, the shared libc.so
          funcAddr = (size_t)dlsym(0, funcName);
          funcAddr -= getLibcbase(getpid());
          funcAddr += libc_addr;
          printf("function %s address is: 0x%lx\n", funcName, funcAddr);
          return funcAddr;
      }
      
      • main代码

        • 为了得到存放shellcode的地址,我们需要先执行mmap,而执行mmap也需要一段可执行地址。这里其实我们可以直接使用libc_base。在libc_base处写入下面的opcode,其中int 0x3是为了发出信号,让我们知道该opcode执行完成。

          call rax
              int 0x3
              ;"\xff\xd0\xcd\x03"
        • 实现mmap调用

          • 备份数据,写入opcode,设置mmap参数

            //save a bak of regs
              ptrace(PTRACE_GETREGS, traced, 0, &regs_bak);
              memcpy(&regs, &regs_bak, sizeof(struct user_regs_struct));
            
              //use libc_base to write our short hook code
              buf.val = ptrace(PTRACE_PEEKTEXT, traced, libc_addr, 0);
              hook_bak.val = buf.val;
            
              memcpy(buf.chars, Call, 4);
              ptrace(PTRACE_POKETEXT, traced, libc_addr, buf.val);
            
              fd = open(argv[2], O_RDONLY);
              fstat(fd, &sb);
              if(fd < 0)
              {   
                  printf("open shellcode error!\n");
                  exit(1);
              }
              shellcode = malloc(sb.st_size + 1);
              read(fd, shellcode, sb.st_size);
            
              regs.rax = mmap_addr;
              //prepare for the mmap args
              regs.rdi = 0;
              regs.rsi = sb.st_size;
              regs.rdx = 0x7;
              regs.rcx = MAP_PRIVATE | MAP_ANONYMOUS;
              regs.r8 = -1;
              regs.r9 = 0;
            
              regs.rip = libc_addr;   //jmp to call rax
              ptrace(PTRACE_SETREGS, traced, 0, &regs);
            
              ptrace(PTRACE_CONT, traced, 0, 0);
              //wait mmap is executed
              waitpid(traced, &status, WUNTRACED);
              if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP){
                      printf("SIGTRAP receivedn\n");
              }
            
        • 这里有几个坑

          • mmap的fd参数,必须是open打开的返回值,而不能是fopen这种。
          • x64传参,顺序是rdi、rsi、rdx、rcx、r8、r9;有些地方可能会说第四个参数是r10,发现有误导。
          void *mmap(void *addr, size_t length, int prot, int flags,
                            int fd, off_t offset);
          
          • 由于我们open是在注入进程得到的fd,而mmap是在被hook进程执行的,所以这个fd是不可以被当作参数的。我们必须使用flag MAP_PRIVATE | MAP_ANONYMOUS
        • 往mmap返回的地址里写入shellcode,并执行。

        ptrace(PTRACE_GETREGS, traced, 0, &regs);
          tmp_addr = regs.rax;/
          printf("We get address from mmap: 0x%lx\n", tmp_addr);
        
          copy_code(tmp_addr, shellcode, sb.st_size);
          tmp_val = ptrace(PTRACE_PEEKTEXT, traced, tmp_addr, 0);
          printf("the mapped val: 0x%lx\n", tmp_val);
        
          //jmp to shellcode from file
        
          regs.rip = tmp_addr;
          ptrace(PTRACE_SETREGS, traced, 0, &regs);
        
          ptrace(PTRACE_CONT, traced, 0, 0);
        
          //wait shellcode is executed
          waitpid(traced, &status, WUNTRACED);
          if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP){
                  printf("SIGTRAP receivedn\n");
          }
        
          ptrace(PTRACE_SETREGS, traced, 0, &regs_bak);
          ptrace(PTRACE_CONT, traced, 0, 0);
          close(fd);
        
      • 其中copy_code的实现通过POKETEXT,每次可以向目标进程任意地址写入long大小的值

        void copy_code(size_t addr, char* shellcode, int len)
        {
          int i=0;
          long word;
          for(i=0; i<len; i+=sizeof(long)){   
              memcpy(&word, shellcode+i, sizeof(long));
              if(ptrace(PTRACE_POKETEXT, traced, addr+i, word) == -1){
                  printerror();
                  exit(1);
              }
          }
        }
        
    • 我的shellcode,就是write(1, 'cba', 8),目标进程就是当前终端/bin/bash看下效果。

  • #### PLT重定向劫持Hook

    • 这个主要是利用ELF文件的,GOT和PLT的方式解决地址无关的链接.so文件的机制。

    • 在第一次调用前,Got里是PLT的地址;一般在调用之后Got里会写入库函数的真实地址。

    • PLT在text段,一般不可写;(所以迷,为啥有这一技术)

      • 是因为ptrace可以无视'rwx',这也是为什么gdb可以修改text,下断点。
      • 所以这个来说,算是plt和ptrace结合的一种方式。
    • 一个简单的在PLT处下断,执行完操作后恢复的样例。这里主要解决的是代码重定位的问题,我们从/proc/pid/maps下读出Codebase即可。

      #include <sys/reg.h>
      #include <sys/ptrace.h>
      #include <sys/user.h>
      #include <sys/wait.h>
      #include <sys/stat.h>
      #include <sys/types.h>
      #include <fcntl.h>
      #include <unistd.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <signal.h>
      #include <errno.h>
      #define Mapfile "/proc/%d/maps"
      //用于获取plt内容
      union pltval{
          size_t val;
          unsigned char chars[sizeof(size_t)];
      };
      void usage(char** argv){
          printf("USAGE: \n --- %s pid plt_offset \n", argv[0]);
      }
      void printerror(){
          printf("Status: %s\n", strerror(errno));
      }
      void mod_handle(pid_t tracee, void* addr1, void* addr2)
      {
          union pltval buf;
          buf.val = ptrace(PTRACE_PEEKDATA, tracee, addr1, 0);
          printf("mod_handle: ");
          printerror();
          memcpy(buf.chars, "hooked", 6);
          buf.chars[6] = 0;
      
          ptrace(PTRACE_POKEDATA, tracee, addr1, buf.val);
          printf("hook: ");
          printerror();
          buf.val = ptrace(PTRACE_PEEKDATA, tracee, addr2, 0);
          printf("mod_handle: ");
          printerror();
          memcpy(buf.chars, "/hooked", 7);
          buf.chars[7] = 0;
      
          ptrace(PTRACE_POKEDATA, tracee, addr2, buf.val);
          printf("/hooked: ");
          printerror();
      }
      
      size_t getCodebase(pid_t pid)
      {
          size_t addr;
          char buf[2 * sizeof(size_t)];
          char* end;
          char* mapfile[0x18];
          sprintf(mapfile, Mapfile, pid);
          int fd = open(mapfile, O_RDONLY);
          if(fd == -1)
          {
              printf("open maps error!");
              exit(1);
          }
          read(fd, buf, 2 * sizeof(size_t));
          end = strchr(buf, '-');
          addr = strtol(buf, &end, 16);
          printf("The codebase is: 0x%lx\n", addr);
      
          close(fd);
          return addr;
      }
      
      int main(int argc, char* argv[]){
          pid_t tracee; 
          union pltval plt;
          struct user_regs_struct regs;
          siginfo_t si;
      
          int status;
          size_t plt_offset, plt_addr, bak;
      
          if(argc < 2){
              usage(argv);
              exit(1);
          }
      
          tracee = atoi(argv[1]);
          plt_offset = atoi(argv[2]);
          //获取codebase
          plt_addr = plt_offset + getCodebase(tracee);
          printf("plt_addr ==> %lx\n", plt_addr);
      
          //attach the process
          ptrace(PTRACE_ATTACH, tracee, 0, 0);
          printf("Attach: ");
          printerror();
      
          wait(&status);
      
          //获取目标的plt值,保存,修改,写入, 继续运行
          plt.val = ptrace(PTRACE_PEEKDATA, tracee, plt_addr, 0);
          bak = plt.val;
          plt.chars[0] = 0xcc;        //breakpoint
          ptrace(PTRACE_POKEDATA, tracee, plt_addr, plt.val);
          ptrace(PTRACE_CONT, tracee, 0, 0);
      
          //监视有没有触发断点
          while(1){
              printf("Wait....\n");
              wait(&status);
              printf("Done!\n");
      
              if(WIFEXITED(status)) break;
      
              //获取regs和sig信息,判断是否到达plt
              ptrace(PTRACE_GETSIGINFO, tracee, 0, &si);
              ptrace(PTRACE_GETREGS, tracee, 0, &regs);
      
              if((si.si_signo != SIGTRAP) || (regs.rip != (size_t)plt_addr + 1)){
                  ptrace(PTRACE_GETREGS, tracee, 0, &regs);
                  ptrace(PTRACE_CONT, tracee, 0, 0);
                  continue;
              }
      
              //hook & modify
              mod_handle(tracee, (void*)argv[0], (void*)argv[1]);
      
              //修改回原值
              plt.val = bak;
              ptrace(PTRACE_POKEDATA, tracee, plt_addr, plt.val);
              //返回0xcc前
              regs.rip -= 1;
              ptrace(PTRACE_SETREGS, tracee, 0, &regs);
              ptrace(PTRACE_SINGLESTEP, tracee, 0, 0);
              wait(0);
      
              ptrace(PTRACE_GETREGS, tracee, 0, &regs);
              plt.chars[0] = 0xcc;
              ptrace(PTRACE_POKEDATA, tracee, plt_addr, plt.val);
              ptrace(PTRACE_CONT, tracee, 0, 0);
      
          }
          return 0;
      }
      
  • #### Ring0级别的Hook

    • ##### 前置知识

      • linux内核的编译

        • 最好选择一个和自己虚拟机内核版本一致的源码,网上也很多教程。
        • 再编译一个busybox的文件系统,为了方便添加文件。
      • 模块编译

        • makefile的基本格式(用于本机加载的模块)

          obj-m += inter.o
          CURRENT_PATH := $(shell pwd)
          LINUX_KERNEL := $(shell uname -r)
          LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
          
          all:
              make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
          clean:
              make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
          
        • 交叉编译(本机和编译的内核不同)

          obj-m += mod1.o
          CURRENT_PATH := $(shell pwd)
          LINUX_KERNEL := $(shell uname -r)
          LINUX_KERNEL_PATH := /home/tree/kernel/linux-5.0.3
          
          all:
              make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
          clean:
              make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
          
    • sys_call_table

      • 都知道linux通过int 0x80或者syscall都可以进入kernel完成系统调用,而我们需要把对应的系统调用号传给rax。两者最终还是用了system_call。

      • int 0x80工作原理

        1、系统维护一个叫做“向量中断表的”,每个int xx都会去对应的向量表的xx处
        2、0x80对应系统调用的服务例程,记录了syscall的地址。
        3、而eax的值,对应具体的系统调用号。
      • syscall的具体实现,可以看到这里有sys_call_table符号。

        .globl system_call, buserr, trap, resume
        .globl sys_call_table
        ................
        ................
        ENTRY(system_call)
          SAVE_ALL_SYS                            //保存
        
          GET_CURRENT(%d1)
          movel   %d1,%a1
        
          | save top of frame
          movel   %sp,%curptr@(TASK_THREAD+THREAD_ESP0)
        
          | syscall trace?                        //有没有被ptrace跟踪
          tstb    %a1@(TINFO_FLAGS+2)
          jmi do_trace_entry
          cmpl    #NR_syscalls,%d0
          jcc badsys
        syscall:                                  //真实的执行系统调用
          jbsr    @(sys_call_table,%d0:l:4)@(0)
          movel   %d0,%sp@(PT_OFF_D0) | save the return value
        ret_from_syscall:
          |oriw   #0x0700,%sr
          movel   %curptr@(TASK_STACK),%a1
          movew   %a1@(TINFO_FLAGS+2),%d0
          jne syscall_exit_work
        1:    RESTORE_ALL
    • 获得sys_call_table地址的方式

      • 由于syscall实现处有sys_call_table的符号,我们可以从这里拿到地址。

        1. 获取中断描述符表(IDT)的地址(使用C ASM汇编)
        2. 从中查找0x80中断(系统调用中断)的服务例程(8*0x80偏移)
        3. 搜索该例程的内存空间
        4. 从其中获取sys_call_table(保存所有系统调用例程的入口地址)的地址
      • 使用kallsyms_lookup_name读取。该函数本身也是一个符号,如果没有导出就不能使用。

        sys_call_table_addr = kallsyms_lookup_name("sys_call_table")
        
      • 读取/proc/kallsyms文件。我的理解就是和/proc/pid/maps差不多特殊的一个文件,由内核动态生成,需要root权限,普通用户读到的全是0(但是加载模块也是需要root权限的,所以不是问题)

        sudo cat /proc/kallsyms | grep sys_call_table
        
      • 修改内核,添加EXPORT_SYMBOL(sys_call_table)或EXPORT_SYMBOL_GPL(sys_call_table)。
        这种方法适用于可以修改内核的情形。在可以修改内核的情况下,这是最简单的方式。

    • 实战——hook系统调用 mkdir,我这里使用kallsyms_lookup_name

      //This kernel module locates the sys_call_table by kallsyms_lookup_name
      #include<linux/init.h>
      #include<linux/module.h>
      #include<linux/moduleparam.h>
      #include<linux/unistd.h>
      #include<linux/sched.h>
      #include<linux/syscalls.h>
      #include<linux/string.h>
      #include<linux/fs.h>
      #include<linux/fdtable.h>
      #include<linux/uaccess.h>
      #include <linux/kallsyms.h>
      #include<linux/rtc.h>
      #include<linux/vmalloc.h>
      #include <linux/slab.h>
      
      //module macros
      MODULE_LICENSE("GPL");
      MODULE_DESCRIPTION("hook sys_mkdir");
      
      //module constructor/destructor
      typedef unsigned long (*sys_call_ptr_t)(void);
      sys_call_ptr_t *_sys_call_table = NULL;
      
      typedef asmlinkage long (*old_mkdir_t)(const char __user *pathname, umode_t mode);
      old_mkdir_t old_mkdir = NULL;
      
      // hooked mkdir function
      asmlinkage long hooked_mkdir(const char __user *pathname, umode_t mode) {
      
              printk("hooked sys_mkdir(), mkdir name: ");
              printk(pathname);
              old_mkdir(pathname, mode);
      }
      
      // memory protection shinanigans
      unsigned int level;
      pte_t *pte;
      
      //obtain sys_call_table
      static int get_sys_call_table(void){
          unsigned long tmp_sys_call_table = 0;
          int ans = 0;
      
          tmp_sys_call_table = kallsyms_lookup_name("sys_call_table");
          if(tmp_sys_call_table != 0)
          {
              ans = 1;
              _sys_call_table = tmp_sys_call_table;
              printk("[+] find sys_call_table: 0x%lx\n", tmp_sys_call_table);
          }
          return ans;
      }
      // initialize the module
      static int hooked_init(void) {
          printk("+ Loading hook_mkdir module\n");
          if(!get_sys_call_table()){
              return 0;
          }
          // now we can hook syscalls ...such as uname
          // first, save the old gate (fptr)
          old_mkdir = (old_mkdir_t) _sys_call_table[__NR_mkdir];
      
          // unprotect sys_call_table memory page
          pte = lookup_address((unsigned long) _sys_call_table, &level);
      
          // change PTE to allow writing
          set_pte_atomic(pte, pte_mkwrite(*pte));
      
          printk("+ unprotected kernel memory page containing sys_call_table\n");
      
          // now overwrite the __NR_uname entry with address to our uname
          _sys_call_table[__NR_mkdir] = (sys_call_ptr_t) hooked_mkdir;
      
          printk("+ sys_mkdir hooked!\n");
      
          return 0;
      }
      
      static void hooked_exit(void) {
          if(old_mkdir != NULL) {
              // restore sys_call_table to original state
              _sys_call_table[__NR_mkdir] = (sys_call_ptr_t) old_mkdir;
      
              // reprotect page
              set_pte_atomic(pte, pte_clear_flags(*pte, _PAGE_RW));
          }
      
          printk("+ Unloading hook_mkdir module\n");
      }
      
      /*entry/exit macros*/
      module_init(hooked_init);
      module_exit(hooked_exit);
      
      • 效果(不知道为啥pathname参数不能输出)
    • system_call函数内存内搜索sys_call_table,实现execve的hook

      • 这里注意这种方式在x86和x64上的区别。

        1x86或者x64的兼容模式:使用int 0x80,MSR寄存器地址为0xc0000083,MSR_CSTAR来代表. 使用sidt获取system_call地址
        2x64的long模式:使用syscall,MSR寄存器地址为0xc0000082,宏MSR_LSTAR来代表. 使用rdmsrl指令获取system_call地址
        3x86 sys_call_table的特征码  \xff\x14\x85
        4x86_64下 sys_call_table的特征码  \xff\x14\xc5
        
      • 网上有一大堆x86的获取sys_call_table的方法,我本来打算用下面的思路实现一个x64的。

        1、在x64下,通过rdmsrl(MSR_LSTAR, xxxx)可以拿到entry_SYSCALL_64的地址。entry_SYSCALL_64的实现里会有sys_call_table的机器码。
        2、所以有两种hook的思路
          a、修改entry_SYSCALL_64起始的几个字节(内联hook)
          b、找到sys_call_table,修改对应的系统调用。
      • 但是在linux内核5.x里,entry_SYSCALL_64的实现改了,不再试图用过call sys_call_table[index]的方式进行系统调用,而是引入了一个do_syscall_64的符号。具体的看下面截取的源码

        ENTRY(entry_SYSCALL_64)
          UNWIND_HINT_EMPTY
          .......
          .......
          /* IRQs are off. */
          movq    %rax, %rdi
          movq    %rsp, %rsi
          call    do_syscall_64       /* returns with IRQs disabled */
      • 于是我找到了do_syscall_64的实现,幸运的是在这里找到了对sys_call_table的直接引用。

        #ifdef CONFIG_X86_64
        __visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
        {
          struct thread_info *ti;
        
          enter_from_user_mode();
          local_irq_enable();
          ti = current_thread_info();
          if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
              nr = syscall_trace_enter(regs);
          /*
           * NB: Native and x32 syscalls are dispatched from the same
           * table.  The only functional difference is the x32 bit in
           * regs->orig_ax, which changes the behavior of some syscalls.
           */
          nr &= __SYSCALL_MASK;
          if (likely(nr < NR_syscalls)) {
              nr = array_index_nospec(nr, NR_syscalls);
              regs->ax = sys_call_table[nr](regs);    //这里sys_call_table的直接引用
          }
        
          syscall_return_slowpath(regs);
        }
        #endif
        
      • 所以,显然我们就需要多一次的搜索,特征码可以在gdb里查或者看它的汇编指令。

        1、在entry_SYSCALL_64里搜索do_syscall_64; 特征码'\x48\x89\xe6\xe8' <= mov rsi, rsp; call ....
        2、在do_syscall_64里搜索sys_call_table;  特征码 '\x48\x8b\x04\xfd' <= mov rax []
      • 通过entry_SYSCALL_64的call do_syscall_64找到call do_syscall_64地址

      • 通过do_syscall_64对sys_call_table的引用找到sys_call_table

      • 代码实现

        static void*
        get_lstar_dosys_addr(void){
          unsigned long lstar;
        
            // temp variables for scan
            unsigned int i;
            unsigned char *off;
            rdmsrl(MSR_LSTAR, lstar);
        
            // print out int 0x80 handler
            printk("[+] entry_SYSCALL_64 is at 0x%lx\n", lstar);
        
            // scan for known pattern(0xff14c5xx)
            // pattern is just before sys_call_table address
            for(i = 0; i <= PAGE_SIZE; i++) {
                off = (char*)lstar + i;
        
                if(*(off) == 0x48 && *(off+1) == 0x89 && *(off+2) == 0xe6) {
                    return (off + 3);                     //call do_syscall_64
                }
            }
          return NULL;
        }
        
        static void*
        get_lstar_dosys(void)
        {
          unsigned long* lstar_dosys_addr = get_lstar_dosys_addr();
          if(lstar_dosys_addr != NULL) {
              printk("[+] call_do_syscall_64 at: 0x%lx\n", lstar_dosys_addr);
              unsigned int offset = *(unsigned int*)((char*)lstar_dosys_addr + 1);
              printk("[+] offset is: 0x%08x\n", offset);
              unsigned long base = 0xffffffff00000000;
        
              return (void*)(base | ((unsigned long)lstar_dosys_addr + 5 + offset));
          }
          return NULL;
        }
        static void*
        get_sys_sct_addr(unsigned long* do_syscall_64_addr)
        {
          unsigned char* off;
          int i;
          for(i = 0; i <= PAGE_SIZE; i++) {
              off = (char*)do_syscall_64_addr + i;
              if(*(off) == 0x48 && *(off+1) == 0x8b && *(off+2) == 0x04 && *(off+3) == 0xfd) {
                  return (off+4);
              }
          }
          return NULL;
        }
        static void*
        get_sys_sct(unsigned long* do_syscall_64_addr) 
        {
          unsigned long* sct_addr = get_sys_sct_addr(do_syscall_64_addr);
          if(!sct_addr){
              return NULL;
          }
          unsigned int offset = *(unsigned int*)(sct_addr);
          unsigned long base = 0xffffffff00000000;
        
          return (void*)(base | offset);
        }
        //hooked execve
        static int hook_execve_init(void){
          printk("[+] Finding sys_call_table\n");
          unsigned long* do_syscall_64_addr = 0;
          do_syscall_64_addr = get_lstar_dosys();
          if(!do_syscall_64_addr){
              printk("[x] Failed to find do_syscall_64_addr\n");
              return 0;
          }
          printk("[+] Found do_syscall_64_addr at: 0x%lx\n", do_syscall_64_addr);
          _sys_call_table = get_sys_sct(do_syscall_64_addr);
          if(!_sys_call_table) {
              printk("[x] Failed to find sys_call_table\n");
              return 0;
          }
          printk("[+] Found sys_call_table at: 0x%lx\n", _sys_call_table);
        
          return 0;
        }
        
      • 注意处理一些细节问题(比如unsigned long 还是 unsinged int),最终可以达到想要的效果。
  • 参考链接

4 条评论
某人
表情
可输入 255