SEED實驗——Environment Variable and Set-UID Program實驗報告


任務一:操作環境變量

  • 實驗過程一: 用printenv或env打印出環境變量。

在終端輸入命令,顯示結果如下圖所示:

經過實驗發現,printenv和env均可輸出當前系統的環境變量。不同的是printenv不加參數和env一樣,而printenv可以打印指定名稱的環境變量。

  • 實驗過程二: 使用export或者unset命令設置或去掉環境變量。

任務二:集成環境變量

實驗過程:child和child2文件略。

實驗結論:

通過比較這兩個文件,可以發現,這兩個文件輸出的環境變量完全相同。說明原環境變量被子進程完全繼承。通過man fork,對fork函數做了進一步了解。fork函數通過系統調用創建一個與原來進程幾乎完全相同的進程,子進程自父進程繼承了進程的資格,環境,堆棧與內存根目錄等;但是子進程沒有繼承父進程的某些特性,比如父進程號,文件描述符,在tms結構中的系統時間,資源使用等。

任務三:環境變量和execve()

  • 實驗過程一:編譯並運行以下程序。描述觀察到的實驗結果。該程序簡單地調用了/usr/bin/env,該系統調用能夠打印出當前進程的環境變量。

    #include <stdio.h>
    #include <stdlib.h>
    
    extern char **environ;
     int main()  
     {
     char *argv[2];
     argv[0] = "/usr/bin/env";
     argv[1] = NULL;
     execve("/usr/bin/env", argv, NULL);
     return 0 ;
     }
    
    

觀察到的實驗結果如圖所示:

execve()

  • 實驗過程二:改變execve()函數的參數,描述你觀察到的結果。

補充:execve()函數的使用方法:

int execve(const char * filename,char * const argv[],char * const envp[])

execve()用來執行參數filename字符串所代表的文件路徑,filename必須是一個二進制的可執行文件,或者是一個腳本以#!格式開頭的解釋器參數。如果是后者,這個解釋器必須是一個可執行的有效的路徑名。第二個參數系利用數組指針來傳遞給執行文件,argv是要調用的程序執行的參數序列,也就是我們要調用的程序需要傳入的參數。envp則為傳遞給執行文件的新環境變量數組,同樣也為參數序列。

  • 實驗結論:請得出關於新程序如何獲取其環境變量的結論。

任務四:環境變量和system()

  • 實驗過程:編譯並運行以下程序:
#include <stdio.h>
#include <stdlib.h>
int main()
{
    system("/usr/bin/env");
    return 0 ;
}

運行結果為:

運行結果分析:

先看一下system()函數的簡單介紹。system函數定義為 int system(const char * string),該函數調用/bin/sh來執行參數指定的命令,/bin/sh一般是一個軟連接,指向某個具體的shell,比如bash,-c 選項是告訴shell從字符串command中讀取命令;在該command執行期間,SIGCHLD信號會被暫時擱置,SIGINT和SIGQUIT則會被忽略,意思是進程收到這兩個信號后沒有任何動作。system()函數的函數返回值有些復雜。為了更好地理解system()函數的返回值,需要了解其執行過程,實際上system()函數執行了三步操作:

1 fork一個子進程;
2 在子進程中調用exec函數去執行command;
3 在父進程中調用wait去等待子進程結束。

若fork失敗,system()函數返回-1。如果exec執行成功,也即command順利執行完畢,則返回command通過exit或return返回的值。(注意,command順利執行不代表執行成功,例如command:“rm debuglog.txt”,不管文件存不存在該command都順利執行了)如果exec執行失敗,也即command沒有順利執行,比如信號被中斷,或者command命令根本不存在,system()函數返回127,如果command為NULL,則system()函數返回值非0,一般為1。

具體看一下system()函數的實現:

