2

我正在做有关 bufferOverflow 的家庭作业。测试程序用于fread()从文件中读取 4096 个字节。

当我将 kernel.randomize_va_space 设置为 0,并在 gdb 中运行程序时,我可以看到 fread() 命令没有返回任何内容。

如果我将 kernel.randomize_va_space 设置为 1 或 2,并使用 gdb 重新运行程序,我可以在 fread 存储文件的缓冲区中看到预期的数据。

为什么 ASLR 会导致 fread 停止正常工作?

仅供参考:这是 ubuntu 12.0.4 64 位,该程序是使用 gcc 的 -c99 和 -m32 标志编译的。

我为此作业给出的测试程序如下:

#include <stdio.h>

#define READSIZE 0x1000

void countLines(FILE* f){
  char buf[0x400];//should be big enough for anybody
  int lines=0;
  fread(buf,READSIZE,1,f);  

  for(int i=0;i<0x400;i++)
    if(buf[i] == '\n')
      lines++;


  printf("The number of lines in the file is %d\n",lines);
  return;
}

int main(int argc,char** argv){
  if(argc<2){
    printf("Proper usage is %s <filename>\n",argv[0]);
    exit(0);
  }
  FILE* myfile=fopen(argv[1],"r"); 
  countLines(myfile);
  return 0;
}

当我在 gdb 中运行它时,我将断点放在该行上:

  for(int i=0;i<0x400;i++)

然后在 gdb 中执行以下操作:

(gdb) x $esp
0xbffff280    0xbffff298

如果我做:

(gdb) x /12wx $esp

我可以看到前 4 个字节是 buf 的地址,接下来的 4 个字节是传递给 fread 的 0x1000,接下来的 4 个字节是也传递给 fread 的 0x01。

这在我看来就像 fread 函数的堆栈框架而不是countLines().

为什么 $esp 不指向当前堆栈帧而不是刚刚退出的堆栈帧?

更新

我修改了代码如下:

#include <stdio.h>

#define READSIZE 0x1000

void countLines(FILE* f){
  char buf[0x400];//should be big enough for anybody
  int lines=0;
int ferr=0;
  fread(buf,READSIZE,1,f);  
ferr=ferror(f);
    if (ferr)
        printf("I/O error when reading (%d)\n",ferr);
    else if (feof(f))
        printf("End of file reached successfully\n");

  for(int i=0;i<0x400;i++)
    if(buf[i] == '\n')
      lines++;

  printf("The number of lines in the file is %d\n",lines);
  return;
}

int main(int argc,char** argv){
  if(argc<2){
    printf("Proper usage is %s <filename>\n",argv[0]);
    exit(0);
  }
  FILE* myfile=fopen(argv[1],"r"); 
  countLines(myfile);
  return 0;
}

当我在禁用 ASLR 的情况下运行它时,我得到:I/O errorwhen readin (1)

如果我在启用 ASLR(值 = 1)的情况下运行它,则达到 EOF。

4

3 回答 3

1

fread()nitems将最多 size 的元素读size入 . 指向的数组中ptr。确保数组足够大是程序员的责任。

最后一部分很重要。fread()无法知道数组的实际大小。它会愉快地读取内容并将其存储到数组的末尾。访问数组末尾会导致未定义行为:无法保证会发生什么,特别是在这种情况下,它可能导致任意代码执行。

至于为什么有时会轰炸有时不会:

没有 ASLR 你有这个:

(gdb) x $esp
0xbffff280    0xbffff298

与地址处的缓冲区0xbffff298。堆栈的顶部位于 处0xbfffffff,在缓冲区的开头和堆栈的结尾之间留下 3432 个字节。当尝试读入缓冲区时,进程0xc0000000在用户空间中没有任何映射(您可以使用 来检查cat /proc/<pid>/maps,以查看进程的用户空间映射),因此底层read()系统调用失败并显示EFAULT - Bad address.

使用 ASLR,堆栈放置有一些随机化。堆栈基数在 x86 ( arch/x86/include/asm/elf.h ) 上具有约 11 位1的随机性:

/* 1GB for 64bit, 8MB for 32bit */
#define STACK_RND_MASK (test_thread_flag(TIF_IA32) ? 0x7ff : 0x3fffff)

并且栈顶有 9 个额外的随机位(arch/x86/kernel/process.c:arch_align_stack(),从fs/binfmt_elf.c:create_elf_tables()调用):

if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
    sp -= get_random_int() % 8192;
return sp & ~0xf;

因此,arch_align_stack()以 16 字节增量偏移堆栈顶部 [0,8176] 字节,即初始堆栈指针可以从其原始位置移动 0、16、32、...、8176 字节。

因此,使用 3432 字节(缓冲区,加上, , ,的空间argc,指向它们的各种指针和填充),加上可变的 ASLR 偏移量,您的堆栈上只有约 8% 的机会少于 4096 字节,这将允许您查看启用了 ASLR 的 SEGV。只要尝试很多次,你应该每 12 次尝试就会看到一次。 argvenvpauxp


1栈基也可以移动一个额外的页面,参见fs/exec.c:setup_arg_pages()

stack_top = arch_align_stack(stack_top);
stack_top = PAGE_ALIGN(stack_top);
于 2013-10-29T16:12:59.487 回答
0

至于fread()堆栈上的参数,这是正确的 - 这些是在调用者的 BP 值被被调用者推送到堆栈之前存储的,因此它们是调用者堆栈帧的一部分 - 检查x86 堆栈帧描述。在 x86-64 上,您不会看到它们,因为它们在寄存器中传递给被调用者。

至于 ASLR 错误,请使用perror(). 或者更好地为 glibc 安装 debuginfo 包,以便您可以跟踪代码fread()本身。

于 2013-10-18T15:29:58.477 回答
-1

我从来没有弄清楚为什么 fread() 函数会失败,所以我用 fgets() 函数替换它并完成了分配。

谢谢大家的帮助。这个论坛很棒!

于 2013-10-28T17:33:52.170 回答