汇编

汇编基础知识

堆栈图

调用函数实现两数相加

因为pop之后,有一部分的值还是之前保留下来的,所以要进行填充

LEA和MOV的区别

lea是“load effective address”的缩写,简单的说,lea指令可以用来将一个内存地址直接赋给目的操作数,例如:

lea eax,[ebx+8]就是将ebx+8这个值直接赋给eax,而不是把ebx+8处的内存地址里的数据赋给eax。

而mov指令则恰恰相反,例如:

mov eax,[ebx+8]则是把内存地址为ebx+8处的数据赋给eax。

函数在汇编的结构

函数调用前,堆栈结构如下

EBP栈底高位,ESP栈顶低位

下面的三个PUSH是把函数的参数压入栈中,这里是立即数,也可以是寄存器里面的值,注意PUSH指令执行完ESP-4,即栈顶往低位移动

堆栈图如下

下一步是CALL指令,F7单步步入,CALL指令会修改EIP的值,将CALL指令下一条指令的地址压入栈顶 ,并且修改EIP的值,相当于一条PUSH和JMP指令,JMP只会修改EIP的值,EIP存放下一条会执行指令的地址

堆栈图

这里JMP直接F8跳转过去,进入函数,入口是PUSH EBP

可以看到ESP没有变化

在函数调用中,这三步是提升栈顶,开辟空间

运行完堆栈应该如下

运行看看

接下来这三步是保存现场,因为在函数调用中,这些寄存器可能被用到,所以要先把里面原本的值压入栈中,此时堆栈图为

EBP是FED0

运行

已经被压入栈中

接下来这步是填充缓冲区

可以看到堆栈图中开辟的空间里面的值并不是为0,这是因为在调用完函数之后,这些空间的值没有被清除,所以需要填充

堆栈图

这里有一点需要说明,local.18是什么意思呢

看下图

我们去OD修改设置

取消掉就可以了,刚才的指令发生了变化

接下来解释这几句指令

lea是取地址,意思是将ebp-0x48的地址存入EDI中

ECX一般用来存放循环次数,这里是0x12,即18次,这个对应开辟空间的大小

mov eax,0xCCCCCCCC是填充缓冲区

最后一句rep表示重复次数,次数由ECX决定,stos dword这条指令表示将EAX内的值放入EDI指定的内存单元中,注意执行一次后EDI会移动4,加减由DF标志位决定

DF决定movs执行完后esi和edi的移动方向,当为0时,加,否则为减。

运行

看接下来的几行指令

1、将0x2赋给ebp地址的前一个内存单元,这里的0x2就是局部变量

2、将EBP+0xc的值赋给eax,eax的值压入栈中,下面也是,画堆栈图

这里压入栈的数值,将作为内部嵌套函数的参数

执行完之后

接下来又是调用函数,将CALL指令下一条指令的地址压入栈顶,EIP修改

F7步入

F8之后

红框部分和前面一样,都是调用函数前提升栈顶、开辟空间、保存现场、填充缓冲区,主要看操作部分

先把堆栈图画好

接下来看操作

这些都没有改变堆栈的值,第一条将0xA赋给EBP-0x4的内存单元,对应堆栈写出操作方式 EAX的值为1+2,最后+0xA,所以最后EAX的值为0xD

运行

此时函数的操作部分已经结束,接下来就是退出函数了

对比一下,跟函数开始正好是反过来的

POP是先出栈再移动 ,这几个POP就相当于恢复现场,要恢复到未调用函数前,这也是为什么要先把这些值压入栈中的原因。

mov就是降低栈底,恢复到原来的位置

retn指令相当于POP EIP,先将栈顶的值赋给EIP,然后栈顶指针移动

堆栈图如下

执行后

可以看到执行完之后,缓冲区的内容不会清理,这也是为什么需要填充缓冲区

回到后我们发现一个问题,就是函数调用前后堆栈不平衡,堆栈平衡是指调用函数前后堆栈应该相等,这里是因为将函数的参数压入了栈,所以需要接下来的ADD平衡堆栈

执行完之后

和调用前一致,所以没问题了

剩下的就是重复之前的操作了,先执行函数操作指令,然后POP还原现场,retn后ADD恢复堆栈平衡

函数的参数存放在ebp+0x8开始因为call指令会将下一条指令地址压入栈中,而局部变量从ebp-0x4开始