Phrack安全杂志:详细分析堆栈溢出Smashing The Stack For Fun And Profit(Aleph One) 关于粉碎堆栈的秘密
Posted 鸿渐之翼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Phrack安全杂志:详细分析堆栈溢出Smashing The Stack For Fun And Profit(Aleph One) 关于粉碎堆栈的秘密相关的知识,希望对你有一定的参考价值。
致敬:Aleph One
感谢:phrack.org
Thank to Phrack Information Security Magazine
翻译:CSDN:鸿渐之翼
校对:CSDN:鸿渐之翼
前言:
原文首发于Phrack安全杂志,这是全球第一篇讲述堆栈漏洞的文章,里面提供了学习二进制安全的方法和基础理论知识,详细的描述了栈溢出的原理是什么?这里首次提到function prolouge 和 function epilogue,什么是Stack ?什么是Bufferoverflow?本文献给所有安全行业的研究者和贡献者,由于译者水平有限,翻译与原文内容稍有不同,请谅解。博主希望用质朴的语言展示二进制安全技术,让二进制安全进入大众视野。本文的实验案例可以Linux平台进行复现验证,文章内博主也增添了一些注,可以供诸位学者参考。
.oO Phrack 49 Oo.
**Volume Seven, Issue FortyNine File 14 of 16
BugTraq, r00t, and Underground.Org
bring you**
‘粉碎的堆栈’[C Programming].在许多C程序运行中很容易通过堆栈向程序里写入恶意数据。这样做的过程叫粉碎堆栈(smash the stack),并可能导致程序返回到一个随机地址。程序可以控制堆栈,让常规运行的程序跳转到一个随机地址。这可能产生让人们知道的一些阴险的BUG。变异体可以删除栈,更改栈,破坏栈,在术语上称作栈未被利用,因为栈从来都不是故意的,可以看垃圾信息(邮件)spam,也可以看报错(alias bug),进入核心(fandango on core),内存泄露,优先损失(precedence lossage),溢出数据(overrun screw)
Introduction:
在过去的几个月中我们在关于栈溢出漏洞的发掘和利用又很大的成就与进展(buffer overflow vulnerabilities discovered and exploited)。例如存在栈溢出漏洞的程序有很多,syslog,splitvt,sendmail8.75,Linux/FreeBSD mount,Xt Library,at etc.本文章将会介绍栈溢出是什么(what buffer overflows are),栈溢出攻击是如何做到的(how their exploits work)。
看本文前,需要汇编基础,还需要理解虚拟内存的概念(virtual memory concepts),还需要具备gdb工具的使用经验但不是必备的。我们需要设想我们工作的环境是Intel x86 CPU,操作系统是Linux。我们在学习之前需要了解一些基础的专业术语:缓冲区只是一个连续的计算机内存块,它保存着相同数据的实例。C程序员往往会把缓冲区数组(buffer arrays)联系起来。最常见的,字符数组。数组,就像C语言中的所有变量一样,可以被生命为静态或者动态,静态分配是在数据段加载时分配的。动态变量是在堆栈运行时分配的。栈溢出就是充满数据(flow),或者填满堆栈的顶部(of fill over the top),注满(brims),或者溢出(bounds)。我们将只关注动态缓冲区的溢出,也称为基于堆栈的缓冲区溢出。
Process Memory Organization
要理解什么是堆栈缓冲区,我们必须首先理解进程在内存中是如何组织的。进程(Process)被分为三个区域:文本、数据和堆栈。我们将集中讨论堆栈区域,但首先要简要介绍一下其他区域。文本区域由程序固定,包括代码(指令)和只读数据。该区域对应于可执行文件的文本部分。进程在内存这一部分通常被标记为只读,任何对它的写入尝试都会导致分段冲突。数据区域包含已初始化和未初始化的数据。静态变量被存储在数据区。数据区域相对于执行的是data-bss这个节(sections)的可执行区域executable file.它的大小可以被系统brk(2)系统调用改变(博主认为这里的brk(2)是Linux内核的一个软中断用于动态分配内存的),如果bss数据或用户堆栈的扩展耗尽了可用内存,进程被阻塞,并被重新调度以使用更大的内存空间再次运行。在数据段和堆栈段之间添加新的内存。[博主注:可以参考Linux内核系统动态内存分配机制,缓冲区溢出也是由于系统设计的一个缺陷造成]
What Is A Stack?
堆栈是计算机科学中常用的一个抽象数据类型。堆栈具有一个属性:最后一个数据放在堆栈上,总是第一个被第一个删除。[博主注:pop和push在堆栈上操作的原理]此属性被称为先进后出的队列。在栈上定义一些操作,其中最重要的两个操作室PUSH和POP。PUSH在堆栈顶部添加一个元素。POP,与之相反,通过删除堆栈顶部的最后一个元素,将堆栈大小减少1。
Why do We Use A Stack?
现代计算机在设计时考虑到了高级语言的需要。由高级语言引入的程序结构最重要的技术是过程或函数。 从一个角度来看,过程调用就像跳转一样改变控制流,但与跳转不同的是,当完成其任务时,函数将控制返回到调用之后的语句或指令。这种高级抽象是在堆栈的帮助下实现的。栈还用于动态分配函数中使用的局部变量,向函数传递参数,以及从函数返回值。
The Stack Region
堆栈是一个包含数据的连续内存块。一个称为堆栈指针(SP)(Stack Pointer)的寄存器指向堆栈的顶部。堆栈的底部是一个固定的地址。它的大小由内核[博主注:Linux Kernel]在运行时动态调整。CPU执行PUSH入栈和POP出栈的指令。堆栈由逻辑堆栈帧组成,调用函数时推入,返回时弹出。堆栈帧包含一个函数的参数、它的局部变量和恢复前一个堆栈所需的数据帧,包括函数调用时的指令指针的值。根据实现的不同,堆栈要么向下(向更低的内存地址)增长,要么向上增长。在我们的例子中,我们会使用向下增长的栈。这是堆栈在包括英特尔在内的许多计算机上增长的方式。这种堆栈增长方式包括许多处理器如Intel,Motorola,SPARC和MIPS。SP(Stack Pointer)也是这样依赖的。它可以指向堆栈上的最后一个地址,也可以指向堆栈后的下一个空闲可用地址。在我们的讨论中,我们假设它指向堆栈上的最后一个地址。除了指向堆栈顶部的堆栈指针(lowest numerical address)之外,使用帧指针(Frame Pointer)通常很方便指向一个框架内的固定位置的指针。一些文字也将其称为本地基指针(LB,local base pointer)。 原则上,局部变量可以通过给出它们在SP(Stack pointer)中的偏移量来引用。然而,当字符被压入堆栈并从堆栈中弹出时,这些偏移量会发生变化。尽管在某些情况下,编译器可以跟踪堆栈上的字数,从而纠正偏移量,但在某些情况下,它不能,而且在所有情况下都需要大量的管理。此外,在某些机器上,如基于intel的处理器,访问距离SP已知的变量需要多条指令。因此,许多编译器使用第二个寄存器FP来引用局部变量和参数,因为它们与FP的距离不会随着PUSH和POP而改变。在Intel CPU上,BP (EBP)(Base Pointer)用于此目的。在Motorola CPU里,除了A7(堆栈指针)之外的任何地址寄存器都可以。因为堆栈的增长方式,实际参数的偏移量为正,局部变量的偏移量为负。
被调用时,过程必须做的第一件事是保存前面的FP(以便可以在过程退出时恢复它)。然后,它将SP复制到FP中以创建新的FP,并向前推进SP以为局部变量保留空间。这段代码被称为prolog。 在过程退出时,必须再次清理堆栈,这称为过程epilog。[如果不明白什么是Prolog与Epilog,可以参考博主写的文章:Example:Function Prologue and Function Epilogue(基础函数调用机制)]
Intel的ENTER和LEAVE指令以及Motorola的LINK和UNLINK指令,可以有效地完成大部分程序prolog和epilog的工作。让我们来看一个简单的例子:
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
这将函数的3个参数向后推到堆栈中,并调用function()。指令’call’将把指令指针(IP)压入堆栈。我们将把保存的IP称为返回地址(RET)。在function中做的第一件事是程序prolog:
pushl %ebp
movl %esp,%ebp
subl $20,%esp
这将帧指针EBP推入堆栈。然后,它将当前SP复制到EBP上,使其成为新的FP指针。我们将把保存的FP指针称为SFP。然后通过从SP中减去局部变量的大小来为它们分配空间。我们必须记住,内存只能以字符\\单词(word)大小的倍数来寻址。在我们的例子中,一个字是4字节,或32位。所以我们的5字节缓冲区实际上需要8个字节(2个字)的内存,我们的10字节缓冲区将占用12字节(3个字)的内存。这就是为什么SP要减去20。考虑到这一点,当function()被调用时,我们的堆栈看起来像这样(每个空间代表一个字节):
bottom of top of
memory memory
buffer2 buffer1 sfp ret a b c
<----- [ ][ ][ ][ ][ ][ ][ ]
top of bottom of
stack stack
Buffer Overflows
缓冲区溢出是将超出其处理能力的数据填充到缓冲区的结果。如何利用这种经常发现的编程错误来执行任意代码?让我们来看另一个例子:
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++) large_string[i] = 'A';
function(large_string); }
这个程序有一个带有典型缓冲区溢出编码错误的函数。该函数使用strcpy()代替strncpy(),复制提供的字符串而不检查边界。
如果你运行这个程序会得到一个内存异常段。让我们看看当我们调用function时它的堆栈是什么样子的:
这是怎么回事?为什么会出现段违规(Segementation violation)?很简单。strcpy()将str (larger_string[])的内容复制到buffer[]中,直到在字符串上找到一个空字符。正如我们看到的,buffer[]要比 str小的更多.buffer[]大小有16bytes,我们正在使用256bytes的数据进行填充栈。这意味着堆栈中的缓冲区之后的所有250[240]字节都将被覆盖。这包括SFP,RET,甚至还有str博主注:str是指的指针指向堆栈!链机制,建议将直的这部分数据],我们给lie_str-ig填满字符“A”。“A”的十六进制字符是0x4fT1.意味着我们现在的返回地址是0x444370。这是超出运行内存之外的空间地址。这就是为什么当函数返回并尝试从该地址读取下一条指令时,你会得到Segmentation violation错误(段违规)[博主注:段错误或段违规(segmentation violation)是由于内存管理单元(负责支持虚拟内存的硬件)的异常所致,而该异常则通常是由于解除引用一个未初始化或非法值的指针引起的。]
参考文献地址:段违规Segementation violation
到这里可能读者无法理解段违规,首先要理解栈保存函数返回地址的机制,这里可以参考博主从前写的一篇文章:
二进制安全:一文讲通Stack and Function Call栈与函数调用原理应用
我们始终要谨记:
CALL完下条要入栈,被调函数要入EIP
ESP向上指,ESP进入EIP,ESP值增加
因此,缓冲区溢出允许我们更改一个函数的返回地址。这样,我们就可以改变程序的执行流程。让我们回到我们的第一个例子,回忆一下堆栈是什么样子:
这个程序的函数存在典型的栈溢出漏洞错误。这个函数提供了一个没有经过strncpy检查的字符串(string),取而代之的是使用strcpy ()。
如果你运行这个程序会达到一个栈错误(segmentation violation).
让我们看看当我们调用函数时,在栈中是什么样子的。
为什么会到这里呢?为什么会出现栈错误(segmentation violation).
很简单strcpy()会将str(large_string[])的内容压入buffer[]直到在字符串(string)中找到空字符(null character)。我们能发现buffer[]的长度比str.buffer[]大16bytes,我们尝试着将栈内填满256 bytes。这就意味着250[240]bytes的大小在栈中将会被溢出。这里面包含了SFP,RET,甚至*str的内容!我们可以将‘A’字符填入large_string中,A的hex编码是0x41。意味现在溢出后返回的值是0x41414141。
这个地址是属于运行内存外的空间。这就是为什么当函数返回的时候准备读取下一条命令的时候会出现栈错误(segmentation violation)
以上是关于Phrack安全杂志:详细分析堆栈溢出Smashing The Stack For Fun And Profit(Aleph One) 关于粉碎堆栈的秘密的主要内容,如果未能解决你的问题,请参考以下文章