Internetwache CTF 2016 Writeup

この大会は2016/2/20 20:00(JST)~2016/2/22 8:00(JST)に開催されました。
今回はチームで参戦。結果は1350点で1492チーム中112位でした。
自分で解けた問題をWriteupとして書いておきます。

Quick Run (misc 60)

与えられたREADME.txtにはBASE64エンコードと思われるデータがたくさん入っている。
以下のスクリプトでそれぞれBASE64デコードしてファイル保存する。

import base64

i = 0
enc = ''
for line_lf in open('README.txt').readlines():
    line = line_lf.replace('\n', '')
    if len(line) == 76:
        enc += line
    else:
        enc += line
        with open('data' + str(i) + '.txt', 'wb') as f:
            f.write(enc.decode('base64').replace('\n', '\r\n'))
        enc = ''
        i = i + 1

これで24個のファイルが作成され、QRコードのようなものが書かれている。
f:id:satou-y:20160223230008p:plain
よく見るとQRコードとしては白黒逆なので、ドラッグで反転させて、QRコードリーダで読み取っていく。
f:id:satou-y:20160223230235p:plain
最初のデータは「F」。あとは順番に読み取っていくと、「Flagis:IW{QR_C0DES_RUL3}」となる。

IW{QR_C0DES_RUL3}

Oh Bob! (crypto 60)

3つの公開鍵ファイルと暗号ファイルが与えられている。暗号ファイルの中にはBASE64文字列が3行入っている。3つあるが、RSA暗号の復号問題と踏んで進める。まず公開鍵ファイルを見てみる。

# openssl rsa -pubin -text < bob.pub
Public-Key: (228 bit)
Modulus:
    0d:56:4b:97:8f:9d:23:35:04:95:8e:ed:8b:74:43:
    73:28:1e:d1:41:8b:29:f1:ec:fa:80:93:d8:cf
Exponent: 65537 (0x10001)
writing RSA key
-----BEGIN PUBLIC KEY-----
MDgwDQYJKoZIhvcNAQEBBQADJwAwJAIdDVZLl4+dIzUElY7ti3RDcyge0UGLKfHs
+oCT2M8CAwEAAQ==
-----END PUBLIC KEY-----
# openssl rsa -pubin -text < bob2.pub
Public-Key: (228 bit)
Modulus:
    0a:23:37:0e:7d:0f:b0:02:32:16:4a:c6:d6:42:84:
    0f:c5:4e:92:02:43:3f:92:7a:60:eb:5a:db:d9
Exponent: 65537 (0x10001)
writing RSA key
-----BEGIN PUBLIC KEY-----
MDgwDQYJKoZIhvcNAQEBBQADJwAwJAIdCiM3Dn0PsAIyFkrG1kKED8VOkgJDP5J6
YOta29kCAwEAAQ==
-----END PUBLIC KEY-----
# openssl rsa -pubin -text < bob3.pub
Public-Key: (228 bit)
Modulus:
    0c:5b:69:e1:97:9e:54:1f:85:da:cd:e2:aa:14:d2:
    72:2a:84:6f:41:b3:db:83:e6:67:e3:b3:d1:1d
Exponent: 65537 (0x10001)
writing RSA key
-----BEGIN PUBLIC KEY-----
MDgwDQYJKoZIhvcNAQEBBQADJwAwJAIdDFtp4ZeeVB+F2s3iqhTSciqEb0Gz24Pm
Z+Oz0R0CAwEAAQ==
-----END PUBLIC KEY-----

それぞれ合成数がわかったので、素因数分解する。

sage: factor(0x0d564b978f9d233504958eed8b744373281ed1418b29f1ecfa8093d8cf)
17963604736595708916714953362445519 * 20016431322579245244930631426505729
sage: factor(0x0a23370e7d0fb00232164ac6d642840fc54e9202433f927a60eb5adbd9)
16514150337068782027309734859141427 * 16549930833331357120312254608496323
sage: factor(0x0c5b69e1979e541f85dacde2aa14d2722a846f41b3db83e667e3b3d11d)
17357677172158834256725194757225793 * 19193025210159847056853811703017693

素因数分解できたので、それぞれ秘密鍵を生成する。

# rsatool.py -f PEM -o bob.sec -p 17963604736595708916714953362445519 -q 20016431322579245244930631426505729
Using (p, q) to initialise RSA instance
      :
Saving PEM as bob.sec
# rsatool.py -f PEM -o bob2.sec -p 16514150337068782027309734859141427 -q 16549930833331357120312254608496323
Using (p, q) to initialise RSA instance
      :
Saving PEM as bob2.sec
# rsatool.py -f PEM -o bob3.sec -p 17357677172158834256725194757225793 -q 19193025210159847056853811703017693
Using (p, q) to initialise RSA instance
      :
Saving PEM as bob3.sec

secret.encの中の3つの文字列をBASE64デコードして、それぞれファイル保存する。

import base64

msg1 = 'DK9dt2MTybMqRz/N2RUMq2qauvqFIOnQ89mLjXY='
msg2 = 'AK/WPYsK5ECFsupuW98bCFKYUApgrQ6LTcm3KxY='
msg3 = 'CiLSeTUCCKkyNf8NVnifGKKS2FJ7VnWKnEdygXY='

with open('sec1', 'wb') as f:
    f.write(msg1.decode('base64'))

with open('sec2', 'wb') as f:
    f.write(msg2.decode('base64'))

with open('sec3', 'wb') as f:
    f.write(msg3.decode('base64'))

秘密鍵で復号する。

# openssl rsautl -decrypt -inkey bob.sec < sec1
IW{WEAK_R
# openssl rsautl -decrypt -inkey bob3.sec < sec2
3_SO_BAD!}
# openssl rsautl -decrypt -inkey bob2.sec < sec3
SA_K3YS_4R

フラグの形式に並び替える。

IW{WEAK_RSA_K3YS_4R3_SO_BAD!}

A numbers game (code 50)

ncで接続すると、このような表示。

# nc 188.166.133.53 11027
Hi, I heard that you're good in math. Prove it!
Level 1.: x + 17 = 37

何回もこのxを答える必要がある。ただxは必ず左端で、四則演算しかなさそうだったので、以下のようなスクリプトで解く。

#!/usr/bin/env python
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('188.166.133.53', 11027))

for i in range(1, 10000):
    data = s.recv(256)
    print data
    data = s.recv(256)
    print data
    formula = data.split(': ')
    elem = formula[1].split(' ')
    if elem[1] == '+':
        ans = str(int(elem[4]) - int(elem[2]))
    elif elem[1] == '-':
        ans = str(int(elem[4]) + int(elem[2]))
    elif elem[1] == '*':
        ans = str(int(elem[4]) / int(elem[2]))
    elif elem[1] == '/':
        ans = str(int(elem[4]) * int(elem[2]))
    print ans
    s.sendall(ans)

100回正解すると、フラグが表示された。

IW{M4TH_1S_34SY}

It's Prime Time! (code 60)

ncで接続すると、このような表示。

# nc 188.166.133.53 11059
Hi, you know that prime numbers are important, don't you? Help me calculating the next prime!
Level 1.: Find the next prime number after 4:

このafterの後の数字より大きい最小の素数を答える必要がある。素数判定のプログラムを探し、それを利用したスクリプトで解く。

#!/usr/bin/env python
import socket

def prime_check(d):
    if d < 2:
        return 0
    if d == 2:
        return 1
    if d % 2 == 0:
        return 0

    a = 3
    while a ** 2 <= d:
        if d % a == 0:
            return 0
        a = a + 2

    return 1

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('188.166.133.53', 11059))

for i in range(1, 10000):
    data = s.recv(256)
    print data
    data = s.recv(256)
    print data
    formula = data.split(':')
    elem = formula[1].split(' ')
    base = elem[7]

    try_digit = int(base) + 1
    while True:
       if prime_check(try_digit) == 1:
           ans = str(try_digit)
           break
       try_digit = try_digit + 1
    print ans
    s.sendall(ans)

100回正解すると、フラグが表示された。

IW{Pr1m3s_4r3_!mp0rt4nt}

A numbers game II (code 70)

ncで接続すると、このような表示。

# nc 188.166.133.53 11071
Hi, I like math and cryptography. Can you talk to me?!
Level 1.: 4.4.5.3.3.3.3.3.3.3.5.6.3.3.3.3.3.4.3.5.3.4.3.3.3.3.3.3.3.4.6.4.3.3.3.3.3.4.3.5.3.4.3.5

暗号のスクリプトが付いているので、それを読み解く。処理の概要は以下の通り。

1文字ずつASCIIコードを32とのXORをとる。
2進数8桁で0パディングする。
パディングした2進数を2桁ずつ区切って、51を足し、ASCII変換で文字表記にし、ドット(.)で区切る。

これをベースに復号すると、code 50と同様の計算式になる。さらに答える際には暗号が必要ということがわかり、それを踏まえてスクリプトで解く。

#!/usr/bin/env python
import socket

def decode(enc):
    l_enc = enc.split('.')

    dec = ''
    dec_tmp = ''
    for i in range(len(l_enc)):
        dec_tmp += format(ord(l_enc[i]) - 51, '02b')
        if i % 4 == 3:
            dec += chr(int(dec_tmp, 2) ^ 32)
            dec_tmp = ''
    return dec

def encode(eq):
    out = []
    for c in eq:
        q = bin(ord(c) ^ 32).lstrip('0b')
        q = "0" * (8 - len(q)) + q
        out.append(q)
    b = ''.join(out)
    pr = []
    for x in range(0, len(b), 2):
        c = chr(int(b[x:x + 2], 2) + 51)
        pr.append(c)
    s = '.'.join(pr)
    return s

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('188.166.133.53', 11071))

for i in range(1, 10000):
    data = s.recv(256)
    print data
    data = s.recv(256)
    print data
    enc_line = data.split(': ')
    enc = enc_line[1].replace('\n', '')
    elem = decode(enc).split(' ')
    if elem[1] == '+':
        calc = str(int(elem[4]) - int(elem[2]))
    elif elem[1] == '-':
        calc = str(int(elem[4]) + int(elem[2]))
    elif elem[1] == '*':
        calc = str(int(elem[4]) / int(elem[2]))
    elif elem[1] == '/':
        calc = str(int(elem[4]) * int(elem[2]))
    ans = encode(calc)
    print ans
    s.sendall(ans)

100回正解すると、フラグが表示された。

IW{Crypt0_c0d3}