栈溢出原理与实现

栈结构:

《汇编原理-王爽》

1.方向:高地址(栈底)->低地址(栈顶)
2.加载完成pe后,系统会为这个pe分配一个栈,这个栈用于 实现(类似C)高级语言中函数的调用。
3.(2)所说的分配的栈是系统自动维护,并且push pop等平衡细节都是透明的。一般来说只有在使用汇编代码的时候,心会和它直接打交道。

函数调用与栈

#include<stdio.h>

void fuc_B()
{
	printf("This is Fuc_B !\n");
}
void fuc_A()
{
	fuc_B();
	printf("This is Fuc_A !\n");
}
int main()
{
	fuc_A();
	printf("This is Fuc_main !\n");
}

上面这段代码,在经过编译器编译后,各个函数对应的机器指令在代码区可能是这样分布的

在这里插入图片描述
可以看到,他们并不是连续的,根据操作系统的不同,编译器和编译选项的不同,同一文件不同函数的代码在内存代码区的分布可能相邻,也可能相离甚远,可能先后有序,也可能无序;但他们都在同一个PE文件中的"节",我们可以简单把他们在内存代码区中的分布位置理解成散乱无关的。

问题: 那散乱无关的代码区段,在我们的代码中又没有直接说明跳转到哪儿,那他是怎么执行main的时候,跳转到fuc_A去,而又在执行fuc_A的时候又去跳转到fuc_B去执行的呢?

简单用OD跟了一下,了解一下这个过程

(1)进函数之前

我们要注意两点

一个是函数的下一跳地址:00d81233
一个是当前主函数所在函数堆的ebp:002DFE34

在这里插入图片描述

(2)进函数之后:

可以看到只要我们进了函数,系统会自动帮我们把下一跳指令的地址压进栈

在这里插入图片描述
流程:

首先压栈下一跳指令地址,
然后再压栈上层函数的ebp(保存),
把当前的esp赋值给ebp(也就是重新给该函数重新开辟一个栈区)。

效果:

  
    // 当前函数栈空间
    //
    -----esp==ebp(当前)     
    //然后把这个esp作为当前函数栈空间的ebp
    
    局部变量
    异常处理代码入口地址 //如果函数设置了异常处理
    安全cookie         //如果编译器加GS选项
    ebp(上层)
    下一跳指令地址
    调用参数
    
    -----ebp          //上层函数的ebp(上层)

(3)函数结束,要返回原先位置

步骤:

//保存返回值到eax,可以作为第0步。
1.add esp //clear self stack 
2.pop ebp //ebp=ebp(上层)
3.retn== pop eip  jmp eip //jump has saved eip

.汇编的retn指令

//不恢复CS寄存器
pop eip
jmp eip

//相对的是retf 恢复cs
pop eip
pop cs
jmp eip

总结图:

《0Day安全》-王清主编

实践1(利用溢出原理修改邻接变量)

直接上代码:

#include<stdio.h>
#include<windows.h>

char *PASSWORD = "1234567";
//验证密码
BOOL v_password(char *password)
{
	BOOL authenticated=false;//注意定义的顺序
	char buffer[8]; //注意定义的顺序
	authenticated = strcmp(password, PASSWORD);

	strcpy(buffer,password);
	return authenticated;

}
int main()
{
	BOOL Flag = false;
	char password[1024];

	while (1)
	{
		printf("please input password \n");
		scanf("%s",password);
		if (v_password(password))
		{
			printf("foolish boy !\n");
		}
		else
			printf("clever boy !\n");

	}
}
//注意:strcpy和scanf在较高版本编译器不允许使用,可自行找办法解决。

代码原理

1.输入密码。
2.判断密码得到authenticated。
3.通过strcpy大于password[8]的数,修改栈中的authenticated,使得返回值为真。

上述代码在栈区的存储方式

在这里插入图片描述

当然这只是个简略的概图。
本次实验中,vs2008编译器会对在栈区的局部变量存储进行优化,会在authenticated和buffer之间放入8字节的安全区,防止栈溢出。(不管设置对齐系数是多少,变量之间插入的安全区大小都是8)。

红色为优化的安全区,绿色为变量内存

在这里插入图片描述

这也就是为什么需要注意定义顺序的原因,由于栈的方向以及先定义先push等原因,只能由定义在authenticated后面的变量溢出,然后去填充authenticated的值。

该实验环境要求:

选项推荐使用的环境备注
操作系统windws XP SP2其他Win32操作系统也可以进行本实验
编译器VisuaL Studio2008如使用其他编译器,需要重新调试
编译选项默认编译选项需要关掉GS编译选项
Build版本debug版本如使用release版本,则需要重新调试

点击简单了解GS选项?
通过了解了GS原理以及所处的位置,其实发现GS并不影响我们本次实验。

从上面代码可以看出:

当strcmp的返回值:authenticated为0时,说明密码正确。

假如我们输入密码88888888(8个8)password的值就是“88888888\0"

这时这个\0就会被填充到8字节的安全缓冲区的最低位上去。

在这里插入图片描述

当我们想通过填充authenticated为0时,就需要输入8888888888888888(十六个8,将安全区也填充),这时就能达到效果,尽管密码不对,但是authenticated的值也已经是0,程序逻辑觉得我们输入了正确密码

在这里插入图片描述
在这里插入图片描述
修改成功。

实践2(利用溢出原理修改函数返回地址)

上面的实验方法是很有用的,但是对代码环境的要求比较苛刻,我们需要一个通用的,强大的方法,那就瞄准栈帧最下方的EBP,和函数返回地址。同样的原理去填充

带有GS编译选项的栈内存
在这里插入图片描述

不带GS编译选项的栈内存

在这里插入图片描述

填充时注意:别忘了01000000(局部变量)与安全Cookie/EBP之间还有四字节的安全区。

上面我们也发现了个问题,从键盘输入的ASCII码有限(0x11,0x12等符号无法直接用键盘输入),所以我们把填充的值改为从文本获取。这样我们就可以把不能键盘注入的ASCII字符用十六进制编辑器写入文件。

代码:

#include<stdio.h>
#include<windows.h>

char *PASSWORD = "1234567";
//验证密码
BOOL v_password(char *password)
{
	BOOL authenticated=false;//注意定义的顺序
	char buffer[8]; //注意定义的顺序
	authenticated = strcmp(password, PASSWORD);
	strcpy(buffer,password);
	return authenticated;

}
int main()
{
	char password[100];
	FILE *fp;
	if(fp=fopen("password.txt","r+"))
	{
		fscanf(fp,"%s",password);
		if (v_password(password))
		{
			printf("foolish boy !\n");
		}
		else
			printf("clever boy !\n");
		fclose(fp);
	}
	
	

}

编译成PE(Debug模式)文件,用OD调试看看该怎么改EIP。

这里为了方便,设置了固定基址为0x400000。找到OD中的v_password函数。

在这里插入图片描述

下一跳指令地址 : 0x004328F5  
EBP : 0x0012FF34  
意图修改到该EIP: 0x0043290B

我们从代码中知道,执行完v_password函数就要判断返回值。

进入函数,我们只需要将txt中读取到password copy到buffer中去。
在这里插入图片描述

我们先尝试123456789看找的地方对不对:

在这里插入图片描述

发现123456789的ASCII已经进来
那我们现在只需要把0x004328F5改为0x43290B

接下来跟实践同样的原理,用8填充栈存储EIP上面的内存,用0x43290B填充存储EIP的位置

计算buffer到EBP存储位置所差字节数=36字节
我们用36刚好填充EIP存储位置,然后由于栈中高地址存储的是高位,所以修改最后四个8为倒序的新的EIP地址

在这里插入图片描述

虽然最后会报缓冲区被填充的错误:

在这里插入图片描述
只要忽略就好了,只是这次EBP仍然被填充了,函数返回会出错,但是没关系,仍能执行到我们想要的结果。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值