
本文以记录个人学习CSAPP的过程,使用blog的方式记录能更专注的思考,不至于走马观花式的做实验。同时我关注的不仅是实验本身还有实验中使用工具的学习。
实验说明
本实验通过向两个程序中进行攻击已达到攻击目的,通过这个实验能了解到通过缓冲区溢出的方法攻击程序的方法,同时对汇编代码、GDB
和OBJDUMP
等工具以及代码在机器中执行的过程的理解有更深的理解。
在动手实验之前需要仔细阅读实验说明, 本实验可以在实验说明的引导下顺利完成。
实验中主要包括两个可执行程序ctarget
和rtarget
,两个可执行程序都会从标准输入中读入一个字符串在缓冲区中,但是并不会检测是否会出现缓冲区溢出。但是程序会在反馈信息中表示是否出现缓冲区溢出。同时还有一个工具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 | (gdb) disas |
在这里能看到实际上分配的空间为0x28B
。并且不会做缓冲区溢出的验证,我们可以先任意填充数据将缓存区填满,并在继续填写将返回值修改为0x00000000004017c0
。但是需要注意的是小端序的存储方式,我们可以将数据以16进制的格式写入一个文本文件,便于 HEX2RAW
转换为我们需要的数据。我填写的数据在key_1.txt
1 | 00 00 00 00 00 00 00 00 |
使用如下命令
1 | $ ./hex2raw -i key_1.txt | ./ctarget -q |
Part1 Level2 (Phase 2)
这个部分的可以看到汇编代码中有如下部分
1 | 0x00000000004017fc <+16>: cmp 0x202ce2(%rip),%edi # 0x6044e4 <cookie> |
直接给出了所需要比较的值cookie
的位置,直接用gdb打印出结果
1 | (gdb) x/x 0x6044e4 |
现在的目标就变成如何将这个值写入到寄存器rdi
中,根据提示信息我们不要使用除ret
以外的其他跳转指令,所以就要借助栈即存储地址又存储指令。我们可以将将示意图表示如下
因为我们只能通过输入字符串的方式去攻击,所以我们能做的就是将函数返回地址改为栈中下一行的地址(这里的索引为示例,实际情况中栈的地址是向下增长的),并将下一行作为注入函数的代码段,同时记得再将返回地址利用mov
写入栈中,并将rsp
寄存器移动到相应地方以利用ret
进入touch2
。
我的注入代码段如下:
1 | mov $0x59b997fa, %rdi |
将上述代码保存在a.S
中,并执行 用于生成二进制代码
1 | 0000000000000000 <.text>: |
这段代码中我们能看出我们需要将rsp
移动b + 1
也就是c
,寻找touch2
函数的地址方式见上一题,最终注入为
1 | 00 00 00 00 00 00 00 00 |
执行结果如下
1 | $ ./hex2raw -i key_2.txt | ./ctarget -q |
Part1 Level3 (Phase 3)
本题和上一题的区别不大,仅仅是改为注入一个字符串指针作为参数,我们可以将字符串的值存在栈中,而将参数传回,具体操作过程如下
注入的代码段和上一题格式相同,需要修改的参数是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 | mov $0x22222222, %rsp |
其中的数后续根据具体地址修改
编译并反汇编后得到如下
1 | Disassembly of section .text: |
我们可以根据上述地址加以修改,gdb
中断点在getbuf
处查看栈指针位置为
1 | (gdb) print $rsp |
说明写入的位置在 0x5561dca0 + 0x8 + 0xe = 0x5561dcb7
和 0x5561dcb7 + 0x8 = 0x5561dcbf
,这样就可以得出最终的代码二进制代码如下
1 | 00 00 00 00 00 00 00 00 |
运行结果如下
1 | $ ./hex2raw -i key_3.txt | ./ctarget -q |
Part2 Level2 (Phase 4)
在第二部分中程序进行了增强,具体如下:
- 栈的地址进行了随机化,意味着无法再直接确定攻击的地址
- 内存中为栈分配的空间没有了执行权限,意味着无法注入代码段在栈中
但是我们“小工具“可以通过其原有代码的部分代码段作为我们执行的二进制代码,从而产生我们需要的效果。这里的小工具实际上是一个函数的最后部分,执行这个片断的代码并返回栈中,继续ret
到栈中下一行所指向的代码,由于在此其间并没有call过这个函数,栈中的数据并不会被破坏修改,从而能按照栈中的地址一步步执行。
在这个部分我们需要在这个限制条件下重新入侵到Phase 2
函数中,并给予限制如下:
- 解决方案需要使用小工具的方式实现
- 只能使用到前八个寄存器
- 限制使用的指令为
movq popq ret nop
实际上看上去是一些限制其实也是一些提示,帮助我们减少检索指令的范围。同时所有可能用到的指令都作为附件放在文档的最后,大家做的时候应该仔细查阅。
接下来开始实验的过程
首先将汇编代码保存本地
1 | $ objdump -d rtarget > rtarget.S |
按照要求找到start_farm
和 mid_farm
之间的汇编代码。根据需求,我们需要修改%rdi
寄存器的值,并将执行的PC
跳转到touch2
函数的位置。首先查看cookie的值(方法同Phase 2
)为0x59b997fa
。现在需要将这个值放入栈中并在小工具中将popq
到某个寄存器中,在执行寄存器movq
指令即可。
观察工具代码部分有如下代码
1 | 00000000004019a7 <addval_219>: |
这里最后的58 90 c3
分别对应三个指令popq %rax
、nop
、ret
,所以我们可以在栈中存放同上面的值0x59b997fa
将其移动到%rax
中。
接下来我们抱着目的性的找movq %rax %rdi
, 也就是48 89 c7
观察以下函数满足规则发现
1 | 00000000004019c3 <setval_426>: |
因此这个栈中的代码放置应该如下
1 | 1: 0x00000000004019ab //addr <addval_219> + 4 |
转换为二进制代码如下
1 | 00 00 00 00 00 00 00 00 |
最终执行结果如下
1 | $ ./hex2raw -i key_4.txt | ./rtarget -q |
Part2 Level3 (Phase 5)
这个部分就完全如法炮制的执行到Phase 3
即可。通过上一题知道,其实执行到touch
部分应该是和ctarget
共用的代码段,所以可以直接认为需要的字符串参数为0x59b997fa
即可。
但是当栈的位置不可知时只能利用%rsp
指针的相对值,意味着我们需要计算出偏移量,对于这道题中如何加偏移量是重点。文档中其实没有给出任何执行运算的二进制代码,因此我们需要自己找。通过查找发现并没有add
指令的操作码83
,但是有lea
的函数如下
1 | 00000000004019d6 <add_xy>: |
所以根据提供的函数,我们可以大致写出以下思路
1 | popq %rax |
偏移量为取出%rsp
的值到string
的距离,为8 * 4 = 32B
也就是0x20
如上图的思路可以转换为二进制代码如下:
1 | 00 00 00 00 00 00 00 00 |
最后运行结果如下:
1 | $ ./hex2raw -i key_5.txt | ./rtarget -q |
总结
这个实验总体来说难度不大,但是很有趣味性,通过对栈溢出的攻击,让我们明白程序检测栈是否溢出的重要性。(不过到最后一题才看到hex2raw
支持注释,对实验的便捷性还是提供一定帮助。)