奇怪的函数调用

共 4993字,需浏览 10分钟

 ·

2021-08-18 16:21


早期文章


        整理移动硬盘时,发现一个名为 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 EBP004117F1    8BEC            MOV EBP,ESP004117F3    81EC DC000000   SUB ESP,0DC004117F9    53              PUSH EBX004117FA    56              PUSH ESI004117FB    57              PUSH EDI004117FC    8DBD 24FFFFFF   LEA EDI,DWORD PTR SS:[EBP-DC]00411802    B9 37000000     MOV ECX,3700411807    B8 CCCCCCCC     MOV EAX,CCCCCCCC0041180C    F3:AB           REP STOS DWORD PTR ES:[EDI]0041180E    C745 E8 0000000>MOV DWORD PTR SS:[EBP-18],000411815    33C0            XOR EAX,EAX00411817    8945 EC         MOV DWORD PTR SS:[EBP-14],EAX0041181A    8945 F0         MOV DWORD PTR SS:[EBP-10],EAX0041181D    8945 F4         MOV DWORD PTR SS:[EBP-C],EAX00411820    8945 F8         MOV DWORD PTR SS:[EBP-8],EAX00411823    B8 04000000     MOV EAX,400411828    6BC8 07         IMUL ECX,EAX,70041182B    C7440D E8 5A104>MOV DWORD PTR SS:[EBP+ECX-18],test.0041105A00411833    33C0            XOR EAX,EAX00411835    52              PUSH EDX00411836    8BCD            MOV ECX,EBP00411838    50              PUSH EAX00411839    8D15 50184100   LEA EDX,DWORD PTR DS:[411850]0041183F    E8 19FAFFFF     CALL test.0041125D00411844    58              POP EAX00411845    5A              POP EDX00411846    5F              POP EDI00411847    5E              POP ESI00411848    5B              POP EBX00411849    8BE5            MOV ESP,EBP0041184B    5D              POP EBP0041184C    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() 函数了。


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


总结

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


更多文章


浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报