版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344560
在探究完类成员变量分布后,来定位一个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函数的第二个参数argv1传入的。看一下argv1是多少
(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 }