linux中非法内存,Linux下数组非法访问导致内存破坏 —— 引发segmentation fault的原因...

2012-02-05 wcdj

1, 调试时必需的栈知识

2, 数组非法访问导致内存破坏

调试时必需的栈知识

栈(stack)是程序存放数据的内存区域之一,其特征是LIFO(Last In First Out, 后进先出)式数据结构,即后放进的数据最先备取出。向栈中存储数据的操作称为PUSH(压入),从栈中取出数据称为POP(弹出)。在保存动态分配的自动变量时要使用栈。此外在函数调用时,栈还用于传递函数参数,以及用于保存返回地址和返回值。

#include

#include

#include

#define MAX (1UL << 20)

typedef unsigned long long u64;

typedef unsigned int u32;

u32 max_addend = MAX;

u64 sum_till_MAX(u32 n)

{

u64 sum;

n++;

sum = n;

if (n < max_addend)

sum += sum_till_MAX(n);

return sum;

}

int main(int argc, char** argv)

{

u64 sum = 0;

if ((argc == 2) && isdigit(*(argv[1])))

max_addend = strtoul(argv[1], NULL, 0);

if (max_addend > MAX || max_addend == 0) {

fprintf(stderr, "Invalid number is specified\n");

return 1;

}

sum = sum_till_MAX(0);

printf("sum(0..%lu) = %llu\n", max_addend, sum);

return 0;

}

计算从0到10的总和:

$ gcc -o sum -g sum.c

$ ./sum 10

sum(0..10) = 55

函数调用和栈的关系 —— 函数调用前后栈的变化情况

栈上依次保存了传给函数的参数、调用者的返回地址、上层栈帧指针和函数内部使用的自动变量。此外,处理有些函数时还会用栈来临时保存寄存器。每个函数都独自拥有这些信息,称为栈帧(stack frame)。此时需要适当地设置表示栈帧起始地址的帧指针(FP)。此外,栈指针(SP)永远指向栈的顶端。

0818b9ca8b590ca3270a3433284dd417.png

数组非法访问导致内存破坏

错误地操作数组导致的典型bug之一就是缓冲区溢出,即向我们分配的内存空间之外写入数据。特别是,如果这类bug发生在栈上的缓冲区中,就可能引发安全漏洞,因此出现了许多预防措施和应对措施,如通过指定缓冲区大小来编写安全的函数、源代码检查工具、编译器在构建时的报警等。即便如此,这种bug仍时有发生。

下面通过一个简单的例子进行分析:

#include

#include

char szInfo[] = "Oops! here is a buffer overflow, wcdj";

void func()

{

char buf[5];

strcpy(buf, names);

}

int main()

{

func();

return 0;

}

编译:

gcc -Wall -g -o hack28 hack28.c

打开coredump:

ulimit -c unlimited

运行:

gerryyang@wcdj:~/test/HACK>./hack28

段错误 (coredumped)

gerryyang@wcdj:~/test/HACK> ls -rtl

总计 160K

-rw-r--r-- 1 gerryyang users    186 2012-02-05 14:17 hack28.c

-rwxr-xr-x 1 gerryyang users   7986 2012-02-05 14:17 hack28

-rw------- 1 gerryyang users 1474562012-02-05 14:17 core

下面对程序进行调试:

gerryyang@wcdj:~/test/HACK> gdb hack28 core

GNU gdb 6.6

Copyright (C) 2006 Free SoftwareFoundation, Inc.

GDB is free software, covered by the GNUGeneral Public License, and you are

welcome to change it and/or distributecopies of it under certain conditions.

Type "show copying" to see theconditions.

There is absolutely no warranty forGDB.  Type "show warranty" fordetails.

This GDB was configured as"i586-suse-linux"...

Using host libthread_db library"/lib/libthread_db.so.1".

warning: Can't read pathname for load map:Input/output error.

Reading symbols from /lib/libc.so.6...done.

Loaded symbols for /lib/libc.so.6

Reading symbols from/lib/ld-linux.so.2...done.

Loaded symbols for /lib/ld-linux.so.2

