1 实验内容

1.1 实验文件的下载和导入
下载课程提供的实验室程序。解压到我的虚拟机中,将得到一个名为 Labsetup的文件夹。这个实验室需要的所有文件都包含在这个文件夹中。

1.2 关闭保护措施
在开始这个实验室之前,我们需要确保关闭地址随机化对策(ASLR)。ASLR是一种安全功能,它随机化进程使用的内存地址,增加攻击者利用内存漏洞的难度。在大多数现代Linux发行版上,默认情况下启用ASLR。可以使用以下命令执行ALSR的关闭:
$ sudo /sbin/sysctl -w kernel.randomize_va_space=0

2.3 了解实验目的
在这个实验室中使用的脆弱程序称为stack.c,它在服务器代码文件夹中。此程序有一个缓冲区溢出漏洞,我们的目的是利用此漏洞并获得根权限。

这个程序存在缓冲区溢出漏洞。它从标准输入中读取数据,然后将数据传递给函数bof()中的另一个缓冲区。原始输入的最大长度可以为517字节,但bof()中的缓冲区只有BUF大小字节长,小于517。因为strcpy()不检查边界,所以将会发生缓冲区溢出。该程序将在具有根权限的服务器上运行,其标准输入将被重定向到服务器和远程用户之间的TCP连接。因此,该程序实际上是从一个远程用户,如果用户可以利用这个缓冲区溢出漏洞,他们就可以在服务器上获得一个 root shell。

2 实验步骤及结果

Task 1: Get Familiar with the Shellcode

看一下shellcode_32.py的源代码,shellcode如下所示,应该是x86_32架构的机器代码。shellcode_64.py略微不同,其为x86_64架构的机器代码。下面的shellcode实现的功能应该是在执行三条bash命令。
对 shellcode_32.py 进行修改,使其能够删除文件,执行rm命令,需要注意的是,shell长度不能变。

运行shellcode_32.py和shellcode_64.py分别生成32位和64位的代码载荷。

使用Makefile编译call_shellcode.c生成32位和64位两个版本的调用shellcode的可执行文件。

然后我们新建 tmpfile 文件并运行 shellcode,全过程和结果如下:

执行完后,ls命令看一下code文件夹下的文件,发现tmpfile被删除掉了。

Task 2: Level-1 Attack

进入 server-code 文件夹下,执行命令
$ make
$ make install
然后返回其目录labsetup,执行命令启动 docker
$ dcbuild
$ dcup
第一个攻击目标运行在10.9.0.5:9090上。使用nc连接一下10.9.0.5 9090,不传入任何载荷,显示Returned Properly表示返回值正确。进入 attack-code 文件夹,执行
$ echo hello | nc 10.9.0.5 9090
Ctrl+z
然后关闭attack-code 文件夹的终端

Attack

(记得先关闭地址随机化,前面提过)执行两次打印出的结果一致且输出地址为 0xffffd528,则说明 memory randomization 已关闭。修改 exploit.py 文件,我们先将其中的 shellcode 替换为 shellcode_32.py的 shellcode

根据exploit.py的源代码,最终的载荷content在服务端地址的开始位置即缓冲区地址。有start、ret、offset三个参数需要设置,第一个参数start表示shellcode的开始位置,第二个参数和第三个参数根据**content[offset:offset + 4] = (ret).to_bytes(4,byteorder=’little’)**这段代码可知是将offset地址处的内容覆盖为ret。

所以exploit生成攻击载荷的思路如下图所示。通过缓冲区溢出,将正确的返回地址覆盖,修改为ret。继续写入,使用空操作和编写的shellcode覆盖正确返回地址更高的栈空间。所以ret应该为shellcode及空操作区域的地址,这样当调用函数的栈帧返回时,会返回到ret指针的位置,即继续执行编写的shellcode以及一些空操作。
start设置为517 - len(shellcode) ,即将shellcode放在覆盖区域的最后面;将ret设置为0xffffd338+4+4,即返回地址+4的位置,这样就将返回地址设置为栈中shellcode和空操作区域的开始地址了。然后设置offset为0xffffd338−0xffffd2c8+4,即正确返回地址的位置相对于缓冲区地址的偏移量,用于将ret准确覆盖正确返回地址。

