网络渗透与防护之缓冲区溢出介绍

一、前言:

最近在找pwn的学习资料,发现在学校的安全知识库里面,有老师录制的几节缓冲区溢出视频,决定好好学习一下。

二、概念:

1、缓冲区:

缓冲区就是应用程序用来保存用户输入的数据、临时数据的内存空间

2、缓冲区溢出:

如果用户输入的数据长度超出了程序为其分配的内存空间,这些数据就会覆盖程序为其它数据分配的内存空间,形成缓冲区溢出。

3、利用:

人为利用一出来执行代码(shellcode),从而获得习题的控制权

例如:

通过制造缓冲区溢出使得程序运行一个用户shell,再通过shell执行其他命令,

如果该shell程序属于root(或者system)权限的话,攻击者可以对系统进行任意的操作。

三、程序在内存中的映像

 https://img2018.cnblogs.com/blog/1529609/201901/1529609-20190110014555032-1294589045.png

1、内存有三个部分,分别是堆栈段、数据段和文本(代码)段。

2、内存从上往下,从内存低地址到内存高地址,呈现递增的方向。 

3、在堆栈段中,具有三部分,自上往下分别是:堆、函数所使用的局部变量所需要的的缓冲区和栈。

4、堆的增长方向是自上而下,由内存低地址向内存高地址增长。

5、栈的增长方向是自下而上,由内存高地址向内存低地址增长。

6、缓冲区溢出的原理主要是利用了栈的溢出。

7、数据段有两部分,分为非初始化数据段和初始化数据段。

8、文本(代码)段分为代码段、系统DLL、PEB&TEB和内核数据代码。

四、栈:

1、概念:

栈是一块连续的内存空间。

2、特点:

1)先入后出:

数据首先压入栈的最后才弹出,以此类推。

2)增长方向:

栈增长方向和内存增长方向相反。内存自上而下,栈自下而上。

3)一个函数或一个线程有各自的一个栈,提供临时存放数据的区域。这个区域称为缓冲区。

3、操作:对栈的操作

1)使用POP指令将数据压入栈中

2)使用PUSH指令将数据从栈中弹出来。

4、指针:

1)CPU中有两个重要的指针,一个叫ESP,另一个叫EBP。

2)ESP寄存器指向栈顶。

3)EBP寄存器指向栈底。

5、栈中内容:缓冲区

1)函数的参数:

当调用某个函数的时候,会把该函数所需要的参数压入栈中。

2)函数的返回地址:

当函数结束的时候,返回到调用函数前要执行的下一条指令的地址。

3)EBP(栈底指针)的值:

4)一些通用寄存器(EDI,ESI,等)的值

5)当前正在指向函数的局部变量:

五、CPU中的三个重要寄存器:ESP、EBP、EIP

1、ESP:栈顶指针

随着数据入栈、出栈而发生变化。

2、EBP:基地址指针或栈底指针

用于标识栈中一个相对稳定的位置。

通过EBP可以方便地引用函数参数以及局部变量

3、EIP:指令寄存器

在将某个函数的栈帧压入栈时,其中就包含当前的EIP值,

即函数调用返回下一个执行语句的地址。

缓冲区溢出就是要将EIP的值改为shellcode的值。

六、函数的调用过程:

 

1、将参数压入栈。

2、保存指令寄存器中(EIP)的内容,作为返回地址。

3、放入堆栈当前的基地址寄存器(EBP)

4、把当前的栈指针(ESP)拷贝到基地址寄存器(EBP),作为新的基地址。

5、为本地变量留出一定的空间,把ESP减去适当的数值。

七、函数调用中栈的工作过程:

 

1、调用函数前,压入栈:

1)上级函数传给A函数的参数

2)返回地址EIP

3)当前的EBP

4)函数的局部变量(缓冲区)

2、调用函数后:

1)恢复EBP

2)恢复EIP

3)局部2变量不作处理

八、调用例子:

1、代码:

int AFunc(int i , int j)
{
    int m = 3;
    int n = 4;
    m = i;
    n = j;
    return 8;
}
int main()
{
    AFunc(5,6);
    return 0;
}

2、解释:

 (1)进入:

https://img2018.cnblogs.com/blog/1529609/201901/1529609-20190110191919445-2106317236.png

(2)退出:

https://img2018.cnblogs.com/blog/1529609/201901/1529609-20190110192123752-1475106228.png

(3)解释:

int AFunc(int i , int j)
{
    int m = 3;
    int n = 4;
    m = i;
    n = j;
    return 8;
}
int main()
{
    AFunc(5,6);
    return 0;
}

3、利用:

(1)利用思路:

 https://img2018.cnblogs.com/blog/1529609/201901/1529609-20190110192152187-1540843873.png

覆盖EIP值(函数被调用后恢复到main()函数的下一条的指令) 

(2)shellcode:

 https://img2018.cnblogs.com/blog/1529609/201901/1529609-20190110192246097-2029619021.png

 功能:安装木马,提示权限,添加用户和组,开启远程shell,下载。

#include <stdio.h>
#include <string.h>
#define PASS_WORD "1234567"

int verify_password(char * password)
{
	int authentitated;
	char buffer[8];
	authentitated = strcmp(password,PASS_WORD);
	strcpy(buffer,password);
	return authentitated;
}

int main()
{
	int valid_flag = 0;
	char password[1024] = {0};
	while (1)
	{
		printf("please input password:");
		scanf("%s",password);
		valid_flag = verify_password(password);
		if(valid_flag)
		{
			printf("incorrect password!\r\n");
		}
		else
		{
			printf("Congratulation ! you have passed the verification !\r\n");
		}
	}
	return 0;
}

1)main函数输入password,调用verify_password函数验证输入的password是否等于1234567,相等返回0,否则返回1。

2)strcpy函数存在漏洞和buffer[8],构造利用条件。

3)利用缓冲区溢出,修改值,返回0,完成验证。

思路:在verify_password栈帧中

-----------------
| buffer        |
-----------------
| authenticated |
-----------------
| EBP           |
-----------------

authenticated位于buffer[8]的下方。

authenticated是int型,在内存中占4个字节。

buffer[8]占8个字节。

控制buffer[]填满8个字节,然后越界1个字节,缓冲区溢出,使得原authenticated的1覆盖为0,返回通过。

6、例子C:修改函数返回地址

控制buffer[8]越界,覆盖其他值,修改eip。