《coredump问题原理探究》Linux x86版3.8节栈布局之栈溢出coredump例子

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/8550219

现在,回到前言的栈,看一下能不能用上面的规律来恢复它的栈。前言的可执行文件是没有使用-fomit-frame-pointer编译选项的。

前言的栈是这样的:

(gdb) bt
#0  0x6f745374 in ?? ()
#1  0x57735571 in ?? ()
#2  0xbff80065 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

先看一下ebp,esp的值:

(gdb) i r ebp esp
ebp            0x6f4e6e61       0x6f4e6e61
esp            0xbff8ef60       0xbff8ef60

毫无疑问,ebp的值是非法的。从前几节的规律可以知道,ebp >= esp。

那么,只能看一下esp所指向的栈内容,来查找函数桢指针fp。在找fp之前,先复习一下栈布局的单链表规律:

1.      fp所在地址大于esp的值

2.      fp下一个单元的内容可以用info symbol来显示出函数名称

3.      fp所指向单元也遵守1,2两个原则。

看一下esp所指向的内容:

(gdb) x /16x $esp
0xbff8ef60:     0x57735571      0xbff80065      0xb778d590      0x43647dc3
0xbff8ef70:     0xbff8ef90      0x4361d3e0      0xbff8ef98      0x08048618
0xbff8ef80:     0x00000003      0xbff8f66b      0xffffffff      0x43647dc3
0xbff8ef90:     0x43622960      0xb778dbb8      0xbff8efb8      0x08048636

由于esp的值是0xbff8ef60,里面有0xbff8ef90,0xbff8ef98,0xbff8f66b和0xbff8efb8都满足第1原则。看一下它们下一个单元满足第2原则不。

(gdb) info symbol 0x4361d3e0
No symbol matches 0x4361d3e0.
(gdb) info symbol 0x08048618
wrapper2(int, char*) + 28 in section .text of /home/buckxu/work/9/1/xuzhina_dump_c1
(gdb) info symbol 0xffffffff
No symbol matches 0xffffffff.
(gdb) info symbol 0x08048636
wrapper3(int, char*) + 28 in section .text of /home/buckxu/work/9/1/xuzhina_dump_c1

可见,0xbff8ef98,0xbff8efb8满足第1,2原则。看一下0xbff8ef98是否满足第3原则不。

(gdb) x /2x 0xbff8ef98
0xbff8ef98:     0xbff8efb8      0x08048636

真是巧,0xbff8ef98指向单元的内容刚好是0xbff8efb8.也就是说,0xbff8ef98满足第3原则。

接着看0xbff8efb8是不是也满足这3个原则

(gdb) x /2x 0xbff8efb8
0xbff8efb8:     0xbff8efd8      0x08048666
(gdb) info symbol 0x08048666
main + 46 in section .text of /home/buckxu/work/9/1/xuzhina_dump_c1

可见0xbff8efb8也是满足了3个原则。

由于程序都是由main函数开始执行,可以看到,实际上已经把真实的栈还原了部分:

wrapper2(int, char*) + 28 in section .text of /home/buckxu/work/9/1/xuzhina_dump_c1
wrapper3(int, char*) + 28 in section .text of /home/buckxu/work/9/1/xuzhina_dump_c1
main + 46 in section .text of /home/buckxu/work/9/1/xuzhina_dump_c1

那么,其余的栈应该怎么还原呢?由于现在还原的栈中,wrapper2是栈顶,那么看一下wrapper2的汇编:

(gdb) disassemble wrapper2  
Dump of assembler code for function _Z8wrapper2iPc:
   0x080485fc <+0>:     push   %ebp
   0x080485fd <+1>:     mov    %esp,%ebp
   0x080485ff <+3>:     sub    $0x18,%esp
   0x08048602 <+6>:     addl   $0x1,0x8(%ebp)
   0x08048606 <+10>:    mov    0xc(%ebp),%eax
   0x08048609 <+13>:    mov    %eax,0x4(%esp)
   0x0804860d <+17>:    mov    0x8(%ebp),%eax
   0x08048610 <+20>:    mov    %eax,(%esp)
   0x08048613 <+23>:    call   0x80485de <_Z8wrapper1iPc>
   0x08048618 <+28>:    leave  
   0x08048619 <+29>:    ret    
