前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >奇怪的函数调用

奇怪的函数调用

作者头像
码农UP2U
发布2021-09-02 15:27:04
1.7K0
发布2021-09-02 15:27:04
举报
文章被收录于专栏:码农UP2U码农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() 函数了。

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

总结

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农UP2U 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档