通过进程2加载shell进程,详解execve
来源:互联网 发布:顾客数据 编辑:程序博客网 时间:2024/06/06 21:06
一直以来都对execve到底做了什么,总是犯迷糊,原来看Linux内核设计的艺术,这部分讲解的非常不细致,这次结合赵博士的书,重新理解了这部分代码。
首先列出代码,如下:
if (!(pid=fork())) {//进程1创建进程2close(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为32int 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=1FFFCif ((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;//信号处理函数为NULLfor (i=0 ; i<NR_OPEN ; i++)//close_on_exec为0if ((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;//内核栈要返回给用户空间的eipeip[3] = p;//内核栈用返回给用户空间的espreturn 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为-1offset = 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为1dir = (unsigned long *) ((from>>20) & 0xffc); //dir为128for ( ; 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对应的位减1mem_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;//数据段的界限是64MBcode_base = get_base(current->ldt[1]);//代码段基地址为128MBdata_base = code_base;//数据段基地址为128set_base(current->ldt[1],code_base);//修改代码段基地址为128MBset_limit(current->ldt[1],code_limit);//修改代码段界限为shell代码段的长度set_base(current->ldt[2],data_base);//修改数据段基地址为128MBset_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;//192MBfor (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) {//MAX_ARG_PAGES为31data_base -= PAGE_SIZE;//data_base为192MB-4096Bif (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,为188if ((*page_table)&1)//目前为0page_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;//内核栈要返回给用户空间的eipeip[3] = p;//内核栈用返回给用户空间的espeip为0,esp为64MB-4-参数和环境变量的字节数,也就是说在用户空间,访问ss:eip,就是访问128MB+64MB-4-参数和环境变量的字节数,在经过分页机制,最后能够访问最终存放参数和环境变量的页面的指定位置。
- 通过进程2加载shell进程,详解execve
- 进程-execve
- 通过execve在两个进程间传递环境变量
- 通过execve在两个进程间传递环境变量
- 进程的创建: fork/execve
- 【进程管理】系统调用execve()
- 通过execve实现不同进程间文件描述符的共享
- fork应用---子进程调用execve
- 6.fork + execve:一个进程的诞生
- 通过堆栈获取进程自身加载模块
- shell-进程
- 进程切换&&中断&&异常&系统调用execve()函数
- 进程加载
- 通过开始执行shell进程,理解缺页异常
- centos shell 编程-通过端口号kill对应的进程
- shell通过端口号获取PID(进程号)
- 用shell通过jps -m来杀死进程
- R0下通过EPROCESS获取进程加载模块
- MyEclipse使用教程:MyEclipse的远程调试
- #学习笔记#Concept of Hashing
- java并发库java.util.concurrent各类的使用示例
- springMVC对于controller处理方法返回值的可选类型
- iOS文件,保存路径. 防止加到iCloud备份
- 通过进程2加载shell进程,详解execve
- 仿iphone动态萤火虫锁屏应用源码
- 文本挖掘实例
- GUILayout.FlexibleSpace()使用
- [设计模式]桥接模式
- 如何处理10000 TCP连接
- 编写一个将输入复制到输出的程序,并将其中的制表符替换成\t,把回退符替换成\b,把反斜杠替换为\\。
- STM32定时器简介
- Linux下安装Oracle11g服务器