【汇编 C】变量与参数的内存布局、函数的返回值以及浅谈黑客基础攻击方式

17 篇文章 1 订阅

目录

前言

变量与参数

        什么是变量?

        全局变量与局部变量

        什么是参数?

        形参与实参

代码准备

变量以及参数的内存布局

        堆栈图总结

        黑客常用攻击方式-缓冲区溢出

函数的返回值

        代码修改

        注意

总结

结语


前言

        首先看本篇文章,最好了解一下程程序运行的堆栈图是什么样的?怎么去画?我的这篇文章有讲过《堆栈图》

变量与参数

        什么是变量?

        变量就是用来存放值的一块内存,变量名就是存放数据地址的别名,这句话是一定要记住的,我们接下来讲的东西会涉及到这一点。

        变量分为全局变量和局部变量。

        全局变量与局部变量

        相同点:他们都是用来存储数据的一块内存地址。

        不同点:

        全局变量定义在函数外面,默认会初始化为0,并且如果只编译一次,那么无论任何时候启动这个exe文件的时候,全局变量的地址都不会改变,并且是独一无二的,除非重新编译。

        局部变量则是定义在函数内部,不初始化局部变量很有可能会有危险(以后会讲),尽管你只编译一次,你每次运行exe文件的时候,局部变量的地址还是会改变。

        什么是参数?

        参数就是用来传递值的变量,本质上也就是个变量,主要是用来给函数传值的。

        参数分为形式参数和实际参数

        形参与实参

         形式参数和实际参数几乎没有什么相同点,非要说一个,那就是他们都属于变量。形参就是定义函数的时候括号里的参数,他就是用来占位置的,主要目的就是告诉别人,你想用这个函数你就要给它几个参数,几个形参就对应应该传递几个参数(抛开缺省问题)。实际参数就是你调用这个函数的时候,你给他的值,这个值才会被函数内部使用。

代码准备

        了解了变量与参数的基本概念后,我们就需要画堆栈图了,然后才能去分析变量与参数的内存布局。对于全局变量的内存布局,下面就不需要讲了,因为我们只需要知道全局变量的内存是独一无二的,只要不重新编译,内存地址就不会改变就行了。

        下面准备画堆栈图用到的代码,并调到反汇编(设置断点,运行,ALT+8)

int plus(int x, int y)				// 定义一个函数,返回类型int,函数名plus,形参x、y
{
	int z=x+y;						// 定义局部变量接收x+y的最终值
	return z;						// 返回局部变量的值
}

void main()							// 程序入口
{
    __asm mov eax,eax;              // 一行没用的汇编指令,用来设置断点
	plus(1,2);						// 调用函数
	return ;						// 返回
}

        另外说一下,我们上面返回了局部变量的值,这是没有错的。虽然局部变量在函数结束的时候就回收了内存(至于为什么以后细说),但是这只能说明,我们不能返回局部变量的地址,因为函数结束后地址已经不是我们的了。但是我们可以返回局部变量的值。

变量以及参数的内存布局

        我们首先来绘制堆栈图,在__asm处设置断点,F7编译,F5调试,ALT+8调出反汇编窗口如下:

        别忘了把内存和寄存器的窗口调出来,之前有讲过

        分析代码:执行call指令一定要用F11

        它们对应的堆栈图:

        初始堆栈:一个框代表四个字节,但是ebp与esp中间差的有点多,这里省略了

        执行push 2之后:

        push 1:

         call        plus (1611CCh):

        函数内的代码

        这里的代码就不一行一行解释了,直接看执行代码后堆栈图的变化 

         push        ebp:

        mov         ebp,esp:

 

        sub         esp,0CCh:

        push        ebx:

        push        esi:

        push        edi:

        lea         edi,[ebp-0CCh]:(将edi中存放刚提升堆栈时esp的值)

         mov         ecx,33h:(将ecx中存放十六进制的33,ecx中的值就是rep指令循环的次数)

        mov         eax,0CCCCCCCCh:(将eax中存放0xCC,0xCC就是断点的值)

        rep stos    dword ptr es:[edi]:(重复ecx中的值的次数,将eax的值赋值给edi指向的地址。通过上面这几行代码,堆栈的变化如下)

        蓝色的区域就是我们俗称的缓冲区

        下面几行代码,由于vs2010优化的原因,我们没有办法看到x,y,z对应的地址信息了,所以理解起来会有点困难,我整理了VC6中与下面几行对应的汇编代码,按照这下面的进行分析 

 

        代码都是一样的,不用担心。

        mov eax,dword ptr [ebp+8]:

        可以看到这里是取出第一个参数放在eax里

         add eax,dword ptr [ebp+0Ch]:

        取得第二个参数,并于eax中的1相加再存入eax中

 

         此时的eax里存放的值是3,他是x和y相加的返回值,我们知道eax就是用来存放返回值的,所以下面这行代码的意思就是,将x+y的返回值赋值给z,ebp-4就是z对应的地址

        mov dword ptr [ebp-4],eax:

        

        下面的代码就先不分析了。

        堆栈图总结

        通过上面的代码,我们大致可以把堆栈图进行划分一下了,如下:

        由堆栈图可知:

        ebp中存放原来ebp的值

        ebp+4存放的是返回地址 

        ebp+8开始往下存放的是参数,有几个参数就占几个格子

        ebp-4往上蓝色的区域就是缓冲区,也就是局部变量存放的地址

        以上便是参数与变量的内存布局

        这基本上就是固定的了,当然不同的编译器处理方式不一样,可能他会把局部变量往上放一点,但是换汤不换药,局部变量最终还是在缓冲区的。

        所以上面对应的东西一定要记住,就比如ebp+4返回地址,这就是一个很重要的东西,在这里提一下缓冲区溢出,现阶段不讨论这些知识,只是简单的提一下

        黑客常用攻击方式-缓冲区溢出

        如果你的缓冲区处理不当,那么黑客就可以往你的缓冲区里使劲塞东西,一旦缓冲区用完了,就会接着往下用,直到使用到了ebp+4这块内存。我们说过,这块内存是存放函数返回地址的,如果这个时候黑客把ebp+4的值填充成了他想要返回的地址了,这个时候我们调用完函数就会返回到他想让你去的地方,这就是缓冲区溢出。

        所以我们一定要记住ebp-4、ebp+4、ebp+8等地址存放的是什么东西,这是成为高手的基础。

        下面把函数的返回值也一起讲了吧。

