pwniumCTF ROT (Prog 300) Writeup
pwniumCTF に少しだけ手をつけたので1問かいておきます。
順を追って書いていくので、全体だけ欲しい方は最後のプログラムをどうぞ
ROT (Programming 300)
Rot 90, Rot -90, Rot 90, Rot -90... nc 41.231.53.40 9090
Hint : No hints for this task yet 与えられたサーバーに接続すると、base64が降ってきます。
iVBORw0KGgoAAAANSUhEUgA(中略)nuxIpAAAAAElFTkSuQmCC Answer:
(こちらに置いておくのでどうぞ→pwnium300base64.txt)
Answer:で、答えを聞かれるのでどうにかデコードして答えてねーと言う問題。
とりあえず与えられたデータをデコードしてみる。
# Python3 >>> fp = open('pwnium300base64.txt', 'r', encoding='utf-8') >>> msg = fp.read() >>> import base64 >>> print(base64.b64decode(msg)) b'x89PNGrnx1anx00x00x00rIHDRx00x00x00xc8x00x00x00(以下略)
PNGっぽいので、保存してみる。
# Python3 >>> fp = open('x.png', 'wb') >>> decmsg = base64.b64decode(raw.encode('utf-8')) >>> fp.write(decmsg) >>> fp.close()
保存したx.pngを開いてみる。
目が痛くなる。。。
なんか文字が書かれています。(解いた後の画像をあげているので、上でデコードしたものとは違うかも)
パスワードらしきものが加工されています。画像をいじれば文字が読めそうなので、きれいな形にぐりぐりいじってから、画像文字認識(OCR)にかけてみる方針でやってみます。以下のスクリプトはPython2.7で書いています。
(1) 半分にスライス
今回は画像加工に、Pythonの画像加工ライブラリ、Pillowを使用しました。
# pip install pillow
でモジュールを追加することができます。
2つにわかれた文字を合成する必要があるので、2つにスライスをします。
image.clop()に矩形を表すタプルを渡してあげればスライスすることができます。保存はimage.save(filename, format)です。
>>> from PIL import Image # ファイルを開く >>> image = Image.open('x.png', 'r') # 半分にスライス >>> boxl = (0, 0, int(image.size[0] / 2), image.size[1]) >>> boxr = (int(image.size[0] / 2), 0, image.size[0], image.size[1]) >>> leftimage = image.crop(boxl) >>> leftimage.save('left.png', 'PNG') >>> rightimage = image.crop(boxr) >>> rightimage.save('right.png', 'PNG')
(2) 反転
文字と文字をちょうどよく合わせるために、左半分を対角反転します。
image.transpose()にimage.FLIP_TOP_BOTTOMで上下反転とimage.FLIP_LEFT_RIGHTで左右反転を同時に行っています。
# 左半分を対角反転 >>> leftRevimage = leftimage.transpose(Image.FLIP_TOP_BOTTOM).transpose(Image.FLIP_LEFT_RIGHT) >>> leftRevimage.save('leftRev.png', 'PNG')
出力されたものがこちら
右半分と合わせればちょうどいい感じになりそうです。
(3) 黒抽出
目が痛くなるようなカラフルフレームを外したいので、黒色だけ抽出します。
黒色を抽出するために、元画像からimage.getpixel()でピクセルの色を取得し、その色が(100, 100, 100)以下ならimage.putpixel()でキャンバスに書き込んでいます。
閾値を設定しているのは、ジャギーを出来るだけ減らしたかったためです。image.getpixel() == (0, 0, 0)とすると、文字が欠けてしまったのでこのような処理にしています。
# 黒抽出 >>> def checkblack(I, V): ... if I[0] < V[0] and I[1] < V[1] and I[2] < V[2]: ... return True ... return False >>> leftBlackimage = Image.new("RGB", leftRevimage.size, (255, 255, 255)) >>> for x in range(leftRevimage.size[0]): ... for y in range(leftRevimage.size[1]): ... if checkblack(leftRevimage.getpixel((x, y)), (100, 100, 100)): ... leftBlackimage.putpixel((x, y), (0, 0, 0)) >>> leftBlackimage.save('leftblack.png', 'PNG') >>> rightBlackimage = Image.new("RGB", rightimage.size, (255, 255, 255)) >>> for x in range(rightimage.size[0]): ... for y in range(rightimage.size[1]): ... if checkblack(rightimage.getpixel((x, y)), (100, 100, 100)): ... rightBlackimage.putpixel((x, y), (0, 0, 0)) >>> rightBlackimage.save('rightblack.png', 'PNG')
出力されたものがこちら。
(4) 合成・回転
左右の画像が出来上がったので合成します。見た感じ単純合成で良さそうなので、左右両方のデータを読んでいき、黒ならキャンバスに書き込んでいます。
>>> blendimage = Image.new("RGB", rightBlackimage.size, (255, 255, 255)) >>> for x in range(rightBlackimage.size[0]): ... for y in range(rightBlackimage.size[1]): ... if rightBlackimage.getpixel((x, y)) != (255, 255, 255): ... blendimage.putpixel((x, y), rightBlackimage.getpixel((x, y))) ... if leftBlackimage.getpixel((x, y)) != (255, 255, 255): ... blendimage.putpixel((x, y), leftBlackimage.getpixel((x, y))) >>> blendimage.save('blend.png', 'PNG')
出力結果はこちら
これなら読めそう。横のままだと読みづらいので回転します
>>> lastimage = blendimage.transpose(Image.ROTATE_270) >>> lastimage.save('last.png', 'PNG')
これならOCRにかけれそうです。
(5) OCRにかける
他アプリケーションを使って手動でOCRにかけるのはさすがにきついので、PythonのOCRモジュールを使用します。
今回はpytesseractというOCRモジュールを使用しました。(python3では使えません)
余談:上のスクリプトはPython2.7になっていますが、最初に解いた時はPython3.4で書いていました。pytesseractはPython2.7用のモジュールでそのままでは使えないので、Python3→Python2へ変換する必要があります。今回は解く途中で2to3コマンドで一括で変換してpytesseractを使用しています。べんり。
(誰かPython3用のOCRモジュール知ってる人いませんか?(切実))
インストールはpipから行えます。
# pip2.7 install pytesseract
上手く行けばそのまま使用可能です。
ということでOCRモジュールを使用してみます。
# OCR >>> import pytesseract >>> message = pytesseract.image_to_string(lastimage) >>> print message
すごい!これだけでOCRにかけることができちゃうんですね。(Python3でもつかえたらいいなぁ)
これでN24P2QV64Qという文字が取得出来ました。この手順を自動化すればよさそうです。
(6) 接続からAnswer提出まで
接続はTelnetlibを使用しました。接続関係のPythonスクリプトはsocketとか使用する人が多いみたいですが、Telnetlibで簡単に記述することができます。(そのうちまとめたい)
import telnetlib tn = telnetlib.Telnet("41.231.53.40", "9090") raw = tn.read_until("\n") ~解析コード~ tn.read_until("Answer:") tn.write(message + '\n') print tn.read_all()
このスクリプトを走らせるとフラグが降ってきます。
Flag: Pwnium{b1a371c90da6a1d2deba2f6ebcfe3fc0}
以上です。
こんなに簡単に300ptもらえていいのだろうか・・・・
ソースコード投げておきます。(Link(text):pwnium-ROT-solve)