CSAPP-AttackLab实验
在程序运行过程中,基于注入攻击代码和面向返回的编程攻击这俩大块内容,通过字符串填充的方式,完成5个阶段的缓冲区攻击。分别基于基本返回地址填充、攻击代码填充、ROP等实现这5个难度递增的阶段的缓冲区溢出攻击。
目录
一、实验目的:
1. 强化机器级表示、汇编语言、调试器和逆向工程等方面基础知识,并结合栈帧工作原理实现简单的栈溢出攻击,掌握其基本攻击基本方式和原理,进一步为编程过程中应对栈溢出攻击打下一定的基础。
2. 理解缓冲区的工作原理和字符填充过程及其特点。对于无边界检测的语言及其工作方式所造成的缓冲区漏洞加深理解。
3. 通过字符串填充的方式,完成5个阶段的缓冲区攻击。分别基于基本返回地址填充、攻击代码填充、ROP等实现这5个难度递增的阶段的缓冲区溢出攻击。
二、实验注意事项:
1.建议在linux下进行攻击字符串的文本编辑。
2.可以使用gdb或IDA、edb、DDD之类的调试软件辅助进行。若有必要,可以使用hex2raw工具先将ASCII字符文件转换为二进制字符文件,例如:
./hex2raw < phase1 > test.txt
然后再使用输入重定向测试缓冲区溢出攻击效果,例如:
./ctarget < test.txt
3.建议实验过程中手绘图表辅助逆向工程分析。
三、实验原理与内容
“AttackLab”是一个Linux下的可执行C程序,包含了5个阶段(phase1~phase5)的不同内容。程序运行过程中,要求学生能够根据缓冲区的工作方式和程序的反汇编代码来确定攻击字符串长度和字符串中的关键内容。每次成功实现缓冲区溢出攻击时都会有提示相应内容,如果攻击失败则单纯的提示segmentation fault相关信息。
要求攻击字符串的执行不许绕开代码中的validate函数,缓冲区溢出之后对应ret的返回地址可以是以下类型:
1.函数touch1、touch2、touch3的首地址;
2.自行注入的攻击的首地址;
3.在后两个阶段中(ROP攻击),与farm.c的对应的可利用的gadget的起始地址,farm.c对应的机器码已经包含在可执行文件中。可以使用的gadget首地址需处于start_farm和end_farm之间的部分。
注意:前三个阶段使用ctarget作为攻击目标文件,后两个阶段中使用rtarget作为攻击目标文件。
每个阶段考察一个缓冲区溢出方式,难度逐级递增:
- 阶段1:使用非ROP方式对ctarget进行攻击,调用touch1,且成功输出Touch1!: You called touch1。若不完全满足题目要求,则会提示“Misfire”和FAIL相关字段。
- 阶段2:使用非ROP方式对ctarget进行攻击,调用touch2,且成功输出Touch2!: You called touch2。攻击过程中需要改写cookie变量的值。若不完全满足题目要求,则会提示“Misfire” 和FAIL相关字段。
- 阶段3:使用非ROP方式对ctarget进行攻击,调用touch3,且成功输出Touch3!: You called touch3。攻击过程中需要使hexmatch的返回值能够正确引导validate函数。若不完全满足题目要求,则会提示“Misfire” 和FAIL相关字段。
- 阶段4:使用ROP方式对rtarget进行攻击,调用touch2,且成功输出Touch2!: You called touch2。若不完全满足题目要求,则会提示“Misfire” 和FAIL相关字段。
- 阶段5:使用ROP方式对rtarget进行攻击,调用touch3,且成功输出Touch3!: You called touch3。若不完全满足题目要求,则会提示“Misfire” 和FAIL相关字段。
ctarget和rtarget都从standard input读入数据,可以以重定向文件的形式进行输入。实验利用getbuf函数中的缓冲区。getbuf函数的结构如下:
unsigned getbuf(){
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
函数中的Gets函数与标准库中的gets函数类似,它从standard input中读取字符(以\n或者EOF结尾)并将它们添加字符串结尾符\0后存入缓冲区中。学生需要根据ctarget和rtarget文件及其反汇编代码来确定缓冲区位置及大小,并想办法构建出攻击字符串。
文件保中包含了可以将字符文件转换为二进制数据文件的小工具hex2raw。
使用hex2raw测试攻击字符串是否有效的方法为:
- 先将攻击字符串存放于txt文档之中。(以两个字符的ASCII码表示十六进制数),如下图所示:
2.使用如下指令进行攻击测试:
cat phase1.txt | ./hex2raw | ./ctarget -q
注意:此命令必须加参数 -q (当前我们使用的实验为离线版,离线版的AttackLab需要使用此命令来抑制程序主动连接服务器的操作)
如果成功,执行效果如下图:
提交答案时使用的文件也应命名为target18220620505.tar,提交的压缩文件的内容必须如下图所示:
必须确认这些答案文本的内容为ASCII文本,如下图所示:
!!!!!不可以是下图中这种二进制字符答案文档!!!!!
推荐以命令方式进行打包,例子如下:
- 进入只包含答案phase1.txt, phase2.txt, phase3.txt, phase4.txt, phase5.txt的文件夹。运行命令行终端:
2.在命令行终端中输入命令(以学号为18220620505的同学为例):
tar -cf target18220620505.tar *
3.命令运行后即生成相应的答案文件压缩包:
四、实验步骤
step1、将可执行文件进行反编译
使用的指令如下:
objdump -S ctarget > ctarget .s (用于阶段1~3)
objdump -S rtarget > rtarget .s(用于阶段4、5)
step2、阅读并理解汇编语言
1)<main>调用 <stable_launch> 调用 <launch> 调用<test>调用<getbuf>调用<Gets>
2)围绕getbuf去思考,通过代码植入攻击或者ROP,调用touch1~3
step3、将要载入栈的内容写在phase1.txt~phase5.txt
step4、利用hex2raw工具,将phase1.txt~phase5.txt转为二进制字符文件
以phase1.txt为例,转换指令为:./hex2raw < phase1.txt > test1.txt
step5、运行
./ctarget < test.txt -q(用于阶段1~3)
./rtarget < test.txt -q(用于阶段4、5)
step6、调试指令
1)gdb ./ctarget(用于阶段1~3)——使用gdb调试可执行文件ctarget
2)gdb ./rtarget(用于阶段4、5)——使用gdb调试可执行文件rtarget
3)b getbuf——在函数getbuf() 的入口处设置断点
4)run -q(简写为r -q)——启动程序的执行,并且在执行过程中抑制冗长的输出信息。在调试过程中减少不必要的干扰,使得程序的输出结果更加清晰可见。
5)layout asm——显示反汇编窗口
6)layout regs——显示寄存器和反汇编窗口
7)nexti(简写为ni)——用于单步执行程序并跳过函数调用
五、实验过程
实验中的phase1~3阶段,都是基于非ROP方式进行攻击,即利用缓冲区漏洞。
基于实验步骤step1,在target20215120305使用objdump指令将ctarget和rtarget两个文件进行反编译,便于后续汇编调试的使用,如下图1所示。
图1:objdump反汇编
Phase1:
如下图2所示,基于实验步骤step2,打开ctarget.s文件查找“getbuf”,从语句中可以看出:getbuf函数要求的缓冲区长度为0x18。为了实现攻击,成功调用touch1,根据栈帧结构,只需要先用24byte的无意义数据覆盖缓冲区,再输入touch1,的首地址就完成了。同时这个attack是免疫栈随机化和栈不可运行的。
图2:查找getbuf及touch1
结合以上分析,新建phase1.txt,由于是小端输入,只需要在无意义数据的后面添上touch1的首地址4018e5即可,如下图3所示。
图3:phase1.txt
有了攻击文件phase1.txt,利用hex2raw工具,将phase1.txt转为二进制字符文件并载入ctarget。在终端中输入“./hex2raw < phase1.txt > test1.txt”指令进行二进制字符转换,再使用指令“./ctarget < test1.txt -q”进行攻击。可以看到成功输出Touch1!: You called touch1,实现攻击。如下图4所示。
图4:phase1攻击实现
Phase2:
如下图5所示,打开ctarget.s文件查询touch2的首地址为401913,打开cookie.txt查看cookie值为0x32546d03。
图5:查询首地址及cookie
如下图6所示,由于phase2不同于phase1,进入touch2后只有当edi与cookie值相等时,才可以成功攻击。因此需要利用缓冲区溢出修改寄存器的值,并写入攻击代码改变rdi/edi的值,考虑将指令写入栈中并在栈上运行。
图6:分析touch2
新建文件inject2.s,在其中写入以下内容,并在终端中执行指令“gcc -c inject2.s”,将汇编语言编译为二进制文件,然后再通过objdump指令“objdump -d inject2.o > inject2.d”进行反汇编,得出汇编指令的机器语言,其指令序列为48 c7 c7 03 6d 54 32。为了存入缓冲区并读取,需要得到缓冲区首地址,因此使用gdb调试将断点打在getbuf处,获取缓冲后首地址,并倒推0x18得到需要的地址为0x5567d6f8,填入phase2.txt。如下图7所示。
图7:攻击代码形成
利用hex2raw工具,将phase2.txt转为二进制字符文件。在终端中输入“./hex2raw < phase2.txt > test2.txt”指令后,使用指令“./ctarget < test2.txt -q”运行。可以看到成功输出Touch2!: You called touch2,实现攻击。如下图8所示。
图8:成功攻击
Phase3:
此阶段依然是代码注入攻击,但不同于之前的阶段,它需要传递字符串作为参数。如下图9所示,打开ctarget.s文件查询“touch3”,查看touch3的首地址为401a2a,并发现攻击字符串中需要包含cookie字符串的表示,考虑转为ASCII十六进制数字并衔接“0”字节,使得语法成立。而hexmatch的调用会将数据压入栈中,覆盖getbuf使用的内存,因此考虑将cookie字符串放置在缓冲区外的位置,需要准确找到地址。
图9:分析touch3
基于ASCII对照表查找cookie值0x32546d03的十六进制数,具体是“33 32 35 34 36 64 30 33 00”,基于阶段二rsp的地址分析,考虑将cookie放入缓冲区后首地址递推0x10的位置,即cookie的地址为0x5567d720。由此即可编写注入文件inject3.s,与阶段二原理相似,改变rdi的值为cookie,如下图10所示。从而在终端中执行指令“gcc -c inject3.s”,将汇编语言编译为二进制文件,然后再通过objdump指令“objdump -d inject3.o > inject3.d”进行反汇编,得出汇编指令的机器语言,并写入phase3.txt文件,成功完成攻击文件编写。
图10:phase3.txt编写
最后使用hex2raw工具,将phase3.txt转为二进制字符文件。在终端中输入“./hex2raw < phase3.txt > test3.txt”指令后,使用指令“./ctarget < test3.txt -q”运行。可以看到成功输出Touch3!: You called touch3,实现攻击。如图11所示。
图11:通过phase3
从phase4开始,要求使用ROP方式进行攻击。
在ROP攻击中,因为栈上限制了不可插入可执行代码,所以不能像上述第二、第三阶段中插入代码。至此我们需要在已经存在的程序中找到特定的指令序列,并且这些指令是以ret结尾,利用它们进行攻击。
汇编指令的字节编码
如图12是部分指令所对应的字节码,由于是面向返回的编程攻击,因此需要使用到汇编指令的字节编码。
图12:指令的十六进制字节编码
Phase4:
在此阶段中,和phase2一样需要将cookie存储进寄存器rdi中。由于是面向返回的编程攻击,所以需要在rtarget.s中找到相应gadget,可以凑出相应的能够实现攻击的指令。在rtarget.s中查找popq %rdi的机器码“5f”,30次出现的结果中并没有我们需要的代码。因此考虑popq R ret再使用movq R,%rdi的形式来实现赋值。通过查找,如下图13-1~3,发现rax符合要求,因此先将寄存器rax的值设置为cookie,然后赋值给rdi。根据这些信息,我们也可以把栈结构示意出来,如表1所示。
图13-1:“58”注意“c3”
图13-2:“48 89 c7”注意“c3”
图13-3:phase4攻击文件机器码
&touch2 |
gadget2 |
cookie |
gadget1 |
getbuf 0x18 |
表1:栈结构
结合图13-1~3的分析,根据栈结构匹配上查询到的地址,并新建phase4.txt将攻击代码的地址填入,完成攻击文件的编写,如下图14所示。
图14:phase4的编写
最后使用hex2raw工具,将phase4.txt转为二进制字符文件。在终端中输入“./hex2raw < phase4.txt > test4.txt”指令后,使用指令“./rtarget < test4.txt -q”运行。可以看到成功输出Touch2!: You called touch2,实现攻击。如图15所示。
图15:通过phase4
Phase5:
在此阶段中,和phase3一样需要将cookie以字符串形式进行传参。但由于是面向返回的编程攻击,无法使用地址来传参cookie的字符串,所以需要在rtarget.s中找到相应gadget,可以凑出相应的能够实现传参的指令,并且考虑cookie字符串的放置位置。具体流程图如下图16所示:
图16:phase5攻击流程图
由于需要地址计算,需要考虑寻找lea计算指令,查找后发现唯一加法函数:
- 因此基于该加法函数来进行传参,计算的操作数有rdi和rsi,然后放入rax中,结合我们需要修改的rdi的值,所以还需要“movq %rax,%rdi”即“48 89 c7”,这一gadget的查找结果等价于phase4中的查找之一,可见图13-2。
- 由于计算的操作数为栈指针位置和偏移量,操作数寄存器分别为rdi和rsi。所以,我们应当将栈指针位置取出,放到rdi寄存器中:将偏移量取出,放到rsi(esi)寄存器中。注意,rsi和esi指向同一个寄存器,只不过使用的位数不同,偏移量使用32位表示即可,所以使用esi来表示。
- 首先,获取栈指针位置需要使用“movq %rsp,X”取出寄存器的值。由于rdi最终存放栈指针的位置参与加法运算,最理想的状态便是:“movq %rsp,%rdi”,查表并寻找但是在gadget farm中并没有找到满足条件的字节代码。因此,只能通过多次movq达到我们的目的:先考虑movq两次,达到我们的目的,将所有movq %rsp,A的字节表示列出,再将所有movq A,%rdi的字节表示列出。在此过程省略,得到了两条可以满足条件的gadget:“movq %rsp,%rax;ret;movq %rax,%rdi;ret”。此时使用了最少gadget达到将栈指针的位置放到了rdi中。
- 计算相对地址的另一个操作数是rsi,保存着地址偏移值,所以地址偏移值是我们自己手动输入的,需要使用“popq %rax”将其取出,它在rax(eax寄存器中)。后续的任务就是将eax的值存放到esi中,参加加法运算。首先看是否可以一次movl达到目的,查看gadget farm中是否有“movl %eax,%esi;ret”。结果是没有;再看是否有使用两次movl达到目的,查看gadget farm是否有“movl %eax,A;ret;movl A,%esi;ret”结果也没有。只能考虑可以使用3次movl达到目的,再次查看gadget farm,最终找到这样的指令:“movl %eax,%edx;ret;movl %edx,%ecx;ret;movl %ecx,%esi;ret”。至此,已经找到了所有的gadget。将栈结构画出来,具体如下表2所示:
cookie字符串 |
&touch3 |
movq %rax,%rdi |
lea (%rdi,%rsi,1),%rax |
movl %ecx,%esi |
movl %edx,%ecx |
movl %eax,%edx |
偏移量 |
popq %rax |
movq %rax,%rdi |
movq %rsp,%rax |
getbuf 0x18 |
表2:栈结构
在栈空间示意图中,计算得cookie的偏移量。相对于栈指针位置,cookie字符串前面有9条指令,9*8=72=0x48。因此偏移量应该为0x48。最终,基于上述指令的查找将机器码在rtarget.s中的地址一一对应,并新建phase5.txt将地址写入攻击文件,如下图17-1~2所示。
图17-1:phase5攻击文件机器码
图17-2:phase5的编写
最后使用hex2raw工具,将phase5.txt转为二进制字符文件。在终端中输入“./hex2raw < phase5.txt > test5.txt”指令后,使用指令“./rtarget < test5.txt -q”运行。可以看到成功输出Touch3!: You called touch3,实现攻击。如图18所示。
图18:通过phase5
更多推荐
所有评论(0)