专栏首页码农UP2U奇怪的函数调用

奇怪的函数调用

早期文章

整理移动硬盘时,发现一个名为 attack 的目录,进去以后发现原来是一段简单的 C 语言代码。代码如下:

#include <stdio.h>

void Attack()
{
    while (1)
    {
        printf("Attack...\r\n");
    }

    getchar();
}

int main(int argc, char* argv[])
{
    int arr[5] = { 0 };

    arr[7] = (int)Attack;

    return 0;
}

看代码猜测,应该是死循环输出 Attack 字符串,因为毕竟是数组的越界访问(很多一些演示栈溢出的程序,都会用到数组的越界访问、字符串的拷贝等)。直接打开 VS 2015 进行编译、连接、运行,发现运行后什么结果都没有输出。当然了,这应该是被 VS 2015 的编译连接选项所导致的。进行一番设置,然后再进行执行。果然是死循环输出 Attack 字符串。

设置编译连接选项

类似这样的程序,在之前 VC 6 的编译环境下比较简单,到了高版本的 VS 下就需要设置相应的项目、编译、连接选项了,否则默认的安全选项会导致测试失败。不过好在这样的选项不多。这里逐一来进行设置。

在项目名上点击鼠标右键,在弹出的对话框上选择“属性”。

在“属性页”的“常规”选项中将字符集设置为“未设置”,如下图所示。

在 C/C++ 的“代码生成”选项中,将“安全检查”设置为“禁用安全检查(/GS-)”,如下图所示。

设置“连接器”下的“高级”选项,“随机地址”设置为“否”,“数据执行保护(DEP)”设置为“否”,如下图所示。

再次进行编译运行,发现死循环的测试成功了。如下图所示。注意观察右侧的滚动条,往下滚动速度很快。

原因分析

为什么会产生这样的显现呢?原因就是数组越界的赋值,代码如下:

arr[7] = (int)Attack;

在 C 语言中,函数名的名称就是函数的首地址。上面的赋值语句是将 arr[7] 的位置赋值为了 Attack 函数的地址。而 arr[7] 又是何物呢?在了解 arr[7] 之前,需要了解的是函数调用与函数的栈帧。

C 语言在调用函数时,根据函数的调用约定(C 语言的调用约定为 _cdcel)先将参数从右至左依次入栈,然后将返回地址压入栈中。当进入被调用的函数后,会先将 EBP 寄存器入栈,然后将 ESP 寄存器赋值给 EBP,最后通过 sub esp 来抬高栈顶,当作被调用函数的栈空间。EBP 作为基址指针,对当前函数(被调用函数)中的局部变量通过 [EBP - 0xXXX] 来进行访问,而对于调用时栈中的参数,则通过 [EBP + 0xXXX] 来进行访问。通常,[EBP + 4] 是返回地址,[EBP + 8] 是第一个参数的(如果有参数的话)。当函数返回时,通过 add esp 来收回栈空间,然后在执行 retn 指令时,会把栈中的保存的返回地址赋值给 EIP 寄存器,然后从返回地址继续执行代码。

有了上面的知识点,我们来看一下,上面程序的反汇编代码,代码如下:

