为什么scanf似乎加载的地址低于我正在写入的缓冲区的地址?

问题描述

我编写了一个C程序,故意为类分配缓冲区溢出。在我的程序中,我有一个主要功能,可以接受用户名称作为长度为50的字符数组。然后,该名称作为长度为50的字符数组传递,并在其中打印消息"Hello,user!"用户将替换为用户提供的名称。我没有对scanf()函数进行任何长度检查,而是获取输入,直到遇到换行符为止。结果,我能够使缓冲区溢出,覆盖main的返回地址并引起分段错误

当我使用GDB命令反汇编main时,可以看到地址[ebp - 0x3a]已装入并压入堆栈以用作scanf的参数功能(见下图)。我以为这是缓冲区的开始,直到我将0x3a转换为十进制并发现其值为58。为什么要另外分配8个字节给字符缓冲区?为什么当我尝试运行此缓冲区溢出时,当缓冲区长度看起来像距ebp开头58个字节和返回地址之外62个字节时,只需要54个字符来溢出缓冲区?再次,我计算了通过使用ebp-0x3a到返回地址的长度。

代码

#include <stdio.h>
#include <string.h>
void printHello(char fname[]);
int main() {
 
    char name[50]; 
    printf("Please enter a name to print a hello message!"); 
    scanf("%[^\n]",name); 

    printHello(name); 
    return 0;
}
void printHello(char fname[50]){

    int strLen = strlen(fname);

    printf("Hello,");
    for(int i=0; i<strLen; i++){

        printf("%c",fname[i]);
     }
       printf("!\n");
}

反汇编的main函数

Dump of assembler code for function main:
   0x080484fb <+0>: lea    ecx,[esp+0x4]
   0x080484ff <+4>: and    esp,0xfffffff0
   0x08048502 <+7>: push   DWORD PTR [ecx-0x4]
   0x08048505 <+10>:    push   ebp
   0x08048506 <+11>:    mov    ebp,esp
   0x08048508 <+13>:    push   ecx
   0x08048509 <+14>:    sub    esp,0x44
   0x0804850c <+17>:    sub    esp,0xc
   0x0804850f <+20>:    push   0x8048640
   0x08048514 <+25>:    call   0x8048390 <printf@plt>
   0x08048519 <+30>:    add    esp,0x10
   0x0804851c <+33>:    sub    esp,0x8
   0x0804851f <+36>:    lea    eax,[ebp-0x3a]
   0x08048522 <+39>:    push   eax
   0x08048523 <+40>:    push   0x804866e
   0x08048528 <+45>:    call   0x80483e0 <__isoc99_scanf@plt>
   0x0804852d <+50>:    add    esp,0x10
   0x08048530 <+53>:    sub    esp,0xc
   0x08048533 <+56>:    lea    eax,[ebp-0x3a]
   0x08048536 <+59>:    push   eax
   0x08048537 <+60>:    call   0x804854c <printHello>
   0x0804853c <+65>:    add    esp,0x10
   0x0804853f <+68>:    mov    eax,0x0
   0x08048544 <+73>:    mov    ecx,DWORD PTR [ebp-0x4]
   0x08048547 <+76>:    leave  
   0x08048548 <+77>:    lea    esp,[ecx-0x4]
   0x0804854b <+80>:    ret    
End of assembler dump.

解决方法

我以为这是缓冲区的开始,直到将0x3a转换为十进制并发现其值为58。

那个缓冲区的开始,但是为什么要假定它应该与ebp有一个特定的偏移量呢?没有书面规则说函数应该具有与其局部变量大小完全相同的堆栈。几乎允许编译器执行任何所需的操作。实际上,它可能最终会占用更多空间以保留寄存器值maintain alignment,或者甚至只是在感觉上就浪费它。这个问题被问了无数次,而且确实没有确切的答案,您不妨成为GCC开发人员来尝试并理解它。

以下是一些已有的问题,给出了很好的参考答案:

除上述内容外,您还没有进行任何优化,正如我从add esp,0x10; sub esp,0x8这样的荒谬的指令中可以看出的。在未启用任何优化的情况下,GCC喜欢将东西往回移和从栈中移出,并且也并不太在意以最佳方式管理栈空间。

为什么当我尝试运行此缓冲区溢出时,只需要54个字符即​​可使缓冲区溢出

从技术上讲,您只需要输入50个字符即可使缓冲区溢出(\0将自动添加终止符scanf())。但是,这些可能不足以“破坏”任何东西。

为了更清楚一点,让我们假设最初称为main()的{​​{1}}时是esp。如果我的数学正确,那么在调用0x1000时(恰好在scanf()之前)的堆栈布局应为以下内容:

call

在上图中,esp -> 0x0fac: 0x804866e // scanf() arg1 0x0fb0: 0x0fbe // scanf() arg2 0x0fb4: ???? 0x0fb8: ???? 0x0fbc: ??AA <-- eax == 0x0fbe == ebp-0x3a 0x0fc0: AAAA 0x0fc4: AAAA 0x0fc8: AAAA 0x0fcc: AAAA 0x0fd0: AAAA 0x0fd4: AAAA 0x0fd8: AAAA 0x0fdc: AAAA 0x0fe0: AAAA 0x0fe4: AAAA 0x0fe8: AAAA 0x0fec: AAAA 0x0ff0: ???? 0x0ff4: 0x1004 // saved original esp+0x4,later used to restore esp ebp -> 0x0ff8: <saved ebp> 0x0ffc: ???? 0x1000: ???? // 0x1000 original esp at start of main() 0x1004: ???? 表示您的数组,该数组从A开始。

您很可能会在54(+1终止符= 55)处遇到分段错误,因为这恰好是更改保存的0x0fbe值(在示例esp+0x4中)和稍后会在用于还原0x1004esp)并最终导致无效的堆栈指针时引起麻烦。