禁用ASLR会导致fread()在c程序中失败

时间:2013-10-17 16:12:36

标签: c stack

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

当我将kernel.randomize_va_space设置为0,并在gdb中运行程序时,我可以看到fread()命令什么都不返回。

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

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

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

我为此作业提供的测试程序如下:

#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个字节是0x01,它也被传递给fread。

这对我来说就像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运行它时,我得到: readin(1)

时的I / O错误

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

3 个答案:

答案 0 :(得分:1)

fread()nitemssize个元素读取到ptr指向的数组中。程序员负责确保阵列足够大。

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

至于为什么有时它会炸弹,有时不会炸弹:

没有ASLR你有这个:

(gdb) x $esp
0xbffff280    0xbffff298

缓冲区位于地址0xbffff298。堆栈的顶部位于0xbfffffff,在缓冲区的开始和堆栈的末尾之间留下3432个字节。当尝试读入缓冲区时,进程没有映射到用户空间中的0xc0000000(可以使用cat /proc/<pid>/maps检查,以查看进程&#39;用户空间映射),因此基础{{1系统调用失败并显示read()

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

EFAULT - Bad address

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

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

因此,if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space) sp -= get_random_int() % 8192; return sp & ~0xf; 以16字节为增量偏移堆栈顶部[0,8176]字节,即初始堆栈指针可以从其原始位置移动0,16,32,...,8176字节位置。

因此,使用3432字节(缓冲区,arch_align_stack()argcargvenvp,指向它们的各种指针和填充的空间,加上变量ASLR偏移量,你的堆栈中只有大约8%的可能性小于4096字节,这样你就可以看到带有ASLR的SEGV。只是尝试了很多次,你应该每12次尝试一次。

<小时/> 1 堆栈库也可以移动另一页,请参阅fs/exec.c:setup_arg_pages()

auxp

答案 1 :(得分:0)

关于堆栈上的fread()参数,这是正确的 - 这些参数在被调用方将调用者的BP值压入堆栈之前存储,因此它们是调用者堆栈帧的一部分 - 检查{{3} }。在x86-64上,你不会看到它们,因为它们会在寄存器中传递给被调用者。

对于ASLR错误,请使用perror()获取错误说明。或者更好地为glibc安装debuginfo包,以便您可以将代码跟踪到fread()本身。

答案 2 :(得分:-1)

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

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