0x01.函数

这节就先讲函数吧,函数大致分为四种类型

1、无参数、无返回值的函数格式

void 函数名()
{
//代码段
}
void Hello()
{
printf("Hello World!");
}

2、有参数,无返回值的函数格式

void 函数名(参数类型 参数名,参数类型 参数名)
{
//代码段
}
void add(int a,int b)
{
int c = a + b;
printf("当前的值:%d",%c);
}

3、无参数,有返回值的函数格式

返回值类型 函数名()
{
//代码段
return 具体的值,需要和返回值类型 匹配
}
int sub()
{
int c = a - b;
return c;
}

4、有参数、有返回值的函数格式

返回值类型 函数名(参数类型 参数名,参数类型,参数名)
{
//代码段
return 具体的值,必须与返回值类型匹配
}
int ret(int b,int c)
{
int c = b * c;
return c;
}

0x02. 函数的反汇编

a.空函数

#include <stdio.h>

void add()
{ } int main()
{
add();
return 0;
}

运行窗口之后,进入反汇编窗口

可以看到这边call了 add函数,这个函数的地址是0x0DF135Ch,这时候我们再f11进去

这边下一步又跳到了jmp(jmp指令是无条件跳转)这边,可以看到它是在调用add的参数了,进行执行了

跳到了 0x0DF3840

终于到了函数内部,这时候我们脑里想象出一个图,

这时候可以发现call的时候会跳到 jmp那边,一跳完后才是真正的函数内部

1)开辟空间

我们先看前三条

00DF3840  push        ebp
00DF3841 mov ebp,esp
00DF3843 sub esp,0C0h

看不懂没关系,我们可以把随便一个东西拉到od中,然后调一下

栈窗口在这边,我们看一下怎么回事

(补充:push是压栈,把数据压进去、pop是出栈,把东西拿出来。拿出来的话是按照先进后出,后进先出的顺序,之后汇编部分会仔细讲。esp寄存器是栈顶,ebp是栈低)

可以看出现在esp是 00D3FAA8,ebp是00D3FAB4

这时候我们把刚刚在visual studio的那段反汇编写进去试试

这边是把ebp栈低压入栈中

压入之后栈顶和栈低就一样了,这时候再把栈顶(esp)的地址,给栈低(ebp),由于一样,就没啥变化

然后在esp减掉 c0

我们计算一下,栈低减栈顶等于c0,说明正确。其实这边就是在栈中开辟一块空间,方便存储数据

2)保护数据恢复数据

00DF3840  push        ebp        开辟空间
00DF3841 mov ebp,esp
00DF3843 sub esp,0C0h 00DF3849 push ebx 保护数据
00DF384A push esi
00DF384B push edi
...
...
00DF385E pop edi 恢复数据
00DF385F pop esi
00DF3860 pop ebx

保护和恢复其实是一个对应的,我就写出来

就类似我们这种web狗,一般打了站要试头像能不能拿shell,就会先把这个管理员的头像保存一下,然后随便搞。之后恢复一下现场

可以看出先压入的数据在下面,最后压入的数据在上面

恢复数据的时候用pop,然后先把edi弹出去,就是后进先出的意思

3)缓冲区填充数据

00DF384C  lea         edi,[ebp-0C0h]
00DF3852 mov ecx,30h
00DF3857 mov eax,0CCCCCCCCh
00DF385C rep stos dword ptr es:[edi]

我们单步走一下,发现ebp-c0的地址给edi了,发现这个地址其实就是栈低的值

(补充:lea是传送地址,mov是传送数据)

ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。

然后把30h给了ecx,这边的30是十六进制的,不是10进制的,最后把CCCCCCCC给eax

rep指令的目的是重复其上面的指令.ECX的值是重复的次数.

STOS指令的作用是将eax中的值拷贝到ES:EDI指向的地址.

最后发现其实就是把其他地址的值变为8个C而已

最后再恢复数据,恢复数据和保存数据差不多,遵守后进先出即可

4)恢复堆栈

00DF3861     mov         esp,ebp
00DF3863 pop ebp

瞬间回到了之前,最后再把原本的栈低恢复

回到了之前的样子了,执行完函数后就用ret返回

b.简单的函数功能

我们再看一下函数内有数据又会是怎么样?

#include <stdio.h>

void add()
{
int c = 10 + 10;
} int main()
{
add();
return 0;
}

发现就多了一行,因为我们代码中就已经把值写死了,我们传个有参数看看怎么样

#include <stdio.h>

