SECCON 2014 quals Online 重ねてみよう Writeup
SECCON 2014 quals Onlineに参加しました。今回はビギナーでも解ける問題をたくさん出題されるということで、少しは貢献できるかな?と思いましたが、人権消滅しかけてます。
チームはwasamusumeです。今回もチームメンバーの皆さんがスパッと解決していました。すごい。
amidaも中途半端だし、他の問題はメンバーさんが強すぎてタイミングなかったので本当は呼吸Writeupになりそうだったんですが、スキマを縫ってFlag提出したものがあるので書いておこうと思います。
Prog100
重ねてみよう
ジャンル:プログラミング
点数:100
問題文:afterimage.gif
afterimage.gifがもらえるので、開くとGIFアニメのよう。
どう見てもQRコードなので、Pythonでスクリプト書きました。
# -*- coding: utf-8 -*- from PIL import Image X = 164 Y = 164 canvas = Image.new("RGB", (X, Y), (255, 255, 255)) for i in range(1, 50): print("img/img_{0:02d}.png".format(i)) base = Image.open("./img/img_{0:02d}.png".format(i)) pixels = base.load() for x in range(X): for y in range(Y): if pixels[x, y] == 1: canvas.putpixel((x, y), (0,0,0)) canvas.save('ans.png', 'PNG')
GIFアニメを適当なツール(今回はGiam)でつるっと抽出して、img/ディレクトリに保存。
PILライブラリを使って一個一個開きつつ、白い部分だけキャンバスに転写していきます。(上のスクリプトは白い部分を黒にして転写しています)
前回の記事や他のCTFで予習してたので調度良かった。
出力がこれです。読み取ると
FLAG{Many dot makes a QR code}
がでました。
今回はいくつかの問題しか手を付けていないので、復習しておかなきゃなと思ってます。
とくにあみだくじはめちゃくちゃ悔しいのでリベンジしておきます!解けたら記事にしようかな。
Python3でOCR(pytesseract)を使えるようにする
CTFを解いていると時々OCRが欲しい場面が出てくるのですが、Python用のOCRライブラリはPython2系列のものばかりで、Python3でのOCRライブラリは検索してもヒットしません。
私はPython3系列派なので、どうしてもOCRをPython3で使いたい!ということで、Python2用のOCRライブラリであるpytesseractをPython3で使えるように書きなおしてみたのでメモしておきます。
意外に簡単に使えるようになったので、使いたい人は試してみてね。
pytesseractはpipで入れることができます。
# pip install pytesseract
しかし、pip3を使用しても入るのはPython2系列用のコードで、Python3では動きません。。(たまにあるよね)
ということで、pipで入れたpytesseract(ver0.1)に修正を加えます
# pip install pytesseract $ cd /Path/to/site-packages $ cd pytesseract $ vim pytesseract.py
/Path/to/site-packagesの部分はPython3のsite-packageを指定してください。私はMacを使っているので、/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/site-packages/にありました。
以下の部分に修正を加えます。先に2to3コマンドで変換しておくと少しだけラクになるかも。
$ 2to3 -w pytesseract
変更箇所はこちら(diff)
47,48c47,48 < import Image < import StringIO --- > from PIL import Image > import io 51a52 > import tempfile 102,103c103,104 < sys.stderr = StringIO.StringIO() < return os.tempnam(None, 'tess_') --- > sys.stderr = io.StringIO() > return tempfile.mkstemp("", 'tess_') 120,122c121,122 < < input_file_name = '%s.bmp' % tempnam() < output_file_name_base = tempnam() --- > input_file_name = '{0}.bmp'.format(tempnam()[1]) > output_file_name_base = tempnam()[1] 124c124 < output_file_name = '%s.txt' % output_file_name_base --- > output_file_name = '{0}.txt'.format(output_file_name_base) 126c126 < output_file_name = '%s.box' % output_file_name_base --- > output_file_name = '{0}.box'.format(output_file_name_base) 136c138 < f = file(output_file_name) --- > f = open(output_file_name) 151c153 < sys.stderr.write('ERROR: Could not open file "%s"\n' % filename) --- > sys.stderr.write('ERROR: Could not open file "{0}"\n'.format(filename)) 153c155 < print image_to_string(image) --- > print(image_to_string(image)) 160c162 < sys.stderr.write('ERROR: Could not open file "%s"\n' % filename) --- > sys.stderr.write('ERROR: Could not open file "{0}"\n'.format(filename)) 162c164 < print image_to_string(image, lang=lang) --- > print(image_to_string(image, lang=lang))
< の行を > に置き換えです。
保存して終わり。実行してみると
>>> import pytesseract >>> from PIL import Image >>> img = Image.open("/Users/hinanawi/tenshi.png") >>> message = pytesseract.image_to_string(img) >>> print(message) 'tenshityanaishiteru'
こんな感じに出力できます:)
% 追記:pyocrはPython3対応らしいです。
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)
3Dマルバツゲームの残骸
DEFCON 22 CTF Qualifications で出題された3dtttを解いてたのですが、惜しくもなく解けなかったので、途中まで組んで出来上がった残骸を放り投げておきます。
3dtttは3D Tic Tac Toe(3Dマルバツゲーム)を指定された条件で解いていく問題でした。WriteUpは __math氏のWriteUpが参考になります。
3dttt(@__math氏) - https://gist.github.com/math314/34ff1da0b4e169b1ed33
プログラム(Python3)
3Dの盤面をランダムに生成して指定した個数だけ○と×が揃っている盤面を抽出する。何かに使えるかもしれない(?)
# -*- coding: utf-8 -*- import random ox = ('o', 'x') # generate 3d tic tac def makeline(): return [random.choice(ox), random.choice(ox), random.choice(ox)] def makeaspect(): return [makeline(), makeline(), makeline()] def maketic(): return [makeaspect(), makeaspect(), makeaspect()] # print 3d tic tac def printtic(tic): for aspect in tic: for line in aspect: print(line) print('') # print aspect #def printasp(tic): # for aspect in tic: # print(aspect) # 横チェック def check_c(aspect): win = [0, 0] for i, c in enumerate(ox): win[i] += sum([1 if line.count(c) == 3 else 0 for line in aspect]) return tuple(win) # 縦チェック def check_r(aspect): win = [0, 0] for i, c in enumerate(ox): win[i] += sum([1 if line.count(c) == 3 else 0 for line in list(map(list, zip(*aspect)))]) return tuple(win) # 斜めチェック def check_d(aspect): win = [0, 0] for i, c in enumerate(ox): win[i] += 1 if [aspect[j][j] for j in range(3)].count(c) == 3 else 0 win[i] += 1 if [aspect[j][2 - j] for j in range(3)].count(c) == 3 else 0 return tuple(win) # 3Dの盤面の斜めチェック def check_tic_d(tic): win = [0, 0] for i, c in enumerate(ox): if tic[0][0][0] == tic[1][1][1] == tic[2][2][2] == c: win[i] += 1 if tic[2][0][0] == tic[1][1][1] == tic[0][2][2] == c: win[i] += 1 if tic[0][2][0] == tic[1][1][1] == tic[2][0][2] == c: win[i] += 1 if tic[2][2][0] == tic[1][1][1] == tic[0][0][2] == c: win[i] += 1 return tuple(win) if __name__ == '__main__': o = 4 x = 2 count = 0 while True: if count % 10000 == 0: print(count) count += 1 # 3D空間にランダムプロット tic = maketic() # ticのスライス1(面を変える) tictrans1 = [] asptrans = [] for i in range(3): for aspect in tic: asptrans.append(aspect[i]) tictrans1.append(asptrans) asptrans = [] # ticのスライス2(面を変える) tictrans2 = [] asptrans = [] for i in range(3): for aspect in tic: aspect = list(map(list, zip(*aspect))) asptrans.append(aspect[i]) tictrans2.append(asptrans) asptrans = [] # 縦横チェック (通常状態 8x3=24通り) chk = [] for aspect in tic: chk.append(check_r(aspect)) chk.append(check_c(aspect)) chk.append(check_d(aspect)) # 別の面からの判定1(縦方向への向き3x3=9通り + 斜め2x3=6通り) for aspect in tictrans1: chk.append(check_r(aspect)) chk.append(check_d(aspect)) # 別の面からの判定2(斜め2x3=6通り) for aspect in tictrans2: chk.append(check_d(aspect)) # 斜め(3Dの対角線 4通り) chk.append(check_tic_d(tic)) # 個数 sumO = sum([X for X, Y in chk]) sumX = sum([Y for X, Y in chk]) if sumO == o and sumX == x: print("Found!: ({0}, {1})".format(o, x)) printtic(tic)
出力
$ python 3dtictac.py 0 Found!: (4, 2) ['x', 'o', 'x'] ['o', 'o', 'x'] ['o', 'o', 'o'] ['o', 'o', 'x'] ['x', 'o', 'o'] ['o', 'x', 'x'] ['x', 'x', 'x'] ['o', 'o', 'x'] ['o', 'x', 'o'] .....(略)
多分あってると思う(たぶん)
競技プログラミング勢は簡単に解いていたみたい。つよい。
crypto200-1 Write up
まともに解いたのこれくらいだった。。
cryptの基礎が詰まってて結構面白かったのでWrite upしておきます。
crypto 200-1
H4x0R got this weird code while coming back from school. can you get a 32char code flag that can make him happy ?
[bash]
//一行
1f8b08089c452c530003737465703900edd85b6ec3300c44d1ffae86dc
ffe61ac7e1437403e42b1a171745d02a946c890713383537f3c7cb3c7e
8e37ec7c99d7fb39cbe3afaa5bac89f1735e5c3597bf263e571ef52ab5
49d6d759bb5eed2de6d7c56bf76d3f6de47d76ac8e9567c9bdefb0d7db
e16c3d67ded997bbb71eae3d8d7db8d796d6fed7fda317f808fb5ceaf8
48f9b48ee1a3e833ebf868f9901f6d9f59c747cb87fca8faacfbc1079f
bbf8ccfa469f1ce323999f4b1d9f6b7d8f4f1e161ff2732b1ff2a3eda3
911f7cb47d78bed6f6213fef7c667da3cfa58e0ff9c1071f7cf0f986cf
ace3830f3e1f7fffb9d4f1c147dd67d6f7f8f43ee0d37d34f29387c547
d6a7c6f828faf48ee1e335191f611ff283cf673e35c647d1a7770c1f2d
9fb33ff8a8fa909f3bf8cc3a3e5a3ee447d9a7c6f8a8f9f8da157c14ff
7f80cf1f3e1af9895ee0a3e973cec147d5270f8b8fa40fcf07da3ed10b
7ca6cfacefcc8f8d3a3e2af9e1f3ed9d8f467ef2b0f8e8e6071f599fe8
053e9a3ee4e7063e39c647d287fce0830f3effd327f6808fa6cfd2317c
b47c7a1ff0d1f3213fda3ee447db87fc68fbb4fbe123e8e36b57f0499f
59df989fd6317ce4f2137bc067f1d1c8cfd99f65ddebe0a976fcfef905
9996b432616b0000
[/bash]
16進数っぽいので、とりあえずASCIIコードに変換・・・
してみるも、全く読めず。元データの長さが944だったので、16で割り切れるな?と思い、16文字のハッシュ探しても見つからなかった。8文字はあったけど、うーん。
ひと通り試してだめだったので、ASCIIコードに変換した時に見つけたstep9という文字から、もしかしたら何かのファイルなのかなということで、バイナリエディタで直接貼り付けて、ファイル化してfileコマンドを実行してみた。
[bash]
$ file raw.bin
raw.bin: gzip compressed data, was "step9", from Unix, last modified: Fri Mar 21 22:58:52 2014
[/bash]
[bash]
// raw2(一行)
010011000101010101100100010000100・・・(中略)・・・1000011011001110011110100111101
[/bash]
2進数でてきた。
読みづらいので16進数に変換してみる。
>>> raw2 = open("raw2.txt", "r")
>>> raw2tohex = open("raw2tohex.txt", "w")
>>> i = raw2.readline()
>>> raw2.close()
>>> h = hex(int(i, 2))
>>> raw2tohex.write(str(h))
>>> raw2tohex.close()
[/python]
開くと
[bash]
0x4C55644255316C42524651714A・・・(中略)・・・56436F6D4C544D7843673D3DL
[/bash]
0xとLを取ってASCIIコードに変換すると
[bash]
LUdBU1lBRFQqJi0yNS1H・・・(中略)・・・MzYtR0FTWUFEVComLTMxCg==
[/bash]
お、base64。デコードしてみる。
[bash]
-GASYADT*&-25-GASYADT*&-33-GASYADT*&-37-GASYADT*&-25-・・・(中略)・・・GASYADT*&-25-GASYADT*&-36-GASYADT*&-
[/bash]
GASYADTってなんぞ?とりあえずGASYADTを取り除いて数値だけ取り出す。
[bash]
//一行
253337253335253336253337253336253337253336253333253
333253631253332253636253332253636253336253333253336
253635253336253336253336253337253337253332253332253
635253336253338253336253636253336253338253336253331
253336253337253336253338253332253635253337253330253
336253332253337253631253332253636253333253337253333
253331253333253333253333253330253333253335253333253
335253333253334253332253636253330253631
[/bash]
だいぶ短くなってきた。
25が多いな?確か25は%だったから、URLエンコードかな?
というわけで、二文字おきに%でつないでURLデコード。
[python]
#coding: utf-8
def splitStr2(str, num):
l = []
for i in range(num):
l.append(str[i::num])
l = ["".join(i) for i in zip(*l)]
rem = len(str) % num # zip で捨てられた余り
if rem:
l.append(str[-rem:])
return l
s = "2533372533352533362533372533362533372533362533332533332
536312533322536362533322536362533362533332533362536352533362
533362533362533372533372533322533322536352533362533382533362
536362533362533382533362533312533362533372533362533382533322
536352533372533302533362533322533372536312533322536362533332
533372533332533312533332533332533332533302533332533352533332
53335253333253334253332253636253330253631"
s2 = splitStr2(s, 2)
ans = ""
for v in s2:
ans += "%" + v
print(ans)
[/python]
出てきたものをURLデコード。
[bash]
%37%35%36%37%36%37%36%33%33%61%32%66%32%66%36%33%36%65%36%36%36%37%37%32%32%65%36%38%36%66%36%38%36%31%36%37%36%38%32%65%37%30%36%32%37%61%32%66%33%37%33%31%33%33%33%30%33%35%33%35%33%34%32%66%30%61
[/bash]
もういっちょ!
[bash]
756767633a2f2f636e6667722e686f686167682e70627a2f373133303535342f0a
[/bash]
ASCIIコードに直してみる。
[bash]
uggc://cnfgr.hohagh.pbz/7130554/
[/bash]
むむむっ これは換字式暗号。rot13をとりあえず試してみる。
[bash]
http://paste.ubuntu.com/7130554/
[/bash]
URLゲット!
アクセスすると
[bash]
5d3144233c46404dba4afc766601b997
[/bash]
32文字のMD5をゲットしました。これがフラグでした。
長かったけど楽しかった。
backdoorCTFは割と簡単な問題が多かったっぽい感じがするので、復習しておかないとな。。