Windows逆向安全(一)之基础知识(三)
创始人
2025-05-31 02:31:37

C语言内联汇编和调用协定

前面我们通过分析反汇编分析了C语言,现在我们来探究如何在C语言里直接自写汇编函数

这里就要引入C语言中裸函数的概念

裸函数

声明裸函数

裸函数与普通函数的区别在于在函数前多声明了

__declspec (naked)

以此来表面该函数是一个裸函数

裸函数作用

要讲裸函数的作用,就不得不提到裸函数与普通函数的区别

裸函数与普通函数区别

前面反汇编C语言的笔记里,我们可以得知一个普通空函数的反汇编代码并不少,保护现场、恢复现场等等都有,那么这些反汇编代码是如何产生的呢?

答案是:编译器

编译器会为我们产生这些反汇编代码

相比之下,只要普通函数加上裸函数前缀转化为裸函数,编译器就会知道这个函数无需额外生成上面所说的保护现场、恢复现场等反汇编代码,函数执行所需的反汇编代码由我们自己来实现

于是裸函数的作用呼之欲出:

当我们不希望编译器为我们生成函数里的汇编代码,而是想要自己实现函数内部的汇编代码时,就可以使用裸函数来告诉编译器不要去额外生成汇编代码

最简单的裸函数

void __declspec (naked) function(){__asm{ret}
}

上面是一个最简单的裸函数,反汇编代码只有一行ret

与普通空函数相比,同样是什么都没做,但却要加上ret,为什么?

我们可以来看看这个最简单的裸函数的执行流程,并注意与普通空函数对比

分析最简单的裸函数

完整代码

先给出完整的代码

在这里插入图片描述

#include "stdafx.h"void __declspec (naked) function(){__asm{ret}
}
int main(int argc, char* argv[])
{function();return 0;
}

接下来进入反汇编的世界

函数外部

在这里插入图片描述

13:       function();
00401078   call        @ILT+10(function) (0040100f)
14:       return 0;
0040107D   xor         eax,eax
15:   }
0040107F   pop         edi
00401080   pop         esi
00401081   pop         ebx
00401082   add         esp,40h
00401085   cmp         ebp,esp
00401087   call        __chkesp (004010e0)
0040108C   mov         esp,ebp
0040108E   pop         ebp
0040108F   ret

函数内部

在这里插入图片描述

6:    void __declspec (naked) function(){
00401030   ret

开始分析

00401078   call        @ILT+10(function) (0040100f)

我们可以发现裸函数的调用和普通函数的调用并没有什么区别,都是call 函数地址

但接着进入函数内部就可以看到

00401030   ret

函数的内部有且仅有我们代码中用__asm所写的ret语句,没有任何其余的代码

执行完ret语句后,函数正常返回,接下来就和普通的空函数没有区别了

在这里插入图片描述
得出结论

于是我们可以知道,裸函数的内部汇编代码完全由我们自己来实现,之所以要写上一个ret也是为了让函数能够正常地返回

再来看看不添加ret语句时的反汇编代码情况

我们将裸函数内部的__asm删除

void __declspec (naked) function(){}

然后再观察函数内部的汇编代码

在这里插入图片描述
我们可以看到函数内部为一堆 int 3,也就是CC即初始化堆栈的内容

程序执行到这里就会产生中断,无法正常执行,所以我们才要加上一条汇编ret语句,让函数能够正常执行返回

大致了解了裸函数后,我们就来使用内联汇编自己实现加法函数

内联汇编实现加法函数

自写加法函数

#include "stdafx.h"int __declspec (naked) Plus(int x,int y){__asm{//保留调用前堆栈push ebp//提升堆栈mov ebp,espsub esp,0x40//保护现场push ebxpush esipush edi//初始化提升的堆栈,填充缓冲区mov eax,0xCCCCCCCCmov ecx,0x10lea edi,dword ptr ds:[ebp-0x40]rep stosd//函数核心功能//取出参数mov eax,dword ptr ds:[ebp+8]//参数相加add eax,dword ptr ds:[ebp+0xC]//恢复现场pop edipop esipop ebx//降低堆栈mov esp,ebppop ebp                //返回ret }        
}
int main(int argc, char* argv[])
{Plus(1,2);return 0;
}

不难发现,其实我们自己实现的加法函数就是模拟了编译器为我们做的事情,此时进到函数内部也会看到

函数内部

