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. Redis主从及哨兵

    Redis主从用法 像MySQL一样,redis是支持主从同步的,而且也支持一主多从以及多级从结构. 主从结构,一是为了纯粹的冗余备份,二是为了提升读性能,比如很消耗性能的SORT就可以由从服务器来承 ...

  2. Jmeter之设置线程组运行次数/时间

    线程组的设置 线程组运行的次数=线程数*循环次数 Ramp-Up Period:表示启动时间 例如:线程数:10,循环次数:10,Ramp-Up Period:2 表示,这个线程组一共有100个线程( ...

  3. Spring实战(六)自动装配的歧义性

    1.Spring进行自动装配时碰到的bean歧义性问题. 在进行自动装配时,只有仅有一个bean匹配所需结果时,才是可行的. 如果不仅仅一个bean能够匹配结果,例如一个接口有多个实现,这种歧义性会阻 ...

  4. 作业2:java内存模型图示

    参考:http://www.infoq.com/cn/minibooks/java_memory_model?utm_source=infoq&utm_campaign=user_page&a ...

  5. java lesson20homework

    package com.xt.lesson20; /** * 简易自动提款机 1. 创建用户类User(包含卡号.姓名.密码.余额等属性),用户开卡时录入的姓名和密码(自动分配一个卡号.初始金额设置为 ...

  6. C# 使用Emit实现动态AOP框架 进阶篇之优化

    目  录 C# 使用Emit实现动态AOP框架 (一) C# 使用Emit实现动态AOP框架 (二) C# 使用Emit实现动态AOP框架 (三) C# 使用Emit实现动态AOP框架 进阶篇之异常处 ...

  7. C# struct结构知识总结

    结构是一种值类型,使用struct关键字定义. 结构可以包含字段.常量.事件.属性.方法.构造函数.索引器.运算符和嵌套类型.但若结构中同时需要上述所有成员,应考虑将结构改为类. 嵌套类型:在类或构造 ...

  8. shiro学习(四、shiro集成spring+springmvc)

    依赖:spring-context,spring-MVC,shiro-core,shiro-spring,shiro-web 实话实说:web.xml,spring,springmvc配置文件好难 大 ...

  9. 查找和杀掉占用GPU显存的进程

    用只有2个G的显卡跑数据就需要在训练之前先把无关进程杀掉,防止跑到一半显存满了 nvidia-smi:显示当前GPU中的线程 kill -9 PID:输入PID以结束线程

  10. centos 配置rsync+inotify数据实时同步2

    一.Rsync服务简介 1. 什么是Rsync 它是一个远程数据同步工具,它在同步文件的同时,可通过LAN/WAN快速同步多台主机间的文件.Rsync使用所谓的“rsync算法”来使本地和远程两个主机 ...