章节大纲

  • 本实验在 SEEDUbuntu20.04 VM 中测试可行。你可以在本页面右端选择虚拟机版本为 SEEDUbuntu20.04,点击“创建虚拟机”来获取虚拟机平台的临时用户名与密码,登录虚拟机平台即可获得一台预先构建好的 SEEDUbuntu20.04 VM,该虚拟机以及用户名密码将在开启 24 小时后自动销毁。
     
    你也可以在其他 VM、物理机器以及云端 VM 上自行配置环境进行实验,但我们不保证实验能在其他 VM 下成功。实验所需的文件可从下方下载,解压后会得到一个名为 Labsetup 的文件夹,该文件夹内包含了完成本实验所需的所有文件。
      • 在 x64 机器(64 位)上的 Return-to-libc 攻击比在 x86 机器(32 位)上的要困难得多。尽管 SEED Ubuntu 20.04 VM 是一台 64 位机器,我们决定继续使用 32 位程序(x64 与 x86 兼容,因此 32 位程序仍可在 x64 机器上运行)。将来,我们可能会为这个实验引入 64 位版本。因此,在此实验中,当我们使用 gcc 编译程序时,我们总是使用 -m32 标志,这意味着将程序编译成 32 位二进制文件。
      • 你可以使用我们预构建的 Ubuntu 虚拟机来执行实验任务。Ubuntu 和其他 Linux 发行版已经实现了几个安全机制,使得缓冲区溢出攻击变得困难。为了简化我们的攻击,我们需要先禁用它们。

        1. 地址空间随机化:Ubuntu 和其他几个基于 Linux 的系统使用地址空间随机化来随机化堆和堆栈的起始地址,使得猜测确切地址变得困难。猜测地址是缓冲区溢出攻击的关键步骤之一。在此实验中,我们使用以下命令关闭地址空间随机化:
         
        $ sudo sysctl -w kernel.randomize_va_space=0

        2. 堆栈保护方案 StackGuard:gcc 编译器实现了一个名为 StackGuard 的安全机制来防止缓冲区溢出。在这种保护存在的情况下,缓冲区溢出攻击无法工作。我们可以通过在编译期间使用 -fno-stack-protector 选项来禁用此保护。例如,要编译一个禁用 StackGuard 的程序 example.c,我们可以这样做:
         
        $ gcc -m32 -fno-stack-protector example.c

        3. 不可执行堆栈:Ubuntu曾经允许可执行堆栈,但现在已改变。程序(和共享库)的二进制映像必须声明它们是否需要可执行堆栈,即它们需要在程序头中标记一个字段。内核或动态链接器使用这个标记来决定是否使此运行程序的堆栈可执行或不可执行。这个标记是由 gcc 自动完成的,默认情况下,堆栈被设置为不可执行。要改变这一点,请在编译程序时使用以下选项:
         
        % 可执行堆栈
        $ gcc -m32 -z execstack  -o test test.c
        
        % 不可执行堆栈
        $ gcc -m32 -z noexecstack  -o test test.c
         
        由于本实验的目标是展示不可执行堆栈保护不起作用,你应该在本实验中使用 "-z noexecstack" 选项来编译你的程序。

         
        4. 配置 /bin/sh:在 Ubuntu 20.04 中,/bin/sh 符号链接指向 /bin/dash。dash 有一个对策,可以阻止自己在 Set-uid 进程中执行。如果 dash 在 Set-uid 进程中执行,它会立即将有效用户 ID 更改为进程的实际用户 ID,实质上放弃了其权限。
         
        由于我们的受害者程序是一个 Set-uid 程序,我们的攻击使用 system() 函数来运行我们选择的命令。这个函数不会直接运行我们的命令,它调用 /bin/sh 来运行我们的命令。因此,/bin/dash 在执行我们的命令之前就放弃了 Set-uid 权限,使我们的攻击更加困难。要禁用此保护,我们将 /bin/sh 链接到另一个没有这种对策的 shell。我们在 VM 中安装了一个名为 zsh 的 shell 程序。我们使用以下命令将 /bin/sh 链接到 zsh:
         
        $ sudo ln -sf /bin/zsh /bin/sh

        应当注意,dash 中实现的对策是可以绕过的。我们将在后续任务中进行。
      • /* 有缓冲区溢出漏洞的程序 retlib.c */
        #include <stdlib.h>
        #include <stdio.h>
        #include <string.h>
        
        #ifndef BUF_SIZE
        #define BUF_SIZE 12
        #endif
        
        int bof(char *str)
        {
            char buffer[BUF_SIZE];
            unsigned int *framep;
        
            // 将ebp复制到framep
            asm("movl %%ebp, %0" : "=r" (framep));      
        
            /* 为了实验目的打印信息 */
            printf("Address of buffer[] inside bof():  0x%.8x\n", (unsigned)buffer);
            printf("Frame Pointer value inside bof():  0x%.8x\n", (unsigned)framep);
        
            strcpy(buffer, str);   (*@\reflectbox{\ding{222}} \textbf{buffer overflow!} @*)
        
            return 1;
        }
        
        int main(int argc, char **argv)
        {
           char input[1000];
           FILE *badfile;
        
           badfile = fopen("badfile", "r");
           int length = fread(input, sizeof(char), 1000, badfile);
           printf("Address of input[] inside main():  0x%x\n", (unsigned int) input);
           printf("Input size: %d\n", length);
        
           bof(input);
        
           printf("(^_^)(^_^) Returned Properly (^_^)(^_^)\n");
           return 1;
        }
        
        // 此函数将在可选任务中使用
        void foo(){
            static int i = 1;
            printf("Function foo() is invoked %d times\n", i++);
            return;
        }
         
        上述程序具有缓冲区溢出漏洞。它首先从名为 badfile 的文件中读取多达 1000 字节的输入。然后,它将输入数据传递给 bof() 函数,该函数使用 strcpy() 将输入复制到其内部缓冲区。然而,内部缓冲区的大小小于 1000,因此存在潜在的缓冲区溢出漏洞。
         
        这个程序是一个 root 拥有的 Set-uid 程序,因此如果普通用户可以利用这个缓冲区溢出漏洞,用户能够获得 root shell。该程序从用户提供的名为 badfile 的文件中获取输入,因此,我们可以构造该文件,以便当有漏洞的程序将文件内容复制到其缓冲区时,可以生成 root shell。
         
        我们首先编译代码并将其变成 root 拥有的 Set-uid 程序。不要忘记用 -fno-stack-protector 选项(关闭 StackGuard 保护)和 "-z noexecstack" 选项(打开不可执行堆栈保护)。还应注意,在打开 Set-uid 位之前必须先更改所有权,因为所有权更改会导致 Set-uid 位被关闭。所有这些命令都包含在提供的 Makefile 中。

        $ gcc -m32 -DBUF_SIZE=N -fno-stack-protector -z noexecstack -o retlib retlib.c
        $ sudo chown root retlib          
        $ sudo chmod 4755 retlib