滴水三期:day08.1-C函数堆栈图

一、visual C++ 6.0

1.创建与关闭项目

  • 最好使用xp系统的虚拟机练习,现在很多win7及以上的系统会出现很多不兼容的问题,整的头大

  • 打开vc6–file–new–先选择一个win32 Console Application(控制台项目)的项目文件,是一个文件夹用来存放你写的所有c程序,起名,并且可以修改路径

    image-20211201190512102

    后面还有win32的项目,有弹框和按钮界面,这里控制台项目就是一个简单的cmd黑框框界面

  • 再选择创建一个hello world的应用环境,选择这个即c语言环境已经帮你搭建好了,对新手比较友好

    image-20211201190715135
  • 创建好后点击fileView,浏览文件信息,可以看到有一个Source Files的文件夹中有一些cpp后缀的文件,这些就是C++文件,我们学c语言就是为了后面C++做铺垫,可以直接使用C++的环境来写c程序,是没问题

    image-20211201191131345
  • 想关闭一个项目,就点击File–Close Workspace

  • 打开一个项目,点击File–Open–选择你刚才创建的项目文件夹–打开当中的一个dsw后缀的文件,命名域项目文件夹名相同

2.vc6的使用(快捷键)

  • F7:生成一个exe文件(建议每次修改完代码后就按一下F7,不断的新生成可执行文件去覆盖原来的)

    如果写好一个c语言代码,此时是无法运行的,只是一堆普通的代码。我们知道要想让一堆代码运行起来,就需要.exe后缀的可执行文件(这里先不要深究到底执行了哪些过程,最后才可执行的,后面学PE会详细讲解,这里先记住大的过程)

  • F5:用来执行此可执行文件

  • shift+F5:用来结束程序的运行

  • F9:用来设置断点

  • F10:单步执行

  • F11:单步进入到函数中执行

  • 打开寄存器窗口和内存窗口:先按F7生成可执行文件–再F9设置断点–F5运行可执行文件会在断点处停下,接着打开view–debug windows–选择你要查看的窗口

    • registers:寄存器窗口(快捷键ALT+5)
    • Memory:内存窗口(快捷键ALT+6)
    image-20211201193155700
  • 如何查看内存中某一个对象(变量、寄存器、通过寻址方式得到的地址中等)的值:打开内存窗口ALT+6,将你要查看的对象直接拖入到内存窗口中的某一个地址即可,此时这一行后面就会显示此对象在内存中的值,记得内存中存储的值是按字节为单位倒着存的,可以自己调整内存中一个地址代表多少字节的内存,我们可以调成我们最熟悉的堆栈的内存样子,每个地址隔4字节,即一个地址表示了四个内存地址中的数据,即4字节

    前提是程序已经执行过、使用过这个对象了,才能用内存窗口查看此对象在内存中的值

    如果是寄存器中的值、或者[ebx+0xc]这种寻址的地址中的值,则不需要等到此指令执行过去在寄存器窗口中查看,只需要在程序执行到此指令前,(根据情况决定我们什么时候查看可以得到对象在内存中的值)将对象拖入查看就可以知道

    屏幕截图 2021-12-01 194340 image-20211201201059429

  • 如何进入反汇编:F7生成可执行文件–F9设置断点–F5执行到断点处–空白处右键–Go To Disassembly即可以进入反汇编界面,在当中可以查看C语言对应的汇编语言

    记住一定要先将程序执行起来,才能进入反汇编

  • 查看汇编指令的二进制硬编码(软件中用十六进制显式):进入反汇编–右键–勾选Code Bytes

    汇编中的指令都是通过二进制硬编码翻译过来的

二、函数格式

1.函数名命名规范

  • 函数名、参数名、变量名的命名规则:只能包含字母、数字和_ ,且不能以数字开头

  • 不能使用以下关键字做为函数名:

    auto break case char const continue default do double else enum extern float for
    goto if int long register return short signed sizeof staic struct switch typedef
    union unsigned void volatile while
    

2.函数格式

1)无参数无返回值
  • 光是一个函数,是无法让程序执行的,程序的入口为int main(…){…},所以函数定义写好后,要将函数在程序入口中调用才行

  • 函数定义及调用如下:

    void Function(){	  //函数的定义
    	//代码 每行以;结尾
    }
    
    //程序的入口
    int main(int argc,char* argv[]){
        Function();        //函数的调用,对应call汇编指令
        return 0;
    }
    

    所谓的函数名在计算机底层不过就是一串地址值;如果一个函数没有参数,没有返回值,那么看到调用函数的代码的反汇编中,只有单独的一个call指令,没有push和add等指令,即没有将参数入栈以及平衡堆栈的操作

2)有参数无返回值
  • 函数定义格式如下:

    void 函数名(参数类型 参数名,参数类型 参数名....)	
    {	
    	//代码 每行以;结尾
    }
    
3)无参数有返回值
  • 函数定义格式如下:

    返回值类型 函数名()		
    {		
    	//代码 每行以;结尾
        
    	return 具体的值,必须与返回值类型匹配;	
    }		
    
4)有参数有返回值
  • 函数定义格式如下:

    返回值类型  函数名(参数类型 参数名,参数类型 参数名....)	
    {	
    	//代码 每行以;结尾
    	
    	return 具体的值,必须与返回值类型匹配;
    }	
    

