首页
社区
课程
招聘
汇编层面分析函数调用
发表于: 2018-9-28 22:23 10786

汇编层面分析函数调用

2018-9-28 22:23
10786

前言

汇编语言是学习逆向的基础,本文通过从汇编的层面分析函数调用来了解 压栈  跳转 执行 返回 的具体实现流程以及对堆栈的应用。
知识有限,如果有错误或则不清楚的地方还请您指出。
您的鼓励是我写文章的动力。

1.函数调用的说明

在介绍函数调用的具体流程前,我们先来了解一下几个知识点。

1.1程序的执行流程

程序是顺序执行的,cpu是怎么进行实现的呢?
程序的执行离不开一个关键的寄存器。
我们首先来了解一下关于程序计数器的概念。

百度百科



由此可见,程序计数器的功能就是存放下一条要执行指令的地址。
在X86汇编中,执行程序计数功能的寄存器叫做EIP,指令指针寄存器。

我们可以写个简单的程序,查看反汇编了解一下。

EIP寄存器里面存放的就是即将要执行指令的地址。

1.2程序的栈结构

在经典的操作系统里,栈总是向下增长的。栈顶由esp寄存器定位。压栈操作使栈顶的地址减小,弹出操作使栈顶地址增大。
函数的调用离不开栈,栈中保存了函数调用当中的所有信息。
  • 函数的返回地址和参数
  • 函数的非静态的局部变量
  • 保存上下文:保存一些不需要改变的寄存器

为了能够正确的处理函数调用的堆栈平衡,我们需要更正两个寄存器ebp,esp的位置。
1.把ebp提升到esp的位置



2.esp的值是动态变化的,随着申请更多的临时变量   e's'p会不断减小

3.调用函数后栈中的结构图


1.3调用约定及堆栈平衡

在函数调用过程中,参数需要提前压栈,而在函数调用结束后,之前压栈的参数也需要退栈。
这样就有一个问题,退栈的工作是交给调用者完成还是交给被调用函数完成?
这就需要有个约定

常见调用约定的堆栈平衡方式

 调用约定 堆栈平衡方式
 __stdcall 函数自己平衡
 __cdecl 调用者负责平衡
 __thiscall 调用者负责平衡
 __fastcall 调用者负责平衡
 __naked 有编写者负责









2.函数调用的流程

了解了基本的概念之后,回到主题:函数调用
  • 压栈:函数参数压栈 返回地址压栈
  • 跳转:跳转到函数所在代码处执行
  • 执行:执行函数代码
  • 返回:平衡堆栈 找出返回地址并跳回

2.1call指令


0x210000 call swap;
0x210005 mov ecx,eax;

call指令可以分解成两个指令
push 0x210005;
jmp swap;

首先push当前指令下一条指令的地址,目的是调用完函数后可以跳回来
然后修改eip  跳到函数的地址

2.2 ret指令 

ret指令表示取出当前栈顶值 作为返回地址  将eip修改为该地址
执行完这一步一个基本的函数调用就完成了

3.函数调用汇编实验


测试程序

在main函数中调用一个交换两个数的函数。
#include <stdio.h>


void _stdcall swap(int& a,int& b);


//交换两个数
void _stdcall swap(int& a, int& b)
{
	int c = a;
	a = b;
	b = c;
}


void main()
{

	int a = 1;
	int b = 2;
	printf("交换前:a =%d,b=%d\n",a,b);
	swap(a,b);
	printf("交换后:a= %d,b=%d\n",a,b);

	getchar();

}



程序的运行结果


3.1压栈


3.1.1函数参数压栈


真正调用函数前先将参数进行压栈(之前写错,感谢评论区指正)




我们通过观察寄存器和内存来查看变化过程


发现参数已经被压入栈中

3.1.2返回地址压入栈中


F11进入swap函数后,可以发现内存当中凭空出现一段数据 它是什么??



其实它就是call指令的下一条指令的地址



3.2跳转


跳转到swap函数内部,下面是详细注解


3.3返回


执行ret前。



执行ret后
帖前先滑动左侧填充拼图



由于使用的是__stdcall的调用约定,在被调用函数内部进行堆栈平衡。

4.结语

这样,一个函数调用的实现就分析完成了。有好多细节的问题没有进行阐述。这样一篇文章用来总结汇编的知识点。希望遇到同道中人。
成为逆向大牛的过程是艰辛的,你已经迈出了第一步,加油。




[课程]Linux pwn 探索篇!

