分析应用程序加载时堆栈中的参数结构(opera)

来源:互联网 发布:网络黑侠最新作品 编辑:程序博客网 时间:2024/06/05 16:02
导读:
标题   分析应用程序加载时堆栈中的参数结构    
作者 opera (enthusiast) 
时间 04/13/01 09:17 PM 
 


内核在运行应用程序之前要在用户堆栈最顶部建立一参数块,缺省配置下参数块最大为32个页面. 

用户堆栈最顶部(0xBFFFFFFC)的字保留为0,接下来依次为执行文件名字符串,环境字符串表,命令行参数字符串表,处理器名称字符串,然后是一段辅助信息表,环境字符串指针表,命令行字符串指针表,最后是命令行参数数量argc. 

对于标准的Linux程序来说,命令行字符串表指针argv和环境字符串表指针envp并不压入argc之上,就是说ELF的入口函数并不是以main(argc,argv,envp)参数形式调用,而是用main(argc,argv[0],argv[1],...,NULL,envp[0],envp[1],...,NULL)这样的形式,我觉得这有点莫名其妙,这样就使得Linux必须用汇编写一启动代码作一次转换才能调用main()函数,Linux采用这种方案的妙处何在? 



struct linux_binprm{
 char buf[BINPRM_BUF_SIZE];
 struct page *page[MAX_ARG_PAGES]; 用户堆栈参数页面的内核映象
 unsigned long p; /* current top of mem */
 int sh_bang;
 struct file * file;
 int e_uid, e_gid;
 kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
 int argc, envc;
 char * filename; /* Name of binary */
 unsigned long loader, exec;
};
; fs/exec.c
asmlinkage int sys_execve(struct pt_regs regs)
{
 int error;
 char * filename;

 filename = getname((char *) regs.ebx);
 error = PTR_ERR(filename);
 if (IS_ERR(filename))
  goto out;
 error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, ?s);
 if (error == 0)
  current->ptrace &= ~PT_DTRACE;
 putname(filename);
out:
 return error;
}
int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)
{
 struct linux_binprm bprm;
 struct file *file;
 int retval;
 int i;

 file = open_exec(filename);

 retval = PTR_ERR(file);
 if (IS_ERR(file))
  return retval;

 bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *); 
  ;保留堆栈最顶部的一个字,
  ;bprm.p是bprm.page上的相对堆栈指针,同时反映了参数页可用内存的大小
 memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0])); 
  ; 清除参数页面指针表
 bprm.file = file;
 bprm.filename = filename;
 bprm.sh_bang = 0;
 bprm.loader = 0;
 bprm.exec = 0;
 if ((bprm.argc = count(argv, bprm.p / sizeof(void *))) < 0) {
  ; 扫描用户参数数组,计算参数个数
  allow_write_access(file);
  fput(file);
  return bprm.argc;
 }

 if ((bprm.envc = count(envp, bprm.p / sizeof(void *))) < 0) {
  ; 扫描用户环境数组,计算环境变量个数
  allow_write_access(file);
  fput(file);
  return bprm.envc;
 }

 retval = prepare_binprm(&bprm);
 if (retval < 0) 
  goto out; 

 retval = copy_strings_kernel(1, &bprm.filename, &bprm); 
 if (retval < 0) 在参数堆栈顶部首先压入可执行文件名
  goto out; 
 bprm.exec = bprm.p;

 retval = copy_strings(bprm.envc, envp, &bprm);
 if (retval < 0) 接着压入环境字符串表
  goto out; 

 retval = copy_strings(bprm.argc, argv, &bprm);
 if (retval < 0) 再压入命令行参数字符串表
  goto out; 

 retval = search_binary_handler(&bprm,regs);
 if (retval >= 0)
  /* execve success */
  return retval;

