没有合适的资源?快使用搜索试试~ 我知道了~
在过去的几个月中,被发现和利用的缓冲区溢出漏洞呈现上升趋势.例如syslog, splitvt, sendmail 8.7.5, Linux/FreeBSD mount, Xt library, at等等.本文试图 解释什么是缓冲区溢出, 以及如何利用. 汇编的基础知识是必需的. 对虚拟内存的概念, 以及使用gdb的经验是十分有益 的, 但不是必需的. 我们还假定使用Intel x86 CPU, 操作系统是Linux.
资源推荐
资源详情
资源评论
辛巴达文章
标 题: 缓冲区溢出的原理和实践(Phrack)
.oO Phrack 49 Oo.
Volume Seven, Issue Forty-Nine
File 14 of 16
BugTraq, r00t, and Underground.Org
bring you
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Smashing The Stack For Fun And Profit
以娱乐和牟利为目的践踏堆栈
(缓冲区溢出的原理和实践)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
原作 by Aleph One
aleph1@underground.org
翻译 xuzq@chinasafer.com
www.chinasafer.com
'践踏堆栈'[C 语言编程] n. 在许多 C 语言的实现中,有可能通过写入例程
中所声明的数组的结尾部分来破坏可执行的堆栈.所谓'践踏堆栈'使用的
代码可以造成例程的返回异常,从而跳到任意的地址.这导致了一些极为
险恶的数据相关漏洞(已人所共知).其变种包括堆栈垃圾化(trash the
stack),堆栈乱写(scribble the stack),堆栈毁坏(mangle the stack);
术语 mung the stack 并不使用,因为这从来不是故意造成的.参阅 spam?
也请参阅同名的漏洞,胡闹内核(fandango on core),内存泄露(memory
leak),优先权丢失(precedence lossage),螺纹滑扣(overrun screw).
简 介
~~~~~~~
在过去的几个月中,被发现和利用的缓冲区溢出漏洞呈现上升趋势.例如 syslog,
splitvt, sendmail 8.7.5, Linux/FreeBSD mount, Xt library, at 等等.本文试图
解释什么是缓冲区溢出, 以及如何利用.
汇编的基础知识是必需的. 对虚拟内存的概念, 以及使用 gdb 的经验是十分有益
的, 但不是必需的. 我们还假定使用 Intel x86 CPU, 操作系统是 Linux.
在开始之前我们给出几个基本的定义: 缓冲区,简单说来是一块连续的计算机内
存区域, 可以保存相同数据类型的多个实例. C 程序员通常和字缓冲区数组打交道.
最常见的是字符数组. 数组, 与 C 语言中所有的变量一样, 可以被声明为静态或动态
的. 静态变量在程序加载时定位于数据段. 动态变量在程序运行时定位于堆栈之中.
溢出, 说白了就是灌满, 使内容物超过顶端, 边缘, 或边界. 我们这里只关心动态
缓冲区的溢出问题, 即基于堆栈的缓冲区溢出.
进程的内存组织形式
~~~~~~~~~~~~~~~~~~~~
为了理解什么是堆栈缓冲区, 我们必须首先理解一个进程是以什么组织形式在
内存中存在的. 进程被分成三个区域: 文本, 数据和堆栈. 我们把精力集中在堆栈
区域, 但首先按照顺序简单介绍一下其他区域.
文本区域是由程序确定的, 包括代码(指令)和只读数据. 该区域相当于可执行
文件的文本段. 这个区域通常被标记为只读, 任何对其写入的操作都会导致段错误
(segmentation violation).
数据区域包含了已初始化和未初始化的数据. 静态变量储存在这个区域中. 数
据区域对应可执行文件中的 data-bss 段. 它的大小可以用系统调用 brk(2)来改变.
如果 bss 数据的扩展或用户堆栈把可用内存消耗光了, 进程就会被阻塞住, 等待有了
一块更大的内存空间之后再运行. 新内存加入到数据和堆栈段的中间.
/------------------\ 内存低地址
| |
| 文本 |
| |
|------------------|
| (已初始化) |
| 数据 |
| (未初始化) |
|------------------|
| |
| 堆栈 |
| |
\------------------/ 内存高地址
Fig. 1 进程内存区域
什么是堆栈?
~~~~~~~~~~~~~
堆栈是一个在计算机科学中经常使用的抽象数据类型. 堆栈中的物体具有一个特性:
最后一个放入堆栈中的物体总是被最先拿出来, 这个特性通常称为后进先处(LIFO)队列.
堆栈中定义了一些操作. 两个最重要的是 PUSH 和 POP. PUSH 操作在堆栈的顶部加入一
个元素. POP 操作相反, 在堆栈顶部移去一个元素, 并将堆栈的大小减一.
为什么使用堆栈?
~~~~~~~~~~~~~~~~
现代计算机被设计成能够理解人们头脑中的高级语言. 在使用高级语言构造程序时
最重要的技术是过程(procedure)和函数(function). 从这一点来看, 一个过程调用可
以象跳转(jump)命令那样改变程序的控制流程, 但是与跳转不同的是, 当工作完成时,
函数把控制权返回给调用之后的语句或指令. 这种高级抽象实现起来要靠堆栈的帮助.
堆栈也用于给函数中使用的局部变量动态分配空间, 同样给函数传递参数和函数返
回值也要用到堆栈.
堆栈区域
~~~~~~~~~~
堆栈是一块保存数据的连续内存. 一个名为堆栈指针(SP)的寄存器指向堆栈的顶部.
堆栈的底部在一个固定的地址. 堆栈的大小在运行时由内核动态地调整. CPU 实现指令
PUSH 和 POP, 向堆栈中添加元素和从中移去元素.
堆栈由逻辑堆栈帧组成. 当调用函数时逻辑堆栈帧被压入栈中, 当函数返回时逻辑
堆栈帧被从栈中弹出. 堆栈帧包括函数的参数, 函数地局部变量, 以及恢复前一个堆栈
帧所需要的数据, 其中包括在函数调用时指令指针(IP)的值.
堆栈既可以向下增长(向内存低地址)也可以向上增长, 这依赖于具体的实现. 在我
们的例子中, 堆栈是向下增长的. 这是很多计算机的实现方式, 包括 Intel, Motorola,
SPARC 和 MIPS 处理器. 堆栈指针(SP)也是依赖于具体实现的. 它可以指向堆栈的最后地址,
或者指向堆栈之后的下一个空闲可用地址. 在我们的讨论当中, SP 指向堆栈的最后地址.
除了堆栈指针(SP 指向堆栈顶部的的低地址)之外, 为了使用方便还有指向帧内固定
地址的指针叫做帧指针(FP). 有些文章把它叫做局部基指针(LB-local base pointer).
从理论上来说, 局部变量可以用 SP 加偏移量来引用. 然而, 当有字被压栈和出栈后, 这
些偏移量就变了. 尽管在某些情况下编译器能够跟踪栈中的字操作, 由此可以修正偏移
量, 但是在某些情况下不能. 而且在所有情况下, 要引入可观的管理开销. 而且在有些
机器上, 比如 Intel 处理器, 由 SP 加偏移量访问一个变量需要多条指令才能实现.
因此, 许多编译器使用第二个寄存器, FP, 对于局部变量和函数参数都可以引用,
因为它们到 FP 的距离不会受到 PUSH 和 POP 操作的影响. 在 Intel CPU 中, BP(EBP)用于这
个目的. 在 Motorola CPU 中, 除了 A7(堆栈指针 SP)之外的任何地址寄存器都可以做 FP.
考虑到我们堆栈的增长方向, 从 FP 的位置开始计算, 函数参数的偏移量是正值, 而局部
变量的偏移量是负值.
当一个例程被调用时所必须做的第一件事是保存前一个 FP(这样当例程退出时就可以
恢复). 然后它把 SP 复制到 FP, 创建新的 FP, 把 SP 向前移动为局部变量保留空间. 这称为
例程的序幕(prolog)工作. 当例程退出时, 堆栈必须被清除干净, 这称为例程的收尾
(epilog)工作. Intel 的 ENTER 和 LEAVE 指令, Motorola 的 LINK 和 UNLINK 指令, 都可以用
于有效地序幕和收尾工作.
下面我们用一个简单的例子来展示堆栈的模样:
example1.c:
------------------------------------------------------------------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
------------------------------------------------------------------------------
为了理解程序在调用 function()时都做了哪些事情, 我们使用 gcc 的-S 选项编译, 以产
生汇编代码输出:
$ gcc -S -o example1.s example1.c
通过查看汇编语言输出, 我们看到对 function()的调用被翻译成:
pushl $3
pushl $2
pushl $1
call function
以从后往前的顺序将 function 的三个参数压入栈中, 然后调用 function(). 指令 call
会把指令指针(IP)也压入栈中. 我们把这被保存的 IP 称为返回地址(RET). 在函数中所做
的第一件事情是例程的序幕工作:
pushl %ebp
movl %esp,%ebp
subl $20,%esp
将帧指针 EBP 压入栈中. 然后把当前的 SP 复制到 EBP, 使其成为新的帧指针. 我们把这个
被保存的 FP 叫做 SFP. 接下来将 SP 的值减小, 为局部变量保留空间.
我们必须牢记:内存只能以字为单位寻址. 在这里一个字是 4 个字节, 32 位. 因此 5 字节
的缓冲区会占用 8 个字节(2 个字)的内存空间, 而 10 个字节的缓冲区会占用 12 个字节(3 个
字)的内存空间. 这就是为什么 SP 要减掉 20 的原因. 这样我们就可以想象 function()被调用时
堆栈的模样(每个空格代表一个字节):
内存低地址 内存高地址
buffer2 buffer1 sfp ret a b c
<------ [ ][ ][ ][ ][ ][ ][ ]
堆栈顶部 堆栈底部
缓冲区溢出
~~~~~~~~~~~~
缓冲区溢出是向一个缓冲区填充超过它处理能力的数据所造成的结果. 如何利用这个
经常出现的编程错误来执行任意代码呢? 让我们来看看另一个例子:
example2.c
------------------------------------------------------------------------------
void function(char *str) {
char buffer[16];
strcpy(buffer,str);
}
void main() {
char large_string[256];
int i;
for( i = 0; i < 255; i++)
剩余63页未读,继续阅读
资源评论
wswss11986
- 粉丝: 2
- 资源: 16
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功