服务器 频道

Freebsd内核模块源码实现以及应用探

    这节列出一些常用的方法,将在后面的具体技术中使用,比如隐藏进程,网络连接。当然这些方法也可以用来实现其他的..
  
  2.1. 修改函数指针
  
  最古老也最经常用的方法,修改函数指针,用来指向你的函数,或者通过改写/dev/kmem达到相同的目的。(下面)
  注意当你修改了函数指针后,你的新的函数要和原来的函数有相同的调用参数。下面介绍了一些通常用来hook的内核函数
  
  2.1.1 系统调用
  
  经典的hook方法,freebsd通过一个全局的sysent结构数组保持了一系列的系统调用,参见/sys/kern/init_sysent.c
  struct sysent sysent[] = {
  { 0, (sy_call_t *)nosys },           /* 0 = syscall */
  { AS(rexit_args), (sy_call_t *)exit },     /* 1 = exit */
  { 0, (sy_call_t *)fork },            /* 2 = fork */
  { AS(read_args), (sy_call_t *)read },      /* 3 = read */
  { AS(write_args), (sy_call_t *)write },     /* 4 = write */
  { AS(open_args), (sy_call_t *)open },      /* 5 = open */
  { AS(close_args), (sy_call_t *)close },     /* 6 = close */
  [...]
  结构sysent在/sys/sys/syscall.h定义,还有系统调用号也在此文件中定义
  比方说你想替换open这个系统调用,在你的模块加载函数的MOD_LOAD节中这样做
  sysent[SYS_open] = (sy_call_t *)your_new_open
  然后在你的模块卸载节中修复原来的系统调用
  sysent[SYS_open].sy_call = (sy_call_t *)open;
  
  2.1.2. 其它一些有用的表
  
  系统调用不是唯一可以修改的地方,在freebsd内核中还有一些其它的地方也可以利用,特别是inetsw和各种文件系统的vnode表.
  struct ipprotosw intesw[]保存了一系列被支持的inet协议的信息,这其中包括了当这种协议的数据报到达时或送出时用来处
  理的函数 参见/sys/netinet/in_proto.c得到更多的信息,所以我们也可以hook这里的函数:)
  下面我们就可以在模块中hook了
  inetsw[ip_protox[IPPROTO_ICMP]].pr_input = new_icmp_input;
  
  通常每种文件系统的vnode表都是由多个具体的函数组成。所以我们可以替换它们来隐藏我们的文件。
  ufs_vnodeop_p[VOFFSET(vop_lookup)] = (vop_t *) new_ufs_lookup;
  
  在内核中当然还有很多地方可以hook,这就取决你的目的了,kernel source 是最重要的文档
  
  2.1.3 单个的函数指针
  
  偶尔我们也会碰到单个的函数函数指针,比如说ip_fw_ctl_ptr,这个函数用来处理ipfw的请求,这里我们也可以用来hook。
  
  2.2. 修改内核队列
  
  也许你想修改内核中的一些数据,一些感兴趣的东西都以队列的形式存储在内核中,如果你从来没有
  使用过/sys/sys/queue.h的一些宏,你先要熟悉一下它然后在进行下面的阅读。这可以让你轻松面对下面的kernel source
  并且在你使用这些宏时不会出错。
  
  一些感兴趣的队列进程队列:struc proclist allproc 和 zombproc 也许你并不想修改这的东西因为进程调度的目的,除非你想重写大部分的
  内核代码,但是你可以过滤它当有用户请求时。
  
  linker_files队列:这个队列中包括了连接到了kernel的文件,每个文件可以包含多个模块,它的描述可以在这里找到(THC art
  icle)这篇文章的连接是http://www.thehackerschoice.com/papers/bsdkern.html),自己找吧。:)这个队列非常重要
  当我们改变符号的地址,或者隐瞒这个文件所包含的模块。
  
  模块队列:module list_t 这个队列包含了加载的内核模块,注意这个模块队列区别于linker_files队列,这对于隐藏模块很重要。
  
  还是那句话,最好的文档就是kernel source。
  
  2.3 读写内核内存
  
  模块并不是唯一的修改内核的途径,我们还可以直接修改内核空间通过/dev/kmem。
  
  2.3.1. 查找一个符号的地址
  
  当你处理内核内存时,你首先感兴趣的是用来读写的符号的正确的地址(比如函数,变量),在freebsd中 函数Fvm(3)提供了一些有
  用的的功能请参考manpage查询具体的用法,下面给出一个例子读取指定的符号的地址 在CY 包中可以找到 tools/findsym.c.
  
  [...]
  char errbuf[_POSIX2_LINE_MAX];
  kvm_t *kd;
  struct nlist nl[] = { { NULL }, { NULL }, };
  
  nl[0].n_name = argv[1];
  
  kd = kvm_openfiles(NULL,NULL,NULL,O_RDONLY,errbuf);
  if(!kd) {
  fprintf(stderr,"ERROR: %s\n",errbuf);
  exit(-1);
  }
  
  if(kvm_nlist(kd,nl) < 0) {
  fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
  exit(-1);
  }
  
  if(nl[0].n_value)
  printf("symbol %s is 0x%x at 0x%x\n",nl[0].n_name,nl[0].n_type,nl[0].n_value);
  else
  printf("%s not found\n",nl[0].n_name);
  
  if(kvm_close(kd) < 0) {
  fprintf(stderr,"ERROR: %s\n",kvm_geterr(kd));
  exit(-1);
  }
  [...]
  
  2.3.2 读数据
  
  现在你找到了一些正确的符号地址(比如说函数,变量),你可能想要读一些数据,利用函数kvm_read ,代码tools/kvmread.c
  和tools/listprocs.c提供了一个例子。
  
  如果你想读取队列的全部,你只要找到队列头然后用next指针来找到下一个元素(结构体),同样你可以获得其他的数据通过
  这个struct 指针比如说用户的表示符(在这个结构中包含了uid,euid) 下面给出了一个例子(在listproc.c),当我们找到了allproc的地址,这个队列
  的头就确定了
  [...]
  
  kvm_read(kd,nl[0].n_value, &allproc, sizeof(struct proclist)); //allproc 是所有进程的队列头
  
  printf("PID\tUID\n\n");
  
  for(p_ptr = allproc.lh_first; p_ptr; p_ptr = p.p_list.le_next) {
  
  /* read this proc structure */
  kvm_read(kd,(u_int32_t)p_ptr, &p, sizeof(struct proc)); //p_ptr指向结构proc 进程控制块
  
  /* read the user credential */
  kvm_read(kd,(u_int32_t)p.p_cred, &cred, sizeof(struct pcred));//p_cred 指向包含ruid,suid的结构pcred
  
  
  printf("%d\t%d\n", p.p_pid, cred.p_ruid);
  
  }
  
  2.3.3 修改内核代码
  
  用同样的方法我们可以来写内核代码了,man函数kvm_write可以得到更多相关内容,后面将会给出一个例子。如果你现在不耐烦了
  请看一会tools/putjump.c吧
0
相关文章