out:
 /* Something went wrong, return the inode and free the argument pages*/
 allow_write_access(bprm.file);
 if (bprm.file)
  fput(bprm.file);

 for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
  struct page * page = bprm.page[ i ];
  if (page)
   __free_page(page);
 }

 return retval;
}
; fs/binfmt_elf.c
static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
{
 ...

 setup_arg_pages(bprm); 将内核的参数页映射到用户堆栈区域

 ...

 bprm->p = (unsigned long)
   create_elf_tables((char *)bprm->p,
   bprm->argc,
   bprm->envc,
   (interpreter_type == INTERPRETER_ELF ? &elf_ex : NULL),
   load_addr, load_bias,
   interp_load_addr,
   (interpreter_type == INTERPRETER_AOUT ? 0 : 1));
 ...
}
static elf_addr_t * 
create_elf_tables(char *p, int argc, int envc,
    struct elfhdr * exec,
    unsigned long load_addr,
    unsigned long load_bias,
    unsigned long interp_load_addr, int ibcs)
{
 elf_caddr_t *argv;
 elf_caddr_t *envp;
 elf_addr_t *sp, *csp;
 char *k_platform, *u_platform;
 long hwcap;
 size_t platform_len = 0;

 ; 这时p指向第1个命令行参数字符串
 /*
  * Get hold of platform and hardware capabilities masks for
  * the machine we are running on.  In some cases (Sparc), 
  * this info is impossible to get, in others (i386) it is
  * merely difficult.
  */

 hwcap = ELF_HWCAP; CPU特性描述字
 k_platform = ELF_PLATFORM; CPU类型名

 if (k_platform) {
  platform_len = strlen(k_platform) + 1;
  u_platform = p - platform_len;
  __copy_to_user(u_platform, k_platform, platform_len);
 } else
  u_platform = p;

 
 /*
  * Force 16 byte _final_ alignment here for generality.
  * Leave an extra 16 bytes free so that on the PowerPC we
  * can move the aux table up to start on a 16-byte boundary.
  */
 
 sp = (elf_addr_t *)((~15UL & (unsigned long)(u_platform)) - 16UL); 
  ; 16字节边界上对齐
 csp = sp;
 csp -= ((exec ? DLINFO_ITEMS*2 : 4) + (k_platform ? 2 : 0));
 csp -= envc+1; 环境字符串表
 csp -= argc+1; 命令行参数字符串表
 csp -= (!ibcs ? 3 : 1); 是否建立argv与envp指针
 if ((unsigned long)csp & 15UL)
  sp -= ((unsigned long)csp & 15UL) / sizeof(*sp); 
  ; 预偏置一下,使得最后的sp能够在16字节边界上对齐
 
 /*
  * Put the ELF interpreter info on the stack
  */
#define NEW_AUX_ENT(nr, id, val) / 由两个字组成的表项
   __put_user ((id), sp+(nr*2)); /
   __put_user ((val), sp+(nr*2+1)); /

 sp -= 2; 分配两个字的空间
 NEW_AUX_ENT(0, AT_NULL, 0); 写入顶部两个零指针作为补充参数表的结束
 if (k_platform) {
  sp -= 2; 
  ; 填写CPU类型名称
  NEW_AUX_ENT(0, AT_PLATFORM, (elf_addr_t)(unsigned long) u_platform);
 }
 sp -= 3*2;
 NEW_AUX_ENT(0, AT_HWCAP, hwcap); 填写CPU特性字
 NEW_AUX_ENT(1, AT_PAGESZ, ELF_EXEC_PAGESIZE); 填写可执行文件页长
 NEW_AUX_ENT(2, AT_CLKTCK, CLOCKS_PER_SEC); 

 if (exec) {
  ; 填写动态链接程序的补充参数表
  sp -= 10*2;

  NEW_AUX_ENT(0, AT_PHDR, load_addr + exec->e_phoff); 程序段表
  NEW_AUX_ENT(1, AT_PHENT, sizeof (struct elf_phdr)); 程序段表项长度
  NEW_AUX_ENT(2, AT_PHNUM, exec->e_phnum); 程序段表项数目
  NEW_AUX_ENT(3, AT_BASE, interp_load_addr); 动态连接器的加载地址
  NEW_AUX_ENT(4, AT_FLAGS, 0);
  NEW_AUX_ENT(5, AT_ENTRY, load_bias + exec->e_entry); 主程序的入口
  NEW_AUX_ENT(6, AT_UID, (elf_addr_t) current->uid);
  NEW_AUX_ENT(7, AT_EUID, (elf_addr_t) current->euid);
  NEW_AUX_ENT(8, AT_GID, (elf_addr_t) current->gid);
  NEW_AUX_ENT(9, AT_EGID, (elf_addr_t) current->egid);
 }
#undef NEW_AUX_ENT

 sp -= envc+1; 分配环境指针表
 envp = (elf_caddr_t *) sp;
 sp -= argc+1; 分配参数指针表
 argv = (elf_caddr_t *) sp;
 if (!ibcs) {
  __put_user((elf_addr_t)(unsigned long) envp,--sp); 填写环境指针表地址
  __put_user((elf_addr_t)(unsigned long) argv,--sp); 填写命令行参数指针表地址
 }

 __put_user((elf_addr_t)argc,--sp); 填写命令行参数数目
 current->mm->arg_start = (unsigned long) p; 第1个命令行参数字符串地址
 while (argc-->0) {
  ; 填写命令行参数指针表
  __put_user((elf_caddr_t)(unsigned long)p,argv++);
  p += strlen_user(p);
 }
 __put_user(NULL, argv); 命令行参数指针表终止指针
 current->mm->arg_end = current->mm->env_start = (unsigned long) p; 第1个参数字符串地址
 while (envc-->0) {
  ; 填写环境指针表
  __put_user((elf_caddr_t)(unsigned long)p,envp++);
  p += strlen_user(p);
 }
 __put_user(NULL, envp); 环境指针表终止指针
 current->mm->env_end = (unsigned long) p; 环境字符串表终止地址
 return sp; 返回指向命令行参数数目的地址
}

