《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)
- fastdfs 图片服务器 使用java端作为客户端上传图片
- Shiro系列(3) - What is shiro?
- 干货|用python抓取摩拜单车API数据并做可视化分析(源码)
- 干货|机器学习:Python实现聚类算法之K-Means
- 美团点餐—listview内部按钮点击事件
- C4C和CRM里获取当前登录用户分配的Organization Unit信息
- 使用Excel调用ABAP系统的函数
- 如何计算并测量ABAP及Java代码的环复杂度Cyclomatic complexity
- 漫谈版本控制系统
- 带你零基础入门express
- 动态控制C4C UI元素的显示和隐藏
- 深度学习(deep learning)发展史
- 遗传算法简述
- Spark详解03Job 物理执行图Job 物理执行图
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- LeetCode 94 | 基础题,如何不用递归中序遍历二叉树?
- 读源码——cglib动态代理
- 每日一题 | 字符串转换问题
- Git同时上传到github和码云(国内知名的男性交友网站)
- 读源码——Guava-Cache
- LeetCode 94 | 构造出所有二叉搜索树
- 每日一题 | 最大考试分数问题
- 计算广告——收入分解
- 浅谈mybatis中的占位符
- Python | 详解Python中的协程,为什么说它的底层是生成器?
- 初识Mybatis中的动态sql
- Raw use of parameterized class 'Future'
- javaweb遇到的报错问题以及解决方案(持续更新)
- Spark Java UDAF 输入struct嵌套结构
- 深入理解Java内存模型