我正在做一个关于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到了。
答案 0 :(得分:1)
fread()
将nitems
个size
个元素读取到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()
,argc
,argv
,envp
,指向它们的各种指针和填充的空间,加上变量ASLR偏移量,你的堆栈中只有大约8%的可能性小于4096字节,这样你就可以看到带有ASLR的SEGV。只是尝试了很多次,你应该每12次尝试一次。
auxp
答案 1 :(得分:0)
关于堆栈上的fread()
参数,这是正确的 - 这些参数在被调用方将调用者的BP值压入堆栈之前存储,因此它们是调用者堆栈帧的一部分 - 检查{{3} }。在x86-64上,你不会看到它们,因为它们会在寄存器中传递给被调用者。
对于ASLR错误,请使用perror()
获取错误说明。或者更好地为glibc安装debuginfo包,以便您可以将代码跟踪到fread()
本身。
答案 2 :(得分:-1)
我从来没有弄清楚为什么fread()函数失败了所以我用fgets()函数替换它并完成了赋值。
感谢大家的帮助。 这个论坛很棒!