int system(const char * cmdstring) 
{ 
pid_t pid; 
int status; 
if(cmdstring == NULL) 
{ 
return (1); //如果cmdstring為空,返回非零值,一般為1 
} 
if((pid = fork())<0) 
{ 
 status = -1; //fork失敗,返回-1 
} 
else if(pid == 0) 
{ 
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); 
_exit(127); // exec執行失敗返回127,注意exec只在失敗時才返回現在的進程,成功的話現在的進程就不存在啦~~ 
} 
else //父進程 
{ 
while(waitpid(pid, &status, 0) < 0) 
{ 
if(errno != EINTR) 
{ 
status = -1; //如果waitpid被信號中斷,則返回-1 
break; 
} 
} 
} 
return status; //如果waitpid成功,則返回子進程的返回狀態 
} 

任務五:環境變量和Set-UID程序

  • 實驗過程一:在當前步驟中寫一個能夠輸出所有環境變量的程序。

我寫的能夠傳輸所有環境變量的程序如下:

/************ task5.c************/
#include <stdio.h>
extern char** environ;

int main()
{
    int nIndex = 0;
    for(nIndex = 0; environ[nIndex] != NULL; nIndex++)
    {
        printf("%s\n",environ[nIndex]);
    }
}
  • 實驗過程二:編譯以上程序,將其權限改為roo權限,使其成為一個Set-UID程序。
    使用如下命令:
    chown root:root task5.c
    將task5.c的權限改為root權限。

  • 實驗過程三:使用一般用戶登錄終端,使用export命令設置如下環境變量:PATH LD_LIBRARY_PATH ANY_NAME

  • 設置PATH環境變量(可執行程序的查找路徑):
    export PATH=$PATH:/xlwang

  • 設置LD_LIBRARY_PATH環境變量(動態庫的查找路徑):
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/xlwang

*設置XLWANG環境變量:
`export XLWANG=$XLWANG:/555

*運行上述程序的可執行文件,得到下列結果。由下圖所示,以上三個被定義的環境變量全部被包括在shell中。

任務六:PATH環境變量和Set-UID程序

  • 實驗過程一:以下Set-UID程序應該執行/bin/ls命令。但是,程序員只能使用ls命令的相對路徑,而不是絕對路徑:
 int main()
   {
       system("ls");
       return 0;
   }

編譯上述程序,並將其所有者改為root,將其設置為Set-UID程序。你可以讓這個Set-UID程序運行你的代碼而不是/bin/ls嗎?描述和解釋你的觀察。

  • 實驗過程二:將上述代碼段補齊,並命名為task6.c,用GCC對其進行編譯,將其編譯后的可執行文件權限改為root權限。運行task6,發現該程序執行的是ls命令。由於system()函數是調用了shell環境變量,運行task5,發現SHELL=/bin/bash。於是將自己的可執行文件夾所在的目錄加在了SHELL環境變量的開頭:
    `export SHELL=/xlwang/3:$SHELL

又將task6.c中的system()函數的參數改為task5,即想讓該程序執行task5程序。

運行./task6。在終端中輸出了所有的環境變量。

任務七:LD_PRELOAD環境變量和Set-UID程序

  • 實驗過程一:我們新建一個動態鏈接庫。命名下面的代碼為mylib.c,該程序基本上覆蓋了libc中的sleep函數:
#include <stdio.h>
  void sleep (int s)
  {
    /* If this is invoked by a privileged program, 
       you can do damages here!  */
    printf("I am not sleeping!\n");
  }

用下列命令編譯mylib.c:
gcc -fPIC -g -c mylib.c #fPIC表示編譯生成代碼與位置無關
gcc -shared -o libmylib.so.1.0.1 mylib.o -lc #讓編譯器知道是要編譯一個共享庫
設置LD_PRELOAD環境變量:

export LD_PRELOAD=./libmylib.so.1.0.1
編譯myprog程序,在鏈接庫libmylib.so.1.0.1的相同目錄下:

/* myprog.c */
  int main()
  {
    sleep(1);
    return 0;
  }

在以下情況中運行myprog程序:

  • 以普通用戶的身份運行myprog程序。

  • 以普通用戶運行擁有root權限的myprog程序。

  • 使myprog成為一個Set-UID user1程序,在user2用戶(非root用戶)中再次設置LD_PRELOAD環境變量,運行myprog程序。