int setup_arg_pages(struct linux_binprm *bprm)
{
 unsigned long stack_base;
 struct vm_area_struct *mpnt;
 int i;

 stack_base = STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE;

 bprm->p += stack_base; 这时bprm->p重定位为用户参数块的堆栈指针
 if (bprm->loader)
  bprm->loader += stack_base; 
 bprm->exec += stack_base; 指向用户堆栈最顶部的可执行文件名

 mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
 if (!mpnt) 
  return -ENOMEM; 
 ; 建立初始的vm_area_struct结构
 down(¤t->mm->mmap_sem);
 {
  mpnt->vm_mm = current->mm;
  mpnt->vm_start = PAGE_MASK & (unsigned long) bprm->p; 虚存起始于参数块的堆栈指针
  mpnt->vm_end = STACK_TOP; 虚存终止于STACK_TOP
  mpnt->vm_page_prot = PAGE_COPY;
  mpnt->vm_flags = VM_STACK_FLAGS;
  mpnt->vm_ops = NULL;
  mpnt->vm_pgoff = 0;
  mpnt->vm_file = NULL;
  mpnt->vm_private_data = (void *) 0;
  insert_vm_struct(current->mm, mpnt);
  current->mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT; 
   ; 目前虚存的总量
 } 

 for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
  struct page *page = bprm->page[ i ];
  if (page) {
   bprm->page[ i ] = NULL;
   current->mm->rss++;
   put_dirty_page(current,page,stack_base);
   ; 将page所指向的物理页面映射到用户参数地址上
  }
  stack_base += PAGE_SIZE;
 }
 up(¤t->mm->mmap_sem);
 
 return 0;
}
; fs/exec.c
; copy_strings_kernel(argc,argv,bprm)将字符串指针数组argv中argc个字符串
; 从上向下压入内核参数页bprm->page之中
int copy_strings(int argc,char ** argv, struct linux_binprm *bprm) 
{
 while (argc-- > 0) {
  char *str;
  int len;
  unsigned long pos;

  if (get_user(str, argv+argc) || !str || !(len = strnlen_user(str, bprm->p))) 
   return -EFAULT;
  if (bprm->p < len) 
   return -E2BIG; 

  bprm->p -= len;
  /* XXX: add architecture specific overflow check here. */ 

  pos = bprm->p;
  while (len > 0) { 在拷贝中动态地分配参数页
   char *kaddr;
   int i, new, err;
   struct page *page;
   int offset, bytes_to_copy;

   offset = pos % PAGE_SIZE;
   i = pos/PAGE_SIZE;
   page = bprm->page[ i ];
   new = 0;
   if (!page) {
    page = alloc_page(GFP_HIGHUSER);
    bprm->page[ i ] = page;
    if (!page)
     return -ENOMEM;
    new = 1;
   }
   kaddr = kmap(page);

   if (new && offset)
    memset(kaddr, 0, offset);
   bytes_to_copy = PAGE_SIZE - offset;
   if (bytes_to_copy > len) {
    bytes_to_copy = len;
    if (new)
     memset(kaddr+offset+len, 0, PAGE_SIZE-offset-len);
   }
   err = copy_from_user(kaddr + offset, str, bytes_to_copy);
   kunmap(page);

   if (err)
    return -EFAULT; 

   pos += bytes_to_copy;
   str += bytes_to_copy;
   len -= bytes_to_copy;
  }
 }
 return 0;
}




Edited by lucian_yao on 04/16/01 01:08 PM.


 
 
标题   Re: 分析应用程序加载时堆栈中的参数结构 [re: opera]   
作者 lucian_yao (addict) 
时间 04/16/01 01:10 PM 
 


(argc,argv,envp)参数形式调用 
--------------不知道是不是这样会有安全漏洞?


 


本文转自
http://www.patching.net/bbs/viewgooddoc_2097_4.html
原创粉丝点击