End of assembler dump.

由于wrapper2存在栈中的返回地址是0x08048618,那么应该是由于

   0x08048613 <+23>:    call   0x80485de <_Z8wrapper1iPc>

引起的栈溢出。而_Z8wrapper1iPc则可以这样知道原型:

(gdb) shell c++filt _Z8wrapper1iPc
wrapper1(int, char*)

由于wrapper2申请了0x18个字节的局部变量空间,wrapper2返回地址0x08048618所在的单元是0xbff8ef7C,和esp的值0xbff8ef60相差0x1C,刚好是0x18局部变量地址区和1个返回地址的和。也就是说,栈刚好是在调用完wrapper1时,出现栈溢出的。

所以,栈应该如下:

wrapper1(int, char*)
wrapper2(int, char*) + 28 in section .text of /home/buckxu/work/9/1/xuzhina_dump_c1
wrapper3(int, char*) + 28 in section .text of /home/buckxu/work/9/1/xuzhina_dump_c1
main + 46 in section .text of /home/buckxu/work/9/1/xuzhina_dump_c1

如果只是还原栈,那么上面已经完成任务了,但溢出的根因是哪里呢?

而从wrapper1的汇编:

(gdb) disassemble wrapper1
Dump of assembler code for function _Z8wrapper1iPc:
   0x080485de <+0>:     push   %ebp
   0x080485df <+1>:     mov    %esp,%ebp
   0x080485e1 <+3>:     sub    $0x18,%esp
   0x080485e4 <+6>:     addl   $0x1,0x8(%ebp)
   0x080485e8 <+10>:    mov    0xc(%ebp),%eax
   0x080485eb <+13>:    mov    %eax,0x4(%esp)
   0x080485ef <+17>:    mov    0x8(%ebp),%eax
   0x080485f2 <+20>:    mov    %eax,(%esp)
   0x080485f5 <+23>:    call   0x80485a0 <_Z8overflowiPc>
   0x080485fa <+28>:    leave  
   0x080485fb <+29>:    ret    
End of assembler dump.

由于在wrapper1里,所有的内存地址操作都由ebp,esp来指定,而ebp是由esp来获取。所以wrapper1运行时,esp的值是应该是wrapper2减去0x18(wrapper1的局部变量空间大小)和返回地址,即0xbff8ef60 - 0x18 - 4 = 0xbff8ef44。

(gdb) x 0xbff8ef44
0xbff8ef44:     0x6d754865

可知,在wrapper1时,esp值所指向的地址是合法的。也就是说,在wrapper1里运行,是不会出现内存越界的。那么应该是由于

   0x080485f5 <+23>:    call   0x80485a0 <_Z8overflowiPc>

这一条指令引起的。

研究一下_Z8overflowiPc:

(gdb) shell c++filt _Z8overflowiPc
overflow(int, char*)
(gdb) disassemble overflow
Dump of assembler code for function _Z8overflowiPc:
   0x080485a0 <+0>:     push   %ebp
   0x080485a1 <+1>:     mov    %esp,%ebp
   0x080485a3 <+3>:     sub    $0x28,%esp
   0x080485a6 <+6>:     mov    0xc(%ebp),%eax
   0x080485a9 <+9>:     mov    %eax,0x4(%esp)
   0x080485ad <+13>:    lea    -0x18(%ebp),%eax
   0x080485b0 <+16>:    mov    %eax,(%esp)
   0x080485b3 <+19>:    call   0x8048460 <strcpy@plt>
   0x080485b8 <+24>:    lea    -0x18(%ebp),%eax
   0x080485bb <+27>:    mov    %eax,0x4(%esp)
   0x080485bf <+31>:    movl   $0x8048704,(%esp)
   0x080485c6 <+38>:    call   0x8048470 <printf@plt>
   0x080485cb <+43>:    addl   $0x1,0x8(%ebp)
   0x080485cf <+47>:    mov    0x8(%ebp),%eax
   0x080485d2 <+50>:    jmp    0x80485dc <_Z8overflowiPc+60>
   0x080485d4 <+52>:    mov    %eax,(%esp)
   0x080485d7 <+55>:    call   0x8048490 <_Unwind_Resume@plt>
   0x080485dc <+60>:    leave  
   0x080485dd <+61>:    ret    
