目录
☆问题排查
☆错误解决
☆总结:
前言:
学习帖子: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。
下面是初始化后的结果如图: