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
",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!
");
  }

用下列命令编译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.
"); 
    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
");
     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
", 15);
    close (fd);
  }
}

Reference

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

原文地址:https://www.cnblogs.com/xlwang1995/p/7323382.html