前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手动编写C函数的汇编代码

手动编写C函数的汇编代码

作者头像
信安本原
发布2020-04-22 15:02:52
9850
发布2020-04-22 15:02:52
举报
文章被收录于专栏:信安本原

在前面的文章里已经清楚计算机是只认识0和1的,那平时编写的程序到运行中间又经历了什么?

这个过程用下面一张图就足以说明所有的问题了

稍微解释一下其中的一些含义

目标文件和可执行文件都是由机器语言指令组成的 目标文件只包含你写的代码所翻译的机器语言代码 可执行文件还包含你写的代码中使用的库函数和启动代码的机器语言代码(启动代码充当着程序和操作系统之间的接口)

编译器到底生成了什么

多说无益,这里用一个空白的C语言函数来看看编译器生成了哪些东西。

代码语言:javascript
复制
void test() {00ED1E70  push        ebp  00ED1E71  mov         ebp,esp  00ED1E73  sub         esp,0C0h  00ED1E79  push        ebx  00ED1E7A  push        esi  00ED1E7B  push        edi  00ED1E7C  lea         edi,[ebp-0C0h]  00ED1E82  mov         ecx,30h  00ED1E87  mov         eax,0CCCCCCCCh  00ED1E8C  rep stos    dword ptr es:[edi]  00ED1E8E  mov         ecx,offset _0BADB893_Source@cpp (0EDC000h)  00ED1E93  call        @__CheckForDebuggerJustMyCode@4 (0ED1208h)  
}00ED1E98  pop         edi  00ED1E99  pop         esi  
}00ED1E9A  pop         ebx  00ED1E9B  add         esp,0C0h  00ED1EA1  cmp         ebp,esp  00ED1EA3  call        __RTC_CheckEsp (0ED1212h)  00ED1EA8  mov         esp,ebp  00ED1EAA  pop         ebp  00ED1EAB  ret

中间的检查堆栈平衡等函数我们可以省略,仔细看看其中的汇编代码,很容易可以看出这其中所进行的操作就是上一篇文章所画的堆栈图,堆栈图也是后面进行分析的关键,手写这段程序的代码也是一键很重要的事情,如果所有的操作都交给编译器去做,那你所有的操作就都是很明确的,又怎么去跟别人进行对抗。

手动编写

这里就需要引入裸函数的概念了,裸函数就是编译器不帮你生成一行代码,所有的代码都必须你自己去手动编写

代码语言:javascript
复制
void __declspec(naked) Function(){
}

在正常情况下,我们写一个空函数是不会出现报错的情况的,但是裸函数则不然,直接用上面的方式写,会跳到一个程序不认识的地方,如果对上一篇文章的堆栈图足够了解,就会知道造成这个情况的原因是什么。

这是因为函数在汇编语言中是通过call来调用的,这个操作包含了两个步骤,一步是把下一条指令的地址push到堆栈中,一步是跳转到函数所要执行的地址,如果是一个空函数,它会再跳回到call指令的下一条地址,但是裸函数不会,因为编译器没有给我们生成任何一条指令,所以要想让一个空的裸函数正常运行, 就需要我们手动添加一段指令,让程序回到原来要执行的位置,那就是添加ret指令,所以可以运行的空的裸函数如下

代码语言:javascript
复制
void __declspec(naked) Function(){    __asm    {        ret    }}

对于手动编写要特别注意对于相关数据的调用,需要明确它们所处的位置在哪里,为了把所有的情况都包含在内,用了下面的这个例子作为说明

代码语言:javascript
复制
int plus(int x, int y, int z) {    int a = 1;    int b = 2;    int c = 3;    return x + y + z + a + b + c;}

其中x、y、z和a、b、c在内存中所存在的位置是完全不同的,想要分清楚这个内容,上一篇文章的堆栈图就特别的关键了,不清楚的去看上一篇文章的说明。

下面直接给出最终的代码,跟编译器所生成的肯定是有差别的,但是在功能实现方面已经足够了,想要看懂其中的含义,堆栈图是必须的,堆栈图是必须的,堆栈图是必须的

代码语言:javascript
复制
int plus(int x, int y, int z) {    int a = 1;    int b = 2;    int c = 3;    return x + y + z + a + b + c;}
int __declspec(naked) plus1(int x, int y, int z) {    __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 dword ptr ds:[ebp-0x8],1        mov dword ptr ds:[ebp-0xC],2        mov dword ptr ds:[ebp-0x10],3
        mov eax,dword ptr ds:[ebp+0x8]        add eax,dword ptr ds:[ebp+0xC]        add eax,dword ptr ds:[ebp+0x10]        add eax,dword ptr ds:[ebp-0x8]        add eax,dword ptr ds:[ebp-0xC]        add eax,dword ptr ds:[ebp-0x10]
        //恢复现场        push edi        push esi        push ebx
        //恢复堆栈        mov esp,ebp        pop ebp
        //返回        ret    }}void test() {
}
int main(int argc, char* argv[]) {    //test();    //plus(1, 2, 3);    plus1(1, 2, 3);    return 0;}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 无心的梦呓 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 编译器到底生成了什么
  • 手动编写
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档