CSAPP 实验III Attack Lab
更新历史:
- 23.03.18:初稿
系列
- CSAPP - 笔记汇总
- I Data Lab - 位操作,数据表示
- II Bomb Lab - 汇编,栈帧与 gdb
- III Attack Lab - 漏洞是如何被攻击的
- IV Cache Lab - 实现一个缓存系统
- V Shell Lab - 实现一个Shell
任务目标
Attack Lab实验,采用 code-injection 和 return-oriented 两种方法攻击程序。
相关内容
./ctarget -q
:运行目标文件用参数-q
,不使用服务器-i FILE
: 文件输入./hex2raw < 1.txt | ./ctarget -q
: 答案不换行,用hex2raw
转换成字符串,注意需要用小端存储。call
指令会将8个字节的rip
值入栈,作为ret
返回地址。- code-injection:关闭了栈随机化,栈不可执行和canary。
- return-oriented:打开栈随机化和不可执行,关闭canary。
题目及解法
思路
和课程中讲解的两种漏洞的破解方式相同:代码注入利用缓冲区溢出,把需要执行的指令输入到栈中。面向返回编程将需要的指令片段返回地址输入到缓冲区中,模拟一个程序计数器,顺序执行需要执行的指令。由于bomb实验对每一条汇编命令都进行了解释,这个实验就不逐条解释了,把之前做过的答案重新整理一遍。
Phase 1
思路:第一个很简单输入函数开辟了0x28的空间用于保存输入,返回地址在rsp+0x28的位置,所以输入一段0x28字节的字符串后面跟上返回touch1的地址,注意地址是小端存放:C0 17 40 00 00 00 00 00
, 以下答案为了方便观看添加了换行,运行程序时需要去掉换行。
gdb进入程序,对test函数反汇编:
调用了getbuf函数,可以查看一下汇编代码,找出缓冲区的大小
缓冲区大小为
0x28
,40个字节,输入的字符串保存在当前rsp
处。然后输入40个字节无用数据,充满缓冲区,继续输入8个字节的touch1地址,覆盖掉原地址。对要跳转的touch1函数反汇编,找一下函数入口地址:
所以需要返回的地址为
0x00000000004017c0
,输入小端存放。
答案:
00 00 00 00 00 00 00 00 |
Phase 2
思路:
第二个需要注入可执行代码, 需要把touch2的参数寄存器rdi
内容设置为cookie, 这样重置ret到touch2才能正确执行, 所有第一次在getbuf中把返回值重置为一个栈地址, 然后在这个地址内写入一系列操作, 每次ret后返回地址会自动退栈, 所以先把rsp
-8, 然后在rdi
写入touch2的地址,然后将rdi
写入rsp
指向的栈地址中,然后把cookie值写入rdi
中,最后ret。
先查看一下touch2函数的入口在哪里:
可以看到需要返回地址为
0x00000000004017ec
, 参数可以进入touch2函数打印一下那个局部变量的值,也可以在上一个题目输出信息那里看到,我的cookie值为0x59b997fa
。这次需要让指令在栈中执行,所以需要知道getbuf时
%rsp
的值,可以在test处打断点,display $rsp
,值为$rsp = (void *) 0x5561dcb0
。test函数中分配8个字节,call getbuf 时使用了8个字节,getbuf分配40个字节,一共分配了56个字节,所以在我们输入字符串时,rsp
指向的地址值应该是0x5561dc78
接下来就是注入一段需要运行的代码,可以先写汇编代码,通过指令
gcc -c foo.s
,生成二进制代码foo.o
, 在通过objdump -d foo.o
反汇编,生成可读的机器指令。
答案:
48 83 ec 08 /* sub $0x8,%rsp */ |
Phase 3
思路:
第三题和第二题基本相同return到touch3后要进行一次cookie对比,第二题是十六进制的数字对比,可以提前把cookie写入rdi
寄存器里,但是第三题的参数是一个字符串指针,所以要把字符串地址写入rsi
第二个参数寄存器里,不能用在栈中写,因为后面还有两次函数调用,栈空间很容易被覆盖,找了一下变量cookie的地址,这个地址0x6044e4
的后面有很长一段地址没有使用过,所以可以把字符串写入0x6044ec
,rsi
的参数传递在0x6044ec
,还有一个地方是字符串0x59b997fa
,每个字符存在一个字节里,一共需要8个字节的空间,可以用mov
,写入十六进制数,但是要注意字符串大端存放,数字是小端存放,翻转一下就可以了.
根据实验指南中提示的函数
hexmatch
, 做了一个字符串对比,第二个参数是字符串地址,而且在函数中将传入的cookie值转换成了一个随机地址的字符串,然后进行字符串对比,所以我们需要提前把字符串写入到固定的地址,然参数传进去。disas touch3
可以看出cookie值存在内存的
0x6044e4
处,打印一下周围的内存值,x/6xg
上面可以看到后面有一部分是没有使用的内存,选一个
0x6044ec
,把我们的字符串写入这里,注意大端存放。接下来注入要运行的指令,和上一题相同,但是要把字符串写入到选定的地址里,字符串
59b997fa
转换为16进制为0x35 0x39 0x62 0x39 0x39 0x37 0x66 0x61
,但是要注意小端存放的值为0x6166373939623935
,所以写入这个值,当做字符串读出时刚好是cookie值。movabs
:用于64位立即数赋值。
答案:
48 83 ec 08 /* sub $0x8,%rsp */ |
Phase 4
思路:
第四题,和第二题相同,需要把代码返回到touch2里面,但是不能像phase2中代码注入运行代码,因为栈内标记为不可执行了,所以要使用ROP的方法来操作,在给出的gadget中选择两个连续的48 89 c7 c3, mov %rax,%rdi, 还有一个58 c3, popq %rax,将这两条指令的地址用栈溢出的方法写到返回里,在返回地址中间加上cookie码。
gcc -c farm.c
:生成目标文件objdump -d farm.o > farm.s
:反汇编生成可读指令这里需要注意优化级别为 -Og,与实验的优化级别相同
objdump -d rtarget.o > rtarget.s
:直接对可执行文件反汇编生成的指令带地址,更方便看一些。在
rtarget.s
中找我们需要的指令,setval_426
函数中有:地址为4019c5
找到我们需要的
48 89 c7
,后续的90
是一个nop,什么也不做,计数器加1,最后跟着c3
ret。上面把
rax
的值存到rdi
中了,接下来我们就是找一个pop指令58 90 c3
,退栈到rax
中,找到getval_280
:地址为4019cc
所以溢出的输入为退栈到
rax
,栈顶是cookie值,然后执行0x4019c5
的mov指令,最后返回touch2。注意所有的操作都是8字节。
答案:
00 00 00 00 00 00 00 00 |
Phase 5
思路:
和Phase 3相同,但是要把代码注入改为target序列执行指令,可能需要对寄存器的低字节进行操作,还没想出怎么做这个题目,后面有时间了再来做。
答案: