12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
查看: 320|回复: 0

逆向基础笔记九 C语言内联汇编和调用协定

[复制链接]

63

主题

63

帖子

136

积分

实习版主

Rank: 7Rank: 7Rank: 7

积分
136
发表于 2021-7-18 18:22:03 | 显示全部楼层 |阅读模式
继续更新个人的学习条记,
别的条记传送门
逆向基础条记一 进制篇
逆向基础条记二 数据宽度和逻辑运算
逆向基础条记三 通用寄存器和内存读写
逆向基础条记四 堆栈篇
逆向基础条记五 标志寄存器
逆向基础条记六 汇编跳转和比较指令
逆向基础条记七 堆栈图(重点)
逆向基础条记八 反汇编分析C语言
逆向基础条记十 汇编寻找C程序入口
逆向基础条记十一 汇编C语言基本类型
逆向基础条记十二 汇编 全局和局部 变量
逆向基础条记十三 汇编C语言类型转换
逆向基础条记十四 汇编嵌套if else
逆向基础条记十五 汇编比较三种循环
逆向基础条记十六 汇编一维数组
逆向基础条记十七 汇编二维数组 位移 乘法
逆向基础条记十八 汇编 布局体和内存对齐
逆向基础条记十九 汇编switch比较if else
逆向基础条记二十 汇编 指针(一)
逆向基础条记二十一 汇编 指针(二)
逆向基础条记二十二 汇编 指针(三)
逆向基础条记二十三 汇编 指针(四)
逆向基础条记二十四 汇编 指针(五) 系列完结
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,eax15:   }0040107F   pop         edi00401080   pop         esi00401081   pop         ebx00401082   add         esp,40h00401085   cmp         ebp,esp00401087   call        __chkesp (004010e0)0040108C   mov         esp,ebp0040108E   pop         ebp0040108F   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,esp                sub esp,0x40                //保护现场                push ebx                push esi                push edi                //初始化提拔的堆栈,填充缓冲区                mov eax,0xCCCCCCCC                mov ecx,0x10                lea edi,dword ptr ds:[ebp-0x40]                rep stosd                //函数核心功能                //取出参数                mov eax,dword ptr ds:[ebp+8]                //参数相加                add eax,dword ptr ds:[ebp+0xC]                //规复现场                pop edi                pop esi                pop ebx                //降低堆栈                mov esp,ebp                pop ebp                                //返回                ret         }        }int main(int argc, char* argv[]){        Plus(1,2);        return 0;}不难发现,其实我们本身实现的加法函数就是模仿了编译器为我们做的事情,此时进到函数内部也会看到
函数内部

6:    int __declspec (naked) Plus(int x,int y){00401030   push        ebp7:        __asm{8:            //保留调用前堆栈9:            push ebp10:           //提拔堆栈11:           mov ebp,esp00401031   mov         ebp,esp12:           sub esp,0x4000401033   sub         esp,40h13:           //保护现场14:           push ebx00401036   push        ebx15:           push esi00401037   push        esi16:           push edi00401038   push        edi17:           //初始化提拔的堆栈,填充缓冲区18:           mov eax,0xCCCCCCCC00401039   mov         eax,0CCCCCCCCh19:           mov ecx,0x100040103E   mov         ecx,10h20:           lea edi,dword ptr ds:[ebp-0x40]00401043   lea         edi,ds:[ebp-40h]21:           rep stosd00401047   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 edi00401051   pop         edi32:           pop esi00401052   pop         esi33:           pop esi00401053   pop         esi34:35:           //降低堆栈36:           mov esp,ebp00401054   mov         esp,ebp37:           pop ebp00401056   pop         ebp38:39:           //返回40:           ret执行的就是我们本身所写的代码,而非编译器所天生的,并且也能够实现加法函数的功能
函数返回后


我们可以发现函数返回后和普通函数并无差异
00401081   add         esp,8都有这一行平衡堆栈的语句,也就是堆栈外平衡,但假如我们想要在函数内部就平衡堆栈,也就是实现堆栈内平衡,也就是希望函数返回后没有这个外部的堆栈平衡语句,让堆栈的平衡工作由我们本身来处理,该怎样做到?
这里就要引入C语言的调用协定这个概念了
调用协定

常见的几种调用协定:
调用协定参数压栈顺序平衡堆栈__cdecl从右至左入栈调用者整理栈__stdcall从右至左入栈自身整理堆栈__fastcallECX/EDX传送前两个  剩下:从右至左入栈自身整理堆栈其中__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__fastcall参数压栈push 2  push1push2 push1mov edx,2     mov ecx,1函数返回值retret 8ret返回后执行语句add esp,8xor eax,eaxxor 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
这里可以参考逆向基础条记四 堆栈篇中堆栈相关汇编指令的push指令
了解了以上调用协定后,我们就可以修改之前的简单加法裸函数,将其改为堆栈内平衡
堆栈内平衡加法函数

__declspec (naked) __stdcall int  Plus(int x,int y){                __asm{                //保留调用前堆栈                push ebp                //提拔堆栈                mov ebp,esp                sub esp,0x40                //保护现场                push ebx                push esi                push edi                //初始化提拔的堆栈,填充缓冲区                mov eax,0xCCCCCCCC                mov ecx,0x10                lea edi,dword ptr ds:[ebp-0x40]                rep stosd                //函数核心功能                //取出参数                mov eax,dword ptr ds:[ebp+8]                //参数相加                add eax,dword ptr ds:[ebp+0xC]                //规复现场                pop edi                pop esi                pop ebx                                //降低堆栈                mov esp,ebp                pop ebp                //返回                ret 8        }        }int main(int argc, char* argv[]){        Plus(1,2);        return 0;}与前面相比,修改ret 为ret 8,本身在函数内实现了堆栈内平衡

来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-3-29 15:38 , Processed in 0.125000 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表