読者です 読者をやめる 読者になる 読者になる

つけじょにーのすぱげていコード

主に、競技プログラミング、セキュリティのお勉強の際に書いたすぱげていコードを書き込みます

Trend Micro CTF 2015 に参加した

CTF

チームsendyで、TMCTFに参加しました。
相も変わらず解ける問題が少なくて人権の無い日々が続きます

結局、僕が解けたのはProgramming 200だけです。
とりあえず、解けた問題について書いてみます。

まず、どんな問題なのか。
netcatで接続すると、計算問題が降ってきて、それに対して正しい答えを返さなければいけないという
どこかで見たことのある問題です。
問題には種類があって、

1.単純な数字の計算問題
2.ローマ数字の計算問題
3.英語表記数値の計算問題
4.1〜3の複合問題

の4つだったと思います。
①は単純にevalに渡してやればいいだけなのですが、②、③、④についてはそうもいきません。
②については、ググってローマ数字を単純な数字に変換するスクリプトを参考にしました。
③については、最初、まぁ、そこまできついのは出さないっしょ! 単純な one, two, three, ... tenの置き換えぐらいっしょ!と
甘くみていたのですが、そうではなく、six hundred eighty six thousand, seven hundred twenty oneみたいなのとかも出てきます。
こんな感じの英語表記全然みないぞ・・・(英語弱い勢)
僕の低レベルなプログラミング能力ではこれをパースするものはかけず、暴挙に出ました。

"""
Wolframというサイトにリクエストを投げ、数値を得るという方法をとります。
"""

早速スクリプト書いてGO!!
しばらく待って、・・・あれ・・・なんかフラグ出てないのに止まったぞ?
Time out(泣)

