通過進程2載入shell進程,詳解execve

來源:互聯網
上載者:User

標籤:

    一直以來都對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;//核心棧用返回給使用者空間的esp
    eip為0,esp為64MB-4-參數和環境變數的位元組數,也就是說在使用者空間,訪問ss:eip,就是訪問128MB+64MB-4-參數和環境變數的位元組數,在經過分頁機制,最後能夠訪問最終存放參數和環境變數的頁面的指定位置。

通過進程2載入shell進程,詳解execve

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.