6:    int __declspec (naked) Plus(int x,int y){
00401030   push        ebp
7:        __asm{
8:            //保留调用前堆栈
9:            push ebp
10:           //提升堆栈
11:           mov ebp,esp
00401031   mov         ebp,esp
12:           sub esp,0x40
00401033   sub         esp,40h
13:           //保护现场
14:           push ebx
00401036   push        ebx
15:           push esi
00401037   push        esi
16:           push edi
00401038   push        edi
17:           //初始化提升的堆栈,填充缓冲区
18:           mov eax,0xCCCCCCCC
00401039   mov         eax,0CCCCCCCCh
19:           mov ecx,0x10
0040103E   mov         ecx,10h
20:           lea edi,dword ptr ds:[ebp-0x40]
00401043   lea         edi,ds:[ebp-40h]
21:           rep stosd
00401047   rep stos    dword ptr [edi]
22:           //函数核心功能
23:
24:           //取出参数
25:           mov eax,dword ptr ds:[ebp+8]
00401049   mov         eax,dword ptr ds:[ebp+8]
26:           //参数相加
27:           add eax,dword ptr ds:[ebp+0xC]
0040104D   add         eax,dword ptr ds:[ebp+0Ch]
28:
29:
30:           //恢复现场
31:           pop edi
00401051   pop         edi
32:           pop esi
00401052   pop         esi
33:           pop esi
00401053   pop         esi
34:
35:           //降低堆栈
36:           mov esp,ebp
00401054   mov         esp,ebp
37:           pop ebp
00401056   pop         ebp
38:
39:           //返回
40:           ret

执行的就是我们自己所写的代码,而非编译器所生成的,并且也能够实现加法函数的功能

函数返回后

在这里插入图片描述
我们可以发现函数返回后和普通函数并无差异

00401081   add         esp,8

都有这一行平衡堆栈的语句,也就是堆栈外平衡,但如果我们想要在函数内部就平衡堆栈,也就是实现堆栈内平衡,也就是希望函数返回后没有这个外部的堆栈平衡语句,让堆栈的平衡工作由我们自己来处理,该如何做到?

这里就要引入C语言的调用协定这个概念了

调用协定
常见的几种调用协定:
在这里插入图片描述
其中__cdecl为C语言默认调用协定

接下来我们来比较一下这三种调用协定

int __cdecl Plus1(int x,int y){return x+y;
}
int __stdcall Plus2(int x,int y){return x+y;
}
int __fastcall Plus3(int x,int y){return x+y;
}

同样都是一个简单的加法函数,分别采用了三种不同的调用协定,我们来用汇编来一察他们的区别

__cdecl

首先是我们最熟悉的__cdecl协定,和我们上一个笔记分析的简单加法函数不无区别

观察反汇编:

函数外部

在这里插入图片描述
函数内部

在这里插入图片描述
我们这里主要是关注三个地方:参数压栈、函数返回值、返回后执行语句

参数压栈:先push 2再 push 1

函数返回值:ret

返回后执行语句:add esp,8

__stdcall

函数外部

在这里插入图片描述
函数内部

在这里插入图片描述接着关注三个地方:参数压栈、函数返回值、返回后执行语句

参数压栈:先push 2再 push 1

函数返回值:ret 8

返回后执行语句:xor eax,eax

__fastcall

函数外部

在这里插入图片描述

函数内部

在这里插入图片描述
依旧关注三个地方:参数压栈、函数返回值、返回后执行语句

参数压栈:先move edx,2再mov ecx,1

函数返回值:ret

返回后执行语句:xor eax,eax

对比三种协定

在这里插入图片描述
我们可以得出结论:

__cdecl是将参数压入栈中,然后在函数执行返回后再平衡堆栈,也就是堆栈外平衡

__stdcall也是将参数压入栈中,但是是在函数内部通过ret xxx来平衡堆栈,也就是堆栈内平衡

__fastcall则是在参数个数小于等于2时直接使用edx和ecx作为参数传递的载体,没用使用到堆栈,自然也就无须平衡堆栈,但是当参数个数大于2时,则多出来的那几个参数则按stdcall的方式来处理,也是采用堆栈内平衡

接下来再谈谈__stdcall中返回值的问题

我们可以看到,我们在上面的加法函数中push了两个立即数2和1,返回值是8

这是不是意味着ret xxxx中xxxx=参数个数*4?

并不是!!!这里ret xxxx里的xxxx和压入参数的数据宽度有关

我们这里压入的两个立即数的数据宽度都是4个字节=32bit,因此我们这里是ret 4+4=8

如果改成push ax,也就是压入2个字节=16bit时则应该ret 2

了解了以上调用协定后,我们就可以修改之前的简单加法裸函数,将其改为堆栈内平衡

堆栈内平衡加法函数

__declspec (naked) __stdcall int  Plus(int x,int y){__asm{//保留调用前堆栈push ebp//提升堆栈mov ebp,espsub esp,0x40//保护现场push ebxpush esipush edi//初始化提升的堆栈,填充缓冲区mov eax,0xCCCCCCCCmov ecx,0x10lea edi,dword ptr ds:[ebp-0x40]rep stosd//函数核心功能//取出参数mov eax,dword ptr ds:[ebp+8]//参数相加add eax,dword ptr ds:[ebp+0xC]//恢复现场pop edipop esipop ebx                //降低堆栈mov esp,ebppop ebp//返回ret 8}        
}
int main(int argc, char* argv[])
{Plus(1,2);return 0;
}

与前面相比,修改ret 为ret 8,自己在函数内实现了堆栈内平衡

相关内容

热门资讯

代码随想录刷题-哈希表-两数之... 文章目录两数之和习题暴力解法哈希表 两数之和 本节对应代码随想录中:代码随想录...
凌恩生物明星产品:让你读懂细胞... 叶绿体和线粒体是真核细胞中不可或缺的重要细胞器,是第二套遗传信息系统,与...
密码如何“加盐加密”处理?程序... 目录 前言 一、手写加盐算法 1.1、加密 1.1.1、加密思路 1.1.2、加密简图 1.1.3、...
黑化什么意思网络用语 极速百科... 黑化,词语,也可指性情大变,比如原本某A是个文青,温文尔雅,突然某天某A大开杀戒,残忍无比,这就是所...
带有古和今的成语,古今的对话:... 带有古和今的成语有很多,例如:古为今用、古往今来、古稀之年、古今中外等等。这些成语都包含了古代和现代...
能力强的人有什么特点,标题建议... 能力强的人往往具备以下特点: 1. 学习能力:能力强的人通常能够快速学习新知识和技能,并能够灵...
跑步机怎么操作大图 极速百科网... 1. 启动跑步机:首先,确保跑步机已经插上电源,然后打开跑步机的电源开关。大多数跑步机都有一个启动/...
ELK+Filebeat+Ka... 文章目录ELK+Filebeat+Kafka分布式日志管理平台搭建为什么选择ELK&...
哈希结构的代码实现(开散列、闭... 哈希结构 unordered系列的关联式容器之所以查找效率比较高,是因为其底层使用了哈...
Java Annotation... 注解 注解(Annotation),又称元数据ÿ...
bim装配式工程师证书有用吗,... 首先,我们需要明确一点,那就是BIM装配式工程师证书肯定是有用的。这个证书证明了持有人掌握了BIM技...
什么东西可以深层清洁毛孔 极速... 首先,我们要明白,毛孔的深层清洁不仅仅是指清洁面部皮肤,还包括清洁身体和头发的毛孔。接下来,我将为你...
vip全称 极速百科网 极速百... VIP的英文全称为Very Important Person,中文翻译为重要人物、要员。一般指VIP...
公共交通工具有哪些 极速百科网... 1. 城市公交:这是大家最熟悉的公共交通工具之一,提供在城市内部的运输服务。公交车根据不同的大小和座...
智能火焰与烟雾检测系统(Pyt... 摘要:智能火焰与烟雾检测系统用于智能日常火灾检测报警,利用摄像头画面实时...
【学习笔记】《Writing ... 文章目录14 Energizing Writing 充满活力的写作14.1. ACTIVE VERS...
基于R语言因果关系推断模型实践... 通过数据得到可靠的因果关系一直是科学研究的主要目标之一。对因果关系的研究已经有千年之久;...
手机qq里怎么分组,手机QQ分... 1. 打开QQ应用,进入联系人界面。 2. 找到你想要分组的联系人或群聊。 3. 长按想...
带指的成语有哪些 极速百科网 ... 指日高升、一指蔽目、屈指可数、十指连心、三指佞臣、染指垂涎、戟指怒目、屈指可数。 如需更多含有...
第十八天 Vue-前端工程化总... 目录 Vue-前端工程化 1. 前后端分离开发 1.1 介绍 1.2 Yapi 2. 前端工程化 2...
急用钱公积金怎么提现,公积金提... 1. 了解公积金提现的条件和手续。在您考虑提现之前,请务必了解您所在地区的具体规定和要求。通常,您需...
杯子刻字励志八个字,志存高远,... 杯子刻字励志八个字建议如下: 1. 志存高远,自强不息。 2. 持之以恒,锐意进取。 ...
YOLOV4详解 1. 为什么要学习YOLOV4? 通过学习YOLOV3这个很重要的算法, 可以学习到作者重新设计Da...
提高曝光率:外贸网站如何充分利... 自从我从事外贸行业以来,谷歌优化一直是我关注的重点。 作为一个外贸从业者,...
Vue3 学习总结补充(一) 文章目录1、Vue3中为什么修改变量的值后,视图不更新?2、使用 ref...
锤哥和锤弟是双胞胎吗,锤哥和锤... 锤哥和锤弟是双胞胎吗?锤哥和锤弟这两个名字可能只是表示两个有亲属关系的人,而并不一定表示他们是双胞胎...
ido什么意思,IDO:意义、... IDO全称“I Do”,源自婚礼誓言,意为“我愿意”。表达对爱情的坚守。IDO钻戒的寓意是珍视爱情的...
形容夜色美的唯美句子,夜色的魅... 1. 夜幕降临,华灯初上,整个城市被柔和的灯光笼罩,宛如一颗璀璨的明珠。 2. 夜色中的星空,...
已婚女人梦见龙预示有什么征兆,... 已婚女人梦见龙,一般来说,是一种非常吉祥的预兆。在中国传统文化中,龙象征着富贵、吉祥和好运。 ...
2022国赛2:神州路由器pp...       PPP Multilink协议(MP)是PPP(...