void add(int a,int b)
{
int c = a + b;
} int main()
{
add(2,3);
return 0;
}

这时候我们可以发现这边先是把3压入栈中,再把2压入栈中,接下来就和上面空函数一样,jmp完之后就是真正的函数内部地址了

00333840  push        ebp
00333841 mov ebp,esp
00333843 sub esp,0CCh 00333849 push ebx
0033384A push esi
0033384B push edi 0033384C lea edi,[ebp-0CCh]
00333852 mov ecx,33h
00333857 mov eax,0CCCCCCCCh
0033385C rep stos dword ptr es:[edi] int c = a + b;
0033385E mov eax,dword ptr [a]
00333861 add eax,dword ptr [b]
00333864 mov dword ptr [c],eax
} 00333867 pop edi
00333868 pop esi
00333869 pop ebx 0033386A mov esp,ebp
0033386C pop ebp
0033386D ret

每一个部分我都加了个换行,这时候可以发现是在填充数据之后,再进行加减法

先是把a的值给eax,然后使用add指令,给eax寄存器加个b的值

(补充:add exp1,exp2

add指令:把exp2的值给exp1

最后我们是定义了用c接受传参,所以它这边把算完后结果的eax给c

0x03.局部变量与全局变量

#include <stdio.h>

int Request = 10;
void add()
{
int b = 8;
int c = b + Request;
printf("%d", c);
} int main()
{
add();
return 0;
}

这时候我们反汇编看一下request的定义

再看一下把变量放到函数内部是什么样子的



0025385E mov dword ptr [Request],0Ah

把0a放到request地址上面,也就是赋值给request

这时候可以总结全局变量的特点了:

1、全局变量在程序编译完成后地址就已经确定下来了,只要程序启动,全局变量就已经存在了,启动后里面是否有值取决于声明时是否给定了初始值,如果没有,默认为0

2、全局变量的值可以被所有函数所修改,里面存储的是最后一次修改的值.

3、全局变量所占内存会一直存在,直到整个进程结束.

4、全局变量的反汇编识别:

MOV 寄存器,byte/word/dword ptr ds:[0x12345678]

通过寄存器的宽度,或者byte/word/dword 来判断全局变量的宽度

局部变量特点:

1、局部变量在程序编译完成后并没有分配固定的地址.

2、在所属的方法没有被调用时,局部变量并不会分配内存地址,只有当所属的程序被调用了,才会在堆栈中分配内存.

3、当局部变量所属的方法执行完毕后,局部变量所占用的内存将变成垃圾数据.局部变量消失.

4、局部变量只能在方法内部使用,函数A无法使用函数B的局部变量.

5、局部变量的反汇编识别:

[ebp-4]

[ebp-8]

[ebp-0xc]

0x04.函数参数分析

先看一下调用约定

void __cdecl Function1(int x, int y, int z)
{
g_r = x + y + z;
}
void __stdcall Function2(int x, int y, int z)
{
g_r = x + y + z;
}
void __fastcall Function3(int x, int y, int z)
{
g_r = x + y + z;
}

然后这边我们要判断看是否存在几个参数,用Function2来试,因为一般默认都是使用stdcall

断点下到函数这个地方,然后我们直接去反汇编窗口看



可以看到这边是压入3个参数,分别是从右往左压入



这时候就和前面讲的差不多了,也看的懂了。通过push压入的值来判断函数参数几个,我们也可以仔细看一下ret的值,也可以得到参数值

或者看下main函数里面的堆栈平衡也可以判断参数,由于都是int类型,4字节,在汇编中是dword类型。0ch其实就是十进制的12。

12 / 4 = 3。这时候也可以算出参数

所以判断函数参数数量有三种方法(补充,main函数也是函数,也是需要开辟空间,保存数据等...)

1、查看push入栈的参数

00D142BE  push        3
00D142C0 push 2
00D142C2 push 1
00D142C4 call Function2 (0D1139Dh)

2、查看堆栈平衡

3、进入函数查看返回值



查看函数内部返回值的值

C语言函数的学习的更多相关文章

  1. R语言函数化学习笔记6

    R语言函数化学习笔记 1.apply函数 可以让list或者vector的元素依次执行一遍调用的函数,输出的结果是list格式 2.sapply函数 原理和list一样,但是输出的结果是一个向量的形式 ...

  2. R语言函数化学习笔记3

    R语言函数化学习笔记3 R语言常用的一些命令函数 1.getwd()查看当前R的工作目录 2.setwd()修改当前工作目录 3.str()可以输出指定对象的结构(类型,位置等),同理还有class( ...

  3. R语言函数化学习笔记4

    条件语句和循环语句 当你说话时候用到了如果,此时条件出现了 举个条件函数的例子 sign_t<-function(x){ if(x>0){ return(1) }else if(x< ...

  4. R语言函数话学习笔记5

    使用Tidyverse完成函数化编程 (参考了家翔学长的笔记) 1.magrittr包的使用 里面有很多的管道函数,,可以减少代码开发时间,提高代码可读性和维护性 1.1 四种pipeline 1.1 ...

  5. C语言(函数)学习之strstr strcasestr

    C语言(函数)学习之[strstr]&[strcasestr]一.strstr函数使用[1]函数原型char*strstr(constchar*haystack,constchar*needl ...

  6. IOS学习笔记07---C语言函数-printf函数

    IOS学习笔记07---C语言函数-printf函数 0 7.C语言5-printf函数 ------------------------- ----------------------------- ...

  7. IOS学习笔记06---C语言函数

    IOS学习笔记06---C语言函数 --------------------------------------------  qq交流群:创梦技术交流群:251572072              ...

  8. 学习LoadRunner之C语言函数

    学习LoadRunner之C语言函数 Action() { /*strchr和strrchr的区别*/ /* char *strTest1="citms citms"; char ...

  9. 【C语言学习笔记】C语言函数执行成功时,返回1和返回0,究竟哪个好?

    基本上,没有人会将大段的C语言代码全部塞入 main() 函数,更好的做法是按照复用率高,耦合性低的原则,尽可能的将代码拆分不同的功能模块,并封装成函数.C语言代码的组合千变万化,因此函数的功能可能会 ...

随机推荐

  1. 刷题[bestphp's revenge]

    前置知识 phpsession反序列化 CRLF注入 即:利用漏洞,注入一个CRLF(\r\n)控制用户的Cookie,或者注入两个CRLF,控制返回给客户端的主体 php内置SoapClient类利 ...

  2. SQL Server 2008 R2执行存储过程sp_MailItemResultSets引起大量PREEMPTIVE_OS_WAITFORSINGLEOBJEC等待

      从监控工具DPA中发现一个数据库(SQL Server 2008 R2)的等待事件突然彪增,下钻分析发现数据库执行存储过程sp_MailItemResultSets时,引起了非常严重的等待(Hig ...

  3. gRPC-Protocol语法指南

    语法指南 (proto3) Defining A Message Type Scalar Value Types Default Values Enumerations Using Other Mes ...

  4. Python-函数式编程-map reduce filter lambda 三元表达式 闭包

    lambda 匿名函数,核心是作为算子,处理逻辑只有一行但具有函数的特性,核心用于函数式编程中 三元运算符 其实本质上是if分支的简化版,满足条件返回 if 前面的值,不满足条件返回 else后面的值 ...

  5. 引用类型之Object

    引用类型 引用类的值(对象)是引用类型的一个实例.在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起. 对象是某个特定引用类型的实例.新对象是使用new操作符后跟一个构造函数 ...

  6. 021 01 Android 零基础入门 01 Java基础语法 03 Java运算符 01 赋值运算符

    021 01 Android 零基础入门 01 Java基础语法 03 Java运算符 01 赋值运算符 本文知识点:Java中的赋值运算符 赋值运算符 赋值运算符从右往左运算 赋值运算符左边不能是常 ...

  7. c++ 动态库的加载

    转载:https://blog.csdn.net/ztq_12345/article/details/99677769 使用ide是vs, 使用Windows.h下的3个函数对动态库进行加载第一个:H ...

  8. 满屏的try-catch,不瘆得慌?

    持续原创输出,点击上方蓝字关注我 目录 前言 Spring Boot 版本 全局统一异常处理的前世今生 Spring Boot的异常如何分类? 如何统一异常处理? 异常匹配的顺序是什么? 总结 前言 ...

  9. 轻松理解JVM的分代模型

    前言 上篇文章我们一起对jvm的内存模型有了比较清晰的认识,小伙伴们可以参考JVM内存模型不再是秘密这篇文章做一个复习. 本篇文章我们将针对jvm堆内存的分代模型做一个详细的解析,和大家一起轻松理解j ...

  10. JVM性能调优(4) —— 性能调优工具

    前序文章: JVM性能调优(1) -- JVM内存模型和类加载运行机制 JVM性能调优(2) -- 垃圾回收器和回收策略 JVM性能调优(3) -- 内存分配和垃圾回收调优 一.JDK工具 先来看看有 ...