任務一:操作環境變量
- 實驗過程一: 用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