End of assembler dump.

由于overflow调用strcpy,而strcpy是有名的不安全的函数,它有可能是这次coredump的根因。

到这里,可以贴一下这个程序的源码来看一下,上面的栈还原是不是准确:

#include <string.h>
#include <stdio.h>
int overflow( int level, char* str )
{
     char buff[16];
     strcpy( buff, str );
     printf( "buffer:%s", buff );
     return ++level;
}
 
int wrapper1( int level, char* str )
{
     return overflow( ++level, str );
}
 
int wrapper2( int level, char* str )
{
     return wrapper1( ++level, str );
}
 
int wrapper3( int level, char* str )
{
     return wrapper2( ++level, str );
}
 
int main( int argc, char* argv[] )
{
    if ( argc < 2 )
     {
         return -1;
     }
     return wrapper3( 0, argv[1] ); 
}

执行时的这样的:

[buckxu@xuzhina 1]$ ./xuzhina_dump_c1 WeAreHumanBeingsNothingCanNotStopUsWe                           
Segmentation fault (core dumped)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java后端技术栈

Redis常见的5种不同的数据类型详解

Redis除了可以存储键还可以存储常见的5种数据类型,分别是:String、List、Set、Hash、ZSet。对于Redis的命令有一部分是可以公用的,但是...

11110
来自专栏python爬虫日记

python 下字符串格式时间比较

python 下有多个有关时间的模块,分别是time、datetime、calendar,今天重点讨论下time写法。

14020
来自专栏知道一点点

sass入门学习篇(二)

一,sass有两种后缀名文件:一种后缀名为sass,不使用大括号和分号;另一种就是我们这里使用的scss文件,建议scss.

11420
来自专栏Golang语言社区

golang的内存模型与new()与make()

要彻底理解new()与make()的区别, 最好从内存模型入手. golang属于c family, 而c程序在unix的内在模型: |低地址|text|dat...

30760
来自专栏GreenLeaves

Proxy代理模式(结构型模式)

在面向对象系统中,有些对象由于某种原因(比如创建对象的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给调用者带来麻烦,那么如何在不损失...

13530
来自专栏程序员的知识天地

Python程序员必备的30个编程技巧

直接交换2个数字的位置 Python 提供了一种直观的方式在一行代码中赋值和交换(变量值)。如下所示:

17320
来自专栏小狼的世界

Linux下不同文件编码的转换

字符编码(Character Encoding)可以说就是让某一字符序列匹配一个指定集合中的某一东西,常见的例子包括长短电键组合起来表示的摩斯电码(Morse ...

11520
来自专栏程序你好

在c#中,如何序列化/反序列化一个字典对象?

.Net提供的各种序列化的类,通过使用这些类,. Net对象的序列化和反序列化变得很容易。但是字典对象的序列化并不是那么容易。为此,您必须创建一个能够序列化自身...

11810
来自专栏desperate633

共享资源的线程安全性Local VariablesLocal Object ReferencesObject Member VariablesThe Thread Control Escape Rul

如果某段代码可以正确的被多线程并发的执行,那么我们就称这段代码是线程安全的,如果一段代码是线程安全的那么他肯定不会出现资源竞速的问题。资源竞速的问题只发生在多个...

6010
来自专栏用户2442861的专栏

STL源码剖析-set容器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/d...

21630

扫码关注云+社区

领取腾讯云代金券