= 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進数とスペース改行以外なら何でもよい }}} やるべきことは次の流れであると考えられる。 1. 仮想マシンの構造を把握する 2. UselessOSにかかってる制限を把握する 3. 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 : rXY ? rY + rX : rY * rX|||| ||f2XY||r15 = rY - rX|||| ||f3XY||r15 = rY / rX|||| ||f4XY||r15 = rY % rX|||| ||f5XY||r15 = rY xor rX|||| ||f6XY||r15 = X> (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 === {{{#!highlight perl use strict; use warnings; use feature ':5.10'; my $file = shift @ARGV; sub r { "r" . hex(shift) } sub reladdr { my $addr = shift; my $reladdr = hex(shift); $addr += 2 + $reladdr; if ($reladdr >= 0x80) { $addr -= 0x100 } sprintf "0x%04x", $addr; } sub disas { my $addr = shift; my $hex = shift; given ($hex) { when (/0010/) { return "ret" } when (/0020/) { return "syscall" } when (/0040/) { return "exitsyscall" } when (/00(.)3/) { return "r2:3 = @{[r($1)]}==0; r2:4 = @{[r($1)]}<0" } when (/00(.)4/) { return "inc @{[r($1)]}" } when (/00(.)5/) { return "dec @{[r($1)]}" } when (/00(.)6/) { return "push.byte @{[r($1)]}" } when (/00(.)7/) { return "push.word @{[r($1)]}" } when (/00(.)8/) { return "pop.byte @{[r($1)]}" } when (/00(.)9/) { return "pop.word @{[r($1)]}" } when (/00(.)a/) { return "swap @{[r($1)]}, x@{[r($1)]}" } when (/10(.)(.)/) { return "@{[r($2)]} = @{[r($1)]}" } when (/20(.)(.)/) { return "@{[r($1)]} = @{[r($2)]}" } when (/30(..)/) { return "call @{[reladdr($addr, $1)]}" } when (/40(..)/) { return "jump @{[reladdr($addr, $1)]}" } when (/50(.)(.)/) { return "@{[r($2)]} = *(byte*)@{[r($1)]}" } when (/60(.)(.)/) { return "*(byte*)@{[r($2)]} = @{[r($1)]}" } when (/70(.)(.)/) { return "@{[r($2)]} = *(word*)@{[r($1)]}" } when (/80(.)(.)/) { return "*(word*)@{[r($2)]} = @{[r($1)]}" } when (/90(..)/) { return "push 0x$1" } when (/c0(.)(.)/) { return "r2 = @{[r($1)]}==@{[r($2)]} ? 1 : @{[r($1)]}<@{[r($2)]} ? 2 : 4" } when (/(.)e(..)/) { return "jump @{[reladdr($addr, $2)]} if r2:$1" } when (/(.)f(..)/) { return "jump @{[reladdr($addr, $2)]} if !r2:$1" } when (/(.)1(.)(.)/) { if (hex($2) > hex($3)) { return "@{[r($1)]} = @{[r($3)]} + @{[r($2)]}" } else { return "@{[r($1)]} = @{[r($3)]} * @{[r($2)]}" } } when (/(.)2(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} - @{[r($2)]}" } when (/(.)3(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} / @{[r($2)]}" } when (/(.)4(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} % @{[r($2)]}" } when (/(.)5(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} xor @{[r($2)]}" } when (/(.)6(.)(.)/) { if (hex($2) < hex($3)) { return "@{[r($1)]} = @{[r($3)]} | @{[r($2)]}" } else { return "@{[r($1)]} = @{[r($3)]} & @{[r($2)]}" } } when (/(.)7(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} << (@{[r($2)]} & 0xff)" } when (/(.)8(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} >> (@{[r($2)]} & 0xff)" } when (/(.)9(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} rot<< (@{[r($2)]} & 0xff)" } when (/(.)a(.)(.)/) { return "@{[r($1)]} = @{[r($3)]} rot>> (@{[r($2)]} & 0xff)" } when (/(.)b(..)/) { return "@{[r($1)]} = 0x$2" } when (/(.)c(..)/) { return "@{[r($1)]}l = 0x$2" } when (/(.)d(..)/) { return "@{[r($1)]}h = 0x$2" } default { "" } } } open my $fh, '<', $file or die; my $addr = 0; while (read($fh, my $word, 2)) { my $h = unpack("H*", $word); my $s = $word; $s =~ s/(.)/if (ord($1) < 0x20 || ord($1) >= 0x7f) { "." } else { $1 }/sge; printf "%04x: %s %s %s\n", $addr, $h, $s, disas($addr, $h); $addr += 2; } }}} === 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