首页 > 代码库 > 《coredump问题原理探究》Linux x86版6.3节有成员变量的类coredump例子
《coredump问题原理探究》Linux x86版6.3节有成员变量的类coredump例子
在探究完类成员变量分布后,来定位一个coredump例子来实践一把:
(gdb) bt #0 0x0804863c in xuzhina_dump_c06_s2_ex::print() () #1 0x08048713 in main ()
看一下xuzhina_dump_c06_s2_ex::print的汇编:
(gdb) disassemble 0x0804863c Dump of assembler code for function _ZN22xuzhina_dump_c06_s2_ex5printEv: 0x08048610 <+0>: push %ebp 0x08048611 <+1>: mov %esp,%ebp 0x08048613 <+3>: sub $0x28,%esp 0x08048616 <+6>: movl $0x0,-0xc(%ebp) 0x0804861d <+13>: jmp 0x804869b <_ZN22xuzhina_dump_c06_s2_ex5printEv+139> 0x0804861f <+15>: mov 0x8(%ebp),%eax 0x08048622 <+18>: movzwl (%eax),%eax 0x08048625 <+21>: cwtl 0x08048626 <+22>: test %eax,%eax 0x08048628 <+24>: je 0x8048631 <_ZN22xuzhina_dump_c06_s2_ex5printEv+33> 0x0804862a <+26>: cmp $0x1,%eax 0x0804862d <+29>: je 0x8048654 <_ZN22xuzhina_dump_c06_s2_ex5printEv+68> 0x0804862f <+31>: jmp 0x8048676 <_ZN22xuzhina_dump_c06_s2_ex5printEv+102> 0x08048631 <+33>: mov 0x8(%ebp),%eax 0x08048634 <+36>: mov 0x14(%eax),%edx 0x08048637 <+39>: mov -0xc(%ebp),%eax 0x0804863a <+42>: add %edx,%eax => 0x0804863c <+44>: movzbl (%eax),%eax 0x0804863f <+47>: movsbl %al,%eax 0x08048642 <+50>: mov %eax,0x4(%esp) 0x08048646 <+54>: movl $0x80487c4,(%esp) 0x0804864d <+61>: call 0x80484a0 <printf@plt> 0x08048652 <+66>: jmp 0x8048697 <_ZN22xuzhina_dump_c06_s2_ex5printEv+135> 0x08048654 <+68>: mov 0x8(%ebp),%eax 0x08048657 <+71>: mov 0x14(%eax),%eax 0x0804865a <+74>: mov -0xc(%ebp),%edx 0x0804865d <+77>: shl $0x2,%edx 0x08048660 <+80>: add %edx,%eax 0x08048662 <+82>: flds (%eax) 0x08048664 <+84>: fstpl 0x4(%esp) 0x08048668 <+88>: movl $0x80487c8,(%esp) 0x0804866f <+95>: call 0x80484a0 <printf@plt> 0x08048674 <+100>: jmp 0x8048697 <_ZN22xuzhina_dump_c06_s2_ex5printEv+135> 0x08048676 <+102>: mov 0x8(%ebp),%eax 0x08048679 <+105>: mov 0x14(%eax),%eax 0x0804867c <+108>: mov -0xc(%ebp),%edx 0x0804867f <+111>: shl $0x2,%edx 0x08048682 <+114>: add %edx,%eax 0x08048684 <+116>: mov (%eax),%eax 0x08048686 <+118>: mov %eax,0x4(%esp) 0x0804868a <+122>: movl $0x80487cc,(%esp) 0x08048691 <+129>: call 0x80484a0 <printf@plt> 0x08048696 <+134>: nop 0x08048697 <+135>: addl $0x1,-0xc(%ebp) 0x0804869b <+139>: mov 0x8(%ebp),%eax 0x0804869e <+142>: mov 0x18(%eax),%eax 0x080486a1 <+145>: cmp -0xc(%ebp),%eax 0x080486a4 <+148>: seta %al 0x080486a7 <+151>: test %al,%al 0x080486a9 <+153>: jne 0x804861f <_ZN22xuzhina_dump_c06_s2_ex5printEv+15> 0x080486af <+159>: leave 0x080486b0 <+160>: ret End of assembler dump.
由于从符号表可以看到xuzhina_dump_c06_s2_ex::print是类xuzhina_dump_c06_s2_ex的成员函数,那么由调用类成员函数时,this指针作为第一个参数传递的规则,知道ebp+8存放着this指针。
再由崩溃指令地址是0x0804863c,可以把这个指令地址附近的汇编分析如下:
0x0804862f <+31>: jmp 0x8048676 <_ZN22xuzhina_dump_c06_s2_ex5printEv+102> 0x08048631 <+33>: mov 0x8(%ebp),%eax //this指针 0x08048634 <+36>: mov 0x14(%eax),%edx //偏移this指针20个字节的成员变量 0x08048637 <+39>: mov -0xc(%ebp),%eax //某个局部变量 0x0804863a <+42>: add %edx,%eax //该成员变量为数组 => 0x0804863c <+44>: movzbl (%eax),%eax //该成员变量是char数组
而0x0804862f是无条件跳转,也就是说,0x08048631这条指令应该由其它地方跳转过来的。
0x08048628 <+24>: je 0x8048631 <_ZN22xuzhina_dump_c06_s2_ex5printEv+33>
查看一下0x00401073附近的汇编,可分析如下
0x0804861f <+15>: mov 0x8(%ebp),%eax //this指针 0x08048622 <+18>: movzwl (%eax),%eax //第一个成员变量,类型是short 0x08048625 <+21>: cwtl 0x08048626 <+22>: test %eax,%eax //第一个成员变量是否为0 0x08048628 <+24>: je 0x8048631 <_ZN22xuzhina_dump_c06_s2_ex5printEv+33> 0x0804862a <+26>: cmp $0x1,%eax //第一个成员变量是否为1 0x0804862d <+29>: je 0x8048654 <_ZN22xuzhina_dump_c06_s2_ex5printEv+68> 0x0804862f <+31>: jmp 0x8048676 <_ZN22xuzhina_dump_c06_s2_ex5printEv+102>
由此可见,当第一个成员变量(命名为flag)为0时,才会执行崩溃指令所在的那个分支。也就是说,这一段汇编的结构如下
if ( flag == 0 ) { … } else if ( flag == 1 ) { …. } else { … }
现在尝试着找出ebp-0xc这个局部变量是处于什么作用。
0x08048616 <+6>: movl $0x0,-0xc(%ebp) 0x0804861d <+13>: jmp x804869b <_ZN22xuzhina_dump_c06_s2_ex5printEv+139> 0x0804861f <+15>: mov 0x8(%ebp),%eax 0x08048697 <+135>: addl $0x1,-0xc(%ebp) 0x0804869b <+139>: mov 0x8(%ebp),%eax 0x0804869e <+142>: mov 0x18(%eax),%eax 0x080486a1 <+145>: cmp -0xc(%ebp),%eax 0x080486a4 <+148>: seta %al 0x080486a7 <+151>: test %al,%al 0x080486a9 <+153>: jne 0x804861f <_ZN22xuzhina_dump_c06_s2_ex5printEv+15>
由上面一段,可以看出,ebp-0xc存放着是一个计数变量,也就是一个索引,且处于一个指令地址由0x804861f-0x080486a9范围组成的循环。那么
0x08048631 <+33>: mov 0x8(%ebp),%eax //this指针 0x08048634 <+36>: mov 0x14(%eax),%edx //偏移this指针20个字节的成员变量 0x08048637 <+39>: mov -0xc(%ebp),%eax //某个局部变量 0x0804863a <+42>: add %edx,%eax //该成员变量为数组 => 0x0804863c <+44>: movzbl (%eax),%eax //该成员变量是char数组
可以看出eax+14h即this指针偏移20个字节的成员变量是char指针,命名为ptr。由
0x0804869b <+139>: mov 0x8(%ebp),%eax 0x0804869e <+142>: mov 0x18(%eax),%eax 0x080486a1 <+145>: cmp -0xc(%ebp),%eax 0x080486a4 <+148>: seta %al 0x080486a7 <+151>: test %al,%al 0x080486a9 <+153>: jne 0x804861f <_ZN22xuzhina_dump_c06_s2_ex5printEv+15>
可推知this指针偏移0x18即24个字节的成员变量是一个最大整数,命名为num。
从上面可以知道,类xuzhina_dump_c06_s2_ex大概如下:
class xuzhina_dump_c06_s2_ex { private: short flag; //第一个成员变量 char unknown[n]; //未知数目成员变量,只以字节来算。n = 16或n=18 char* ptr; int num; //指定上面ptr的元素个数 public: void print(); …………. };
由
(gdb) x /x $ebp+8 0xbfe09690: 0xbfe096b4 (gdb) x /x 0xbfe096b4+0x14 0xbfe096c8: 0x69766544 (gdb) x /x 0x69766544 0x69766544: Cannot access memory at address 0x69766544
可知ptr这个类成员变量被修改为0x69766544,所以才会coredump。现在看一下这个对象的数值:
(gdb) x /8x 0xbfe096b4 0xbfe096b4: 0x68540000 0x73497369 0x726f5741 0x7546646c 0xbfe096c4: 0x664f6c6c 0x69766544 0x4f6f4e6c 0x6143656e
从这些数值来看,它们大多处于ASCII码的范围,就是0-127(0x7f)。试一下看是不是。
由于第一个成员变量flag占两个字节,那么查看的起始地址应该是0xbfe096b6.
(gdb) x /s 0xbfe096b6 0xbfe096b6: "ThisIsAWorldFullOfDevilNoOneCanSurvie"
它的长度是37。也就是说,它把ptr,num的值都覆盖了。
那么它是从哪里来的,是在构造时就已经设置还是在别的地方。为了弄清楚这个,先看一下main函数的汇编:
(gdb) disassemble main Dump of assembler code for function main: 0x080486b1 <+0>: push %ebp 0x080486b2 <+1>: mov %esp,%ebp 0x080486b4 <+3>: and $0xfffffff0,%esp 0x080486b7 <+6>: sub $0x40,%esp 0x080486ba <+9>: cmpl $0x2,0x8(%ebp) 0x080486be <+13>: jg 0x80486c7 <main+22> 0x080486c0 <+15>: mov $0xffffffff,%eax 0x080486c5 <+20>: jmp 0x8048718 <main+103> 0x080486c7 <+22>: mov 0xc(%ebp),%eax 0x080486ca <+25>: add $0x8,%eax 0x080486cd <+28>: mov (%eax),%eax 0x080486cf <+30>: mov %eax,(%esp) 0x080486d2 <+33>: call 0x8048480 <strlen@plt> 0x080486d7 <+38>: mov 0xc(%ebp),%edx 0x080486da <+41>: add $0x8,%edx 0x080486dd <+44>: mov (%edx),%ecx 0x080486df <+46>: mov 0xc(%ebp),%edx 0x080486e2 <+49>: add $0x4,%edx 0x080486e5 <+52>: mov (%edx),%edx 0x080486e7 <+54>: mov %eax,0x10(%esp) 0x080486eb <+58>: mov %ecx,0xc(%esp) 0x080486ef <+62>: movl $0x0,0x8(%esp) 0x080486f7 <+70>: mov %edx,0x4(%esp) 0x080486fb <+74>: lea 0x24(%esp),%eax 0x080486ff <+78>: mov %eax,(%esp) 0x08048702 <+81>: call 0x80485d0 <_ZN22xuzhina_dump_c06_s2_exC2EPcsPvj> 0x08048707 <+86>: lea 0x24(%esp),%eax 0x0804870b <+90>: mov %eax,(%esp) 0x0804870e <+93>: call 0x8048610 <_ZN22xuzhina_dump_c06_s2_ex5printEv> 0x08048713 <+98>: mov $0x0,%eax 0x08048718 <+103>: jmp 0x8048722 <main+113> 0x0804871a <+105>: mov %eax,(%esp) 0x0804871d <+108>: call 0x80484c0 <_Unwind_Resume@plt> 0x08048722 <+113>: leave 0x08048723 <+114>: ret End of assembler dump.
通过结合上面汇编和
(gdb) shell c++filt _ZN22xuzhina_dump_c06_s2_exC2EPcsPvj xuzhina_dump_c06_s2_ex::xuzhina_dump_c06_s2_ex(char*, short, void*, unsigned int) (gdb) shell c++filt _ZN22xuzhina_dump_c06_s2_ex5printEv xuzhina_dump_c06_s2_ex::print()
可知,main函数只是调用了四个函数,strlen,类xuzhina_dump_c06_s2_ex的构造函数和成员函数print,及_Unwind_Resume(说明可见http://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/baselib--unwind-resume.html)
可见,main函数只是调用了类xuzhina_dump_c06_s2_ex两个对外接口,一个构造函数,一个print成员函数。那么,类xuzhina_dump_c06_s2_ex的成员变量ptr,num应该是在构造函数里初始化的。
由下面
0x080486e7 <+54>: mov %eax,0x10(%esp) 0x080486eb <+58>: mov %ecx,0xc(%esp) 0x080486ef <+62>: movl $0x0,0x8(%esp) 0x080486f7 <+70>: mov %edx,0x4(%esp) 0x080486fb <+74>: lea 0x24(%esp),%eax 0x080486ff <+78>: mov %eax,(%esp) 0x08048702 <+81>: call 0x80485d0 <_ZN22xuzhina_dump_c06_s2_exC2EPcsPvj>
从四个push指令,可以猜测,类xuzhina_dump_c06_s2_ex构造函数有四个参数(五个参数减去默认的this指针这个参数)。
看一下构造函数:
(gdb) disassemble _ZN22xuzhina_dump_c06_s2_exC2EPcsPvj Dump of assembler code for function _ZN22xuzhina_dump_c06_s2_exC2EPcsPvj: 0x080485d0 <+0>: push %ebp 0x080485d1 <+1>: mov %esp,%ebp 0x080485d3 <+3>: sub $0x28,%esp 0x080485d6 <+6>: mov 0x10(%ebp),%eax 0x080485d9 <+9>: mov %ax,-0xc(%ebp) 0x080485dd <+13>: mov 0x8(%ebp),%eax 0x080485e0 <+16>: mov 0x14(%ebp),%edx 0x080485e3 <+19>: mov %edx,0x14(%eax) 0x080485e6 <+22>: mov 0x8(%ebp),%eax 0x080485e9 <+25>: mov 0x18(%ebp),%edx 0x080485ec <+28>: mov %edx,0x18(%eax) 0x080485ef <+31>: mov 0x8(%ebp),%eax 0x080485f2 <+34>: lea 0x2(%eax),%edx 0x080485f5 <+37>: mov 0xc(%ebp),%eax 0x080485f8 <+40>: mov %eax,0x4(%esp) 0x080485fc <+44>: mov %edx,(%esp) 0x080485ff <+47>: call 0x8048490 <strcpy@plt> 0x08048604 <+52>: mov 0x8(%ebp),%eax 0x08048607 <+55>: movzwl -0xc(%ebp),%edx 0x0804860b <+59>: mov %dx,(%eax) 0x0804860e <+62>: leave 0x0804860f <+63>: ret End of assembler dump.
由
0x080485d6 <+6>: mov 0x10(%ebp),%eax 0x080485dd <+13>: mov 0x8(%ebp),%eax 0x080485e0 <+16>: mov 0x14(%ebp),%edx 0x080485e6 <+22>: mov 0x8(%ebp),%eax 0x080485f5 <+37>: mov 0xc(%ebp),%eax
这五条指令来看,类xuzhina_dump_c06_s2_ex构造函数的参数确实是四个。
再由ebp+8放着this指针和下面四段代码
0x080485dd <+13>: mov 0x8(%ebp),%eax 0x080485e0 <+16>: mov 0x14(%ebp),%edx 0x080485e3 <+19>: mov %edx,0x14(%eax) 0x080485e6 <+22>: mov 0x8(%ebp),%eax 0x080485e9 <+25>: mov 0x18(%ebp),%edx 0x080485ec <+28>: mov %edx,0x18(%eax) 0x080485ef <+31>: mov 0x8(%ebp),%eax 0x080485f2 <+34>: lea 0x2(%eax),%edx 0x080485f5 <+37>: mov 0xc(%ebp),%eax 0x08048604 <+52>: mov 0x8(%ebp),%eax 0x08048607 <+55>: movzwl -0xc(%ebp),%edx 0x0804860b <+59>: mov %dx,(%eax)
可知,类xuzhina_dump_c06_s2_ex只有四个成员变量。且由
0x080485ef <+31>: mov 0x8(%ebp),%eax 0x080485f2 <+34>: lea 0x2(%eax),%edx //只是偏移2个字节 0x080485f5 <+37>: mov 0xc(%ebp),%eax 0x080485f8 <+40>: mov %eax,0x4(%esp) 0x080485fc <+44>: mov %edx,(%esp) 0x080485ff <+47>: call 0x8048490 <strcpy@plt>
可知,第二个成员变量是char数组,大小是18的字节。也就是说,这个类的定义大概如下:
class xuzhina_dump_c06_s2_ex { private: short flag; //第一个成员变量 char str[18]; //暂命名为str char* ptr; int num; //指定ptr的元素个数 public: void print(); …………. };
由于ptr(偏移值在0x14h)这个成员变量是在str后面,且
0x080485ef <+31>: mov 0x8(%ebp),%eax 0x080485f2 <+34>: lea 0x2(%eax),%edx 0x080485f5 <+37>: mov 0xc(%ebp),%eax 0x080485f8 <+40>: mov %eax,0x4(%esp) 0x080485fc <+44>: mov %edx,(%esp) 0x080485ff <+47>: call 0x8048490 <strcpy@plt>
所以,ptr的值有可能是在strcpy时被覆盖的。由strcpy的原型
char *strcpy(char *dest, const char *src);
和
0x080485f5 <+37>: mov 0xc(%ebp),%eax
0x080485f8 <+40>: mov %eax,0x4(%esp)
可知,src是类xuzhina_dump_c06_s2_ex构造函数的第一个参数(不是指默认this指针)。那么跳到main函数桢看一下,传递给构造函数的第一个参数是从哪里来的。
由main函数这一段汇编
0x080486df <+46>: mov 0xc(%ebp),%edx //第二个参数argv 0x080486e2 <+49>: add $0x4,%edx // argv[1]的地址 0x080486e5 <+52>: mov (%edx),%edx 0x080486e7 <+54>: mov %eax,0x10(%esp) 0x080486eb <+58>: mov %ecx,0xc(%esp) 0x080486ef <+62>: movl $0x0,0x8(%esp) 0x080486f7 <+70>: mov %edx,0x4(%esp) 0x080486fb <+74>: lea 0x24(%esp),%eax 0x080486ff <+78>: mov %eax,(%esp) 0x08048702 <+81>: call 0x80485d0 <_ZN22xuzhina_dump_c06_s2_exC2EPcsPvj>
可知,类xuzhina_dump_c06_s2_ex构造函数的第一个参数是由main函数的第二个参数argv[1]传入的。看一下argv[1]是多少
(gdb) x $ebp+0xc 0xbfe096e4: 0xbfe09774 (gdb) x 0xbfe09774+4 0xbfe09778: 0xbfe0a6a3 (gdb) x /s 0xbfe0a6a3 0xbfe0a6a3: "ThisIsAWorldFullOfDevilNoOneCanSurvie"
和上面分析出来的字符串正好是一样的。也就是说,由于不正确使用了strcpy,导致改写了ptr,导致了coredump。
例子的源代码:
1 #include <string.h> 2 #include <stdio.h> 3 class xuzhina_dump_c06_s2_ex 4 { 5 private: 6 short m_type; 7 char m_name[16]; 8 void* m_ptr; 9 unsigned int m_len; 10 public: 11 xuzhina_dump_c06_s2_ex( char* name, short type, 12 void* data, unsigned int len ); 13 void print(); 14 }; 15 16 xuzhina_dump_c06_s2_ex::xuzhina_dump_c06_s2_ex( char* name, short type, 17 void* data, unsigned int len ) 18 { 19 m_ptr = data; 20 m_len = len; 21 strcpy( m_name, name ); 22 m_type = type; 23 24 } 25 26 void xuzhina_dump_c06_s2_ex::print() 27 { 28 for ( unsigned int i = 0; i < m_len; i++ ) 29 { 30 switch( m_type ) 31 { 32 case 0: 33 printf( "%c ", *((char*)m_ptr + i ) ); 34 break; 35 case 1: 36 printf( "%f ", *((float*)m_ptr + i ) ); 37 break; 38 default: 39 printf( "%d ", *((int*)m_ptr + i ) ); 40 break; 41 } 42 } 43 } 44 45 int main(int argc, char* argv[] ) 46 { 47 if ( argc < 3 ) 48 { 49 return -1; 50 } 51 xuzhina_dump_c06_s2_ex test( argv[1], 0, 52 argv[2], strlen( argv[2] ) ); 53 test.print(); 54 55 return 0; 56 }
《coredump问题原理探究》Linux x86版6.3节有成员变量的类coredump例子