UEFIでEject専用機を作ろうとした
Eject専用PCを作りたい衝動に駆られたので、EjectするだけのUEFI Applicationを書いた。
6f70/efieject · GitHub
某所で話した時にはEFI_EXT_SCSI_PASS_THRU_PROTOCOLを使っていたが、EFI_SCSI_IO_PROTOCOLの方がシンプルなので切り替えた。
これをブートローダーに設定すると、起動画面はこんな感じになる。
VirtualBox 4.3.4上でパススルーのホストドライブをeject出来る事を確認した。binary/にefiファイルとisoファイルを入れておいたので手軽にejectできる。
ただ、残念なことに手元の実機(M/B: GA-H77-D3H)では動かなかった。LocateHandleBufferでEFI_NOT_FOUNDが返ってくる。EFI_SCSI_IO_PROTOCOLが実装されていないか、何か勘違いしているのかもしれない。
Hack.lu 2013 CTF FluxArchiv 1&2 Write-up
気が向いたのでWrite-upを書く。
FluxArchiv (Part 1) (Reversing 400)
These funny humans try to exclude us from the delicious beer of the Oktoberfest! They made up a passcode for everyone who wants to enter the Festzelt. Sadly, our human informant friend could not learn the passcode for us. But he heard a conversation between two drunken humans, that they were using the same passcode for this intercepted archive file. They claimed that the format is is absolutely secure and solves any kind of security issue. It's written by this funny hacker group named FluxFingers. Real jerks if you ask me. Anyway, it seems that the capability of drunken humans to remember things is limited. So they just used a 6 character passcode with only numbers and upper-case letters. So crack this passcode and get our ticket to their delicious german beer!
Here is the challenge: https://ctf.fluxfingers.net/static/downloads/fluxarchiv/hacklu2013_archiv_challenge1.tar.gz
こんな感じ。
% tar zxvf hacklu2013_archiv_challenge1.tar.gz hacklu2013_archiv_challenge1/ hacklu2013_archiv_challenge1/archiv hacklu2013_archiv_challenge1/FluxArchiv.arc % file hacklu2013_archiv_challenge1/archiv hacklu2013_archiv_challenge1/archiv: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0x0fa7f6566e2ff973c4d045b10f6f751898c70053, not stripped % file hacklu2013_archiv_challenge1/FluxArchiv.arc hacklu2013_archiv_challenge1/FluxArchiv.arc: data % xxd -g 1 hacklu2013_archiv_challenge1/FluxArchiv.arc | head 0000000: 46 6c 75 58 41 72 43 68 69 56 31 33 37 29 42 df FluXArChiV137)B. 0000010: 27 12 82 45 05 d8 17 1f 4f 0b cb 14 15 3d 39 ba '..E....O....=9. 0000020: 94 75 7c 42 2d e5 a1 d1 7f 19 09 1a 61 d4 d2 85 .u|B-.......a... 0000030: d3 19 09 1a 61 d4 d2 85 c4 19 09 1a 61 d4 d2 85 ....a.......a... 0000040: 2d f3 33 48 35 14 8a ff 24 8a e9 fc 98 66 29 76 -.3H5...$....f)v 0000050: b3 6d 7d 7f 0f a0 bb ea 9f 42 6a 79 e1 e7 f6 ed .m}......Bjy.... 0000060: 52 5a 92 fb 17 a9 7d 8f 0e 48 09 af ff 4d b4 04 RZ....}..H...M.. 0000070: e1 6f 19 5a a9 60 e8 20 25 6a 93 27 ed 09 b6 88 .o.Z.`. %j.'.... 0000080: 93 f6 26 1d 27 26 3f 67 6b 77 54 8c cf cb 72 9c ..&.'&?gkwT...r. 0000090: 70 1e bf 00 51 84 8f bd 54 d8 94 5b c5 81 17 73 p...Q...T..[...s
archivというのが独自形式のアーカイバで、そのアーカイバで作成されたFluxArchiv.arcというアーカイブファイルのパスワードを求めればいいらしい。
IDAで処理の流れを追った所、hash_of_passwordという配列にパスワードのSHA1ハッシュが格納されていて、そのハッシュを秘密鍵としてRC4でファイルを暗号化していた。hash_of_passwordを参照している部分を中心に見ていくと、verifyArchiv関数が以下の様な処理をしている事を発見した。
#!/usr/bin/env python import sys import hashlib archive = "" hash_of_password = "" def verifyArchiv(): SHUFFLE_TABLE = [0, 7, 14, 1, 8, 15, 2, 9, 16, 3, 10, 17, 4, 11, 18, 5, 12, 19, 6, 13] hash1_correct = "" with open(archive) as f: hash1_correct = f.read()[0x0C:0x20] hash0 = hash_of_password hash0_shuffled = ''.join([hash0[SHUFFLE_TABLE[i]] for i in xrange(20)]) sha1 = hashlib.sha1() sha1.update(hash0_shuffled) hash1 = sha1.digest() return 1 if hash1 == hash1_correct else 0 def main(argv): global archive, hash_of_password archive = argv[1] password = argv[2] sha1 = hashlib.sha1() sha1.update(password) hash_of_password = sha1.digest() print 'OK' if verifyArchiv() else 'NG' if __name__ == '__main__': main(sys.argv)
問題文によればパスワードは英数字大文字6文字という事だから、36 ** 6 = 約22億通りなのでなんとか総当りできるか。こんな感じのコードを書いた。
#!/usr/bin/env python import hashlib import string import itertools import multiprocessing import sys HASH1_CORRECT = open('FluxArchiv.arc').read()[0x0C:0x20] CHARS = string.ascii_uppercase + string.digits SHUFFLE_TABLE = [0, 7, 14, 1, 8, 15, 2, 9, 16, 3, 10, 17, 4, 11, 18, 5, 12, 19, 6, 13] def test_password(pw): sha1 = hashlib.sha1() sha1.update(pw) hash0 = sha1.digest() hash0_shuffled = ''.join([hash0[SHUFFLE_TABLE[i]] for i in xrange(20)]) sha1 = hashlib.sha1() sha1.update(hash0_shuffled) hash1 = sha1.digest() return hash1 == HASH1_CORRECT def process(headch): for _pw in itertools.product(CHARS, repeat=5): if test_password(headch + ''.join(_pw)): print headch + ''.join(_pw) break def main(argv): num_pool = int(argv[1]) for i in xrange(0, len(CHARS), num_pool): pool = multiprocessing.Pool(processes=num_pool) pool.map(process, CHARS[i:min(i + num_pool, len(CHARS))]) pool.close() if __name__ == '__main__': main(sys.argv)
(multiprocessingの使い方これでいいのか知らない)
最初はシングルプロセスのコードだった。i5-3317U上のシングルプロセスで2~3時間程度回していたらヒットした。
% ./bruteforce.py PWF41L
マルチプロセスのコードも書いて他の環境でも回してみたところ、Xeon X5570 * 2上の16プロセスとi7-3770上の8プロセスで、どちらも15分程度でヒットした。
FluxArchiv (Part 2) (Reversing 500)
These sneaky humans! They do not just use one passcode, but two to enter the Festzelt. We heard that the passcode is hidden inside the archive file. It seems that the FluxFingers overrated their programming skill and had a major logical flaw in the archive file structure. Some of the drunken Oktoberfest humans found it and abused this flaw in order to transfer hidden messages. Find this passcode so we can finally drink their beer!
(only solvable when FluxArchiv (Part 1) was solved)
Here is the challenge: https://ctf.fluxfingers.net/static/downloads/fluxarchiv/hacklu2013_archiv_challenge1.tar.gz
archivのロジックの欠陥を利用してFluxArchiv.arcの中にフラグが隠してあるという事らしい。FluxArchiv.arcに格納されているファイル4つを全てarchivで展開して調べたが、もちろん何も無かった。
FluxArchiv.arcは先頭32bytesのヘッダと1040bytesのブロックの繰り返しで構成されているので、全てのブロックの中にどのファイルでも使われていないブロックが有るのではないかという推測が立った。格納されているファイルが使用しているブロックの一覧等の情報を出力するコードを書いた。
#!/usr/bin/env python import hashlib import sys from struct import pack, unpack from Crypto.Cipher import ARC4 # 'FluXL1sT' MAGIC_FILEENTRY = pack('<Q', 0x5473314C58756C46) # sha1('PWF41L') KEY = '328042503975adb0c62f536d8416b5f78babea15'.decode('hex') ARCFILE = open(sys.argv[1]) BLOCKSIZE = 0x410 def read_archive(length): rc4 = ARC4.new(KEY) dec = rc4.decrypt(ARCFILE.read(length)) return dec def list_blocks(blocki, blocknum): blocklist = [blocki] for blockcnt in xrange(blocknum - 1): ARCFILE.seek(0x20 + blocki * BLOCKSIZE) blocki = unpack('<Q', read_archive(0x08))[0] blocklist.append(blocki) return blocklist def main(argv): blocki = 0 ARCFILE.seek(0x20) while read_archive(0x08) == MAGIC_FILEENTRY: for filei in xrange(8): ARCFILE.seek((0x20 + 0x10 + blocki * BLOCKSIZE) + filei * 0x80) blocki_start = unpack('<Q', read_archive(0x08))[0] blocknum = unpack('<Q', read_archive(0x08))[0] if blocki_start != 0: print 'FILE %d:' % filei print ' index of start block: 0x%08x' % blocki_start print ' number of blocks: 0x%08x' % blocknum print ' md5sum: %s' % read_archive(0x10).encode('hex') print ' filename: %s' % read_archive(0x60).rstrip('\x00') print ' blocks: %s' % ', '.join(map(str, list_blocks(blocki_start, blocknum))) # next file entry ARCFILE.seek(0x20 + 0x08 + blocki * BLOCKSIZE) blocki = unpack('<Q', read_archive(0x08))[0] if blocki != 0: print '\nindex of next file entry block: 0x%08x\n' % blocki ARCFILE.seek(0x20 + blocki * BLOCKSIZE) else: break if __name__ == '__main__': main(sys.argv)
(元のコードが残念だったので終わってからかなり書き直した。)
出力結果。
FILE 0: index of start block: 0x00000001 number of blocks: 0x00000016 md5sum: ffea3a5254c0587ad5b2ece81be8bab5 filename: attentionzombie.mp3 blocks: 1, 3, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157 FILE 1: index of start block: 0x00000002 number of blocks: 0x00000086 md5sum: ec5638da4810f75e0735a3df9e30c1b0 filename: Did_You_Know.jpg blocks: 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136 FILE 3: index of start block: 0x00000089 number of blocks: 0x00000009 md5sum: 2bee8bec6c1a8ad658dffbe14bc14792 filename: fluxfingers.png blocks: 137, 162, 163, 164, 165, 166, 167, 168, 169 index of next file entry block: 0x000000ad FILE 4: index of start block: 0x000000b2 number of blocks: 0x0000007e md5sum: f1df033f0179fe101d11700fe796b4fd filename: th_oh-noes-everybody-panic.gif blocks: 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303
使用されているブロックを調べるとブロック158-161, 170-177のがどのファイルからも使われていない事が分かったので、適当に復号して調べたら、ブロック160でflagを見つけた。
#!/usr/bin/env python import sys from struct import pack, unpack from Crypto.Cipher import ARC4 # sha1('PWF41L') KEY = '328042503975adb0c62f536d8416b5f78babea15'.decode('hex') ARCFILE = open(sys.argv[1]) BLOCKSIZE = 0x410 def read_archive(length): rc4 = ARC4.new(KEY) dec = rc4.decrypt(ARCFILE.read(length)) return dec def main(argv): blocki = int(argv[2]) outfile = argv[3] ARCFILE.seek(0x20 + blocki * BLOCKSIZE) with open(outfile, 'w') as f: f.write(read_archive(0x08) + read_archive(BLOCKSIZE - 0x08)) if __name__ == '__main__': main(sys.argv)
% xxd -g 1 block160.bin 0000000: 00 00 00 00 00 00 00 00 65 20 65 6c 65 63 74 72 ........e electr 0000010: 6f 6e 20 61 6e 64 20 74 68 65 20 73 77 69 74 63 on and the switc 0000020: 68 2c 20 74 68 65 0a 62 65 61 75 74 79 20 6f 66 h, the.beauty of ... 00003a0: 20 4d 65 6e 74 6f 72 2b 2b 2b 0a 0a 46 6c 61 67 Mentor+++..Flag 00003b0: 3a 20 44 33 6c 65 74 69 6e 47 2d 31 6e 64 33 78 : D3letinG-1nd3x 00003c0: 5f 46 34 69 4c 0a 0a 1f 16 f8 73 37 72 64 c5 5c _F4iL.....s7rd.\ ...
どうでもいいけどこんなのとかもあった。
% xxd -g 1 block161.bin | head -n 2 0000000: 00 00 00 00 00 00 00 00 44 69 64 20 59 6f 75 20 ........Did You 0000010: 4b 6e 6f 77 00 00 00 00 00 00 00 00 00 00 00 00 Know............ % xxd -g 1 block170.bin | head -n 2 0000000: 00 00 00 00 00 00 00 00 54 68 61 74 20 47 6f 64 ........That God 0000010: 73 20 4f 66 20 44 65 61 74 68 00 00 00 00 00 00 s Of Death...... % xxd -g 1 block171.bin | head -n 2 0000000: 00 00 00 00 00 00 00 00 4c 6f 76 65 73 20 41 70 ........Loves Ap 0000010: 70 6c 65 73 3f 00 00 00 00 00 00 00 00 00 00 00 ples?...........
SECCON信州大会CTF Write-up
解いた順。(多分)
パスワードを探せ (バイナリ, 100pt)
stamp.exe
exeのダウンロードリンクのみが与えられた。exeを起動すると以下のウィンドウが表示された。
IDAに読み込ませて眺めていると、以下の様な関数を発見した。
2つのループでそれぞれ別々にTextOutAを繰り返し呼んでいる。片方では座標を配列から決定していて、もう片方ではrandから決定している。
怪しい。OllyDbgで両方のTextOutAにブレークポイントを仕掛けて実行し、何度かポチポチしていたらフラグが出てきた。
解答
ST4MPS3CURITY
2012/4/27 11:08:54 に作成されたエントリの名前は何ですか? (フォレンジック, 100pt)
ファイル Filesystem001.bin の内容を確認し、次の設問に回答してください。
2012/4/27 11:08:54 に作成されたエントリの名前は何ですか?Filesystem001.bin
こんな感じ。
0000000: 2e 20 20 20 20 20 20 20 20 20 20 10 00 59 dc 58 . ..Y.X 0000010: 9b 40 9b 40 00 00 dd 58 9b 40 05 00 00 00 00 00 .@.@...X.@...... 0000020: 2e 2e 20 20 20 20 20 20 20 20 20 10 00 59 dc 58 .. ..Y.X 0000030: 9b 40 9b 40 00 00 dd 58 9b 40 00 00 00 00 00 00 .@.@...X.@...... 0000040: 42 2e 00 6a 00 70 00 67 00 00 00 0f 00 c7 ff ff B..j.p.g........ 0000050: ff ff ff ff ff ff ff ff ff ff 00 00 ff ff ff ff ................ 0000060: 01 43 00 68 00 72 00 79 00 73 00 0f 00 c7 61 00 .C.h.r.y.s....a. 0000070: 6e 00 74 00 68 00 65 00 6d 00 00 00 75 00 6d 00 n.t.h.e.m...u.m. 0000080: 43 48 52 59 53 41 7e 31 4a 50 47 20 00 1d e3 58 CHRYSA~1JPG ...X 0000090: 9b 40 9b 40 00 00 8d 6e ee 3a 06 00 22 6b 0d 00 .@.@...n.:.."k.. 00000a0: e5 44 00 65 00 73 00 65 00 72 00 0f 00 e3 74 00 .D.e.s.e.r....t. 00000b0: 2e 00 6a 00 70 00 67 00 00 00 00 00 ff ff ff ff ..j.p.g......... 00000c0: e5 45 53 45 52 54 20 20 4a 50 47 20 00 20 e3 58 .ESERT JPG . .X 00000d0: 9b 40 9b 40 00 00 8d 6e ee 3a dd 00 75 e8 0c 00 .@.@...n.:..u... 00000e0: e5 67 00 00 00 ff ff ff ff ff ff 0f 00 1d ff ff .g.............. 00000f0: ff ff ff ff ff ff ff ff ff ff 00 00 ff ff ff ff ................ ...... 0000b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000b90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000ba0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000bb0: 00 00 00 00 00 00 00 00 ........
8.3ファイル名が見えるのでFATの一部を切り出したバイナリっぽい。適当なFATのイメージを作って同じタイムスタンプのファイルを作ることにする。
dd if=/dev/zero of=fat16.bin count=65536 mkfs.vfat -F 16 fat16.bin sudo mount -o loop fat16.bin /mnt/work0 sudo touch -t 1204271108.54 /mnt/work0/MIKU sudo umount /mnt/work0
作成したファイル名でfat16.bin中を検索。
00107f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0010800: 4d 49 4b 55 20 20 20 20 20 20 20 20 00 00 2d 5e MIKU ..-^ 0010810: 46 43 9b 40 00 00 1b 59 9b 40 00 00 00 00 00 00 FC.@...Y.@...... 0010820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0010816:0x001081aの1b 59 9b 40がなんとなくタイムスタンプっぽかったので、その値でFilesystem001.bin中を検索した。
00004a0: 41 50 00 61 00 6e 00 64 00 61 00 0f 00 90 2e 00 AP.a.n.d.a...... 00004b0: 6a 00 70 00 67 00 00 00 ff ff 00 00 ff ff ff ff j.p.g........... 00004c0: 50 41 4e 44 41 20 20 20 4a 50 47 10 00 04 1b 59 PANDA JPG....Y 00004d0: 9b 40 9b 40 00 00 1c 59 9b 40 9b 05 00 00 00 00 .@.@...Y.@......
解答
Panda.jpg
RXでアセンブラ短歌 (プログラミング, 100pt)
以下のアセンブラ短歌で出力される文字列は? fb 7e 67 6d 62 fb 62 15 08 0c 65 03 fc 37 67 7e a7 66 11 66 43 66 55 03 ef 02 75 60 ff 67 01
( ´_ゝ`)……。
IDAではRXシリーズは逆アセンブルできなかった。objdumpで逆アセンブルしようと思って "objdump rx renesas" 等のキーワードで検索したがヒットしない。まだ対応していないのかと思いルネサスの開発キットやマニュアル等を当たっていた所、隣で同じ問題を解いていた@maytheplicが「RXシリーズ用のgccならあった」 と言ってRX用gccについてのWebページを見せてくれた。これで気付いた。gccがあるならbinutilsもあるはずであり、検索するならobjdumpではなくbinutilsで検索すべきだった。
"binutils rx"等のキーワードで検索をして、このページに行き着いた。アーキテクチャ欄に"elf32-rx-be"等があるので使えるはず。早速逆アセンブル。
>all-objdump.exe -D -b binary -mrx rxasm.bin rxasm.bin: file format binary Disassembly of section .data: 00000000 <.data>: 0: fb 7e 67 6d 62 mov.l #0x626d67, r7 5: fb 62 15 08 0c 65 mov.l #0x650c0815, r6 b: 03 nop c: fc 37 67 xor r6, r7 f: 7e a7 push.l r7 11: 66 11 mov.l #1, r1 13: 66 43 mov.l #4, r3 15: 66 55 mov.l #5, r5 17: 03 nop 18: ef 02 mov.l r0, r2 1a: 75 60 ff int #255 1d: 67 01 rtsd #4
解答
% python -c "from struct import pack; print pack('<I', 0x626d67 ^ 0x650c0815)" rene
キーワードは番組の中で。 (ネットワーク, 200pt)
10.0.2.3 2222/tcpでサーバが動作しています。
パーツを集めて正解文字列を特定してください。
答えは正解文字列のsha1ハッシュ値です。
10.0.2.3:2222にncで接続すると4~5桁の数字が送られてきて数秒間(?)停止した後に通信が切れる という様な挙動を示した。送られてくる数字は以下の6つの数字のいずれかで接続毎(1秒毎?)に不規則に変わっていた。
7372 8271 9812 16212 39812 54721
数字の規則性などを見出そうとしばらく悩んだ後、この問題のジャンルがネットワークである事からこれらの数字はポート番号ではないかと気付いた。通信が停止している最中にncでそのポートに接続してみた所、文字が1文字だけ送られてきて通信が切れた。ポート番号毎に送られてくる文字は決まっていて、以下のように対応していた。
{7372: 's', 8271: '3', 9812: 'C', 16212: 'C', 39812: '0', 54721: 'N'}
解答
% echo -n "s3Cc0N" | sha1sum fdc6a976e1971ca680ed5328814333cbff321881 -
キーワードは番組の中で(2) (ネットワーク, 300pt)
10.0.2.3 3333/tcpでサーバが動作しています。
パーツを集めて答えを見つけてください。
今回はたぶん人力では無理です、かしこ。
基本は1と同じ。ポート番号を送ってくる接続がすぐ切れる点と、送られてきたポート番号で接続しに行った時に降ってくるデータがやたらデカい点が違っていた。
3333/tcpにポート番号を聞きに行って各ポートからの受信データを全てつなぎ合わせて保存するPythonスクリプトを適当に書いた。
#!/usr/bin/env python import subprocess import time def set_nonblocking(fh): import fcntl import os fd = fh.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) ports = {} for _ in xrange(50): target0 = subprocess.Popen(['nc', '10.0.2.3', '3333'], stdout=subprocess.PIPE) set_nonblocking(target0.stdout) time.sleep(0.1) portnum = target0.stdout.read().rstrip() target1 = subprocess.Popen(['nc', '10.0.2.3', portnum], stdout=subprocess.PIPE) set_nonblocking(target1.stdout) time.sleep(0.3) recv = target1.stdout.read() target0.kill() target1.kill() if int(portnum) not in ports or len(ports[int(portnum)]) < len(recv): ports[int(portnum)] = recv recvdata = "" for key in sorted(ports.iterkeys()): recvdata += ports[key] with open('recvdata.bin', 'w') as f: f.write(recvdata)
(接続周りが変だったので変なことをしている)
% file recvdata.bin recvdata.bin: Zip archive data, at least v2.0 to extract % unzip recvdata.bin Archive: recvdata.bin inflating: q3.exe
解答
>q3.exe key = sashimi-umai
反省とか
4時間の短期決戦だったので、ピンと来ない問題を避けて解ける問題に集中した。正しい選択だったと思う。
"We are still here"が解けなかったのが残念。実行ファイルではなくSECCON横浜の"King of 目grep"的な問題かと思っていたがWriteupを読んで納得した。Web問の "管理者ページからキーを探せ" は解けなかったがチームメイトが頑張ってくれていた。お疲れ様と言いたい。
参加者の皆さん、今回のCTFを企画して下さったSECCON実行委員会の皆さんお疲れ様でした。ありがとうございました。(メール対応お手数おかけしました。ありがとうございました)
新春パズル 延長戦らいとあっぷ
- 問題
http://www1.axfc.net/uploader/so/2743059.zip
- 1問目
ヤサヤサ ソゥウールニプソ ゲヌ
チークァミウマ トオオ シハハモンシモンチュフド
アルキゾニューツマワニヤヘス
アキヨ
- 解答
FFXのアルベド語。(アルベド語 - Wikipedia)
翻訳すると以下の文章となる。
またまた とぅるーくりぱと ぜす
きーふぁいるは おのの にななよんによんきゅうぼ
かくしごりゅーむはありませぬ
かしこ
Axfcの2742495をkeyfileとして、同梱のQUEST_EX1をTrueCryptでマウント。
- 2問目
0バイトのtxtと真っ白pngが入ったFATなディスクがマウントされる。
そちらには情報は無く、ディスク内にQUEST_Ex3.zipというファイルが削除済みで残っているので適当なユーティリティで復元
- 3問目
最終問題です。
iesys_src.7zを格納した記憶媒体を、私の友達の裕(ゆたか)くんに預けました。
江ノ島に住む地域ネコです。(私が名前をつけました。)
ゆたかくんをさがしてください。
写真4枚が同梱されている。Exifは無し。内1枚に1月4日の神奈川新聞が写っている。
DEFCON CTF Quals grab400 うろ覚えwriteup
2012年6月2日, 3日(UTC)に行われたDEFCON CTF Qualsに参加しました。その内の一問 grab bag 400 のwriteupです。
ペネトレテスターの辻さんもblogで同じ問題のwriteupを書かれていますが、僕は少し違う解法で解きました。スクリーンショットとか色々取るのを失念していたので、その辺は辻さんのblogを参照してください。
問題
What is Jeff Moss' checking account balance?
Bank site => http://140.197.217.85:8080/boa_bank
User:blacksheep, Password:luvMeSomeSheep
ざっとサイトを調べた所、任意のパラメータを与えられそうなのはログイン画面のID, Passと支店検索フォームのZIP codeの3つでした。支店検索フォームにシングルクオートを入力したらSQLのエラーメッセージが表示されたので、ここから攻める事にします。
まず、テーブル名を列挙しました。辻さんはinformation_schemaを使っていますが、僕は確かpg_databaseテーブルからdatnameを引っ張って来ました。クエリはこんな感じ。
0 UNION SELECT datname,'','','','','' FROM pg_database
列挙されたテーブル一覧を眺めるとaccountというテーブルがありました。怪しいですね。調べます。それっぽいカラム名を入力してみたりHAVINGとGROUP BYでカラム名をエラーメッセージとして表示させてみた所、使えそうなカラムが以下のような感じである事が判明しました。
account.type => 'checking', 'saving'
account.balance => Integer
account.owner => Integer
account.name => (以上3カラムの内容その他をカンマで区切って連結したもの)
そこで、以下のようなクエリを投げて(うろ覚え)、全checking口座のbalanceとownerを列挙した所、balanceが全て0.00であることが判明。(!?)
0 UNION SELECT account.name,'','','','','' FROM account WHERE account.type = 'checking'
この時点で0.00と解答しても良かったのですが、どうも判然としないのでJeff Mossの口座を突き止める事にします。accountテーブルでは口座所有者の情報はownerの数値しか見つからなかったので、別のテーブルの情報と結びつける事を考えました。今一度テーブル一覧を眺めると、customerなるテーブルがあるようです。調べます。
accountテーブルと同様にcustomerテーブルも調べた所、customer.id, customer.firstname, customer.lastnameがあるようなので列挙させました。"Jeff", "Moss"でページを検索しましたが、彼らしき人物はヒットしなかったので"Jeff Moss"で検索した所、Wikipediaに以下のような記述を発見。
Jeff Moss, also known as The Dark Tangent, is the founder of the Black Hat and DEF CON computer hacker conferences.
"Tangent"でページ検索をかけたら、居ました。Dark Tangentさん。(どんな名前だ…)彼のcustomer.idが分かったので、account.ownerと結びつけた上でaccount.balanceを今一度確認しましたが、やっぱり0.00です。仕方がないので、"0.00"と解答した所、"CORRECT!"となり400ptをゲットしました。
反省
非効率極まりないですねorz
account.ownerとcustomer.idが同一の値であるという前提も勘に寄ったものですし、これは好ましくない解法です。全ユーザーのbalanceが0.00だったのは、何かの間違いだったのか、それとも意図されたものだったのか…。SQL Injectionはあまり知らなかったので、いい勉強になりました。
forensic 300も解きましたが、概ね辻さんと同じアプローチでした。
反転したのはどのbit? - SECCON CTF福岡大会 問題レポート
2012年2月18日, 19日に開催されたSECCON CTF福岡大会のレポートを、実行委員の園田さんが@ITで執筆されていました。僕は残念ながら参加する事が出来なかったのですが、大会で出題された問題の内の一題がレポート中で引用されていたので、解いてみました。
以下、そのレポート(とオマケ)です。ネタバレを含むので、これから解こうと思っている人は注意してください。
問題は
「以下のファイルダンプは宇宙からの中性子線で1bitのデータが反転したものである。(略)1bitだけ異なる正常なデータファイルを復元し、秘密の鍵を答えよ。」
というもの。実行委員長の竹迫さん作成の問題です。
まずは、ダンプのテキストを素のバイナリに戻します。テキストを corrupted.txt との名で保存し、毎行頭のアドレスを削除した上で、
cat corrupted.txt | xxd -r -p > corrupted.bin
とすると、素のバイナリがcorrupted.binとして保存されます。
次に、corrupted.binがどういうファイルなのか調べます。fileコマンドに入力したところ、出力は以下の通りでした。
corrupted.bin: gzip compressed data, was "z.txt", from Unix, last modified: Tue Feb 14 00:28:39 2012, max compression
これにより、このファイルはどうやらgzipで圧縮されたものらしいという事が分かったので、拡張子をgzに変えてgzip -dで解凍を試みたところ、
incomplete distance tree gzip: corrupted.gz: invalid compressed data--format violated
となりました。確かにこのファイルは破損しているようです。
corrupted.gzの大きさは357bytes = 2856bitsで、反転したビットは1ビットのみである事が分かっているので、総当りで1bitずつ反転させて解凍を試みる方法が現実的に実行可能であると推測できます。後は、corrupted.gzを読み込んで全ビットを1つずつ反転させながら保存していくプログラムを書き、保存された2856個のgzファイル全てに対しシェルスクリプトでgzip -dを試みて、そのうちの1つ、ファイル先頭から383bit目を反転させたファイルの解凍に成功しました。その中身がまさに「秘密の鍵」だったのですが、それは敢えてここには書きません。是非自分でやってみましょう。
オマケ
とりあえず上の方法で解くことは出来たものの、この流れではおそらく定石であろう総当たりだけをして終わるのは、もったいない。
というわけで、エラーメッセージを手がかりにした総当りによらない反転箇所の絞り込みを試みました。以下詳細。
出力されたエラーメッセージを眺めてみると
incomplete distance tree
という文がいかにも反転箇所の手がかりになりそうです。"distance tree"が何処にあるのか突き止めるため、まずはgzipのファイルフォーマットについて調べます。"gzip ヘッダ"等でぐぐって、以下のページを発見しました。
[Studying HTTP] gzip
gzip format
上のページの内容とcorrupted.gzのバイナリを突き合わせた所、corrupted.gzの中身は以下のようになっている事が分かりました。
+-----------+-------------+---------+---------+ |header(16B)|payload(333B)|CRC32(4B)|ISIZE(4B)| +-----------+-------------+---------+---------+
括弧内の数字はその部分のバイト数を表しています。ヘッダにも末尾のデータにも"distance tree"らしき物が無いので、どうやらペイロードが怪しいです。ヘッダによれば、このペイロードにはDeflateで圧縮されたデータが格納されているとの事なので、今度はDeflateについて調べます。ぐぐって出てきた以下のページを参考に考えてみることにします。
デフレート圧縮(LZ77圧縮)処理の概要 - ウェブで用いられる画像形式。
RFC 1951 DEFLATE Compressed Data Format Specification version 1.3 日本語訳 - futomi's CGI Cafe
Notes on Deflate in PNG files
上のページ曰く、Deflateのバイナリの構造はこんな感じ。
+----------+--------+---------+---------+ |header(3b)|HLIT(5b)|HDIST(5b)|HCLEN(4b)| -> +----------+--------+---------+---------+ +-----------------------------+-------------------+--------------------+ -> |len for len((HCLEN + 4) * 3b)|len for literal(?b)|len for distance(?b)| -> +-----------------------------+-------------------+--------------------+ +---------------+---+ -> |compressed data|EOD| +---------------+---+
今度は、括弧内の数字はビット数を表しています。各部分の名称は適当に略してありますが、ここで言う"len for distance"がエラーメッセージの"distance tree"を指しているようです。
反転していると思しき部分はこれで判明しましたが、問題はその範囲です。上のページや図を見るとわかるように、Deflateのバイナリは符号化されたビット単位の可変長データが複数組み合わさって構成されており、"len for distance"の範囲を求めるのは容易ではありません。もしかするとこの世の何処かにはDeflateのバイナリをそのまま読んで解読できるイカれたよく訓練されたバイナリアンが居るのかもしれませんが、僕には無理です。
そこで、gzipコマンドのソースコードを弄って、解凍中に当該部分の範囲を出力させてみる事にしました。gzipのソースコード(gzip-1.4.tar.gz)を落としてきて、まずは元々のエラーメッセージでgrepします。inflate.cのinflate_dynamic関数中でエラーとなっているようです。エラー箇所の前後や定義されているマクロを調べると、inflate_dynamic関数中で、DUMPBITSというマクロを用いて、"HLIT"から"len for distance"までを読み込んでいる事が分かりました。そこで、
< DUMPBITS(j) --- > DUMPBITS(j);printf("%d,",j);
という風にinflate_dynamic関数中のDUMPBITSマクロを置換し、読み込みが発生する度にそのビット長を出力させる事にしました。また、"len for literal"と"len for distance"が同一while文中(/* read in literal and distance code lengths */)でまとめて読み込まれているようなので、while文先頭に
if (i == nl) printf("D,");
というコードを挟み、"len for distance"部分に入ったら"D"という文字が出力されるようにしました。以上の改造を施したgzipでcorrupted.gzの解凍を試みると
5,5,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3,3,3,3,4,7,4,4,3,2,4,7,4,7,2,4,3,3,3,3,3,2,2,2,2,4,2,6,4,4,2,4,2,2,2,2,2,3,3,6,D,2,4,3,3,3,2,3,2,6,2,4,2,6,2,2,3,2,3,2,2,2,
という文字列が出力されました。後は、この文字列を表計算ソフトに読み込ませて、ヘッダのサイズに留意しつつ"len for distance"の範囲を求めると、"len for distance"はgz全体で342 - 401bitの範囲にある事が分かりました。実際に反転していた箇所は383bit目なので、求まった範囲とも合致します。これにより、総当りによらず、反転していると推測される箇所の範囲を60/2856まで絞り込むことが出来ました。
まとめ
この問題については、範囲絞り込みの手間より総当りの手間のほうが明らかに少ないので、総当たりを選ぶべきです。しかし、問題によっては、何かしらの方法で範囲を絞り込む事が必要になることもあるでしょう。
誤りがあれば指摘お願いします。