$1 缓冲区溢出相关概念
1.1 缓冲区
-
从程序的角度,缓冲区就是应用程序用来保存用户输入数据、程序临时数据的内存空间
-
缓冲区的本质:数组
-
存储位置
-
Stack
-
Heap
-
数据段
-
1.2 缓冲区溢出
- 如果用户输入的数据长度超出了程序为其分配的内存空间,这些数据就会覆盖程序为其它数据分配的内存空间,形成所谓的缓冲区溢出
- 人为的溢出则是有一定企图的,攻击者编写一个超过缓冲区长度的字符串,植入到缓冲区,这时可能会出现两个结果:
- 一是过长的字符串覆盖了相邻的存储单元,引起程序运行失败,严重的可导致系统崩溃;
- 另一个结果就是利用这种漏洞可以执行任意指令,甚至可以取得系统root特级权限
1.3 简单溢出实例
void func1(char *input)
{
char buffer[16];
strcpy(buffer,input);
}
-
上面的strcpy()将直接把input中的内容复制到buffer中。这样只要input的长度大于16,就会造成buffer的溢出,使程序运行出错。
-
存在像strcpy这样问题的标准函数还有strcat(),sprintf(),vsprintf(),gets(),scanf()以及在循环内的getc(),fgetc(),getchar()等。
-
当然,随便往缓冲区中植入数据造成它溢出一般只会出现Segmentation fault 错误,而不能达到攻击的目的
-
最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell,再通过shell执行其他命令。如果该程序属于root且有suid权限的话,攻击者就获得了一个有root权限的shell,便可以对系统进行任意操作
用户shell:
- shell是一种命令行界面,用户可以在其中键入命令以执行各种操作,包括文件管理、程序运行、系统配置等
- 用户shell可以理解为用户与操作系统进行交互的方式,它允许用户通过键入文本命令来与计算机进行通信
1.4 危害
- 应用程序异常
- 系统不稳定甚至崩溃
- 程序跳转到恶意代码,控制权被窃
$2 缓冲区溢出原理
- 预备知识:
- 理解程序内存空间
- 理解堆栈
- 理解函数调用过程
- 理解缓冲区溢出的原理
- 程序空间由何构成?
- 堆栈是什么?
- 堆栈里面放的都是什么信息?
- 程序使用超过了堆栈默认的大小怎么办?
- 在一次函数调用中,堆栈是如何工作的?
2.1 程序内存空间
程序在内存中的映像
- PEB(Process Environment Block,进程环境块)存放进程信息,每个进程都有自己的PEB信息。位于用户地址空间
- 在Win 2000下,进程环境块的地址对于每个进程来说是固定的,在0x7FFDF000处,这是用户地址空间,所以程序能够直接访问。在用户态下WinDbg中可用命令$proc取得PEB地址
- TEB(Thread Environment Block,线程环境块)系统在此TEB中保存频繁使用的线程相关的数据。位于用户地址空间,在比 PEB 所在地址低的地方
- 进程中的每个线程都有自己的一个TEB。一个进程的所有TEB都以堆栈的方式,存放在从0x7FFDE000开始的线性内存中,每4KB为一个完整的TEB,不过该内存区域是向下扩展的。
2.2 堆栈
一、栈
1. 介绍
-
栈是一块连续的内存空间
- 先入后出
- 生长方向与内存的生长方向正好相反, 从高地址向低地址生长
-
每一个线程有自己的栈
- 提供一个暂时存放数据的区域
-
使用POP/PUSH指令来对栈进行操作
-
使用ESP寄存器指向栈顶,EBP指向栈帧底
这个栈实际上起到一个环境的保存作用,它保存了函数的参数、返回地址(一边调用用完能回到返回地址)等等,下面有介绍
2. 栈内容
- 函数的参数
- 函数返回地址EIP
- EBP的值
- 一些通用寄存器(EDI,ESI…)的值
- 当前正在执行的函数的局部变量
二、三个重要寄存器
1. SP(ESP)
即栈顶指针,随着数据入栈出栈而发生变化
2. BP(EBP)
即基地址指针,用于标识栈中一个相对稳定的位置。通过BP,可以方便地引用函数参数以及局部变量
3. IP(EIP)
即指令寄存器的内容,在将某个函数的栈帧压入栈中时,其中就包含当前的IP值,即函数调用返回后下一个执行语句的地址
‘’指向下一条指令的位置‘’
4.2.3 函数调用过程
一、函数调用与恢复过程
运行,遇到函数,开始函数调用:
1. 调用
(1):把参数压入栈
(2):保存指令寄存器中的内容,也就是保存返回地址(EIP)
这个地址是函数执行完毕后,程序应该返回到的位置,即函数调用点的**下一条指令地址**
(3):放入堆栈当前的基址寄存器(EBP的值)
也就是,压入堆栈原先的基址,以保存之前任务的信息
(4):把当前的栈指针(ESP)拷贝到基址寄存器,作为新的基地址
即将ESP的值赋给EBP,作为新开辟的函数堆栈的栈底
(5):为本地变量留出一定空间,更新ESP–把ESP减去适当的数值
2. 恢复
函数返回后,开始恢复调用前的状态
(6):从EBP中取出之前的ESP的值
恢复调用前的栈顶位置
(7):恢复栈顶后,之前(3)步存的原先的EBP刚好在栈顶位置的前面,因此也可以恢复调用前的栈底位置
至此,堆栈恢复了函数调用前的状态
二、调用中栈的工作过程
1. 调用函数前
- 压入栈
- 上级函数传给A函数的参数
- 返回地址(EIP)
- 当前的EBP
- 函数的局部变量
2. 调用函数后
- 恢复EBP
- 恢复EIP
- 局部变量不作处理
2.4 缓冲区溢出原理及其利用
一、缓冲区溢出种类
1. 栈溢出
(1)特点
- 缓冲区在栈中分配
- 拷贝的数据过长
- 覆盖了函数的返回地址或其它一些重要数据结构、函数指针
(2)实例
2. 堆溢出
3. 整型溢出
(1)整数&整型溢出是什么?
- 整数:
- 整数就是一个没有小数部分的实数。通常,整数具有同指针一样的宽度(在32位机器中,如i386,整数是32的,在64位机器上,整数就是64位的
- 整型溢出
- 由于计算机中整数都有一个宽度(本文中为32位),因此它就有一个可以表示的最大值。当我们试图保存一个比它可以表示的最大值还大的数时,就会发生整数溢出
(2)为什么整型溢出很危险?
- 在整数溢出确实发生之前我们是无法得知它会溢出的,因此程序是没有办法区分先前计算出的值是否正确。如果计算结果作为一个缓冲区的大小或数组的下标时将会非常危险。
- 当然,大多数整数溢出我们是没有办法利用的。因为我们无法直接改写内存单元,但有时整数溢出将会导致其它类型的缺陷,比如很容易发生的缓冲区溢出。整数溢出有时是很难发现的,也正因为如此,即使经过仔细审查的代码有时候也不可避免。
(3)整型溢出时到底会发生什么?
-
“当计算所涉及的操作数是无符号数时不会发生溢出,因为如果计算结果无法用无符号型整数表示时,计算结 果将会通过与无符号整数所能表示的最大值加一这个值取模运算截短。”
-
实例
(4)分类
-
宽度溢出(Widthness Overflow)
-
尝试存储一个超过变量表示范围的大数到变量中
-
实例
-
整型提升
- 整型提升是一种类型转换规则,用于处理表达式中不同大小的整数类型之间的运算
-
-
运算溢出(Arithmetic Overflow)
-
如果存储值是一个运算操作,稍后使用这个结果的程序的任何一部分都将错误的运行,因为这个计算结果是不正确的
-
实例
-
-
符号溢出(Signedness Bug)
- 一个无符号的变量被看作有符号,或者一个有符号的变量被看作无符号
4. 格式化字符串溢出
- 格式化字符串漏洞的产生根源主要源于对用户输入未进行过滤,这些输入数据都作为数据传递给某些执行格式化操作的函数,如printf,sprintf,vprintf,vprintf。
- 恶意用户可以使用"%s",“%x"来得到堆栈的数据,甚至可以通过”%n"来对任意地址进行读写,导致任意代码读写。
(1)有哪些格式化函数
fprintf:将格式化的数据打印至文件;
printf:将格式化的数据打印至标准输出stdout;
sprintf:将格式化的数据存储到缓冲区中;
snprintf:将指定长度的格式化数据存储到缓冲区中;
vfprintf:将va_arg结构中的格式化数据打印到文件;
vprintf:将va_arg结构中的格式化数据打印到标准输出stdout;
vsprintf:将va_arg结构中的格式化数据存储到缓冲区中;
vsnprintf:将va_arg结构中指定长度的格式化数据存储到缓冲区中
(2)参数format有哪些输出格式
(3)格式化字符串函数的主要功能
(4)例子
#include
void main(int argc, char *argv[]){
int count = 1;
while(argc > 1){
printf(argv[count]);//这里出现了漏洞:若用户的输入包含格式化字符(如 %s, %d 等)时,printf //可能会以格式化字符串的方式解释这些字符,导致安全漏洞
printf(“ “);
count ++;
argc --;
}
}
/*
argv 是一个指针数组,它的元素个数是argc,存放的是指向每一个参数的指针,他的第一个元素即argv[0]为编译生成的可执行文件名。从第二个元素(argv[1])开始,是每一个参数
argc 表示argv的大小,是实际参数个数+1,其中+1是因为argv[0]是编译后的可执行文件名
*/
printf(argv[count]);
中的argv[count]
是一个字符串,它是从命令行参数中获取的。那么,如果参数中包含了格式化字符(例如
%s
,%d
,%x
等),printf
函数将会尝试根据这些格式化字符解释并处理它们
- 这么理解:如果用户端输入的是:
myprogram %s
,现在printf
将尝试按照%s
的格式化说明来解释这个字符串- 但是
argv[count]
中并没有实际的%s
格式化字符串对应的数据,因此printf
将会尝试从栈中读取下一个内存位置的内容作为%s
应该显示的内容,这可能导致程序读取到无效或未知的数据,并在最坏的情况下导致程序崩溃或暴露敏感信息
5. 其它溢出类型
-
Data section溢出
-
PEB/TEB溢出
-
文件流溢出
6. 归纳:溢出的共性
- 大object向小object复制数据(字符串或整型),容纳不下造成溢出
- 溢出会覆盖一些关键性数据(返回地址、管理数据、异常处理或文件指针等)
- 利用程序的后续流程,得到程序的控制权
二、缓冲区溢出的利用
1. shellcode
通常以二进制形式编写
(1)定义
- Shellcode实际是一段代码,是用来在程序发生溢出后,程序将要执行的代码
- Shellcode的作用就是实现漏洞利用者想要达到的目的,一般我们看到的Shellcode都是用来安装木马或者提升权限的
(2)功能
-
基本功能
-
添加administrator or root组用户
-
远程可用shell
- Shell 是计算机操作系统的用户界面,允许用户通过命令行输入命令来控制计算机系统。也就是我们常开的终端
- 远程可用 Shell 指的是能够通过网络远程访问并与目标计算机系统进行交互的命令行界面或终端
-
下载程序(Trojan or Rootkit)执行
Trojan:特洛伊木马,伪装成合法软件的恶意软件
Rookit:是一种恶意软件,其目的是隐藏其存在和活动,常常被用于获取系统的管理员或root权限,以便攻击者可以悄悄地控制受感染系统
-
-
高级功能
-
抗NIDS检测
网络入侵检测系统(NIDS,Network Intrusion Detection System)
-
穿透防火墙
-
(3)为什么不通用
- 不同硬件平台
- IBM PC、Alpha,PowerPC
- 不同系统平台
- Unix、Windows
- 不同内核与补丁版本
- 不同漏洞对字符串限制不同
$3 溢出保护技术
一、人—代码作者
- 编写正确的代码
- 方法
- 学习安全编程
- 软件质量控制
- 源码级纠错工具
二、编译器
-
数组边界检查
-
编译时加入条件
-
例如canary保护
“Canaries” 探测:
要检测对函数栈的破坏,需要修改函数栈的组织,在缓冲区和控制信息(如 EBP 等)间插入一个 canary word。这样,当缓冲区被溢出时,在返回地址被覆盖之前 canary word 会首先被覆盖。通过检查 canary word 的值是否被修改,就可以判断是否发生了溢出攻击
-
三、语言
- C/C++出于效率的考虑,不检查数组的边界(语言固有缺陷)
- 类型非安全语言——>类型安全语言
- C,C++——>C#,Java?
四、RunTime保护
-
二进制地址重写
Address Space Layout Randomization, ASLR 是一种安全机制,通过在系统启动时对程序、库、内核和堆栈等关键组件的地址进行随机化,防止攻击者利用固定的内存布局进行攻击
-
Hook危险函数技术
-
允许在程序运行时截获函数调用并修改其行为
-
在安全领域中,Hooking 技术可以用于拦截对于系统或危险函数的调用
- 例如,通过Hooking可以对危险的函数(如
strcpy
、printf
等)进行拦截,检查输入参数,确保它们不会导致缓冲区溢出等漏洞
- 例如,通过Hooking可以对危险的函数(如
-
五、操作系统
-
非执行缓冲区
-
缓冲区是存放数据地方,我们可以在硬件或操作系统层次上强制缓冲区的内容不得执行
-
许多内核补丁用来阻止缓冲区执行
-
六、硬件
- X86 CPU上采用4GB平坦模式,数据段和代码段的线性地址是重叠的,页面只要可读就可以执行,诸多内核补丁才会费尽心机设计了各种方法来使数据段不可执行
- Alpha、PPC、PA-RISC、SPARC、SPARC64、AMD64、IA64都提供了页执行bit位。Intel及AMD新增加的页执行bit位称为NX安全技术
- Windows XP SP2及Linux Kernel 2.6都支持NX
4.4 安全编程技术
-
设计安全的系统
-
代码的规范和风格
-
危险的函数
strcpy\wcscpy\lstrcpy_tcscpy_mbscpy
strcat\wcscat\lstrcat_tcscat_mbscat
strncpy……
memcpy……
printf\sprintf……
gets
scanf