C 缓冲区溢出 - 输入多少字节

C Buffer Overflow - how many bytes to input

我编写了一个简单的 C 程序来尝试理解缓冲区溢出。我试图溢出输入缓冲区,以便将标志更改为 true 并且程序输出 "got here"。程序在这里(假设你有 password.txt 其中有 hey):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv){
  int flag = 0;

  char pwd[5];
  char input[5];

  FILE *f = fopen("password.txt", "r");

  fscanf(f, "%s", pwd);
  fclose(f);

  strcpy(input, argv[1]);

  if(strcmp(input, pwd)){
    printf("wrong password!\n");
  }
  else{
    flag = 1;
    printf("correct password!\n");
  }
  if(flag){
    printf("you got into the secret place\n");
  }
  return 0;
}

所以我猜在堆栈上我们有这样的东西:

[high addresses]
flag   ---> 4 bytes
pwd    ---> 8 bytes
input  ---> 8 bytes
[low addresses]

所以我想我只需要给程序 17 个字节来覆盖标志变量。所以我给了它 aaaaaaaaaaaaaaaaa 但这没有用。我不得不给它 a 23 次,所以 23 个字节。为什么 17 个字节不够?

尝试此操作时,您可能 运行 遇到多个问题。

  • 局部变量在栈上的顺序不一定与代码中的顺序相同。编译器可以随意重新排列它们。

  • 编译器通常将数据与边界对齐以加快执行速度。这意味着不同局部变量之间的 space 可能比您想象的要大得多。堆栈变量在 8 或 16 字节边界上对齐的情况并不少见,即使它的内容要小得多。

您可以使用 objdump -D 反汇编您的程序或使用 gdb 调试它。这将使您更好地了解程序中的堆栈对齐方式。

免责声明:我将 Ubuntu 14.04 与 gcc 版本 4.8.4 一起使用,并符合您的要求 因此代码 gcc -m32 -g -ansi -pedantic -Wall temp.c -o temp。不同的 编译器或 gcc 的不同选项很可能会给出 不同的结果。

我还稍微修改了你的代码,以便更容易找到东西,

  1. 我将第 6 行更改为 int flag = 0x41414141;
  2. 我将第 25 行更改为 if(flag==1){

编译后我运行GDB下的可执行文件,设置了断点 在主要。然后反汇编 main (在将 disassembly-flavor 设置为 英特尔),我们得到:

(gdb) disass
Dump of assembler code for function main:
   0x0804857d <+0>: push   ebp
   0x0804857e <+1>: mov    ebp,esp
   0x08048580 <+3>: and    esp,0xfffffff0
   0x08048583 <+6>: sub    esp,0x30
   0x08048586 <+9>: mov    eax,DWORD PTR [ebp+0xc]
   0x08048589 <+12>:    mov    DWORD PTR [esp+0xc],eax
=> 0x0804858d <+16>:    mov    eax,gs:0x14
   0x08048593 <+22>:    mov    DWORD PTR [esp+0x2c],eax
   0x08048597 <+26>:    xor    eax,eax
   0x08048599 <+28>:    mov    DWORD PTR [esp+0x18],0x41414141

前四行是main的序言 注意是 sub esp,0x30 行,我们在其中设置 函数的栈帧。如您所见,我们正在减去 48 个字节 从特别是(实际上多了一点,因为我们首先对齐了栈帧 到 16 字节边界)。

现在,我们可以通过查看值来查看堆栈框架的位置 对于 ESP 和 EBP:

(gdb) info registers esp
 esp            0xffffd110  0xffffd110

(gdb) info registers ebp
ebp            0xffffd148   0xffffd148

我们可以找到堆栈帧中的位置;

(gdb) print &pwd
 = (char (*)[5]) 0xffffd132

(gdb) print &flag
 = (int *) 0xffffd128

(gdb) print &input
 = (char (*)[5]) 0xffffd137

(gdb) print &f
 = (FILE **) 0xffffd12c

由此我们现在可以推断出我们的堆栈布局。这个记忆影像是 在程序读取命令行的 运行ning 之后采取 参数是字符串 BBBBB(回想一下 B 的 ASCII 代码是 0x42,所以很容易看到 0x42 字节的序列)

(gdb) x/56xb $esp
0xffffd110: 0x37    0xd1    0xff    0xff    
0xffffd114: 0xbf    0xd3    0xff    0xff
0xffffd118: 0x32    0xd1    0xff    0xff    
0xffffd11c: 0xe4    0xd1    0xff    0xff
0xffffd120: 0x02    0x00    0x00    0x00    
0xffffd124: 0xe4    0xd1    0xff    0xff
0xffffd128: 0x41    0x41    0x41    0x41         (flag) 
0xffffd12c: 0x08    0xb0    0x04    0x08         (f)
0xffffd130: 0xc4    0xf3    
0xffffd132: 0x68    0x65    0x79    0x00    0xff (pwd buffer)   
0xffffd137: 0x42    0x42    0x42    0x42    0x42 (input buffer) 
0xffffd13c: 0x00    0xd5    0x61    0x5d
0xffffd140: 0x60    0x86    0x04    0x08    
0xffffd144: 0x00    0x00    0x00    0x00

另请注意,如果我将命令行参数设置为 BBBBBBBB,我们就会得到这个 对于我们堆栈框架的内容

(gdb) x/56xb $esp
0xffffd110: 0x37    0xd1    0xff    0xff    0xbc    0xd3    0xff    0xff
0xffffd118: 0x32    0xd1    0xff    0xff    0xe4    0xd1    0xff    0xff
0xffffd120: 0x02    0x00    0x00    0x00    0xe4    0xd1    0xff    0xff
0xffffd128: 0x41    0x41    0x41    0x41    0x08    0xb0    0x04    0x08
0xffffd130: 0xc4    0xf3    0x68    0x65    0x79    0x00    0xff    0x42
0xffffd138: 0x42    0x42    0x42    0x42    0x42    0x42    0x42    0x00
0xffffd140: 0x60    0x86    0x04    0x08    0x00    0x00    0x00    0x00

注意标志变量的内容保持不变,但现在溢出的输入缓冲区的内容移向堆栈的顶部。回想一下,在 x86 中,堆栈向下增长(较低的内存地址)。另外,因为缓冲区向上增长,所以我们可以使用缓冲区溢出来覆盖堆栈上存储的 EIP。

所以在我的系统上,我认为不可能用用户输入覆盖 flag 变量。您的系统在堆栈上的布局可能不同(您需要做类似的练习来验证这一点)。

另外请注意,变量在堆栈上的位置与它们在源文件中声明的顺序无关。