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