X86 栈溢出原理与实现

前言

本篇旨在通过简单的例子来学习32位栈利用,随手散记。

一、函数栈帧

        每一个函数独占自己的栈帧空间。当前正在运行的函数栈帧总是在栈顶。Win32 系统提供两个特殊的寄存器用于表示位于系统栈顶端的栈帧:ESP(栈指针寄存器)和EBP(基址指针寄存器)。

       在函数栈帧中,一般包括以下几类重要信息:

        (1)局部变量:为函数局部变量开辟的内存空间。

        (2)栈帧状态值:保存前栈帧底部,用于在本栈帧被弹出后恢复上一个栈帧。

        (3)函数返回地址:保存函数调用前的指令位置。

                 ---------------- 0x00001234                                               
      /------- |      ebp      |             
      |          ----------------                                                                                                 
      |          |  ret_addr  |             
      |          ----------------                                                 
      |          |     data     |             
      |          ----------------        
      |          |     data     |             
      |          ----------------                                                    
      \------>|      ebp      |-----\          
                 ----------------       |                                                                                             
                 |  ret_addr  |       |         
                 ----------------       |                                              
                 |     data     |       |        
                 ----------------       | 
                 |     data     |       |        
                 ----------------       |                                               
                 |     ebp      |<----/        
                 ----------------                                                                                                  
                 |  ret_addr  |             
                 ---------------- 0x0000125c

函数调用时的系统栈结构如上所示。

二、修改邻接变量

1.原理

        如上,我们知道了函数调用时栈中数据分布情况。函数的局部变量在栈中一个挨一个排列(上图中两data部分)。如果这些局部变量中有数组之类的缓冲区,并且程序中存在数组越界的缺陷,那么越界的数组元素就有可能破坏栈中相邻变量的值,甚至破坏战阵中的保存的EBP值、返回地址等重要数据。利用简单的实验验证上述可能。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define PASSWORD "1234567"

int verify_password(char* password)
{
	int authenticated;
	char buffer[8];
	authenticated = strcmp(password, PASSWORD);  
	strcpy(buffer, password);
	return authenticated;
}
int main()
{
	int valid_flag = 0;
	char password[1024];
	while (1)
	{
		printf("please input password:		");
		scanf("%s", password);
		valid_flag = verify_password(password);
		if (valid_flag)
		{
			printf("incorrect password!\n\n");
		}
		else
		{
			printf("Congratulation! You have passed the verificaction!\n");
			break;
		}
	}
	return 0;
}

实验环境:

        Win10 + VS2019 + IDA + x86dbg

代码分析:

        通过的strcmp比较输入的密码与设置的密码,若两者相同则 authenticated = 0,反之authenticated = 1 。

实验目的:

        通过修改临界变量来突破密码验证。为了达到这一目的,在子函数中在 authenticated 之后设置了数组buffer 。

2.测试

        假如输入7个"q",按照字符串的序关系 "qqqqqqq" > "1234567",strcmp应返回1,既 authenticated = 1。x86dbg动态调试的实际内存情况如下:

(1)主函数:

(2)步入子函数verify_password:

(3)strcmp执行结束:

        此时可以看出 "qqqqqqq" 和 "1234567" 经strcmp比较后为1,保存在eax中。

(4)authenticated变量在栈中被分配:

        注:0x004115FA处: mov dword ptr ss:[ebp-4], eax 

        此时eax中存放的既是 authenticated = strcmp(password, PASSWORD) 的值。

        栈中0x0019FA04既是authenticated在栈中的地址,也是我们要修改的邻接变量的地址。

(5)strcpy执行结束

        此时eax中存放的地址0x0019F9FC,在栈中可以观察到如上所示,即为变量char buffer[8];

        如此是否可以通过strcpy函数造成缓冲区溢出,使得缓冲区buffer的邻接变量authenticated被覆盖为0,从而突破密码验证?

        字符串数据最后都有作为结束的NULL(0),当输入8个"q"时,按上述分析,buffer所拥有的8个字节将全部被 'q' 填满,而结尾的NULL将写入内存0x0019FA04。        

三、修改返回地址

        如上可以掌握函数栈帧中变量的位置。那么通过输入超长的字符串,从而覆盖函数返回地址便是有可能的。既然返回地址是手动覆盖的,那么我们所给出的返回地址从键盘不方便键入,改为从txt文件中读取。更改代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <iostream>
#define PASSWORD "1234567"

int verify_password(char* password)
{
	int authenticated;
	char buffer[8];
	authenticated = strcmp(password, PASSWORD);
	strcpy(buffer, password);
	return authenticated;
}
int main()
{
	int valid_flag = 0;
	char password[1024];
	FILE* fp;
	if (!(fp = fopen("C:\\password.txt", "r+")))
	{
		printf("1");
		exit(0);
	}
	fscanf(fp, "%s", password);
	valid_flag = verify_password(password);
	if (valid_flag)
	{
		printf("incorrect password!\n");
	}
	else
	{
		printf("Congratulation! You have passed the verificaction!\n");
	}
	return 0;
}

        c盘下txt文件内容如下:

         在x32dbg中观察 verify_password 函数 strcpy执行后的栈帧情况:

         思路:

        (1)若以 "4321" 为一个单位,则第三个 "4321" 将覆盖掉 authenticated,第四个将覆盖掉前栈帧ebp,第五个则将覆盖掉返回地址;

        (2)返回地址的选择问题,既然我们已经可以控制函数的执行流程,不妨在函数返回时直接跳转到验证成功的分支。

        实验验证

        在x32dbg中可以观察到,0x0041184c处为将输出的验证成功字符串压栈,0x00411851处为调用printf函数。则txt文件中第五个单元即为 "0x0041184c"。

         运行程序结果如下:

        由于栈内 EBP 等被覆盖为无效地址,从而导致程序在退出时堆栈无法平衡,导致崩溃。但我们成功地覆盖了返回地址并控制了执行流程。

        结束

        本篇复现《0day安全》中的简单实验,也是希望加深自己的理解。下一篇讨论x64栈帧以及x64缓冲区溢出:https://blog.csdn.net/weixin_41487541/article/details/119960765

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值