前言
《深入理解计算机系统》官网:http://csapp.cs.cmu.edu/3e/labs.html
该篇文章是实验三Attack Lab的Writeup(attacklab.pdf)机翻。
原文:http://csapp.cs.cmu.edu/3e/attacklab.pdf
阅读文档能对实验有所帮助。
在官网点击下方即可下载实验三的文件
1.介绍
这项任务涉及对两个具有不同安全vul漏洞的程序生成总共五次攻击。你将从这个实验室获得的成果包括:
- 您将了解攻击者利用安全漏洞的不同方法,当程序不能很好地保护自己防止缓冲区溢出时。
- 通过这些,您将更好地了解如何编写更安全的程序,以及编译器和操作系统提供的一些特性,以降低程序的脆弱性。
- 您将更深入地理解x86-64机器码的堆栈和参数传递机制。
- 您将更深入地理解x86-64指令是如何编码的。
- 您将获得更多调试工具(如GDB和OBJDUMP)的经验。
请注意:在本实验中,您将获得利用操作系统和网络服务器安全漏洞的方法的第一手经验。我们的目的是帮助您了解程序的运行时操作,并了解这些安全弱点的性质,以便您在编写系统代码时能够避免这些弱点。我们不允许使用任何其他形式的攻击来获得未经授权的访问任何系统资源。
您将需要学习CS:APP3e书的3.10.3和3.10.4章节作为本实验室的参考材料。
2.组织工作
和往常一样,这是一个个人项目。您将对为您定制的生成的目标程序生成攻击。
2.1得到文件
获取文件你可以通过将Web浏览器指向以下地址获取文件:
服务器将构建您的文件,并将它们以tar文件targetk.tar的形式返回给您的浏览器,其中k是您的目标程序的唯一编号。
请注意:构建和下载目标需要几秒钟,所以请耐心等待。
将targetk.tar文件保存在您计划在其中进行工作的(受保护的)Linux目录中。然后输入命令:tar -xvf targetk.tar。这将提取一个目录targetk,其中包含下面描述的文件。
您应该只下载一组文件。如果由于某种原因您下载了多个目标,请选择一个目标并删除其余的目标。
警告:如果在PC上使用Winzip之类的工具扩展targetk.tar,或者让浏览器进行解压,则可能会重新设置可执行文件的权限位。
targetk中的文件包括:
README.txt:描述目录内容的文件
ctarget:易受代码注入攻击的可执行程序
rtarget:易受面向返回编程攻击的可执行程序
cookie.txt:8位十六进制代码,您将在攻击中使用它作为唯一标识符。
farm.c:目标“gadget farm”的源代码,您将使用它生成面向返回的编程攻击。
hex2raw:生成攻击字符串的实用程序。
在下面的说明中,我们将假设您已经将文件复制到一个受保护的本地目录,并且正在该本地目录中执行程序。
2.2重要的几点
以下是关于本实验室有效解决方案的一些重要规则的总结。当您第一次阅读本文档时,这些要点可能没有多大意义。一旦开始,它们将作为规则的中心参考呈现在这里
- 您必须在与生成目标的机器类似的机器上执行分配。
- 您的解决方案不能使用攻击来绕过程序中的验证代码。具体来说,将任何地址合并到ret指令使用的攻击字符串中,都应该指向以下目的地之一:
函数touch1、touch2或touch3的地址
注入代码的地址
你在gadgets里的gadgets的地址。 - 只能从文件rtarget中构造gadget,其地址范围介于start_farm和end_farm函数之间。
3.目标程序
CTARGET和RTARGET都从标准输入中读取字符串。它们使用下面定义的函数getbuf来实现:
Gets函数类似于标准库的get函数——它从标准输入(以’ \n '或文件结束符结束)中读取字符串,并将其(连同空结束符)存储在指定的目的地。
在这段代码中,您可以看到目标是一个数组buf,声明为具有BUFFER_SIZE字节。在生成目标时,BUFFER_SIZE是特定于程序版本的编译时常量。
函数Gets()和gets()无法确定它们的目标缓冲区是否足够大,以存储它们读取的字符串。它们只是复制字节序列,可能会超出在目的地分配的存储边界。
如果用户输入并由getbuf读取的字符串足够短,那么很明显,getbuf将返回1,如下面的执行示例所示:
通常,如果您键入长字符串,就会发生错误:
(请注意显示的cookie值与您的值不同。)程序RTARGET将具有相同的行为。正如错误消息所指出的,溢出缓冲区通常会导致程序状态损坏,从而导致内存访问错误。您的任务是更聪明地使用您提供给CTARGET和RTARGET的字符串,以便它们能够做更有趣的事情。这些被称为利用字符串。CTARGET和RTARGET都接受几个不同的命令行参数:
-h:输出可能的命令行参数列表
-q:不要把成绩发给评分服务器
-i FILE:从文件提供输入,而不是从标准输入
您的漏洞利用字符串通常包含与打印字符的 ASCII 值不对应的字节值。 程序 HEX2RAW 将使您能够生成这些原始字符串。 有关如何使用 HEX2RAW 的更多信息,请参阅附录 A
重要的几点:
- 你的利用字符串不能在任何中间位置包含字节值0x0a,因为这是换行符(’ \n ')的ASCII代码。当Gets遇到这个字节时,它将假定您打算终止该字符串
- HEX2RAW期望由一个或多个空格分隔的两位十六进制值。所以如果你想创建一个十六进制值为0的字节,你需要把它写成00。要创建单词0xdeadbeef,应该将“ef be ad de”传递给HEX2RAW(注意小端字节顺序所需的反转)
当你正确地解决了其中一个关卡,你的目标程序将自动发送通知给评分服务器。例如:
CI:代码注入
ROP:面向返回编程
服务器将测试您的漏洞利用字符串以确保它确实有效,并将更新 Attacklab 记分板页面,指示您的用户 ID(按匿名目标编号列出)已完成此阶段
您可以通过将Web浏览器指向来查看计分板
4.第一部分:代码注入攻击
对于前三个阶段,您的漏洞利用字符串将攻击 CTARGET。 该程序的设置方式使堆栈位置从一次运行到下一次运行保持一致,因此可以将堆栈上的数据视为可执行代码。 这些功能使程序容易受到攻击,其中漏洞利用字符串包含可执行代码的字节编码。
4.1 Level 1
对于阶段 1,您不会注入新代码。 相反,您的漏洞利用字符串将重定向程序以执行现有程序
函数 getbuf 在 CTARGET 中由具有以下 C 代码的函数测试调用
当getbuf执行它的返回语句(getbuf的第5行)时,程序通常在函数test中继续执行(在该函数的第5行)。我们想要改变这种行为。在文件ctarget中,有一个函数touch1的代码,它具有如下的C表示形式
您的任务是让CTARGET在getbuf执行它的return语句时执行touch1的代码,而不是返回到test。注意,利用字符串也可能破坏与此阶段不直接相关的堆栈部分,但这不会造成问题,因为touch1会导致程序直接退出
一些建议:
- 您可以通过检查 CTARGET 的反汇编版本来确定为此级别设计漏洞利用字符串所需的所有信息。 使用 objdump -d 得到这个反汇编版本
- 其思想是定位touch1的起始地址的字节表示,以便位于getbuf代码末尾的ret指令将控制传递给touch1
- 小心字节排序
- 您可能希望使用GDB来逐步执行getbuf的最后几条指令,以确保程序正在做正确的事情。
- buf在getbuf堆栈框架中的位置取决于编译时常量BUFFER_SIZE的值,以及GCC使用的分配策略。您需要检查反汇编代码以确定其位置。
4.2 Level 2
阶段2涉及注入少量代码作为利用字符串的一部分。在文件ctarget中,有一个函数touch2的代码,它具有如下的C表示形式:
您的任务是让 CTARGET 执行 touch2 的代码,而不是返回测试。 但是,在这种情况下,您必须使它看起来像 touch2,好像您已将 cookie 作为其参数传递一样
一些建议:
- 您将希望定位注入代码的地址的字节表示,以便在getbuf的代码末尾的ret指令将控制权传递给它
- 回想一下,函数的第一个参数是在寄存器 %rdi 中传递的
- 您注入的代码应该将寄存器设置为您的 cookie,然后使用 ret 指令将控制权转移到 touch2 中的第一条指令。
- 不要尝试在您的漏洞利用代码中使用 jmp 或 call 指令。 这些指令的目标地址编码很难制定。 对所有控制转移使用 ret 指令,即使您没有从呼叫中返回。
- 请参阅附录B中关于如何使用工具生成指令序列的字节级表示的讨论。
4.3 Level 3
阶段3还涉及到代码注入攻击,但要传递一个字符串作为参数。
在文件ctarget中,有函数hexmatch和touch3的代码,它们具有以下C表示形式:
您的任务是让 CTARGET 执行 touch3 的代码,而不是返回测试。 您必须让它在 touch3 看来就像您传递了 cookie 的字符串表示作为其参数一样
一些建议:
- 您需要在漏洞利用字符串中包含 cookie 的字符串表示。 该字符串应包含八个十六进制数字(从最高到最低有效的顺序(ordered from most to least significant)),没有前导“0x”。
- 回想一下,在C语言中,字符串表示为一个字节序列,后面跟着一个值为0的字节。在任何Linux机器上键入“man ascii”以查看所需字符的字节表示。
- 注入的代码应该将寄存器%rdi设置为这个字符串的地址。
- 当函数 hexmatch 和 strncmp 被调用时,它们将数据压入堆栈,覆盖保存 getbuf 使用的缓冲区的内存部分。 因此,您需要小心放置 cookie 的字符串表示形式。
5.第二部分:面向返回的编程
对程序 RTARGET 执行代码注入攻击比对 CTARGET 执行代码注入攻击要困难得多,因为它使用两种技术来阻止此类攻击
- 它使用随机化,以便堆栈位置从一次运行到另一次运行不同。 这使得无法确定注入的代码所在的位置
- 它将保存堆栈的内存部分标记为不可执行,因此即使您可以将程序计数器设置为注入代码的开头,程序也会因分段错误而失败。
幸运的是,聪明的人已经设计出了一些策略,通过执行现有代码而不是注入新代码来在程序中完成有用的事情。最普遍的形式被称为面向返回的编程(ROP)[1,2]。ROP的策略是在现有的程序中识别字节序列,该程序包含一个或多个指令,后面跟着指令ret。这样的段称为gadget。
这个函数用于攻击系统的可能性似乎非常小。但是,这个函数的分解机器码显示了一个有趣的字节序列
字节序列 48 89 c7 对指令 movq %rax, %rdi 进行编码。 (有关有用 movq 指令的编码,请参见图 3A。)此序列后跟字节值 c3,它对 ret 指令进行编码。 函数从地址 0x400f15 开始,序列从函数的第四个字节开始。 因此,此代码包含一个起始地址为 0x400f18 的小工具,它将寄存器 %rax 中的 64 位值复制到寄存器 %rdi。
RTARGET的代码包含许多类似于上面所示的setval_210函数的函数,这些函数位于我们称为gadget farm的区域中。您的工作将是在gadget farm中识别有用的gadget,并使用这些gadget执行类似于您在阶段2和阶段3中所做的攻击。
重要的:小工具场由 rtarget 副本中的函数 start_farm 和 end_farm 划分。 不要尝试从程序代码的其他部分构建小工具。
5.1 Level 2
对于第 4 阶段,您将重复第 2 阶段的攻击,但使用来自您的小工具群的小工具在程序 RTARGET 上执行此操作。 您可以使用由以下指令类型组成的小工具构建您的解决方案,并且仅使用前八个 x86-64 寄存器 (%rax–%rdi)。
movq:这些代码如图3A所示。
popq:这些代码如图3B所示。
ret:该指令由单个字节0xc3编码。
nop:这条指令(发音为“no op”,是“no operation”的缩写)由单个字节0x90编码。它的唯一作用是使程序计数器加1。
一些建议:
- 您需要的所有gadget都可以在rtarget的代码区域中找到,该区域由start_farm和mid_farm函数划分。
- 您只需使用两个小工具就可以进行这种攻击。
- 当小工具使用 popq 指令时,它将从堆栈中弹出数据。 因此,您的漏洞利用字符串将包含小工具地址和数据的组合。
5.2 Lecel 3
在你开始阶段5之前,停下来考虑一下到目前为止你已经完成了什么。在阶段2和阶段3中,您使程序执行您自己设计的机器代码。如果CTARGET是一个网络服务器,您可以将自己的代码注入到远程机器中。在阶段4中,您绕过了现代系统用来阻止缓冲区溢出攻击的两个主要设备。尽管您没有注入自己的代码,但您可以注入一种通过将现有代码序列拼接在一起来操作的程序类型。你的实验室也得到了95/100分。那是个不错的分数。如果你有其他紧迫的任务,考虑立即停止。
阶段 5 要求您对 RTARGET 进行 ROP 攻击,以使用指向 cookie 字符串表示的指针调用函数 touch3。 这似乎并不比使用 ROP 攻击来调用 touch2 困难得多,除非我们已经这样做了。 此外,第 5 阶段仅计 5 分,这并不是对其所需努力的真正衡量标准。 对于那些想要超出课程正常预期的人来说,这更像是一个额外的学分问题。
A. movq指令编码
B. popq指令编码
C. movl指令的编码
D. 2字节功能性nop指令的编码
- 您将希望查看movl指令对寄存器的上4个字节的影响,如正文第183页(中文是123页)所述。
- 官方解决方案需要八个小工具(并非所有小工具都是唯一的)。
祝你好运,玩得开心!
A 使用 HEX2RAW
HEX2RAW接受一个十六进制格式的字符串作为输入。在这种格式中,每个字节值由两个十六进制数字表示。例如,字符串“012345”可以十六进制格式输入为“30 31 32 33 34 35 00”。(回想一下,十进制数字x的ASCII码是0x3x,字符串的结尾由一个空字节表示。)
您传递给 HEX2RAW 的十六进制字符应由空格(空格或换行符)分隔。 我们建议您在处理漏洞时用换行符分隔漏洞利用字符串的不同部分。 HEX2RAW 支持 C 风格的块注释,因此您可以标记出漏洞利用字符串的部分。 例如:
请确保在开始和结束的注释字符串(" /* “,” */ ")周围留下空格,以便正确地忽略注释。
如果您在文件exploit.txt 中生成一个十六进制格式的漏洞利用字符串,您可以通过几种不同的方式将原始字符串应用于CTARGET 或RTARGET:
- 您可以设置一系列管道来通过HEX2RAW传递字符串。
- 你可以将原始字符串存储在一个文件中,并使用I/O重定向:
当从GDB中运行时,也可以使用这种方法:
3.你可以将原始字符串存储在一个文件中,并将文件名作为命令行参数提供:
当从GDB中运行时,也可以使用这种方法。
B 生成字节码
使用GCC作为汇编器,使用OBJDUMP作为反汇编器,可以方便地生成指令序列的字节代码。例如,假设您编写了一个文件示例。包含以下程序集代码:
代码可以包含指令和数据的混合。’ # '字符右侧的任何内容都是注释。
现在可以组装和反汇编此文件:
生成的文件example.d包含以下内容:
example.o: 文件格式elf64 - x86 - 64
文本的反汇编:
底部的行显示了从汇编语言指令生成的机器代码。 每行左边有一个十六进制数表示指令的起始地址(从0开始),而“:”字符后面的十六进制数字表示指令的字节码。 因此,我们可以看到,指令 pushq $0xABCDEF 具有十六进制格式的字节码 68 ef cd ab 00。
从这个文件中,您可以获得代码的字节序列:
然后可以通过 HEX2RAW 传递此字符串以生成目标程序的输入字符串。或者,您可以编辑 example.d 以省略无关值并包含 C 样式注释以提高可读性,产生
这也是在发送到目标程序之前可以通过HEX2RAW传递的有效输入。