Core was generated by `./hack28'.

Program terminated with signal 11,Segmentation fault.

#0 0x73692065 in ?? ()

(gdb) bt

#0 0x73692065 in ?? ()

#1 0x62206120 in ?? ()

#2 0x65666675 in ?? ()

#3 0x766f2072 in ?? ()

#4 0x6c667265 in ?? ()

#5 0x202c776f in ?? ()

#6 0x6a646377 in ?? ()

#7 0xbfb48600 in ?? ()

#8 0xb7e5a89c in __libc_start_main () from /lib/libc.so.6

Backtrace stopped: previous frame inner tothis frame (corrupt stack?)

(gdb)x/i 0x73692065

0x73692065:     Cannot access memory at address 0x73692065

(gdb)x/i 0x62206120

0x62206120:    Cannot access memory at address0x62206120

(gdb)

backtrace无法正确显示,在这种情况下backtrace是不可靠的。因为backtrace显示的内容很可能不是实际跟踪的内容。即代码突然跳转到或者调用了错误的地址 0x73692065,导致了segmentation fault的发生。

注意:栈帧#0、#1显示的地址上很难放置程序、共享内存等。大多数i386架构的Linux发行版中,程序被定位到0x08000000地址附近,共享库被定位到0xb0000000之后的地址。因此,记住在调试对象的环境中,标准情况下程序和共享库会被定位到什么地址,在阅读backtrace时就会很方便。

(gdb) x/30c $sp

0xbfb48660:     32 ' ' 97 'a'  32 ' '  98 'b' 117 'u' 102 'f' 102 'f' 101 'e'

0xbfb48668:     114 'r' 32 ' '  111 'o' 118 'v' 101 'e' 114 'r' 102 'f' 108'l'

0xbfb48670:     111 'o' 119 'w' 44 ','  32 ' ' 119 'w' 99 'c'  100 'd' 106 'j'

0xbfb48678:     0 '\0' -122 '\206'     -76 '´' -65 '¿' -100 '\234'     -88 '¨'

(gdb) x/30c $sp-15

0xbfb48651:     31 '\037'       -8 'ø' 79 'O'  111 'o' 112 'p' 115 's' 33'!'  32 ' '

0xbfb48659:     104 'h' 101 'e' 114 'r'101 'e' 32 ' '  105 'i' 115's'32 ' '

0xbfb48661:     97 'a' 32 ' '  98 'b'  117 'u' 102 'f' 102 'f' 101 'e' 114 'r'

0xbfb48669:     32 ' ' 111 'o' 118 'v' 101 'e' 114 'r' 102 'f'

(gdb)p(char*)$sp-15

$1 = 0xbfb48651 "\037øOops! here is abuffer overflow, wcdj"

(gdb) x/30w $sp-15

0xbfb48651:     0x6f4ff81f      0x20217370      0x65726568      0x20736920

0xbfb48661:     0x75622061      0x72656666      0x65766f20      0x6f6c6672

0xbfb48671:     0x77202c77      0x006a6463      0x9cbfb486      0x01b7e5a8

0xbfb48681:     0x04000000      0x0cbfb487      0x3dbfb487      0x00b7f9cc

0xbfb48691:     0x90000000      0x01b7f8f6      0x01000000      0xf4000000

0xbfb486a1:     0xc0b7f81f      0x00b7fabc      0xd8000000      0x80bfb486

0xbfb486b1:     0x6140be86      0x0048efa8      0x00000000      0x00000000

0xbfb486c1:     0x70000000      0xcdb7fa20

寻找错误地写入0x73692065这个数据的地方。很重要的是需要怀疑数据是否为字符串的一部分,因为错误地将数据写入地址的典型情况之一就是字符串复制。由于字符串的输入长度很难预测,若缓冲区过小,再加上对输入字符串的长度检查不完善,就可能发生这种状况。考虑到i386架构为littleendian,因此怀疑是“...eis...”这个字符串的一部分被写入了。然后此假设进行验证。首先栈指针指向的空间前后几乎都是字符串,因此将这种字符串写入栈上的错误位置的可能性很高,而且通过代码发现buf只有5字节长,复制的字符串超过了这个长度,从而导致缓冲区溢出。

通过以上分析,错误的原因就是,strcpy()写入字符串的空间,原本是用于保存返回main()地址的空间,所以func()结束之后返回main()时,就把“...e is...”对应的0x73692065数值当做了函数的返回地址,从而导致segmentation fault。

本例中的地址是字符串的一部分,比较容易判断,而只是将数值错误地写入的情况会更难调查。即便如此,如果那个数值经常在程序中出现,就可以将调查范围缩小到使用该数值的部分。

有时确定在哪里写入的错误数值不好确定,还可以借用objdump工具。

gerryyang@wcdj:~/test/HACK> objdump -shack28

hack28:   文件格式 elf32-i386

.interp 节的内容:

8048154 2f6c6962 2f6c642d 6c696e75782e736f  /lib/ld-linux.so

8048164 2e3200

......

.got.plt 节的内容:

8049644 78950408 00000000 0000000006830408  x...............

8049654 16830408 26830408                    ....&...

.data 节的内容:

8049660 00000000 00000000 70950408 00000000  ........p.......

8049670 00000000 00000000 0000000000000000  ................

8049680 4f6f7073 21206865 72652069 73206120  Oops! here is a

8049690 62756666 6572206f 766572666c6f772c  buffer overflow,

80496a0 20776364 6a000000                     wcdj...

.comment 节的内容:

......

总结

确定内存内容被破坏的过程可以按照某种步骤进行,但找到引发破坏的地方就必须依靠一定的感觉和经验。

参考:Debug Hacks

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值