1.裸函数
c语言的普通函数中即使什么都不写,编译器在编译时也会给它加上一些汇编代码;比如开栈、返回等;
裸函数就是编译器什么都不管,一切都需要自己来处理;
裸函数的优点是自由度高,可以应用在钩子程序等地方;
 
例如:一个裸函数
#include "stdio.h"
 
void __declspec(naked) method(){
 
 
}
 
int main(int argc, char* argv[])
{
    
    method();
    return 0;
}
 
空的裸函数由于编译器不会给它添加返回指令,所以运行时会报错,可以自己在其中添加汇编指令;
void __declspec(naked) method(){
    __asm{
        ret
    }
}
然后反汇编时就有了ret指令:
 
1)裸函数实现加法运算
#include "stdio.h"
 
//实现1+2+3+4+5+6
int __declspec(naked) method(int x, int y, int z){
    __asm{
        //保留之前的栈底
        push ebp
        //开栈        ->注意十六进制数前最好加上0x防止被当成十进制数
        mov ebp,esp
        sub esp,0x40
        //保护现场
        push ebx
        push esi
        push edi
        //填充缓冲区
        lea edi,[ebp-0x40]
        mov ecx,0x10
        mov eax,0xcccccccc
        rep stos dword ptr [edi]
        //往缓冲区存放局部变量
        mov dword ptr [ebp-0x04],4
        mov dword ptr [ebp-0x08],5
        mov dword ptr [ebp-0x0c],6
        //计算
        mov eax,dword ptr [ebp+0x08]
        add eax,dword ptr [ebp+0x0c]
        add eax,dword ptr [ebp+0x10]
        add eax,dword ptr [ebp-0x04]
        add eax,dword ptr [ebp-0x08]
        add eax,dword ptr [ebp-0x0c]
        //恢复现场
        pop edi
        pop esi
        pop ebx
        //恢复堆栈
        mov esp,ebp
        pop ebp
        //返回
        ret
    }
}
 
//同效果的函数
int plus(int x, int y, int z){
    int a = 4;
    int b = 5;
    int c = 6;
    return x+y+z+a+b+c;
}
 
int main(int argc, char* argv[])
{
    
    method(1,2,3);
    //plus(1,2,3);
 
    return 0;
}
 
2.调用约定
常用的调用约定:
调用约定
参数压栈顺序
平衡堆栈
__cdecl
从右至左入栈
调用者清理栈
__stdcall
从右至左入栈
自身清理堆栈
__fastcall
ECX/EDX传送前两个
剩下:从右至左入栈
自身清理堆栈
1)外平栈
默认调用约定;
int __cdecl Plus(int a, int b)                
{                
    return a+b;            
}                
                
push        2                
push        1                
call        @ILT+15(Plus) (00401014)                
add         esp,8
 
2)内平栈
ret后面的参数为平衡堆栈所需的内存单元数;
int __stdcall Plus(int a, int b)            
{            
    return a+b;        
}            
            
push        2            
push        1            
call        @ILT+10(Plus) (0040100f)            
            
函数内部:            
ret         8    
 
3)快速调用
用寄存器来保存参数;
最多两个参数可保存在寄存器;
不需要平衡堆栈;
比较适合用在参数多次传递的地方,比如循环;
 
int __fastcall Plus(int a, int b)                
{                
    return a+b;            
}                
                
mov         edx,2                
mov         ecx,1                
call        @ILT+0(Plus) (00401005)                
                
函数内部:                
ret                         
 
4)超过两个参数的快速调用
其它参数保持在栈中,用内平栈的方式来平衡堆栈;
int __fastcall Plus4(int a, int b,int c,int d)                    
{                    
    return a+b+c+d;                
}                    
                    
push        4                    
push        3                    
mov         edx,2                    
mov         ecx,1                    
call        @ILT+5(Plus) (0040100a)                    
                    
函数内部:                    
                    
ret         8    
 
3.函数入口
(1) main 或WinMain 是“语法规定的用户入口”,而不是“应用程序入口”。应用程序入口通常是启动函数。
例如:vc6中修改入口函数名
右键单击项目-》Setting
 
(2) mainCRTStartup 和 wmainCRTStartup 是控制台环境下多字节编码和Unicode 编码的启动函数.    
而WinMainCRTStartup 和wWinMainCRTStartup 是windows 环境下多字节编码和Unicode 编码的启动函数.
可以在vc6的调用栈中看到:
 
3)找main函数
    main 函数被调用前要先调用的函数如下:    
    GetVersion()      ->获得当前操作系统的版本   
    _heap_init()      ->初始化堆空间的大小   
    GetCommandLineA()    ->获取命令行参数     
    _crtGetEnvironmentStringsA()     ->获取环境变量    
    _setargv()    
    _setenvp()    
    _cinit()
 
查看mainCRTStartup()可知main函数在经过编译器处理后有3个参数:
 
这些函数调用结束后就会调用main 函数,根据main 函数调用的特征,将3 个参数压入栈内作为函数的参数。
也就是说找到上面的函数后,它们后面如果有传入3个参数的函数被调用,那么这个函数很可能就是main函数;
 
4.逆向实例
1)找到程序入口main
用od打开目标程序进行分析:
 
2)进入mian函数分析
这里有个两个函数,只分析第一个;
注意:参数的入栈顺序是从右向左的;
目前可以确定的程序代码:
#include "stdio.h"
 
int __fastcall plus(int a, int b, int c, int d, int e){
 
}
 
int main(int argc, char* argv[]){
    plus(1,3,4,6,7);
    return 0;
}
 
3)分析第一个函数
可以确定的代码:
#include "stdio.h"
 
int __stdcall plus1(int x, int y, int z){
        
}
 
 
int plus2(int m, int n){
    
}
 