然后执行
$ ./exploit.py
$ cat badfile | nc 10.9.0.5 9090
根据我在代码中设置的标志:**echo super_hacker! **再查看服务端,当我们看到输出super_hacker!的时候表示攻击成功。

Reverse Shell

根据 Task 要求,通过缓冲区溢出漏洞拿到对方的反弹shell。我们将 shellcode 改为 reverse shell,将 exploit.py 文件修改为如下:

重新编译exploit.py,向10.9.0.5发送badfile文件进行攻击

启动新 terminal ,执行监听,可以看到获得了权限,可以执行目标主机上的命令。

Task 3: Level-2 Attack

在这项任务中,我们将通过不显示一个必要的信息来稍微增加攻击的难度。我们的目标服务器是 10.9.0.6(端口号仍然是9090,易攻击的程序仍然是一个32位程序)。让我们先向此服务器发送一条良性消息。我们将看到由目标容器打印出的以下消息。
重点在于处理不知道大小的 buffer。解决方法很简单:不知道 offset,那就挨个试一遍(考虑for循环)。同样的,我们先 echo hello进入 attack-code 文件夹,执行

得到:

修改 exploit.py,依旧设置start =** 517- len(shellcode)让shellcode在payload的最后。将ret设置为缓冲区地址+buffer大小+8,由于这里的buffer大小未知,但是大小范围可知为[100, 300],故为保险将ret设置为缓冲区地址+300+8**,即ret = 0xffffd198+300+8。接下来要覆盖正确的返回地址,我们不知道正确返回地址在哪里,但是我们可以将所有可能的地址都设置为ret,这样就可以保证覆盖正确返回地址。

然后执行

得到了结果

Task 4: Level-3 Attack

本 task 重点在于处理 64 位地址的 buffer,因此 exploit.py 中的 shellcode
要换成 shellcode_64.py 中的 shellcode

和前两个实验一样,先查看一下服务器的相关参数

得到了rbp和buffer的基址

  • start 设定为一个较小的值,可以直接取 10,表示shellcode地址接近buffer地址
  • ret = rbp + n :
  • ebp 就是刚刚 echo hello 中得到的 rbp,因为关闭了地址随机化,所以每次都一样;
  • n ∈ [buffer, buffer + start];
  • offset = 0x00007fffffffe690 − 0x00007fffffffe5c0 + 8

运行 py 文件,将 badfile 传给 server 服务端

得到:super-hacker!

**Task 5: Level-4 Attack **

本 task 重点在于执行 return-to-libc 攻击, 还是和前两个实验一样,先查看一下服务器的相关参数(这里我虚拟机重启过,记得重新关闭随机地址!)

得到:

缓冲区远小于shellcode大小,因此跟task4一样无法将shellcode放入缓冲区,而放入返回地址之后又会被00截断。
但是在执行strcpy时,虽然被00截断了,buffer没有shellcode了,但是shellcode还存在在str中。故如果能够return到str的位置,那么还是可以执行shellcode。所以我们需要知道str相对于rbp或者buffer的大概位置。
修改 exploit.py,ret 后加的n取一个较大的值,大概在 1000 到 1400之间,使返回值落在shellcode之前的可能性增大

然后执行

得到了结果

Task 6: Experimenting with the Address Randomization

打开地址随机化:$ sudo /sbin/sysctl -w kernel.randomize_va_space=2
再将以下代码都执行两遍:


可以看到,每次地址都不相同,导致攻击困难

使用 Task2 中 reverse shell 的 exploit.py 代码

执行命令

可以看到很快,大概用时3秒钟,暴力破解几百次后,就获取了控制权

Tasks 7: Experimenting with Other Countermeasures

进入 server-code 文件夹,去除 -fno-stack-protector 编译 stack.c,
并将 badfile 作为输入

可以看到检测到了 stack smashing。进入 shellcode 文件夹,去除 -z
execstack 编译 call_shellcode.c 并运行

可以看到,栈不再可执行。开启栈不可执行可以提供一定程度的保护,但并不能完全避免缓冲区溢出攻击。即使栈不可执行,攻击者仍然可以利用其他方式进行攻击,例如利用Return-to-libc攻击。