20199327 2019-2020-2 《网络攻防实践》第十周作业

理论学习

1. 软件安全概述

攻击者能够轻易地对系统和网络实施攻击,很大程度上是因为安全漏洞在软件中的大规模存在,攻击者可以利用这些漏洞来违背系统和网络的安全属性。安全漏洞的类型多种多样,从最基本的缓冲区溢出,到格式化字符串漏洞,竞态条件漏洞,整数溢出,以及流行的XSS跨站脚本、SQL注入等。而针对安全漏洞的渗透利用技术也在与安全防护机制的博弈中日新月异,层出不穷,从最简单的栈溢出,到更难操控的堆溢出,内核溢出,对抗DEP(数据执行保护)机制的ret2libc攻击、对抗ASLR(地址空间布局随机化)机制的Heap Spraying攻击,最新的能够同时对抗DEP和ASLR的JIT Spraying攻击,在软件代码与内存空间上演着精彩的对弈。

1.1 软件安全漏洞威胁

安全漏洞定义:在系统安全流程、设计、实现或内部控制中所存在的缺陷或弱点,能够被攻击者所利用并导致安全侵害或对系统安全策略的违反。

三个基本元素:系统的脆弱性或缺陷、攻击者对缺陷的可访问性、攻击者对缺陷的可利用性。

软件安全困境

  • 复杂性:代码的复杂性,集成度。

  • 可扩展性:现代大部分软件经常要支持可扩展性,设计可扩展机制,都必须要考虑安全特性。

  • 连通性:互联网遍布全世界各地,高度连通性意味着网络安全威胁的全球化,与真实世界的连通意味着网络安全威胁的现实影响越来越大.

软件安全漏洞分类

  • 内存安全违规类:在软件开发过程中在处理RAM内存访问时所引入的安全缺陷,缓冲区溢出漏洞是一种最基础的内存安全问题。

  • 输入验证类:软件程序在对用户输入进行数据验证存在的错误,没有保证输入数据的正确性、合法性和安全性,从而导致可能被恶意攻击与利用。如XSS、SQL注入、远程文件包含、HTTP Header注入等。

  • 竞争条件类:系统或进程中一类比较特殊的错误,通常在涉及多进程或多线程处理的程序中出现,是指处理进程的输出或结果无法预测,并依赖于其他进程事件发生的次序或时间时,所导致的错误。

  • 权限混淆与提升类:滥用权限所导致的漏洞,权限提升漏洞通常被攻击者用于获取系统管理员或系统开发者预期之外的更高访问权限,用于执行内核级别的操作

1.2 缓冲区溢出基本概念

缓冲区溢出:计算机程序中存在的一类内存安全违规类漏洞,在计算机程序向特定缓冲区内填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性。理想情况下,程序应检查每个输入缓冲区的数据长度,并不允许输入超出缓冲区本身分配的空间容量,但是大量程序总是假设数据长度是与所分配的存储空间相匹配的,因而很容易产生缓冲区溢出漏洞。

本质原因:缺乏缓冲区边界保护(C/C++语言程序:效率优先、memcpy()、strcpy()等内存与字符串拷贝 函数并不检查内存越界问题、程序员缺乏安全编程意识、经验与技巧)。
冯·诺依曼体系存在本质安全缺陷,计算机程序的数据和指令都在同一内存中进行存储,而没有严格的分离。使得攻击者可以将输入的数据,通过利用缓冲区溢出漏洞,覆盖修改程序在内存空间中与数据区相邻存储的关键指令,从而达到使程序执行恶意注入指令的攻击目的

GDB调试器
详细学习内容请参考GDB调试器使用总结。
断点相关指令(break/clear, disable/enable/delete,watch-表达式值改变时,程序中断)。
执行相关指令(run/continue/next/step,attach-调试已运行的进程,finish/return)。
信息查看相关指令(info reg/break/files/args/frame/functions/... ,backtrace-函数调用栈,x/nfu addr–显示指定内存地址的内容,disass func-反汇编指定函数)。

通用寄存器:eax,ebx,ecx,edx主要用于普通的算术运算,保存数据、地址、偏移量、计数值等

段寄存器:在IA32架构中是16位的,一般用作段基址寄存器

控制寄存器:用来控制处理器的执行流程,其中最关键的是eip,也被称为指令指针,它保存了下一条即将执行的机器指令的地址

