您的位置:首页 > 运维架构 > Linux

通过hook Linux内核函数,监控进程/线程创建与销毁

2013-12-09 19:49 399 查看
还是实验室蛋疼项目的需求催出来的东西,蛋疼要死,CS专业读研难道就等于干2-3年义工??本人对Linux内核无爱,好吗!!!

Linux实际上木有线程这玩意,具体到内核里面就是个进程组头+一堆轻量级进程

太感谢Linus了,工作量瞬间下来了,在内核线程/进程无差别的,写一套东西就忽悠交差说是两套都做了,反正那帮子人也不懂。。。。

所有的进程创建都是通过do_fork()内核函数来做的,所有进程销毁都是走do_exit(),系统调用什么的都是这两个函数的封装而已

比如下面,和创建进程/线程相关的系统调用的处理函数。。。

asmlinkage int sys_fork(struct pt_regs regs)
{
	return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
	unsigned long clone_flags;
	unsigned long newsp;
	int __user *parent_tidptr, *child_tidptr;

	clone_flags = regs.ebx;
	newsp = regs.ecx;
	parent_tidptr = (int __user *)regs.edx;
	child_tidptr = (int __user *)regs.edi;
	if (!newsp)
		newsp = regs.esp;
	return do_fork(clone_flags, newsp, ®s, 0, parent_tidptr, child_tidptr);
}

/*
 * This is trivial, and on the face of it looks like it
 * could equally well be done in user mode.
 *
 * Not so, for quite unobvious reasons - register pressure.
 * In user mode vfork() cannot have a stack frame, and if
 * done by calling the "clone()" system call directly, you
 * do not have enough call-clobbered registers to hold all
 * the information you need.
 */
asmlinkage int sys_vfork(struct pt_regs regs)
{
	return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}


hook了这两个函数就能监控全部的创建销毁了,,

关于怎么hook,在飞客杂志上找到几篇inline hook,都是修改被hook函数入口处的汇编,插入JMP到自己代码再跑完再手动平衡堆栈JMP回去,累不累额,,,

然后看到这个Ph4nt0m Security Team小组的文章http://blog.csdn.net/lucien_cc/article/details/7544834

真可惜,这个小组现在的主站已经不能访问了,还好在Google Sites里面还能找到小组以前的文章
http://www.80vul.com/ 貌似是新站

OK,回来,文章提到不改动入口,而是修改被hook函数的里面调用下层函数的call,貌似实现简单的多啦。。。

看看do_fork 和 do_exit 的代码

fastcall NORET_TYPE void do_exit(long code)
{
	struct task_struct *tsk = current;
	struct taskstats *tidstats;
	int group_dead;
	unsigned int mycpu;

	profile_task_exit(tsk);

	WARN_ON(atomic_read(&tsk->fs_excl));

	...
}//任何进程进入这个函数就是死定了,上下文到这里就没有了

long do_fork(unsigned long clone_flags,
	...
{
	struct task_struct *p;
	int trace = 0;
	struct pid *pid = alloc_pid();

	...

	p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, nr);

	if (!IS_ERR(p)) {

		...

	} else {
		free_pid(pid);
		nr = PTR_ERR(p);
	}
	return nr;
}


代码很明白了do_exit会调用到profile_task_exit,并且进程是必死的。。。

do_fork会调用copy_process复制进程,如果copy_process成功,进程就创建成功了,最后把PID值返回回去

SO,只需要把这两次函数调用的call语句修改了就能监控全部的创建与销毁了。

最后实现代码如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/smp_lock.h>

MODULE_LICENSE("GPL");

#define _FORK_copy_process 0xc04234f9
#define _EXIT_profile_task_exit 0xc04268cf
#define _DO_EXIT_ 0xc0427ec1
#define _DO_FORK_ 0xc0424944 //从 /boot/System.map-$(uname -r) 能找到这些地址

