ひよこになりたい

Programming Server Network Security and so on

SECCON 2014 quals Online 重ねてみよう Writeup

SECCON 2014 quals Onlineに参加しました。今回はビギナーでも解ける問題をたくさん出題されるということで、少しは貢献できるかな?と思いましたが、人権消滅しかけてます。

チームはwasamusumeです。今回もチームメンバーの皆さんがスパッと解決していました。すごい。

amidaも中途半端だし、他の問題はメンバーさんが強すぎてタイミングなかったので本当は呼吸Writeupになりそうだったんですが、スキマを縫ってFlag提出したものがあるので書いておこうと思います。

Prog100

重ねてみよう
ジャンル:プログラミング
点数:100
問題文:afterimage.gif

afterimage.gifがもらえるので、開くとGIFアニメのよう。

f:id:zipsan:20150214060921g:plain

どう見ても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で予習してたので調度良かった。

f:id:zipsan:20150214061125p:plain

出力がこれです。読み取ると

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を開いてみる。

f:id:zipsan:20150214145008p:plain

目が痛くなる。。。

なんか文字が書かれています。(解いた後の画像をあげているので、上でデコードしたものとは違うかも)

パスワードらしきものが加工されています。画像をいじれば文字が読めそうなので、きれいな形にぐりぐりいじってから、画像文字認識(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')

出力されたleft.pngとright.pngはこちら

f:id:zipsan:20150214145016p:plain f:id:zipsan:20150214145018p:plain

(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')

出力されたものがこちら

f:id:zipsan:20150214145432p:plain

右半分と合わせればちょうどいい感じになりそうです。

(3) 黒抽出

目が痛くなるようなカラフルフレームを外したいので、黒色だけ抽出します。

黒色を抽出するために、元画像からimage.getpixel()でピクセルの色を取得し、その色が(100, 100, 100)以下ならimage.putpixel()でキャンバスに書き込んでいます。

閾値を設定しているのは、ジャギーを出来るだけ減らしたかったためです。image.getpixel() == (0, 0, 0)とすると、文字が欠けてしまったのでこのような処理にしています。

# 黒抽出
>>> def checkblack(I, V):
... if I[0] &lt; V[0] and I[1] &lt; V[1] and I[2] &lt; 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')

出力されたものがこちら。

f:id:zipsan:20150214145438p:plain f:id:zipsan:20150214145441p:plain

(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')

出力結果はこちら

f:id:zipsan:20150214145724p:plain

これなら読めそう。横のままだと読みづらいので回転します

>>> lastimage = blendimage.transpose(Image.ROTATE_270)
>>> lastimage.save('last.png', 'PNG')

f:id:zipsan:20150214145728p:plain

これならOCRにかけれそうです。

(5) OCRにかける

他アプリケーションを使って手動でOCRにかけるのはさすがにきついので、PythonOCRモジュールを使用します。

今回は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]

お、gzipぽい。早速gzipを解凍。

[bash]

// raw2(一行)

010011000101010101100100010000100・・・(中略)・・・1000011011001110011110100111101

[/bash]

2進数でてきた。

読みづらいので16進数に変換してみる。

[python]$ python

>>> 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は割と簡単な問題が多かったっぽい感じがするので、復習しておかないとな。。