《GDB —— 数组越界导致内存被破坏》
1.例程
test.c
#include <string.h> char name[] = "book cat dog building vegetable curry"; void func(void) { char buf[5]; strcpy(buf, names); } int main(void) { func(); return 0; }
编译程序
gcc -fno-stack-protector -g test.c
其中 -fno-stack-protector禁止用栈保护。如果开启栈保护,会使用canary机制。
不指定默认百度说gcc是禁止用栈保护,但是我自己编译出来是使能栈保护。
运行程序:
Segmentation fault (core dumped)
如果使能栈保护的话,运行后的结果是:
*** stack smashing detected ***: ./a.out terminated Aborted (core dumped)
具体可以查看canary机制。我们首先用禁止栈保护来调试程序。
2.gdb调试
(gdb) r Starting program: /home/zhuang/test/a.out1 Program received signal SIGSEGV, Segmentation fault. 0x676e6964 in ?? () (gdb) bt #0 0x676e6964 in ?? () #1 0x67657620 in ?? () #2 0x62617465 in ?? () #3 0x6320656c in ?? () #4 0x79727275 in ?? () #5 0x00000000 in ?? ()
首先可以看到,当程序停止后,bt看栈的时候,都是??。
这种情况可能是代码跳转到或者调用了错误的地址0x676e6964。
(gdb) x/i 0x676e6964 => 0x676e6964: Cannot access memory at address 0x676e6964
可以看到无法访问该地址。
运行地址的改变
那么是从哪里跳转到或调用了不存在的地址?
1.直接指定地址并调用(函数调用)。
2.指定一块内存区域,其中保存了跳转地址(GOT/PLT调用函数的原理)
3.执行ret命令,用于函数结束时返回调用者函数。ret命令将栈指针指向的位置的值作为跳转(返回)地址使用。
分析:
1.函数调用的地址比较难破坏,一般都保存在只读空间。所以一般尝试写操作就会产生segmentation fault。此时,core文件会记录PC的值,比较好分析。
2.上述的第二种和第三种使用的地址保存在GOT或栈中,即使数据被破坏,也不会保存。等到访问已经被破坏的地址,才会报错。
解决思路:
确定破坏地址值的位置。就是将错误地址当作数据,寻找是在哪里将这个数据写到这个地方的。所以我们寻找写入0x676e6964这个数据的地方。
(gdb) x/c $esp 0xbffff050: 32 ' '
(38条消息) EAX、ESP、EBP等寄存器的作用_桂花鱼_的博客-CSDN博客_esp寄存器
当前的esp的地址是0xbffff050,可以看到是个空格。
(gdb) x/30c $esp-15 0xbffff041: 97 'a' 116 't' 32 ' ' 100 'd' 111 'o' 103 'g' 32 ' ' 98 'b' 0xbffff049: 117 'u' 105 'i' 108 'l' 100 'd' 105 'i' 110 'n' 103 'g' 32 ' ' 0xbffff051: 118 'v' 101 'e' 103 'g' 101 'e' 116 't' 97 'a' 98 'b' 108 'l' 0xbffff059: 101 'e' 32 ' ' 99 'c' 117 'u' 114 'r' 114 'r'
$esp-15的意思就是0xbffff050-15(15为十进制),也就是0xbffff041。然后30c表示读取从0xbffff041开始的30个字符。
可以看出来,esp的前后都是字符串。因此考虑是不是字符串复制导致的越界。
(gdb) p (char *)$esp $1 = 0xbffff050 " vegetable curry" (gdb) p (char *)$esp-20 $2 = 0xbffff03c "ook cat dog building vegetable curry" (gdb) p (char *)$esp-25 $3 = 0xbffff037 "\277\374\360\377book cat dog building vegetable curry" (gdb) p (char *)$esp-21 $4 = 0xbffff03b "book cat dog building vegetable curry"
可以看到$esp-21开始是一个完整字符串。
因为调查的重点是寻找写入0x676e6964这个数据的地方。可以发现这个数据的ASCII是gnid。考虑到i386架构采用小端模式,所以数据应该是ding。
所以确定是字符串复制的时候越界导致的栈返回地址被改写。
考虑一下确认栈的返回地址位置。watch查看
__stack_chk_fail栈检查失败 - smartch - 博客园 (cnblogs.com)
(38条消息) 【GDB】__stack_chk_fail 栈溢出问题定位_pcj_888的博客-CSDN博客_如何定位栈溢出
(38条消息) C语言函数栈帧详解_CDQ0818的博客-CSDN博客_c语言栈帧
记录一次完整的Canary保护 - eur1ka - 博客园 (cnblogs.com)
一:canary(栈保护)
栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。
gcc在4.2版本中添加了-fstack-protector和-fstack-protector-all编译参数以支持栈保护功能,
因此在编译时可以控制是否开启栈保护以及程度,例如:
1、gcc -o test test.c // 默认情况下,不开启Canary保护
2、gcc -fno-stack-protector -o test test.c //禁用栈保护
3、gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
4、gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码