⇤ ← 2015-01-15 01:23:13時点のリビジョン1
サイズ: 12297
コメント:
|
← 2015-01-15 01:25:48時点のリビジョン2 ⇥
サイズ: 12376
コメント:
|
削除された箇所はこのように表示されます。 | 追加された箇所はこのように表示されます。 |
行 17: | 行 17: |
公開された想定exploitと別の方法で解いていたようだ。 |
vm
昔解いた問題の蔵出し。いつの間にか問題がexploit込みで公開されていた。http://archive.aachen.ccc.de/sigintctf-2013/challenges/22/
出題時の環境を再現するには多分,makeした後,ディレクトリを分けて
- bios.bin
- cmd
- exit
- hexdump
- hexpaste
- ls
- uname
をコピーし,mainを vm.bin という名前で配置し,flagファイルにフラグを置いて
% socat tcp-listen:1024,fork exec:./vm.bin
すれば良い。
公開された想定exploitと別の方法で解いていたようだ。
初動分析
よくわからない仮想マシンvm.binが動いている。仮想マシンはx86-64で書かれている。しかしvm.binの全体を取り出す方法がわからない。
Cmd> ls vm.bin exploit . hexdump hexpaste .. ls bios.bin uname exit cmd flag
hexdumpコマンドでサーバ上のファイルを読み出せるが先頭4KBまでのようである。
Cmd> hexdump File? ls 10 1f 1c 00 1d 01 12 1f 3b 0c 00 20 10 3e 00 e3 3e 20 00 e5 3b 09 10 e4 10 15 00 20 10 1d 3b 06 ...
ただしflagファイルについてはアクセスすることができない。
Cmd> hexdump File? flag Access Forbidden! Not Found
hexpasteコマンドでVM上にファイルを配置できる。
Cmd> hexpaste File? ABC 01020304 . <-16進数とスペース改行以外なら何でもよい
やるべきことは次の流れであると考えられる。
- 仮想マシンの構造を把握する
- UselessOSにかかってる制限を把握する
- flagを読み出すプログラムを書いてアップロードして実行
VMの構造の解析
クラッシュ時に16個のレジスタの値が表示されるようになっている。 これを利用して,次のようなコマンド列を流しこんでレジスタにどのような変化が生じるか挙動を観察する。
hexpaste a 0090 0000 0010 . a
いろいろ試してみると次のようなことに気づく。
- 1命令2バイト
- レジスタ0がプログラムカウンタ
- コマンド実行時の最初の命令のアドレスは57344
- レジスタ1がスタックトップ
- レジスタ2がフラグレジスタ
- レジスタ15が元のスタックトップ?
- cmdを二重に起動したらr15=57338になった
- メモリの0x0000-0x0359にアクセス制限がかかっている
- 裏にbios用?の退避レジスタがさらに16個、表のレジスタとswapのみできる
ディスアセンブラを作成しつつ命令の詳細を把握する。bios.binやunameの中身を見ながら, 不明な命令の挙動を推測することもかなり役に立った。
instructions
- 第1ニブル: dst register
- ただし第2ニブルが0,eの時はregisterを意味しない
- 第2ニブル: opcode
- 第2オクテット: 即値 or レジスタ番号
0000 |
(crash) |
|
0001 |
(r1 -= 2) |
|
0010 |
ret |
|
0020 |
syscall |
r3にシステムコール番号、r4に引数, biosに制御を移す, bios中から呼び出すとvmの機能を呼び出し |
0040 |
exitsyscall |
biosからプログラムに制御を戻す |
00X3 |
r2:3 = rX==0; r2:4 = rX<0 |
|
00X4 |
inc rX |
|
00X5 |
dec rX |
|
00X6 |
push.byte rX |
*((byte*)r1)-- = rX |
00X7 |
push.word rX |
*((word*)r1)-- = rX |
00X8 |
pop.byte rX |
rX = *((byte*)r1)++ |
00X9 |
pop.word rX |
rX = *((word*)r1)++ |
00Xa |
swap rX, xrX |
|
10XY |
rY = rX |
|
20XY |
rX = rY |
|
30YY |
call +YY |
push r0 and jump |
40YY |
jump +YY |
|
50XY |
rY = *(byte*)rX |
|
60XY |
*(byte*)rY = rX |
|
70XY |
rY = *(word*)rX |
|
80XY |
*(word*)rY = rX |
|
90XX |
push 0xXX |
|
c0XY |
r2 = rX==rY ? 1 : rX<rY ? 2 : 4 |
|
XeYY |
jump +YY if r2:X |
|
XfYY |
jump +YY if !r2:X |
f1XY |
r15 = X>Y ? rY + rX : rY * rX |
|
f2XY |
r15 = rY - rX |
|
f3XY |
r15 = rY / rX |
|
f4XY |
r15 = rY % rX |
|
f5XY |
r15 = rY xor rX |
|
f6XY |
r15 = X<Y ? rX|rY : rX&rY |
後でレジスタに値をセットしてから確認 |
f7XY |
r15 = rY << (rX & 0xff) |
|
f8XY |
r15 = rY >> (rX & 0xff) |
|
f9XY |
r15 = rY rot<< (rX & 0xff) |
|
faXY |
r15 = rY rot>> (rX & 0xff) |
|
fbXX |
r15 = XX |
|
fcXX |
r15l = XX |
|
fdXX |
r15h = XX |
disasm.pl
1 use strict;
2 use warnings;
3 use feature ':5.10';
4
5 my $file = shift @ARGV;
6
7 sub r { "r" . hex(shift) }
8 sub reladdr {
9 my $addr = shift;
10 my $reladdr = hex(shift);
11 $addr += 2 + $reladdr;
12 if ($reladdr >= 0x80) { $addr -= 0x100 }
13 sprintf "0x%04x", $addr;
14 }
15
16 sub disas {
17 my $addr = shift;
18 my $hex = shift;
19 given ($hex) {
20 when (/0010/) { return "ret" }
21 when (/0020/) { return "syscall" }
22 when (/0040/) { return "exitsyscall" }
23 when (/00(.)3/) { return "r2:3 = @{[r($1)]}==0; r2:4 = @{[r($1)]}<0" }
24 when (/00(.)4/) { return "inc @{[r($1)]}" }
25 when (/00(.)5/) { return "dec @{[r($1)]}" }
26 when (/00(.)6/) { return "push.byte @{[r($1)]}" }
27 when (/00(.)7/) { return "push.word @{[r($1)]}" }
28 when (/00(.)8/) { return "pop.byte @{[r($1)]}" }
29 when (/00(.)9/) { return "pop.word @{[r($1)]}" }
30 when (/00(.)a/) { return "swap @{[r($1)]}, x@{[r($1)]}" }
31 when (/10(.)(.)/) { return "@{[r($2)]} = @{[r($1)]}" }
32 when (/20(.)(.)/) { return "@{[r($1)]} = @{[r($2)]}" }
33 when (/30(..)/) { return "call @{[reladdr($addr, $1)]}" }
34 when (/40(..)/) { return "jump @{[reladdr($addr, $1)]}" }
35 when (/50(.)(.)/) { return "@{[r($2)]} = *(byte*)@{[r($1)]}" }
36 when (/60(.)(.)/) { return "*(byte*)@{[r($2)]} = @{[r($1)]}" }
37 when (/70(.)(.)/) { return "@{[r($2)]} = *(word*)@{[r($1)]}" }
38 when (/80(.)(.)/) { return "*(word*)@{[r($2)]} = @{[r($1)]}" }
39 when (/90(..)/) { return "push 0x$1" }
40 when (/c0(.)(.)/) { return "r2 = @{[r($1)]}==@{[r($2)]} ? 1 : @{[r($1)]}<@{[r($2)]} ? 2 : 4" }
41 when (/(.)e(..)/) { return "jump @{[reladdr($addr, $2)]} if r2:$1" }
42 when (/(.)f(..)/) { return "jump @{[reladdr($addr, $2)]} if !r2:$1" }
43
44 when (/(.)1(.)(.)/) {
45 if (hex($2) > hex($3)) {
46 return "@{[r($1)]} = @{[r($3)]} + @{[r($2)]}"
47 } else {
48 return "@{[r($1)]} = @{[r($3)]} * @{[r($2)]}"
49 }
50 }
51 when (/(.)2(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} - @{[r($2)]}" }
52 when (/(.)3(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} / @{[r($2)]}" }
53 when (/(.)4(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} % @{[r($2)]}" }
54 when (/(.)5(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} xor @{[r($2)]}" }
55 when (/(.)6(.)(.)/) {
56 if (hex($2) < hex($3)) {
57 return "@{[r($1)]} = @{[r($3)]} | @{[r($2)]}"
58 } else {
59 return "@{[r($1)]} = @{[r($3)]} & @{[r($2)]}"
60 }
61 }
62 when (/(.)7(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} << (@{[r($2)]} & 0xff)" }
63 when (/(.)8(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} >> (@{[r($2)]} & 0xff)" }
64 when (/(.)9(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} rot<< (@{[r($2)]} & 0xff)" }
65 when (/(.)a(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} rot>> (@{[r($2)]} & 0xff)" }
66 when (/(.)b(..)/) { return "@{[r($1)]} = 0x$2" }
67 when (/(.)c(..)/) { return "@{[r($1)]}l = 0x$2" }
68 when (/(.)d(..)/) { return "@{[r($1)]}h = 0x$2" }
69 default { "" }
70 }
71 }
72
73 open my $fh, '<', $file or die;
74 my $addr = 0;
75 while (read($fh, my $word, 2)) {
76 my $h = unpack("H*", $word);
77 my $s = $word;
78 $s =~ s/(.)/if (ord($1) < 0x20 || ord($1) >= 0x7f) { "." } else { $1 }/sge;
79 printf "%04x: %s %s %s\n", $addr, $h, $s, disas($addr, $h);
80 $addr += 2;
81 }
dumpmem
何のために作ったかいまいち覚えていないメモリダンプツール
注: abcdefはそれぞれ:;<=>?で出力される
# dump 0xf000-0xffff hexpaste a 101e 1df0 1c00 9d10 9c00 8b01 bb10 ab30 0058 0093 3e1c 9289 3b06 3b06 43b5 454a 0020 3b06 44b5 454a 0020 3b06 4b20 0020 40de 10e1 0010 . a
101e r14 = r1 1df0 r1 = 0xf000 1c00 9d01 r9 = 0x100 9c00 8b01 r8 = 1 bb10 r11 = 0x10 ab30 r10 = 0x30 // '0' LOOP: 0058 r5 = *((byte*)r1)++ 0093 r2:3 = r9 == 0 3e1c jump EXITLOOP if r2:3 9289 r9 = r9 - r8 3b06 r3 = 0x06 43b5 r4 = r5 / r11 454a r4 = r4 + r10 0020 syscall 3b06 r3 = 0x06 44b5 r4 = r5 % r11 454a r4 = r4 + r10 0020 syscall 3b06 r3 = 0x06 4b20 r4 = 0x20 // ' ' 0020 syscall 40de jump LOOP 10e1 r1 = r14 EXITLOOP: 0010 ret
制限1: フラグへのアクセス制限
bios.binを全て抜き出して見てみると0x12aの位置に"Access Forbidden!"という文字列が,0x155の位置に"flag"という文字列が含まれている。
エラーメッセージはどこで参照されているかというと0x0058の位置で,
0058: fd01 .. r15h = 0x01 005a: fc2a .* r15l = 0x2a 005c: 3066 0f call 0x00c4
ここに到達する条件は0x0042における比較で *0x003a == r4 となった時にアクセス禁止エラーとなる。
003c: fd00 .. r15h = 0x00 003e: fc3a .: r15l = 0x3a 0040: 70ff p. r15 = *(word*)r15 0042: c04f .O r2 = r4==r15 ? 1 : r4<r15 ? 2 : 4 0044: 0e12 .. jump 0x0058 if r2:0
よってこの条件分岐を削除することでアクセス制限を回避することが出来ると考えられる。
制限2: BIOSメモリ領域へのアクセス制限
フラグへのアクセス制限を行っている場所がわかったので命令を書き換えてしまいたいが, BIOSのメモリ領域に対してはアプリケーション側からはアクセス制限がかかっており, アクセスしようとするとクラッシュしてしまう。
アクセス制限されている範囲を調べてみると0x0-0x359の範囲であることが分かるので, bios中にそれらしい即値がないかを調べてみると各所に0x35aという値を発見することが出来る。
000e: 1d03 .. r1h = 0x03 0010: 1c5a .Z r1l = 0x5a 0012: 0047 .G push.word r4 ...
0072: 4d03 M. r4h = 0x03 0074: 4c5a LZ r4l = 0x5a 0076: 3b0d ;. r3 = 0x0d 0078: 0020 . syscall ...
00e2: ed03 .. r14h = 0x03 00e4: ec5a .Z r14l = 0x5a 00e6: c04e .N r2 = r4==r14 ? 1 : r4<r14 ? 2 : 4 00e8: 1e22 ." jump 0x010c if r2:1 ...
特に二番目の
0072: 4d03 M. r4h = 0x03 0074: 4c5a LZ r4l = 0x5a 0076: 3b0d ;. r3 = 0x0d 0078: 0020 . syscall
はアクセス制限を設定するVM命令ではないかと疑われる。
ところで,ユーザモードでsyscallされた時にbiosの0番地から処理が実行されると仮定すると 0x03, 0x0a, 0x0f以外のsyscallはそのままVMを呼び出すようになっていると考えられる。
0016: fb03 .. r15 = 0x03 0018: c03f .? r2 = r3==r15 ? 1 : r3<r15 ? 2 : 4 001a: 0e20 . jump 0x003c if r2:0 001c: fb0a .. r15 = 0x0a 001e: c03f .? r2 = r3==r15 ? 1 : r3<r15 ? 2 : 4 0020: 0e1a .. jump 0x003c if r2:0 0022: fb0f .. r15 = 0x0f 0024: c03f .? r2 = r3==r15 ? 1 : r3<r15 ? 2 : 4 0026: 0e3c .< jump 0x0064 if r2:0
先ほどのフラグへのアクセス制限の処理が0x003cから呼び出されていたことや, unameで用いられているsyscall(0x0f)がbiosに含まれる文字列を出力することからこれは正しい仮定であると思える。
実際,ユーザモードから r3 = 0x0d, r4 = 0 でsyscallを呼び出してみれば, BIOSのメモリ領域へのアクセス制限が予想通り解除できた。これで攻撃に必要なエクスプロイトが出揃った。
解法
メモリ制限を解除
4b00 r4 = 0 3b0d r3 = 0x0d 0020 syscall
bios.binのflagへのアクセスチェック部分をNOPで潰す
3d90 r3 = 0x9000 3c00 4d00 r4 = 0x0044 4c44 8034 *(word*)r4 = r3
- hexdump flag
次の順で送信することでフラグが得られる。
hexpaste a 4d00 4c00 3b0d 0020 3d90 3c00 4d00 4c44 8034 0010 . a hexdump flag