004117F0    55              PUSH EBP
004117F1    8BEC            MOV EBP,ESP
004117F3    81EC DC000000   SUB ESP,0DC
004117F9    53              PUSH EBX
004117FA    56              PUSH ESI
004117FB    57              PUSH EDI
004117FC    8DBD 24FFFFFF   LEA EDI,DWORD PTR SS:[EBP-DC]
00411802    B9 37000000     MOV ECX,37
00411807    B8 CCCCCCCC     MOV EAX,CCCCCCCC
0041180C    F3:AB           REP STOS DWORD PTR ES:[EDI]
0041180E    C745 E8 0000000>MOV DWORD PTR SS:[EBP-18],0
00411815    33C0            XOR EAX,EAX
00411817    8945 EC         MOV DWORD PTR SS:[EBP-14],EAX
0041181A    8945 F0         MOV DWORD PTR SS:[EBP-10],EAX
0041181D    8945 F4         MOV DWORD PTR SS:[EBP-C],EAX
00411820    8945 F8         MOV DWORD PTR SS:[EBP-8],EAX
00411823    B8 04000000     MOV EAX,4
00411828    6BC8 07         IMUL ECX,EAX,7
0041182B    C7440D E8 5A104>MOV DWORD PTR SS:[EBP+ECX-18],test.0041105A
00411833    33C0            XOR EAX,EAX
00411835    52              PUSH EDX
00411836    8BCD            MOV ECX,EBP
00411838    50              PUSH EAX
00411839    8D15 50184100   LEA EDX,DWORD PTR DS:[411850]
0041183F    E8 19FAFFFF     CALL test.0041125D
00411844    58              POP EAX
00411845    5A              POP EDX
00411846    5F              POP EDI
00411847    5E              POP ESI
00411848    5B              POP EBX
00411849    8BE5            MOV ESP,EBP
0041184B    5D              POP EBP
0041184C    C3              RETN

以上反汇编代码来自 OD,如下图所示。

在上面 0041180E 到 0041181D 的位置处,是对 arr 数组进行初始化的过程。对应代码的如下:

int arr[5] = { 0 };

可以看到,C 语言的一行代码,对应到汇编就有好多行。在 0041182B 处也是一行赋值语句,代码如下:

MOV DWORD PTR SS:[EBP+ECX-18],test.0041105A

EBP + ECX - 18,此处 ECX 的值为 1C,1C - 18 = 4,那么此处相当于是如下代码:

MOV DWORD PTR SS:[EBP + 4], test.0041105A

回顾我们前面提到的,[EBP + 4] 的位置处保存着返回地址,也就是调用当前函数的函数的下一条指令。比如,A 函数中调用了 B 函数,当 B 函数执行完成后,会接着执行 A 函数中,调用 B 函数处的下一条指令。而此时,返回地址被覆盖为 0041105A,那么,这个 0041105A 是什么值?回顾上面的代码,代码如下:

arr[7] = (int)Attack;

0041105A 是 Attack 函数的首地址。那么当 main 函数返回时,相当于调用了 Attack 函数。而 Attack 函数中是一个死循环。

观察内存变化

看一下代码执行到 0041180E 时 ebp 的情况,如下图所示。

此时,可以看到 [ebp + 4] 中的值是 00411FCE,然后再观察 [ebp - 18] 到 [ebp - 8] 内存中的值都为 cc。然后,将代码执行到 00411823 处,观察 ebp 的情况,如下图所示。

可以看到 [ebp - 18] 到 [ebp - 8] 的栈空间都被初始化为了 0。接着继续执行代码,到 00411833 的地址处,再次观察 ebp 的情况,如下图所示。

可以看到,[ebp + 4] 的栈地址处的值被修改了,接着将代码执行向下执行,执行到 0041184C 后,也就是执行完 retn 后观察 EIP 寄存器的值,如下图所示。

可以看到,此时 EIP 的值为 0041105A,而反汇编代码处是一个跳表的位置。在当前位置接着在单步一下,如下图所示。

从图中可以看到,在注释位置有一个“attack...”字符串的提示,从这点就可以看出,该段反汇编代码是 Attack() 函数了。

到此,整个程序的执行就说清楚了。

总结

这种程序虽小,但是考察的是对函数调用时内存结构相关的知识。虽然简单的,但还是很有意思的。