函数的返回值

        先看图

        关于int z = x + y;对应的汇编,我们已经讲过了,就差return z的了。

        在C语言中,我们如果要返回一个值,一般都是return + 需要返回的值,但是在汇编中,我们一般都把需要返回的值存放在eax中。 

        mov eax,dword ptr [ebp-4]:

        ebp-4大家还有印象吧,他就是局部变量的地址,那么dword ptr [ebp-4]就是局部变量的值,所以这里是将局部变量的值存入eax里,从这里就能确定了,eax基本上就是用来存放返回值的。

        我们执行一下,看会发生什么

        没有变化,因为我们上面把eax的值赋值给了变量,这里返回的时候又把变量的值赋值给了eax所以肯定没有变化啊。

        我们一直执行直到函数返回 

        我们观察发现,貌似从把局部变量的值放在eax之后,直达main函数结束,这之间的十几行汇编指令貌似也没有用到eax啊。那肯定啊,因为你主函数都没有使用到这个值,怎么可能会看到eax的值呢? 

        代码修改

        我们简单的修改一下代码,接收eax的值(函数的返回值)就行了

int plus(int x, int y)				// 定义一个函数,返回类型int,函数名plus,形参x、y
{
	int z=x+y;						// 定义局部变量接收x+y的最终值
	return z;						// 返回局部变量的值
}

void main()							// 程序入口
{
    __asm mov eax,eax;              // 一行没用的汇编指令,用来设置断点
    int ret;                        // 定义接收eax值的局部变量
    ret = plus(1,2);				// 调用函数并接收返回值
	return ;						// 返回
}

        我们继续F7编译,F5调试,ALT+8反汇编,执行到刚刚的地方(这里在call指令的地方可以直接F10,代表跳过函数内部,也就是单步步过,之前文章说过《Fake F8》 ,这里的F11就相当于F7,F10就相当于F8)

        直到下面这一步:

        我用的vs2010还是不显示ret局部变量的地址。我们看看VC6里的这行代码是怎么写的

        在vc6中就不会以ret的形式去写,而是直接写了ret对应的地址 

        回想,ebp-4是什么?

        局部变量的地址

        这就可以确定了,我们的eax在执行这一步的时候,存放的是函数plus的返回值。那么我们将eax中的值又给了ebp-4这个地址,ebp-4就是局部变量的地址。那么这一步汇编指令的意思就是:将plus函数的返回值赋值给ret这个局部变量。

        注意

        注意!!!main函数也是个普通的函数,所以这里的ebp-4指的是main函数的缓冲区,不是plus函数的缓冲区。如果从main函数开始的地方就查看反汇编,就可以知道,main函数和普通函数一样都会提升堆栈设置缓冲区。所以通过上面可以知道,main函数也是把局部变量存放在缓冲区里的。

总结

        通过上面的文章我们知道了,什么是参数什么是变量,以及局部变量是存放在缓冲区中的,这都是必须要记住的,还有ebp-4,ebp+4,ebp+8分别对应的是什么,这些东西都要刻在脑子里的。最后留一个问题:请说一下为什么局部变量必须要赋初始值?不要去往概念方面想多画堆栈图思考为什么。好了,本篇文章到此结束。

结语

         本文章如果有讲错的地方,请大佬一定指出,讲的听不懂的地方也可以跟我说,之后我们更改,尽量更加通俗易懂,谢谢大家。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值