という結果になってしまい(予測できてはいたのですが・・・
仕方なく、one, two, three, ..., twenty, thirty , ...ninetyあたりは自分でパースします
それに加え、実行速度を上げるためにCythonを使います。

結果、時折失敗しますが(回線の問題で)だいたい通るのがかけました。
英語表記数字のパースの仕方がわからない人なので、もうだいぶむちゃくちゃな書き方しました
他の方のWriteUpみて、だいぶ恥ずかしくなりました
本当に汚いスクリプト(リモート、ローカルの分けもコメントアウトするだけのやつ)ですが、
一応のっけます

#-*- coding: utf-8 -*-
from requests import*
import socket
import re

HOST = "ctfquest.trendmicro.co.jp"
PORT = 51740
bufsize = 2048

DIGIT_STRINGS = {
    1: dict(enumerate("I II III IV V VI VII VIII IX".split(), 1)),
    2: dict(enumerate("X XX XXX XL L LX LXX LXXX XC".split(), 1)),
    3: dict(enumerate("C CC CCC CD D DC DCC DCCC CM".split(), 1)),
    4: dict(enumerate("M MM MMM".split(), 1)),
}

ROMAN_REGEX = re.compile(r"""
(?:(?P<_3000>MMM)|(?P<_2000>MM)|(?P<_1000>M))?
(?:(?P<_900>CM)|(?P<_800>DCCC)|(?P<_700>DCC)|(?P<_600>DC)
    |(?P<_500>D)|(?P<_400>CD)|(?P<_300>CCC)|(?P<_200>CC)|(?P<_100>C))?
(?:(?P<_90>XC)|(?P<_80>LXXX)|(?P<_70>LXX)|(?P<_60>LX)
    |(?P<_50>L)|(?P<_40>XL)|(?P<_30>XXX)|(?P<_20>XX)|(?P<_10>X))?
(?:(?P<_9>IX)|(?P<_8>VIII)|(?P<_7>VII)|(?P<_6>VI)
    |(?P<_5>V)|(?P<_4>IV)|(?P<_3>III)|(?P<_2>II)|(?P<_1>I))?
    $
""", re.VERBOSE | re.IGNORECASE)

eni = {"zero":"0", "one":"1", "two":"2", "three":"3", "four":"4", "five":"5", "six":"6", "seven":"7", "eight":"8", "nine":"9", "ten":"10",
 "eleven":"11", "twelve":"12", "thirteen":"13", "fourteen":"14", "fifteen":"15", "sixteen":"16", "seventeen":"17", "eighteen":"18", "nineteen":"19" 
,"twenty":"20", "thirty":"30", "forty":"40", "fifty":"50", "sixty":"60", "seventy":"70", "eighty":"80", "ninety":"90"}

# Roman => Integer
def roman_to_int(roman):
    if not roman:
        #raise ValueError("{} is not valid roman".format(repr(roman)))
        return roman
    m = ROMAN_REGEX.match(roman)
    if m is None:
        #raise ValueError("{} is not valid roman".format(repr(roman)))
        return roman
    ret = 0
    for key, value in m.groupdict().items():
        if value is not None:
            ret += int(key[1:])
    assert ret >= 0
    return ret

#English => Integer
def en2num(englishArray):
    #Wolframにリクエストを投げる
    uri = "http://www.wolframalpha.com/input/?i="
    englishText = ''.join(englishArray)
    #print "requesting " + (uri+englishText) + "..."
    res = requests.get(uri+englishText)
    result = re.search('javascript:showmathpop\(\'.+\'\)', res.text).group()
    #print "Result is " + str(result)
    #result = str(result.group()[24:32])
    result = re.sub(r'javascript:showmathpop\(\'(\d+)\'\)', r'\1', result)
    #print "Return : " + str(result)
    return str(result)

#English => Integer (1 or 2 digits)
def en2num_two_digits(en):
    if en.count(" ") >= 1:
        spl = en.split(' ')
        spl[0] = eni[spl[0]][0]
        spl[1] = eni[spl[1]]
        return str(spl[0]+spl[1])
    else:
        return eni[en]

#Parse Formula to evaluate formula.
def convert(q):
    splitted = q.split(' ')

    for (index, num) in enumerate(splitted):
        if not (num in list("+-*/1234567890") or re.search(r'\(\d+', num) or re.search(r'\d+\)', num)):
            splitted[index] = str(roman_to_int(num))

    splitted = ' '.join(splitted)
    splitted = re.sub('([\+\-\*\/\(\)])', r'@\1@' , splitted)
    splitted = re.split('@', splitted)

    for idx, spl in enumerate(splitted):
        splitted[idx] = splitted[idx].strip()
    for idx, spl in enumerate(splitted):
        if splitted[idx] in ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"]:
            splitted[idx] = eni[splitted[idx]]

    for idx, spl in enumerate(splitted):
        if re.match('^[a-zA-Z\s]+$', spl):    
            splitted[idx] = en2num(spl)

    return " ".join(splitted)


def solve():
    #Remote
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    while True:
        formula = s.recv(bufsize)
        print "Received: " + formula
        if re.match(r'Cong.*', formula):
            flag = s.recv(bufsize)
            print "Flag is " + flag
        formula = re.sub(r',', '', formula)
        formula = (convert(formula.split('=')[0])).strip()
        formula = re.sub(r' ', '', formula)
        #print "Formula Converted: " + formula
        formula = formula.replace('R', '(')
        formula = formula.replace('L', ')')
        #print "Finally formula = " + str(formula)
        s.send(str(eval(formula)))
        print "Sended: " + str(eval(formula.strip()))

    #Local
    """
    formula = "4,090 + ( one hundred ten thousand seventy six - 783,757 ) * ( sixty nine - two ) * sixty three - 2 + sixty three - 342 ="
    #print "Received: " + formula
    formula = re.sub(r',', '', formula)
    formula = (convert(formula.split('=')[0])).strip()
    formula = re.sub(r' ', '', formula)
    #print "Formula Converted: " + formula
    formula = formula.replace('R', '(')
    formula = formula.replace('L', ')')
    #print "Finally formula = " + str(formula)
    #print str(eval(formula))
    ##print "Sended: " + str(eval(formula.strip()))
    """

実行するときは

$ python setup.py build_ext --inplace
$ python
Python 2.7.10 (default, Jul 14 2015, 19:46:27)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import calcTMProg200
>>> calcTMProg200.solve()
...
省略
...
Congratulations!
The flag is TMCTF{U D1D 17!}

普通にハッシュテーブルをinitしておいて、あとは複雑な英語表記の解析をするというやり方が一番スムーズな気はします
そうすればCython使わなくても全然通るはず(笑)