Linux 86版3.8節スタックレイアウトのスタックオーバーフローcoredump例
7585 ワード
今、前言のスタックに戻って、上の法則でスタックを回復できるかどうかを見てみましょう.前言の実行可能ファイルには-fomit-frame-pointerコンパイルオプションはありません.
前言のスタックはこうです.
まずebp,espの値を見てみましょう.
間違いなくebpの値は不正です.前節の法則から分かるように,ebp>=espである.
では,espが指すスタックの内容を見て,関数フレームポインタfpを探すしかない.fpを探す前に、スタックレイアウトの単一チェーンテーブルの法則を復習します.
1.fpが存在するアドレスがespより大きい値
2.fp次のユニットの内容はinfo symbolで関数名を表示できます
3.fpが指すユニットも1,2の2つの原則を遵守する.
espが指す内容を見てみましょう.
espの値は0 xbff 8 ef 60であるため、中には0 xbff 8 ef 90、0 xbff 8 ef 98、0 xbff 8 f 66 b、0 xbff 8 efb 8が第1の原則を満たしている.次のユニットが第2の原則を満たしているかどうかを見てみましょう.
0 xbff 8 ef 98,0 xbff 8 efb 8は第1,2の原則を満たすことがわかる.0 xbff 8 ef 98が第3原則を満たしているかどうかを見てみましょう.
偶然ですね.0 xbff 8 ef 98がユニットを指す内容はちょうど0 xbff 8 efb 8です.すなわち、0 xbff 8 ef 98は第3の原則を満たす.
次に0 xbff 8 efb 8もこの3つの原則を満たしているかどうかを見てみましょう.
0 xbff 8 efb 8も3つの原則を満たしていることがわかる.
プログラムはmain関数から実行されるため、実際には実際のスタックの一部が復元されていることがわかります.
では、残りのスタックはどのように復元すればいいのでしょうか.現在復元されているスタックのうち、wrapper 2はスタックトップであるため、wrapper 2のアセンブリを見てみましょう.
wrapper 2が存在するスタックの戻りアドレスは0 x 08048618であるため、
スタックオーバーフローを引き起こします.そして_Z 8 wrapper 1 iPcでは、プロトタイプを知ることができます.
wrapper 2が0 x 18バイトのローカル変数空間を申請したため、wrapper 2戻りアドレス0 x 088618が存在するセルは0 xbff 8 ef 7 Cであり、espの値0 xbff 8 ef 60と0 x 1 C差があり、ちょうど0 x 18ローカル変数アドレス領域と1戻りアドレスの和である.すなわち、スタックは、wrapper 1の呼び出しが完了すると、スタックオーバーフローが発生する.
したがって、スタックは次のようになります.
スタックを復元するだけなら、上はすでにタスクが完了していますが、オーバーフローの原因はどこですか?
wrapper 1のアセンブリから:
wrapper 1では、すべてのメモリアドレス操作がebp,espによって指定されるため、ebpはespによって取得される.したがって、wrapper 1の実行時、espの値は、wrapper 2から0 x 18(wrapper 1の局所変数空間サイズ)を減算し、0 xbff 8 ef 60−0 x 18−4=0 xbff 8 ef 44を返すアドレスであるべきである.
から
wrapper 1ではesp値が指すアドレスが合法であることがわかる.つまり、wrapper 1で実行すると、メモリの境界が外れることはありません.それは
この命令によって引き起こされた.
研究してみようZ8overflowiPc:
overflowはstrcpyを呼び出すため、strcpyは有名な不安全な関数であり、今回のcoredumpの原因である可能性があります.
ここまで、このプログラムのソースコードを貼ってみてください.上のスタックの復元が正確かどうか見てみましょう.
次のように実行されます.
前言のスタックはこうです.
(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の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の値は0 xbff 8 ef 60であるため、中には0 xbff 8 ef 90、0 xbff 8 ef 98、0 xbff 8 f 66 b、0 xbff 8 efb 8が第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
0 xbff 8 ef 98,0 xbff 8 efb 8は第1,2の原則を満たすことがわかる.0 xbff 8 ef 98が第3原則を満たしているかどうかを見てみましょう.
(gdb) x /2x 0xbff8ef98
0xbff8ef98: 0xbff8efb8 0x08048636
偶然ですね.0 xbff 8 ef 98がユニットを指す内容はちょうど0 xbff 8 efb 8です.すなわち、0 xbff 8 ef 98は第3の原則を満たす.
次に0 xbff 8 efb 8もこの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
0 xbff 8 efb 8も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
では、残りのスタックはどのように復元すればいいのでしょうか.現在復元されているスタックのうち、wrapper 2はスタックトップであるため、wrapper 2のアセンブリを見てみましょう.
(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.
wrapper 2が存在するスタックの戻りアドレスは0 x 08048618であるため、
0x08048613 <+23>: call 0x80485de <_Z8wrapper1iPc>
スタックオーバーフローを引き起こします.そして_Z 8 wrapper 1 iPcでは、プロトタイプを知ることができます.
(gdb) shell c++filt _Z8wrapper1iPc
wrapper1(int, char*)
wrapper 2が0 x 18バイトのローカル変数空間を申請したため、wrapper 2戻りアドレス0 x 088618が存在するセルは0 xbff 8 ef 7 Cであり、espの値0 xbff 8 ef 60と0 x 1 C差があり、ちょうど0 x 18ローカル変数アドレス領域と1戻りアドレスの和である.すなわち、スタックは、wrapper 1の呼び出しが完了すると、スタックオーバーフローが発生する.
したがって、スタックは次のようになります.
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
スタックを復元するだけなら、上はすでにタスクが完了していますが、オーバーフローの原因はどこですか?
wrapper 1のアセンブリから:
(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.
wrapper 1では、すべてのメモリアドレス操作がebp,espによって指定されるため、ebpはespによって取得される.したがって、wrapper 1の実行時、espの値は、wrapper 2から0 x 18(wrapper 1の局所変数空間サイズ)を減算し、0 xbff 8 ef 60−0 x 18−4=0 xbff 8 ef 44を返すアドレスであるべきである.
から
(gdb) x 0xbff8ef44
0xbff8ef44: 0x6d754865
wrapper 1ではesp値が指すアドレスが合法であることがわかる.つまり、wrapper 1で実行すると、メモリの境界が外れることはありません.それは
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)