最后于 2019-1-22 15:37 被kanxue编辑 ,原因:
收藏
免费 2
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  junkboy   +1.00 2018/09/29
最新回复 (30)
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
支持
2018-9-29 00:03
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
junkboy 支持
感谢
2018-9-29 08:19
0
雪    币: 1539
活跃值: (69)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
感谢分享!
3.1.1 第一张图里的描述改为:“取b的地址作为参数 - 压栈” 恰当些吧。
2018-9-29 10:35
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
5
呼噜 感谢分享! 3.1.1 第一张图里的描述改为:“取b的地址作为参数 - 压栈” 恰当些吧。
是的,谢谢指正
2018-9-29 10:40
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
谢谢
2018-9-29 10:41
0
雪    币: 1539
活跃值: (69)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
Jabez 是的,谢谢指正
你貌似没有理解我意思,我指的不是‘b’放错位置了,而是swap这个函数的参数是个地址,所以这个b和a地址才是参数,而不是b和a是参数。描述“取参数b的地址”意味着b是参数,所以不恰当,我说的“取b的地址作为参数”表明了b的地址才是真正的参数。所以下面那句参数a也不恰当。
2018-9-29 10:53
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
呼噜 你貌似没有理解我意思,我指的不是‘b’放错位置了,而是swap这个函数的参数是个地址,所以这个b和a地址才是参数,而不是b和a是参数。描述“取参数b的地址”意味着b是参数,所以不恰当,我说的“取b的地 ...
是我疏忽了,我重新编辑一下
2018-9-29 12:38
0
雪    币: 23080
活跃值: (3432)
能力值: (RANK:648 )
在线值:
发帖
回帖
粉丝
9
赞一个!期待楼主更多的文章
2018-9-29 12:45
0
雪    币: 405
活跃值: (2150)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
在vmp 横行的年代还是珍惜生命,远离逆向 。
2018-9-29 14:09
0
雪    币: 10726
活跃值: (2730)
能力值: ( LV5,RANK:71 )
在线值:
发帖
回帖
粉丝
11
wowocock 在vmp 横行的年代还是珍惜生命,远离逆向 。
学的x86的汇编,发现用的时候确实另外一套,廉颇老矣。
2018-9-29 17:47
0
雪    币: 310
活跃值: (2227)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
mark
2018-9-29 18:38
0
雪    币: 5511
活跃值: (2072)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
必读 Revers Engineering for Beginners
2018-9-30 08:32
0
雪    币: 781
活跃值: (63)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
赞一个!期待楼主更多的文章
2018-9-30 14:50
0
雪    币: 4
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
2张图片里的esp值为啥不一样呢
2018-10-3 20:22
0
雪    币: 1
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
Jabez 是我疏忽了,我重新编辑一下
这个标记应该是引用,不是指针
2018-10-3 21:48
0
雪    币: 4
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
void main 也不符合标准,返回地址在被调函数中也写了一个,应该没这个返回地址
2018-10-4 00:05
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
18
张海军 2张图片里的esp值为啥不一样呢
您说的是3.1.1和3.1.2那两张图么
2018-10-4 09:02
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
19
张海军 void main 也不符合标准,返回地址在被调函数中也写了一个,应该没这个返回地址
被调函数中的返回地址就是之前加载进去的那一个啊
2018-10-4 09:09
0
雪    币: 4
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
是3.1.1 和3.1.2 ,你运行了2次?截了2次图? 还有返回地址是ret 8后被出栈到eip,esp 也返回到原来的位置了,一张图好像不能明白说明这个流程
2018-10-4 10:16
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
21
张海军 是3.1.1 和3.1.2 ,你运行了2次?截了2次图? 还有返回地址是ret 8后被出栈到eip,esp 也返回到原来的位置了,一张图好像不能明白说明这个流程
1.运行了一次,esp不一样是因为中途调用了一次printf函数      2.esp返回到原来位置是因为函数使用了__stdcall的调用约定,在函数内部进行堆栈平衡,esp+8
2018-10-4 10:43
0
雪    币: 4
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
对于第2条我的意思是内平衡后 堆栈图esp回到原位置,一张图是无法表达清楚的. 对于第一条 printf 是swap 之前做的 不影响swap的堆栈吧,前面esp 压入 a、b 地址时 是0073fb20,后面怎么变为00b5fbd0  
2018-10-4 12:47
0
雪    币: 4
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
可以加个qq 35741132
2018-10-4 12:50
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
24
张海军 对于第2条我的意思是内平衡后 堆栈图esp回到原位置,一张图是无法表达清楚的. 对于第一条 printf 是swap 之前做的 不影响swap的堆栈吧,前面esp 压入 a、b 地址时 是0073fb ...
我上传的时候传错了。。。。。
2018-10-4 20:04
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
25
张海军 可以加个qq 35741132
加了,感谢指正,我找时间重新编辑一下
2018-10-4 20:08
0
游客
登录 | 注册 方可回帖
返回
//