執行結果:

以普通用戶的身份運行myprog程序時,輸出:I am not sleeping!
以普通用戶運行擁有root權限的myprog程序時,無輸出。
在user2用戶中再次設置LD_PRELOAD環境變量並運行myprog程序時,輸出:I am not sleeping!

觀察以上三次程序的執行結果,理解導致他們不同的原因。環境變量起了作用。設計實驗證明主要因素,並解釋第二步中行為的不同。

導致他們不同的原因就在於LD_PRELOAD環境變量。LD_PRELOAD環境變量是Unix動態鏈接庫的世界中的一個環境變量,它可以影響程序的運行時的鏈接,它允許你定義在程序運行前優先加載的動態鏈接庫。這個功能主要是用來有選擇性的載入不同動態鏈接庫中的相同函數。在該實驗中,mylib.c通過sleep函數,生成了一個libmylib.so.1.0.1鏈接庫。然后將該鏈接庫添加到LD_PRELOAD環境變量上。比較這三次實驗,第一次和第三次實驗myprog程序均具有seed用戶權限,而在seed用戶的LD_PRELOAD環境變量中也添加了該鏈接庫。因此,這兩個實驗

任務八:使用system()和execve()調用外部程序

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  char *v[3];
  char *command;

  if(argc < 2) {
    printf("Please type a file name.\n"); 
    return 1;
  }

  v[0] = "/bin/cat"; v[1] = argv[1]; v[2] = NULL;

  command = malloc(strlen(v[0]) + strlen(v[1]) + 2);
  sprintf(command, "%s %s", v[0], v[1]);

  // Use only one of the followings.
  system(command);
  // execve(v[0], v, NULL);

  return 0 ;
}
  • 實驗過程一:編譯上面的程序,賦予其root用戶權限,並將其變為SET-UID程序:

    該程序將會使用system()來調用命令。若將上述代碼中的
    v[0] = "/bin/cat"改為v[0] = "rm",即刪除命令。並新建一個test.c文件,將其權限改為000,執行以下命令:
    ./task8 ./test.c
    可發現test.c文件被刪除。整個過程如下圖所示。

本來test.c對seed用戶是不可寫的,但因為task8是SET-UID程序,且時root權限,因此可以刪除test.c文件。由此可得出結論:set-UID程序是非常危險的。

  • 實驗過程二:注釋掉system(command)語句,並取消注釋execve()語句;程序將使用execve()來調用命令。編譯程序,並使其成為Set-UID程序。那么在步驟一中的攻擊是否仍然有效?

任務九:權能泄露

  • 實驗過程:編譯以下程序,將其所有者更改為root,並將其設置為Set-UID程序。以普通用戶身份運行程序,並描述您所觀察到的內容。文件/etc/zzz是否被修改?

請解釋你的觀察過程。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

void main()
{ int fd;

  /* Assume that /etc/zzz is an important system file,
   * and it is owned by root with permission 0644.
   * Before running this program, you should creat
   * the file /etc/zzz first. */
  fd = open("/etc/zzz", O_RDWR | O_APPEND);
  if (fd == -1) {
     printf("Cannot open /etc/zzz\n");
     exit(0);
  }

  /* Simulate the tasks conducted by the program */
  sleep(1);

  /* After the task, the root privileges are no longer needed,
     it's time to relinquish the root privileges permanently. */
  setuid(getuid());  /* getuid() returns the real uid */

  if (fork()) { /* In the parent process */
    close (fd);
    exit(0);
  } else { /* in the child process */
    /* Now, assume that the child process is compromised, malicious
       attackers have injected the following statements
       into this process */

    write (fd, "Malicious Data\n", 15);
    close (fd);
  }
}

Reference

http://blog.sina.com.cn/s/blog_8043547601017qk0.html
http://blog.csdn.net/haoel/article/details/1602108


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM