码迷,mamicode.com
首页 > 系统相关 > 详细

通过进程2加载shell进程,详解execve

时间:2015-02-07 14:31:47      阅读:292      评论:0      收藏:0      [点我收藏+]

标签:

    一直以来都对execve到底做了什么,总是犯迷糊,原来看Linux内核设计的艺术,这部分讲解的非常不细致,这次结合赵博士的书,重新理解了这部分代码。

    首先列出代码,如下:

	if (!(pid=fork())) {//进程1创建进程2
		close(0);
		if (open("/etc/rc",O_RDONLY,0))
			_exit(1);
		execve("/bin/sh",argv_rc,envp_rc);
		_exit(2);
	}
    进程1创建进程2,进程2的页目录表及页表如图1,页目录表项是第32位,由于页目录表从内核0x0的位置,所以进程2的页目录项的位置为32,由于每个页目录项所占的字节数为4,所以内存地址为128。

技术分享

                                          图 1

    此时用命令行,查看内核地址为128的数据。0xffa027,就是进程2页表的首地址 。

    技术分享

                             图 2

    0xffa000开始存放的进程2的页表项,如下图:

    技术分享

                            图 3


    下面看真正的execve,代码如下:

int do_execve(unsigned long * eip,long tmp,char * filename,
	char ** argv, char ** envp)
{
	struct m_inode * inode;
	struct buffer_head * bh;
	struct exec ex;
	unsigned long page[MAX_ARG_PAGES];//MAX_ARG_PAGES为32
	int i,argc,envc;
	int e_uid, e_gid;
	int retval;
	int sh_bang = 0;
	unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;//4096*32-4=131068=1FFFC

	if ((0xffff & eip[1]) != 0x000f)
		panic("execve called from supervisor mode");
	for (i=0 ; i<MAX_ARG_PAGES ; i++)	/* clear page-table */
		page[i]=0;
	if (!(inode=namei(filename)))		//找到/bin/sh的i节点
		return -ENOENT;
	argc = count(argv);//参数的数量
	envc = count(envp);//环境变量的数量
	
restart_interp:
	.....
	if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {//读取第一块的数据
		retval = -EACCES;
		goto exec_error2;
	}
	ex = *((struct exec *) bh->b_data);	//赋值给文件头
	.....
	if (!sh_bang) {
		p = copy_strings(envc,envp,page,p,0);
		p = copy_strings(argc,argv,page,p,0);//最后返回的p是131068减去参数和环境变量的字节数,堆栈的指针。目前page数组中,page[31]已经是一个新申请页面的地址了。
		if (!p) {
			retval = -ENOMEM;
			goto exec_error2;
		}
	}
/* OK, This is the point of no return */
	if (current->executable)
		iput(current->executable);
	current->executable = inode;//刚刚获取的/bin/sh节点
	for (i=0 ; i<32 ; i++)
		current->sigaction[i].sa_handler = NULL;//信号处理函数为NULL
	for (i=0 ; i<NR_OPEN ; i++)//close_on_exec为0
		if ((current->close_on_exec>>i)&1)//不会执行
			sys_close(i);
	current->close_on_exec = 0;
	free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));//get_base(current->ldt[1])为128MB,get_limit(0x0f)为640KB,页目录项的第32项清零,它所指向的页表也都清0了。
	free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
	if (last_task_used_math == current)
		last_task_used_math = NULL;
	current->used_math = 0;
	p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;//p最后为PAGE_SIZE*MAX_ARG_PAGES-4 - 参数和环境变量的字节数 + 64MB - MAX_ARG_PAGES*PAGE_SIZE,最后就是64MB-4-
参数和环境变量的字节数,也就是换成了以64MB为界限的堆栈值了。
	p = (unsigned long) create_tables((char *)p,argc,envc);
	current->brk = ex.a_bss +(current->end_data = ex.a_data +(current->end_code = ex.a_text));//代码段,数据段,bss段
	current->start_stack = p & 0xfffff000;
	current->euid = e_uid;
	current->egid = e_gid;
	......
	eip[0] = ex.a_entry;		//内核栈要返回给用户空间的eip
	eip[3] = p;			//内核栈用返回给用户空间的esp
	return 0;
	....
	return(retval);
}

    1、inode=namei(filename),找到/bin/sh的i节点。

    2、argc = count(argv); envc = count(envp),计算参数及环境变量的数量。

    3、bh = bread(inode->i_dev,inode->i_zone[0]);ex = *((struct exec *) bh->b_data),找到/bin/sh的文件头。


    下面是copy_strings(envc,envp,page,p,0)。代码如下:

static unsigned long copy_strings(int argc,char ** argv,unsigned long *page,
		unsigned long p, int from_kmem)
{
	char *tmp, *pag=NULL;
	int len, offset = 0;
	unsigned long old_fs, new_fs;

	if (!p)
		return 0;	/* bullet-proofing */
	new_fs = get_ds();
	old_fs = get_fs();
	.....
	while (argc-- > 0) {//参数的个数
		.....
		if (!(tmp = (char *)get_fs_long(((unsigned long *)argv)+argc)))//第一个参数的指针
			panic("argc is wrong");
		.....
		len=0;		/* remember zero-padding */
		do {
			len++;
		} while (get_fs_byte(tmp++));//获取第一个参数的长度
		if (p-len < 0) {	/* this shouldn‘t happen - 128kB */
			set_fs(old_fs);
			return 0;
		}
		while (len) {
			--p; --tmp; --len;
			if (--offset < 0) {//第一次进入offset为-1
				offset = p % PAGE_SIZE;//offset为4092
				......
				if (!(pag = (char *) page[p/PAGE_SIZE]) && //p/PAGE_SIZE为31
				    !(pag = (char *) page[p/PAGE_SIZE] =
				      (unsigned long *) get_free_page())) //page[31]存的是新申请页面的地址
					return 0;
				.....

			}
			*(pag + offset) = get_fs_byte(tmp);//伴随循环,参数被写到新申请的页面(从4092依次向低地址4091,4090.....)
		}
	}
        ......
	return p;//最后返回的131068-参数的字节,堆栈的指针。
}
    由于from_kmem为0,我们去掉from_kmem为其他值的情况。

    参数的含义如下:p为131068,argc位参数的个数,agrv为参数的指针数组,page是page[MAX_ARG_PAGES]的首地址。

    我们假设只申请了一个页面就足够存参数和环境变量了,最后返回的p是131068减去参数和环境变量的字节数,堆栈的指针。目前page数组中,page[31]已经是一个新申请页面的地址了。


    下面分析,free_page_tables代码,如下:

int free_page_tables(unsigned long from,unsigned long size)//from为128MB,size为640KB
{
	unsigned long *pg_table;
	unsigned long * dir, nr;

	if (from & 0x3fffff)
		panic("free_page_tables called with wrong alignment");
	if (!from)
		panic("Trying to free up swapper memory space");
	size = (size + 0x3fffff) >> 22;//size为1
	dir = (unsigned long *) ((from>>20) & 0xffc); //dir为128
	for ( ; size-->0 ; dir++) {
		if (!(1 & *dir))
			continue;
		pg_table = (unsigned long *) (0xfffff000 & *dir);//pg_table为页目录项第32项所指向的内存地址
		for (nr=0 ; nr<1024 ; nr++) {
			if (1 & *pg_table)
				free_page(0xfffff000 & *pg_table);//mem_map对应的位减1,也许会清0,可以重新被用于分配
			*pg_table = 0;//对应的页表项都清零
			pg_table++;
		}
		free_page(0xfffff000 & *dir);//由于地址小于1MB,所以直接返回
		*dir = 0;//第32项页目录项页清零
	}
	invalidate();
	return 0;
}
    free_page,代码如下:

void free_page(unsigned long addr)
{
	if (addr < LOW_MEM) return;//1MB以上
	if (addr >= HIGH_MEMORY)
		panic("trying to free nonexistent page");
	addr -= LOW_MEM;
	addr >>= 12;
	if (mem_map[addr]--) return;//mem_map对应的位减1
	mem_map[addr]=0;
	panic("trying to free free page");
}
    free_page_tables,页目录项的第32项清零,它所指向的页表也都清0了。

技术分享

                     图 4


    下面来看change_ldt,代码如下:

static unsigned long change_ldt(unsigned long text_size,unsigned long * page)
{
	unsigned long code_limit,data_limit,code_base,data_base;
	int i;

	code_limit = text_size+PAGE_SIZE -1;//text_size为shell代码段的长度
	code_limit &= 0xFFFFF000;//代码段的界限就是shell代码段的长度
	data_limit = 0x4000000;//数据段的界限是64MB
	code_base = get_base(current->ldt[1]);//代码段基地址为128MB
	data_base = code_base;//数据段基地址为128
	set_base(current->ldt[1],code_base);//修改代码段基地址为128MB
	set_limit(current->ldt[1],code_limit);//修改代码段界限为shell代码段的长度
	set_base(current->ldt[2],data_base);//修改数据段基地址为128MB
	set_limit(current->ldt[2],data_limit);//修改数据段界限为64MB
/* make sure fs points to the NEW data segment */
	__asm__("pushl $0x17\n\tpop %%fs"::);
	data_base += data_limit;//192MB
	for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) {//MAX_ARG_PAGES为31
		data_base -= PAGE_SIZE;//data_base为192MB-4096B
		if (page[i])//现在只有page[31]有地址,里面存放着参数和环境变量的页面的首地址
			put_page(page[i],data_base);
	}
	return data_limit;//返回64MB
}
    change_ldt,修改了代码段和数据段的基地址和段界限,数据段的段界限为64MB,由于一个页目录项可以代表4MB的内存地址,所以需要16个页目录项。也就是从第32个页目录项到第48个页目录项。


    下面来看put_page,代码如下:

unsigned long put_page(unsigned long page,unsigned long address)//address为0xBFFF000
{
	unsigned long tmp, *page_table;

/* NOTE !!! This uses the fact that _pg_dir=0 */

	if (page < LOW_MEM || page >= HIGH_MEMORY)
		printk("Trying to put page %p at %p\n",page,address);
	if (mem_map[(page-LOW_MEM)>>12] != 1)
		printk("mem_map disagrees with %p at %p\n",page,address);
	page_table = (unsigned long *) ((address>>20) & 0xffc);//BxBC,为188
	if ((*page_table)&1)//目前为0
		page_table = (unsigned long *) (0xfffff000 & *page_table);
	else {//走到这里
		if (!(tmp=get_free_page()))//获取存放页表项的页表
			return 0;
		*page_table = tmp|7;//页目录表第188项存放刚刚获取的页表地址
		page_table = (unsigned long *) tmp;//把页表地址赋值给page_table
	}
	page_table[(address>>12) & 0x3ff] = page | 7;//页表的最后一项存放的是page(存放的参数和环境变量的页面的首地址)
/* no need for invalidate */
	return page;
}
    put_page,address为0xBFFF000,page为page[31],存放的参数和环境变量的页面的首地址。执行完put_page后,内存的图如下:

   技术分享  技术分享 

   页目录项的第48位,指向页表的首地址。页表的首地址+4092,这个地址存放的内容的就是存放的参数和环境变量的页面的首地址。


    p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;//p最后为PAGE_SIZE*MAX_ARG_PAGES-4 - 参数和环境变量的字节数 + 64MB - MAX_ARG_PAGES*PAGE_SIZE,最后就是64MB-4- 参数和环境变量的字节数,也就是换成了以64MB为界限的堆栈值了。


    然后调用create_tables,代码如下:

static unsigned long * create_tables(char * p,int argc,int envc)
{
	unsigned long *argv,*envp;
	unsigned long * sp;

	sp = (unsigned long *) (0xfffffffc & (unsigned long) p);
	sp -= envc+1;
	envp = sp;
	sp -= argc+1;
	argv = sp;
	put_fs_long((unsigned long)envp,--sp);
	put_fs_long((unsigned long)argv,--sp);
	put_fs_long((unsigned long)argc,--sp);
	while (argc-->0) {
		put_fs_long((unsigned long) p,argv++);
		while (get_fs_byte(p++)) /* nothing */ ;
	}
	put_fs_long(0,argv);
	while (envc-->0) {
		put_fs_long((unsigned long) p,envp++);
		while (get_fs_byte(p++)) /* nothing */ ;
	}
	put_fs_long(0,envp);
	return sp;
}

     最后形成如下图:

技术分享


    最后的点睛之作,

	eip[0] = ex.a_entry;		//内核栈要返回给用户空间的eip
	eip[3] = p;			//内核栈用返回给用户空间的esp
    eip为0,esp为64MB-4-参数和环境变量的字节数,也就是说在用户空间,访问ss:eip,就是访问128MB+64MB-4-参数和环境变量的字节数,在经过分页机制,最后能够访问最终存放参数和环境变量的页面的指定位置。

通过进程2加载shell进程,详解execve

标签:

原文地址:http://blog.csdn.net/jltxgcy/article/details/43560229

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!