• 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

  • 相关阅读:
    sentinel.conf样例
    哨兵模式java实例
    哨兵模式启动redis
    华为笔试:直角三角形个数
    leetcode 337. 打家劫舍iii
    leetcode 494. 目标数
    leetcode 394. 字符串解码
    leetcode 128. 最长连续子序列
    链表快排
    leetcode 877. 石子游戏
  • 原文地址:https://www.cnblogs.com/xlwang1995/p/7323382.html
Copyright © 2020-2023  润新知