int __fastcall plus(int a, int b, int c, int d, int e){
    int j = plus1(a,b,c);
    int k = plus2(a,b);
    
    return plus2(j,k);
 
}
 
int main(int argc, char* argv[]){
    plus(1,3,4,6,7);
    return 0;
}
 
3)分析函数的子函数
用同样的方法分析另一个子函数;
代码:
#include "stdio.h"
 
int __stdcall plus1(int x, int y, int z){
     return x+y+z;   
}
 
int plus2(int m, int n){
    return m+n;
}
 
 
int __fastcall plus(int a, int b, int c, int d, int e){
    int j = plus1(a,b,c);
    int k = plus2(a,b);
    
    return plus2(j,k);
}
 
 
int main(int argc, char* argv[]){
    plus(1,3,4,6,7);
    return 0;
}
 
4)查看程序结果
接着运行到函数调用完,查看结果,结果保存在eax中;
换算成十进制:
0x0c=12
也就是结果为:12
(1+3+4)+(1+3)=12;
和分析的结果一样;
 
 
 
 
 

简单的c程序分析的更多相关文章

  1. 第一讲 一个简单的Qt程序分析

    本文概要:通过一个简单的Qt程序来介绍Qt程序编写的基本框架与一些Qt程序中常见的概念 #include <QApplication> #include <QPushButton&g ...

  2. UNP学习笔记2——从一个简单的ECHO程序分析TCP客户/服务器之间的通信

    1 概述 编写一个简单的ECHO(回复)程序来分析TCP客户和服务器之间的通信流程,要求如下: 客户从标准输入读入一行文本,并发送给服务器 服务器从网络输入读取这个文本,并回复给客户 客户从网络输入读 ...

  3. 通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的

    秦鼎涛  <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验一 通过汇编一个简单的C程序,分析汇编代码 ...

  4. 第一周:通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的

    姓名:吕松鸿 学号:20135229 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...

  5. 通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的

    实验一:通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的 学号:20135114 姓名:王朝宪 注: 原创作品转载请注明出处   <Linux内核分析>MOOC课程http: ...

  6. Flink源码分析 - 剖析一个简单的Flink程序

    本篇文章首发于头条号Flink程序是如何执行的?通过源码来剖析一个简单的Flink程序,欢迎关注头条号和微信公众号"大数据技术和人工智能"(微信搜索bigdata_ai_tech) ...

  7. 对Java数组中去除重复项程序分析

    我作为一个Java菜鸟,只会用简单的办法来处理这个问题.如果有大神看到,请略过,感激不尽! 所以首先先分析这道题目:数组中重复的数据进行删除,并且要让数组里的数据按原来的顺序排列,中间不能留空. 既然 ...

  8. 编写一个简单的C++程序

    编写一个简单的C++程序 每个C++程序都包含一个或多个函数(function),其中一个必须命名为main.操作系统通过调用main来运行C++程序.下面是一个非常简单的main函数,它什么也不干, ...

  9. C#编写简单的聊天程序

    这是一篇基于Socket进行网络编程的入门文章,我对于网络编程的学习并不够深入,这篇文章是对于自己知识的一个巩固,同时希望能为初学的朋友提供一点参考.文章大体分为四个部分:程序的分析与设计.C#网络编 ...

随机推荐

  1. 使用winsw包装服务将nginx包装为Windows服务

    **Nginx本身在Windows上并不支持以服务的形式运行,官方文件中有提到.http://nginx.org/en/docs/windows.html,所以在Windows下使用winsw将Ngi ...

  2. MFC之CImageList(1)

    CImageList BOOL Create( int cx, int cy, UINT nFlags, int nInitial, int nGrow ); 其中各项参数的含义为:cx定义图像的宽度 ...

  3. 如何使用Jedis操作redis

    public class JredisTest { private static Jedis jedis = new Jedis("localhost", 6379); publi ...

  4. vue 超大 table

    https://github.com/ColdDay/vue-fast-table https://coldday.github.io/vue-fast-table/dist/index.html h ...

  5. cookie以一个或多个空格开头

    作为一个整体,如果cookie以一个或多个空格开头,请用空格替换所有空格:如果cookie以一个或多个空格结尾,请用空格替换所有空格. 象征意义: \ S:空间,空间 +一个或多个 ^开始,^s,以空 ...

  6. RPC性能优化

    优化 1:元数据共享 hessian 序列化会将两种信息写到输出流: 元数据:即类全名,字段名 值数据:即各个字段对应值(如果字段是复杂类型,则会递归传递该复杂类型 的元数据和内部字段的值数据) 在 ...

  7. Dreamweaver CS6 破解安装

    安装 双击Dreamweaver.dmg文件,然后Command+N,新建一个Finder,接着将Adobe Dreamweaver CS6拖到新建Finder的应用程序中.   在Finder中应用 ...

  8. 实现单点登录功能的思路以及kafka同步数据

    单点登录以及用户数据同步思路与方案 当公司业务分布于多个子系统时, 同一用户在A系统注册,即可在其他所有关联系统使用, 并支持登录A系统后,自动在其他系统登录,退出同理. 在A平台修改通用的用户数据( ...

  9. redis编译和安装出现错误

    redis编译到时候出现错误,记录一下原因 1.下载redis,https://redis.io/download ,一般选择稳定版本,稳定版的版本号是偶数,当前最新版本是5.0.4,Linux可直接 ...

  10. python常用模块:pickle、shelve、json、xml、configparser

    今日内容主要有: 一.pickle模块二.shelve模块三.json模块四.json练习五.xml模块 六.xml练习七.configparser模块 一.pickle模块 #pickle是一个用来 ...