//CR0的宏,网上扒的
#define CLEAR_CR0    asm ("pushl %eax\n\t"             \
"movl %cr0, %eax\n\t"        \
"andl $0xfffeffff, %eax\n\t"     \
"movl %eax, %cr0\n\t"        \
"popl %eax");

#define SET_CR0        asm ("pushl %eax\n\t"             \
"movl %cr0, %eax\n\t"         \
"orl $0x00010000, %eax\n\t"     \
"movl %eax, %cr0\n\t"        \
"popl %eax");

struct task_struct *(*orig_copy_process)(unsigned long clone_flags,
					unsigned long stack_start,
					struct pt_regs *regs,
					unsigned long stack_size,
					int __user *parent_tidptr,
					int __user *child_tidptr,
					int pid); //从内核源代码复制过来的函数声明

static struct task_struct *my_copy_process(unsigned long clone_flags,
					unsigned long stack_start,
					struct pt_regs *regs,
					unsigned long stack_size,
					int __user *parent_tidptr,
					int __user *child_tidptr,
					int pid)
{
	struct task_struct * ret;
	ret = (*orig_copy_process)(clone_flags,stack_start,regs,stack_size,parent_tidptr,child_tidptr,pid);
	if(!IS_ERR(ret))
		printk("---z---\tPID:%d fork %d successed!\n",current->pid,pid);
	else
		printk("---z---\tPID:%d fork %d failed!\n",current->pid,pid);
	return ret;

}

void (*orig_profile_task_exit)(struct task_struct * task);

void my_profile_task_exit(struct task_struct * task)
{//这个函数不是每个内核都有的,在CONFIG_PROFILING=n的情况下编译的内核,profile_task_exit不存在的,但是呢,在centos 5.5的内核里面是有的,管其他呢。。。。
	printk("---z---\tPID:%d exited!\n",current->pid);
}

static int replace_fun(unsigned long handle, unsigned long old_fun, unsigned long new_fun)
{
	unsigned char *p = (unsigned char *)handle;
	int i = 0;
	while(1)
	{
		if(i++ > 128)
			return 0;
		if(*p == 0xe8)
		{//e8是GCC编译出来的普通函数调用的call,当然也可能是某个立即数里面的一个字节
			if((*(int *)(p+1) + (unsigned long)p + 5) == old_fun)
			{//so需要看下看e8后面是不是老地址的偏移值,是的话替换掉
				*(int *)(p+1) = new_fun - (unsigned long)p - 5;
				return 1;
			}
		}
		p++;
	}
}

static int _init_module(void ) {
	printk("---z---\t+++++++++++\n");
	orig_copy_process = _FORK_copy_process;
	orig_profile_task_exit = _EXIT_profile_task_exit;
	lock_kernel();
	CLEAR_CR0
	replace_fun(_DO_FORK_, _FORK_copy_process, (unsigned long)my_copy_process);
	replace_fun(_DO_EXIT_, _EXIT_profile_task_exit, (unsigned long)my_profile_task_exit);
	SET_CR0
	unlock_kernel();
	return 0;
}

static void _cleanup_module(void) {
    printk("---z---\t---------\n");
    lock_kernel();
	CLEAR_CR0
	replace_fun(_DO_FORK_, (unsigned long)my_copy_process, _FORK_copy_process);
	replace_fun(_DO_EXIT_, (unsigned long)my_profile_task_exit, _EXIT_profile_task_exit);
	SET_CR0
	unlock_kernel();
}

module_init(_init_module);
module_exit(_cleanup_module);


装载卸载内核,dmesg看输出

---z--- +++++++++++
---z--- PID:12668 exited!
---z--- PID:642 fork 12671 successed!
---z--- PID:12671 exited!
---z--- PID:4422 fork 12672 successed!
---z--- PID:12672 exited!
---z--- PID:6992 fork 12673 successed!
---z--- PID:11 fork 12674 successed!
---z--- PID:12674 fork 12675 successed!
---z--- PID:12675 exited!
---z--- PID:12674 exited!
---z--- ---------


OK ,很好,,,,,,
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: