【逆向】栈溢出漏洞学习过程(一)通过字符串验证的简单程序分析

本文详细介绍了栈溢出漏洞的学习过程,包括程序的准备工作、分析程序执行流程、栈溢出原理的探讨,以及利用栈溢出进行攻击的思考。在分析过程中,讲解了主函数中的寄存器知识、涉及的汇编指令,并通过OllyDbg进行动态分析。在思考利用方式时,提出了通过修改字符串长度、覆盖返回地址到特定函数等方式实现漏洞利用。最终通过实践成功触发了漏洞,实现了程序控制流的改变。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

前言:

1 准备程序

PS:翻车现场:

2 分析程序执行流程(栈溢出原理)

2.1 看代码中忘记的知识补充一下:

2.1.1 主函数中涉及的寄存器相关知识:

2.1.2 涉及到的指令

地址传送指令LEA

PTR

REP STOS 

int 3中断

段超越指令前缀

2.2 main函数中的初始化部分

2.3 scanf()函数部分

2.4 函数verify_password

2.5 回到主函数

2.6 回顾过程

3 思考利用方式

栈的存储结构     

字符数组的定义和使用

构造利用栈溢出

4 我的想法有:

4.1 修改字符串

4.2 接着向下溢出

4.2.1 首先我们要找到输出success的地址

4.2.2 准备十六进制编辑工具

4.2.3 检验结果

4.3 插入shellcode,也是覆盖EIP

4.3.1 编写shellcode

4.3.2 MessageBox函数 

MessageBox函数的调用方式

MessageBox函数的位置

完成整个调用汇编代码的编写

确定修改之后的程序的栈空间走向和EIP位置(即如何覆盖)

出现错误:地址不可读

问题排查

错误解决

总结:


 

 


前言:

学习帖子:https://blog.youkuaiyun.com/Breeze_CAT/article/details/89788864

大致流程是跟着这篇帖子走下去的,还是比较适合萌新(自己)的。

过程中有些地方看不懂需要延伸的和自己的理解,写在了下面。因为自己真的是个小白,好多不明白的东西,╮(╯▽╰)╭ 。

1.准备程序

首先打开VC6.0或者其他编辑器codeblocks等,将如下代码编译成exe文件

#include<stdio.h>
#include<string.h>
#include<stdlib.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];
	scanf("%s",password); //输入密码
	valid_flag=verify_password(password);
	if(valid_flag) //返回0代表正确,返回1代表错误
	{
		printf("incorrect password!\n");
	}
	else
	{
		printf("success\n");
	}
	//getchar();//暂停一下
	return 0;
}

一开始我选择使用的是codeblocks,运行程序结果如图:

通过观察代码可以知道,输入的数据是和定义的宏“1234567”对比的,显然当输入1234566时如下图,返回不正确的提示。

那么,当输入1234567时,可以看到程序返回了正确提示:

 

文章里讲到使用Debug编译器,大概指用debug模式下生成exe文件吧。

用OllyDbg打开刚刚编译好的exe程序如图:

接下来就是寻找主函数的入口了 ,也许是初次接触,我还是觉得这像是一门玄学。

F8(我的F8需要和Fn一起使用,比较麻烦,网上看到有教程,在开机时进入BIOS模式然后进行修改云云,我还是老老实实先点击界面的按钮吧)

==================================

PS:翻车现场:

在我使用了codeblocks编译生成的exe后,od打开程序,代码顺序流程和在VC6.0即博客中的完全不一样,找不到主函数了,

o(╥﹏╥)o,这是什么原因,

逼得我只按F7。

按了好几个轮回,都卡在直接输入了,还没看到main函数的影子然后就提示输入了,完了输入后 直接就返回结果结束了???什么情况,是我没有注意到main函数跳过去了吗?

==================================

2.分析程序执行流程(栈溢出原理)

所以现在我又老老实实选择了VC6.0编译运行出exe,跟着博客亦步亦趋的来吧!

果然换了VC编译器之后,就和博客中的一样了,

一直按F8(根据总结的经验看到GetCommandLineA后开始小心,然后看到有连续push的地方下面的call估计就是主函数了,然后F7跟进去看看是不是的。)

至于为什么codeblocks编译出来的exe程序用OD打开来和vc的不一样,现在自己还是个小白,先跟着博客正常的来一遍,然后再回过头讨论这个问题!

F8(也许碰到长的像的也要F7进去看一圈)点点点 ,也总算是找到了入口点了吧···

现在F7进入看看吧,这就是主函数的代码了,可以看到基本的输入输出函数在右边的注释中,在代码段中也可以看到call了几个函数:

老规矩,按照汇编代码画出栈图,自己分析下:

 顺便一提,改了个字体大小:

 

好了,现在开始看。

=============================================================

2.1.看代码中忘记的知识补充一下:

2.1.1.主函数中涉及的寄存器相关知识:

EDI 目的变址寄存器(Destination Index Register):用于指向字符串或数组的操作数。

ESI 源变址寄存器(Source Index Register):用于指向字符串或数组的源操作数。

EBX 基址寄存器(Base Address Register):用来存放存储器地址,以方便指向变量或数组中的元素。

2.1.2.涉及到的指令

地址传送指令LEA

存储器操作数具有地址属性,利用地址传送指令可以获取其地址。其中,最常用的是获取有效地址指令LEA(Load Effective Address),格式如下:

LEA r16/r32,mem   ;r16/r32=mem 的有效地址EA(不需要类型一致)

LEA将存储器操作数的有效地址(段内偏移地址)传送至16位或32位通用寄存器中,它的作用等同于汇编程序MASM的地址操作符OFFSET。但是,LEA指令是在指令执行时计算偏移地址,而OFFSET是在汇编阶段取得变量的偏移地址,后者执行速度更快。

(对于这句话的理解:比如编写汇编代码时,定数数据变量在.data部分声明了一个dvar dword 41424344h,那么在下面的代码部分两种方式都可以来获取该变量的地址,即在汇编阶段就取得的地址。而如果这个变量没有声明,在运行之前不知道这个地址,那么就只能选择LEA指令。

另外,LEA指令获取的是偏移地址,并没有读取变量内容。

不过,对于在汇编阶段无法确定的偏移地址,就只能用LEA指令获取了。

那么对于下面这条指令的意思即是

0040109C          |. 8DBD B8FBFFFF       LEA EDI,DWORD PTR SS:[EBP-448]

将EBP-448处的有效地址赋值给EDI  ---目的变址寄存器(用于指向字符串或数组的操作数)

至于其中的DWORD是双字类型,

PTR

PTR:类型转换操作符PTR是用来更改变量名的类型。

那么再来看这句话的意思就是:

将 [EBP-448]处的地址 以DWORD双字类型 存储在32位目的变址寄存器EDI中,让EDI指向[EBP-448]处(即栈中变量存储的区域)。

接着又看到一个陌生的指令:

REP STOS 

004010AC          |. F3:AB               REP STOS DWORD PTR ES:[EDI]

 网上查阅资料:参考博客:https://www.cnblogs.com/yuqiao-ray-vision/p/3754856.html

该指令是:

rep指令的目的是重复其上面的指令ECX的值是重复的次数.


STOS指令的作用是将eax中的值拷贝到ES:EDI指向的地址.

(如果设置了direction flag, 那么edi会在该指令执行后减小, 
如果没有设置direction flag, 那么edi的值会增加)

 那么分析一下这行命令的意思就是:

REP  :  重复0x112(十进制274)次,即274*4=1096个字节(一个EAX寄存器存储的是32位4个字节)

STOS:将EAX中的CCCCCCCC(8个C)赋值给EDI指向的地址,EDI的值增加,指向高地址,向下继续填充274次,将这些栈中的空间都赋值为CCCCCCCC。

int 3中断

值得一提的是0xCC是int 3中断,网上看了一些论坛 大多都是2009年的回答,可能那个时候就有了吧。如下:

网址:https://bbs.pediy.com/thread-103395.htm

就是汇编中的一个中断指令,原来的DOS就是一种单任务系统,如果执行一个任务而要想执行另外一个任务就需要中断当前执行的任务所以就有了中断指令!

 int 3是引起中断的指令
在内存中的显示就刚好是CC  你看到的那么多int 3就是某一连续填充CC的空间  理论上这些地方是不会访问到的  一旦访问到了就是程序逻辑错误 所以填充CC(int 3) 被访问则产生告警

int 3断点,程序运行到这里就会触发调试器 

大概理解了何为int 3。希望以后遇到具体事例的时候再加以分析。 

 

段超越指令前缀

只能跟随在具有存储器操作数的指令之前的指令。如:

SS:[EBP-448]

CS是代码段寄存器          CS:  读取指令                    偏移地址:EIP 指令指针寄存器
SS是堆栈段寄存器          SS:  堆栈操作                    偏移地址:ESP 堆栈指针寄存器
DS是数据段寄存器          ES:  一般的数据访问         偏移地址:有效地址EA

============================================================= 

好了,预备知识回顾完了,开始分析: 

2.2 main函数中的初始化部分

下面是对应OD中给scanf()函数push参数之前(用红色框圈住的地方),

代码的分析,自己画的图,(图一),加深下理解 (不是很好看,一会下面的函数尽量画的再整齐些。)

(在博客中看到其实可以从OD界面右下角观察栈的存储情况,不用像我这么麻烦,不过考虑到自己太小白,还是手工先理思路流程吧。)

                                                                                                (图一) 

  •    中间的一段初始化代码为初始化申请的空间为0xcc,可以看到从[EBP-448]处开始向下循环赋值1096个字节正好对应(图一)中粉红字体画出来的空白处1096个字节
  • 其中valid_flag(即SS:[EBP-4],第一个变量,对应源程序主函数中的valid_flag变量)初始化为0。  

 下面是初始化后的结果如图:

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值