三、使用反汇编分析代码

0.逆向分析小技巧

  • 以前我们是一行一行的汇编指令去分析堆栈的变化,现在熟练了可以一段一段的看,知道哪几行指令在干什么事情即可,然后如果分析时遇到跳转特别多的情况,建议先不要进入call指令中分析,先分析返回后下面的指令是在干什么,先了解整体的流程,大概猜测这个函数要干什么,然后再进如call中分析。
  • 比如我们可以先关注call指令后面有没有eax相关的操作,然后去看看此时eax中存的值和最开始入栈的参数有什么关系,可以大概推断出函数用了参数做了哪些运算,因为一般底层会将函数的返回值存入到eax中。
  • 但是如果call后面几个指令操作的是某块内存,那么就要跟进去看看
  • [ebp+8]开始往下一般就是存储的函数参数,[ebp-4]开始往上一般就是函数中定义的局部变量,[ebp+4]一般存储的都是函数的返回地址
  • add… cmp… call…:如果看到这几行指令和自己写的函数没什么关系,我们前面已经学过,这个就是检查堆栈是否平衡用的,是编译器自己调用的

1.无参无返回值

  • C语言代码如下:

    void func1(){  //函数定义
    	
    }
    
    int main(int argc,char* argv[]){
        func1();   //函数调用,在此处设置断点,然后F5执行到这里停下,进入反汇编开始分析
        return 0;
    }
    
  • 反汇编分析:

    image-20211202105632914

2.有参数无返回值

  • C语言代码如下:

    void func2(int x,int y){
    	x + y;
    }
    
    int main(int argc, char* argv[]) //程序的入口
    {
    	func2(1,2);
    	return 0;
    }
    
  • 反汇编分析:

    image-20211202111742759

3.无参数有返回值

  • 函数代码如下

    int func3(){
    	return 2+3;
    }
    
    int main(int argc, char* argv[]) //程序的入口
    {
    	func3();
    	return 0;
    }
    
  • 反汇编分析:

    image-20211202112405841

4.有参数有返回值

  • 函数代码如下:

    int func4(int x,int y){
    	return x + y;
    }
    
    int main(int argc, char* argv[]) //程序的入口
    {
    	func4(4,6);
    	return 0;
    }
    
  • 反汇编分析:

    image-20211202113359463

四、作业

1.编写一个函数能够对任意2个整数实现加法,并分析函数的反汇编

  • C语言代码如下

    int Plus1(int x,int y){
    	return x + y;
    }
    
    int main(int argc, char* argv[]) //程序的入口
    {
    	Plus1(2,3);
    	return 0;
    }	
    
  • 反汇编分析:

    image-20211202114740296

2.有三个参数及返回值并嵌套

  • 要求要用局部变量来存储中间结果,且用到函数嵌套

    int Plus2(int a,int b){  //注意:如果Plus3函数中要调用Plus2函数,必须要把Plus2写到Plus3上面
    	return a + b;
    }
    
    int Plus3(int a,int b,int c){
    	int x;     //局部变量
    	int y;
        x = Plus2(a,b);    //函数中调用其他的函数,将结果返回到变量中存储起来
        y = Plus2(x,c);
        return y;
    }
    
    int main(int argc, char* argv[]) 
    {
    	Plus3(12,45,23);   //函数调用,有返回值也可以不接,汇编中是用eax来存储返回值
    	return 0;
    }
    
  • 反汇编分析:(详见连接当中的作业二:https://edimadezhou-typora.oss-cn-chengdu.aliyuncs.com/%E5%8F%8D%E6%B1%87%E7%BC%96%E5%88%86%E6%9E%90.xlsx)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K6v222jF-1677256093537)(https://edimadezhou-typora.oss-cn-chengdu.aliyuncs.com/QQ%E5%9B%BE%E7%89%8720230110161515.png)]

  • 总结小技巧:如果一个函数执行完后有一条mov 内存,eax的指令,多半是c代码中要把函数的返回值赋给一个变量;如果一个函数中有mov eax,内存中的值这条指令,多半是此函数要将此结果作为返回值返回

3.编写一个函数,能够实现对任意5个整数实现加法(使用Plus1和Plus2)

  • C语言代码如下:

    int Plus1(int a,int b){
    	return a + b;
    }
    
    int Plus2(int a,int b,int c){
    	int x;     //局部变量
    	int y;
        x = Plus1(a,b);    //函数中调用其他的函数,将结果返回到变量中存储起来
        y = Plus1(x,c);
        return y;
    }
    
    int Plus3(int a,int b,int c,int d,int e)				
    {	
    	int x;
    	int y;
    	int z;
    	x = Plus1(a,b);
    	y = Plus1(x,c);
    	z = Plus2(y,d,e);
    	return z;
    }	
    
    void main(int argc, char* argv[]) 
    {
    	Plus3(2,4,6,8,10);
    }
    
  • 反汇编分析如下:(如果觉得不清晰,详见连接当中的作业三:https://edimadezhou-typora.oss-cn-chengdu.aliyuncs.com/%E5%8F%8D%E6%B1%87%E7%BC%96%E5%88%86%E6%9E%90.xlsx)

    3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值