其他寄存器:值得关注的是“扩展标志”eflags寄存器,由不同的标志位组成,用于保存指令执行后的状态和控制指令执行流程的标志信息。

IA32架构中的关键寄存器及其功能如下图:

  • 进程内存管理:

    内存空间的分配与回收。
    地址转换:在多道程序环境下,程序中的逻辑地址与内存中的物理地址不可能一致,因此存储管理必须提供地址变换功能,把逻辑地址转换成相应的物理地址。
    内存空间的扩充:利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存。
    存储保护:保证各道作业在各自的存储空间内运行,互不干扰。

  • 函数调用:
    调用(call):调用参数和返回地址(eip)压栈,跳转到函数入口。
    序言(prologue):调用函数的栈基址进行压栈保存,并创建自身函数的栈结构。
    返回(return):恢复调用者原有栈,弹出返回地址,继续执行下一条指令。

缓冲区溢出攻击原理

根本问题:用户输入可控制的缓冲区操作缺乏对目标缓冲区的边界安全保护。

成功溢出攻击的三个挑战:如何找出缓冲区溢出要覆盖和修改的敏感位置? 将敏感位置的值修改成什么?执行什么代码指令来达到攻击目的?

类型:缓冲区溢出漏洞根据缓冲区在进程内存空间中的位置不同,又分为栈溢出、堆溢出和内核溢出三种具体技术形态。

栈溢出:存储在栈上的一些缓冲区变量由于存在缺乏边界保护问题,能够被溢出并修改栈上的敏感信息,从而导致程序流程的改变

堆溢出:存储在堆上的缓冲区变量缺乏边界保护所遭受溢出攻击的安全问题

内核溢出:由于进程内存空间内核态中存储的缓冲区变量被溢出造成的

缓冲区溢出的主要点在于数据的淹没,即超过缓冲区区域的高地址部分数据会淹没原本的其他栈数据。

  • 淹没了其他的局部变量:如果被淹没的局部变量是条件变量,那么可能会改变函数原本的执行流程。这种方式可以用于破解简单的软件验证。

  • 淹没了ebp的值:修改了函数执行结束后要恢复的栈指针,将会导致栈帧失去平衡。

  • 淹没了返回地址:这是栈溢出原理的核心所在,通过淹没的方式修改函数的返回地址,使程序代码执行“意外”的流程。

  • 淹没参数变量:修改函数的参数变量也可能改变当前函数的执行结果和流程。

  • 淹没上级函数的栈帧:情况与上述4点类似,只不过影响的是上级函数的执行。当然这里的前提是保证函数能正常返回,即函数地址不能被随意修改。

1.3 Linux栈溢出与Shellcode

Linux本地缓冲区溢出的特权提升:
运行时刻可以提升至根用户权限进行一些操作。
攻击者就可以在注入shellcode中增加一个setreuid(0)的系统调用。
给出根用户权限的Shell。

Linux远程shellcode实现机制:这里其实就是创建socket连接,并将shellcode通过socket注入,同时需要将命令行与socket绑定。
Shellcode通用的编写方法:

  • 先用高级编程语言,通常用C,来编写shellcode程序。

  • 编译并反汇编调试这个shellcode程序。

  • 从汇编语言代码级别分析程序执行流程。

  • 整理生成的汇编代码,尽量减小它的体积并使它可注入,并可通过嵌入C语言进行运行测试和调试。

  • 提取汇编代码所对应的opcode二进制指令(操作码,每个设备处理单元上的执行的指令),创建shellcode指令数组。

Linux平台中的栈溢出攻击按照攻击数据的构造方式不同,主要有NSR,RNS,RS三种模式

  • NSR模式:适用于被溢出的缓冲区变量比较大,足以容纳Shellcode的情况,其攻击数据从低地址到高地址的构造方式是一堆Nop指令(即空操作指令)之后填充Shellcode,再加上一些期望覆盖RET返回地址的跳转地址,从而构成了NSR攻击数据缓冲区

  • RNS模式:
    一般用于被溢出的变量比较小,不足以容纳Shellcode的情况,攻击数据从低地址到高地址的构造方式是首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆Nop指令填充出着陆区,最后再是Shellcode,在溢出攻击之后,攻击数据将在RET区段即溢出了目标漏洞程序的小缓冲区,并覆盖了栈中的返回地址,然后跳转至Nop指令所构成的着陆区,并最终指向Shellcode

  • RS模式:精确定位出shellcode在目标漏洞程序进程空间中的起始地址,因此也就无须引入Nop空指令构建着陆区。这种模式将shellcode放置在目标漏洞程序执行时的环境变量中,由于环境变量是位于Linux进程空间的栈底位置,因而不会受到各种变量内存分配与对齐因素的影响,其位置是固定的可通如下公式计算:
    ret=0xc0000000-sizeof(void *)-sizeof(FILENAME)-sizeof(Shellcode)

1.4 Windows栈溢出与Shellcode

Windows平台栈溢出与Linux平台的区别和原理:

  • 对废弃栈的处理导致NSR模式不适用于Win32,Linux对废弃栈不进行任何处理,而Windows会写入一些随机的数据。

  • 进程内存空间的分布导致RNS模式不适用于Win32,Linux栈在3G(0xC0000000)附近,R地址中没有空字节,Windows栈在0x00FFFFFF以下的用户空间,R地址中有空字节。

  • shellcode实现机制不同,Linux通过中断进行系统调用,而Windows通过调用系统DLL提供的接口函数。

解决:通过Jmp/Call ESP指令跳转,跳转指令一般在进程内存空间中1G至2G区间中装载的系统核心 DLL(如Kernel32.dll、User32.dll等)、Windows代码页中的地址、应用程序加载的用户DLL、OllyUni插件提供Overflow Return Address功能。

Windows平台的Shellcode实现:

  • 所需的Win32 API函数,生成函数调用表。

  • 加载所需API函数库,定位函数加载地址。

  • 消除空字节,编码对抗过滤。

  • 确保自己可以正常退出,使目标程序进程继续运行或终止。

  • 在目标系统环境存在异常处理和安全防护机制时,shellcode还需进一步考虑如何对抗这些机制。

Windows远程Shellcode:

  • 创建一个服务器端socket,并在指定的端口上监听。

  • 通过accept()接受客户端的网络连接。

  • 创建子进程,运行cmd.exe,启动命令行。

  • 创建两个管道,并且将shell连接至socket。

    • 命令管道将服务器端socket接收(recv)到的客户端通过网络输入的执行命令,连接至cmd.exe的标准输入。

    • 输出管道将cmd.exe的标准输出连接至服务器端socket的send,通过网络将运行结果反馈给客户端。

1.5 堆溢出攻击(heap overflow)

堆溢出攻击:在内存中的一些数据区,.text包含进程的代码,.data包含已经初始化的数据,.bss 包含未经初始化的数据,heap运行时刻动态分配的数据区,在这些数据区溢出的情形,都称为heap overflow.

这些数据区的特点是: 数据的增长由低地址向高地址。

  • 指针改写:先定义一个buffer,再定义一个指针,当对buffer填充数据的时候,如果不进行边界判断和控制的话,自然就会溢出到指针的内存 区,从而改变指针的值。

  • C++对象虚函数表改写:编译器为每一个包含虚函数的class建立起vtable,vtable中存放的是虚函数的地址,编译器也在每个class对象的内存区放入一个指向vtable 的指针(称为vptr),vptr的位置随编译器的不同而不 同,VC放在对象的起始处,gcc放在对象的末尾,设法改写vptr,让它指向另一段代码。

  • Linux堆内存管理漏洞:

    • glibc库free()函数本身存在漏洞,攻击者可以通过精心构造unlinkme内存块进行free()函数堆溢出攻击。

    • 当unlink宏被调用时,在what位置的值将覆盖到where位置上。

    • Where: 栈返回地址、GOT全局偏移入口地址、 DTORS析构函数地址。

    • What: shellcode地址。

1.6 缓冲区溢出攻击的防御技术

  • 尝试杜绝溢出的防御技术,编写正确代码、查错: Fuzz注入测试、编译器引入缓冲区边界保护检查。

  • 允许溢出但不让程序改变执行流程的防御技术

    • StackGuard: 返回地址前添加检测标记,返回前检查。
    • VS栈保护编译选项。
    • gcc: -fstack-protector。
  • 无法让攻击代码执行的防御技术

原理实践

没有,nice!

学习中遇到的问题及解决

1.对机器码和汇编代码还是不熟悉。

关于汇编语言和计算机组成的内容要再学习

2.学了理论但shellcode还不会写。。。

参考资料

汇编语言入门教程
《网络攻防技术实践》
冯文浩的博客

posted @ 2020-05-07 10:54  肖子玉20199327  阅读(209)  评论(0编辑  收藏  举报