版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344503
上一节已经阐述完毕结构体的特征了,需要一个coredump例子:
(gdb) bt
#0 0x00000000 in ?? ()
#1 0x080485a1 in result(xuzhina_dump_c05_s3_ex*, int) ()
#2 0x080487f8 in main ()
从栈顶来看,顶层的地址竟然是0,eip的值也是0.会出现eip为0的情况,只可能是调用了函数指针,且函数指针为0.看一下result的汇编:
(gdb) disassemble result
Dump of assembler code for function _Z6resultP22xuzhina_dump_c05_s3_exi:
0x08048540 <+0>: push %ebp
0x08048541 <+1>: mov %esp,%ebp
0x08048543 <+3>: push %ebx
0x08048544 <+4>: sub $0x24,%esp
0x08048547 <+7>: movl $0x0,-0xc(%ebp)
0x0804854e <+14>: movl $0x0,-0x10(%ebp)
0x08048555 <+21>: jmp 0x80485a8 <_Z6resultP22xuzhina_dump_c05_s3_exi+104>
0x08048557 <+23>: mov -0x10(%ebp),%edx
0x0804855a <+26>: mov %edx,%eax
0x0804855c <+28>: add %eax,%eax
0x0804855e <+30>: add %edx,%eax
0x08048560 <+32>: shl $0x2,%eax
0x08048563 <+35>: mov %eax,%edx
0x08048565 <+37>: mov 0x8(%ebp),%eax
0x08048568 <+40>: add %edx,%eax
0x0804856a <+42>: mov 0x8(%eax),%ecx
0x0804856d <+45>: mov -0x10(%ebp),%edx
0x08048570 <+48>: mov %edx,%eax
0x08048572 <+50>: add %eax,%eax
0x08048574 <+52>: add %edx,%eax
0x08048576 <+54>: shl $0x2,%eax
0x08048579 <+57>: mov %eax,%edx
0x0804857b <+59>: mov 0x8(%ebp),%eax
0x0804857e <+62>: add %edx,%eax
0x08048580 <+64>: mov 0x4(%eax),%ebx
0x08048583 <+67>: mov -0x10(%ebp),%edx
0x08048586 <+70>: mov %edx,%eax
0x08048588 <+72>: add %eax,%eax
0x0804858a <+74>: add %edx,%eax
0x0804858c <+76>: shl $0x2,%eax
0x0804858f <+79>: mov %eax,%edx
0x08048591 <+81>: mov 0x8(%ebp),%eax
0x08048594 <+84>: add %edx,%eax
0x08048596 <+86>: mov (%eax),%eax
0x08048598 <+88>: mov %ebx,0x4(%esp)
0x0804859c <+92>: mov %eax,(%esp)
0x0804859f <+95>: call *%ecx
0x080485a1 <+97>: add %eax,-0xc(%ebp)
0x080485a4 <+100>: addl $0x1,-0x10(%ebp)
0x080485a8 <+104>: mov -0x10(%ebp),%eax
0x080485ab <+107>: cmp 0xc(%ebp),%eax
0x080485ae <+110>: setl %al
0x080485b1 <+113>: test %al,%al
0x080485b3 <+115>: jne 0x8048557 <_Z6resultP22xuzhina_dump_c05_s3_exi+23>
0x080485b5 <+117>: mov -0xc(%ebp),%eax
0x080485b8 <+120>: add $0x24,%esp
0x080485bb <+123>: pop %ebx
0x080485bc <+124>: pop %ebp
0x080485bd <+125>: ret
End of assembler dump.
在上面汇编,指令地址0x08048557-0x080485b3是一个循环,循环条件是判断ebp-0x10单元的值是不是与函数第三个参数(ebp+0xc)相等,由此可知,ebp-0x10存放着循环计数,取名为cnt。而栈顶的返回值0x080485a1也位于这个循环里,
那么coredump指令是由
0x0804859f <+95>: call *%ecx
引起,也就是说ecx为0,如下图。
(gdb) frame 1
#1 0x080485a1 in result(xuzhina_dump_c05_s3_ex*, int) ()
(gdb) i r
eax 0x3 3
ecx 0x0 0
edx 0x24 36
ebx 0x3 3
esp 0xbfac0250 0xbfac0250
ebp 0xbfac0278 0xbfac0278
esi 0x0 0
edi 0x0 0
eip 0x80485a1 0x80485a1 <result(xuzhina_dump_c05_s3_ex*, int)+97>
eflags 0x10296 [ PF AF SF IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
ecx这个函数指针是由下面的指令来获得:
0x08048557 <+23>: mov -0x10(%ebp),%edx
0x0804855a <+26>: mov %edx,%eax
0x0804855c <+28>: add %eax,%eax
0x0804855e <+30>: add %edx,%eax
0x08048560 <+32>: shl $0x2,%eax
0x08048563 <+35>: mov %eax,%edx
0x08048565 <+37>: mov 0x8(%ebp),%eax
0x08048568 <+40>: add %edx,%eax
0x0804856a <+42>: mov 0x8(%eax),%ecx
ecx函数指针的第一个参数的获得:
0x08048583 <+67>: mov -0x10(%ebp),%edx
0x08048586 <+70>: mov %edx,%eax
0x08048588 <+72>: add %eax,%eax
0x0804858a <+74>: add %edx,%eax
0x0804858c <+76>: shl $0x2,%eax
0x0804858f <+79>: mov %eax,%edx
0x08048591 <+81>: mov 0x8(%ebp),%eax
0x08048594 <+84>: add %edx,%eax
0x08048596 <+86>: mov (%eax),%eax
ecx函数指针的第二个参数的获得:
0x0804856d <+45>: mov -0x10(%ebp),%edx
0x08048570 <+48>: mov %edx,%eax
0x08048572 <+50>: add %eax,%eax
0x08048574 <+52>: add %edx,%eax
0x08048576 <+54>: shl $0x2,%eax
0x08048579 <+57>: mov %eax,%edx
0x0804857b <+59>: mov 0x8(%ebp),%eax
0x0804857e <+62>: add %edx,%eax
0x08048580 <+64>: mov 0x4(%eax),%ebx
由上面可以看到,这三个值的获取只有最后一条指令是不一样,其它都一样。
先分析一下前面相同的指令:
0x0804856d <+45>: mov -0x10(%ebp),%edx ;把cnt(ebp-0x10)的内容放入edx
0x08048570 <+48>: mov %edx,%eax ;eax = edx
0x08048572 <+50>: add %eax,%eax ;eax *= 2
0x08048574 <+52>: add %edx,%eax ;eax += edx = 3*edx
0x08048576 <+54>: shl $0x2,%eax ;eax <<= 2. eax = 12*cnt
0x08048579 <+57>: mov %eax,%edx ;edx = eax
0x0804857b <+59>: mov 0x8(%ebp),%eax ;result函数的第一个参数
0x0804857e <+62>: add %edx,%eax ;eax += edx
由上面的汇编可以看出,取得ecx所指向的函数及它两个参数,都是在result函数的第一参数加一定偏移值来得到,而这个偏移值则是12*cnt。
而cnt在每次循环比较结束条件时,都会:
0x080485a4 <+100>: addl $0x1,-0x10(%ebp)
即cnt++。也就是说,每次循环获取函数指针放入ecx及函数指针两个参数时,计算的偏移值都会比上一次循环的偏移值多上12,是一个固定值。根据数组的特征,可见result函数的第一个参数是一个数组,它每个元素的大小是12
且cnt在循环比较结束条件时,都会
0x080485ab <+107>: cmp 0xc(%ebp),%eax
可见,result函数的第二个参数是数组要遍历的个数。
再者,由于函数指针和它两个参数的获得只是最后一条指令不一样,且它们是以数组的元素为基地址的。可以推断它们是结构体。
所以,现在就是要看一下究竟是数组哪个元素的第三个成员为空。
由于cnt是放在ebp-0x10,看一下ebp-0x10的内容就知道:
(gdb) frame
#1 0x080485a1 in result(xuzhina_dump_c05_s3_ex*, int) ()
(gdb) x /x $ebp-0x10
0xbfac0268: 0x00000003
可见是数组的第四个元素的第三个成员为空。
看一下代码:
typedef int (*operation)( int a, int b );
struct xuzhina_dump_c05_s3_ex
{
int a;
int b;
operation oper;
};
int result( struct xuzhina_dump_c05_s3_ex test[], int num )
{
int res = 0;
for ( int i = 0; i < num; i++ )
{
res += test[i].oper( test[i].a, test[i].b );
}
return res;
}
int add( int a, int b )
{
return a+b;
}
int sub( int a, int b )
{
return a-b;
}
int mul( int a, int b )
{
return a*b;
}
void init( struct xuzhina_dump_c05_s3_ex test[], int num )
{
for( int i = 0; i < num; i++ )
{
switch( i % 4 )
{
case 0:
test[i].a = i/4;
test[i].b = 0;
test[i].oper = add;
break;
case 1:
test[i].a = i/4;
test[i].b = i%4;
test[i].oper = mul;
break;
case 2:
test[i].a = i%4;
test[i].b = i/4;
test[i].oper = sub;
break;
default:
test[i].a = i;
test[i].b = i%4;
test[i].oper = 0;
break;
}
}
}
int main()
{
struct xuzhina_dump_c05_s3_ex test[15];
init( test, 15 );
return result( test, 15 );
}
可见是由于init的这一块代码所导致的
54 default:
55 test[i].a = i;
56 test[i].b = i%4;
57 test[i].oper = 0;
58 break;