【问题标题】:Why does scanf() appear to load an address lower than that of the buffer I am writing to?为什么 scanf() 似乎加载的地址低于我正在写入的缓冲区的地址?
【发布时间】:2020-10-19 22:08:24
【问题描述】:

我为班级分配编写了一个故意缓冲区溢出的 C 程序。在我的程序中,我有一个 main 函数来接受来自用户的名称作为长度为 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.

【问题讨论】:

  • 不相关,在 -O2 优化中对此进行的铿锵反汇编做了一些我觉得很了不起的事情。 printf 内部的 for 循环被优化为使用 putchar 代替。我确实没有看到这一点。 See it here.
  • 请注意,您不需要覆盖返回地址,只要覆盖[ebp-0x4](54 字节外)就足够了,它最终会以esp 结束,从而导致崩溃。跨度>
  • ebp-0x3aname 的地址。额外的 8 个字节是堆栈帧的其余部分,这是 ebp 被推送的地方。
  • @WhozCraig :大多数现代 GCC 和 CLANG 都会对 printf 做同样的事情,如果可以的话,将其解析为 putsputchar。见:stackoverflow.com/questions/36343733/…
  • 另外,gcc 处理main 特别是堆栈对齐代码。正常的功能看起来会有所不同。

标签: c assembly x86 buffer-overflow


【解决方案1】:

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

缓冲区的开始,但是为什么你会假设它应该在ebp的特定偏移处呢?没有书面规则说函数应该有一个与其局部变量大小完全相同的堆栈。编译器几乎可以做它想做的任何事情。事实上,它最终可能会使用更多空间来保存寄存器值maintain alignment,或者甚至只是在感觉如此的时候浪费它。这个问题被问过无数次了,可惜真的没有一个确定的答案,你不妨成为一个GCC开发者来尝试理解它:')

这里有一些现有的问题以及很好的答案供参考:

除了上述之外,您正在编译时没有进行任何优化,正如我从 add esp,0x10; sub esp,0x8 这样的无意义指令中可以看出的。 GCC 喜欢在未启用优化时将内容移回堆栈或移出堆栈,并且也不太注意以尽可能最佳的方式管理堆栈空间。

为什么当我尝试运行这个缓冲区溢出时,只需要 54 个字符就可以溢出缓冲区

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

为了更清楚地说明这一点,我们假设最初调用main() 时的esp0x1000。如果我的数学是正确的,调用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: ????

在上图中,As 表示您的数组,它从0x0fbe 开始。

您很可能恰好在 54(+1 终止符 = 55)处遇到分段错误,因为这正是更改保存的 esp+0x4 值(在示例中为 0x1004)并在以后引起麻烦所需的最低限度当它用于恢复 esp (mov ecx,DWORD PTR [ebp-0x4]; leave; lea esp,[ecx-0x4]) 时以无效的堆栈指针结束。

【讨论】:

    猜你喜欢
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    • 2014-10-17
    • 2011-04-23
    • 2018-10-20
    • 2021-06-13
    • 1970-01-01
    • 2013-07-25
    相关资源
    最近更新 更多