本文分享自微信公众号 - 码农UP2U(gh_3c91b47a82e0),作者:码农UP2U

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 奇怪,Python有的函数调用需要两对括号?(2)

    在Python中,允许嵌套定义函数,也就是在一个函数A中可以定义另一个函数B。另外,在Python中,可调用对象可以分为三类:1)函数,2)类,3)含有特殊方法...

    Python小屋屋主
  • 奇怪,有的Python函数或方法调用需要两对括号?

    本文源自于一位读者的问题:为啥有的函数或方法调用要使用两对括号呢? 但是在我的印象里并没有这种用法啊。于是我简单扫了一眼代码,发现这位朋友说的并不是函数调用需要...

    Python小屋屋主
  • 洛谷P2759 奇怪的函数(log 二分)

    attack
  • SQL语句之奇形怪状的冷门函数

    lag() over() SELECT C.*,LAG(C.column,1) OVER(ORDER BY C.column) FROM Table C; 第...

    互联网金融打杂
  • 用SparkStreaming做奇怪的事

    作者:尹会生 无需授权即可转载,甚至无需保留以上版权声明 Spark Steaming 是非常著名的流式计算工具,这次用它来搞一个奇葩的需求:开发给定一个日志...

    刀刀老高
  • 千奇百怪的数据库故障

    总有很多情形,你无法想象,数据库的故障遭遇也是如此。 分享几则我们遇到过的客户恢复故障,与大家共为警醒: 服务器找不到了 某次客户找我们恢复数据库,说某个数据库...

    数据和云
  • 奇怪的 Python 整数缓存机制。

    当 a 和 b 的值皆为 1024 的时候,a is b 为 False,那这里我有一个问题:当 a 和 b 的值皆为 6 的时候,a is b 的输出结果是什...

    编程文青李狗蛋
  • 牛客 奇怪的排序问题(单调栈/遍历)

    链接:https://ac.nowcoder.com/acm/contest/10166/B 来源:牛客网

    Michael阿明
  • MyBatis 操作数据oracle 奇怪的乱码

    oracle 服务端的编码   NLS_LANG SIMPLIFIED CHINESE_CHINA.ZHS16GBK ? 两个编码集是一样的 排查代码 虽然整...

    冷冷
  • 细数 TS 中那些奇怪的符号

    TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象...

    阿宝哥
  • 用回调函数调用异步流回调函数内的数据

    本文的标题看起来也许比较绕,看不懂也没关系,后面会慢慢说明。写这篇文章是为了记录一下前几天我碰到的一个问题。

    凌虚
  • String是一个奇怪的引用类型

    这个例子,string是纯粹的引用类型,但是在函数传值时类似于值传递;我之前给前后示例的内存变化图吧:

    有态度的马甲
  • python3的函数调用

    python定义一个函数 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 () 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义...

    py3study
  • MYSQL的奇怪问题:varchar与数值比较

    我在工作中很少遇到所谓的‘奇怪的问题’。所以对于‘奇怪的问题’我还是很期盼的,可能很早的时候就被某些XX开发规范给限制住了,也就很少遇到这些所谓的奇怪的问题。所...

    CainGao
  • 浅谈javascript中的回调函数javascript中的函数匿名函数回调函数回调函数的使用回调函数实例总结

    要理解javascript中的回调函数,首先我们就要对javascript中的函数有一定的理解,所以我们先从javascript中函数谈起,讲讲它与其他语言中的...

    desperate633
  • 神奇的δ-函数

    大学时曾上过一门电工学的课,这也是为数不多能真正意义上学懂的专业课之一,其中有一章就是电路暂态分析,即当电压发生突变时的电容电感电流的变化情况,一般来讲这里的暂...

    用户7506105
  • Pytorch 各种奇葩古怪的使用方法

    不间断更新。。。 增减layer 增加layer 增加layer很方便,可以使用model.add_module('layer name', layer)。 ...

    marsggbo
  • 用C++模拟"奇怪"的手机计算器

    风水轮流转,出道多年一向默默无闻的手机计算器突然成了网红,类似手机计算器“全线阵亡”这样的言论充斥各种媒体。问题的起源是网友发现手机计算器上计算10%+10%这...

    诸葛青云
  • 回调函数的工作机制 回调函数的用途

    在一般人的眼中,对回调函数并不是十分的了解。实际上,在现在的互联网技术上这种函数有着十分重要的地位。这种函数不仅仅可以使得编程的效率大大提升,还是实现一些特殊功...

    用户8739405

扫码关注云+社区

领取腾讯云代金券