EasyCTF IV Writeup

この大会は2018/2/10 10:00(JST)~2018/2/20 13:00(JST)に開催されました。
今回もチームで参戦。結果は2256点で2146チーム中101位でした。
自分で解けた問題をWriteupとして書いておきます。

Hello, world! (Intro 10)

Hello, World!と表示させるプログラムを書けばよい。

print 'Hello, world!'

Linux (Intro 10)

$ ls -a                                                                                                    
.  ..  .bash_logout  .bashrc  .cache  .cloud-locale-test.skip  .flag  .profile
$ cat .flag                                                                                                
easyctf{i_know_how_2_find_hidden_files!}

隠しファイルにフラグが書いてあった。

easyctf{i_know_how_2_find_hidden_files!}

The Oldest Trick in the Book (Intro 10)

シーザー暗号。https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。

easyctf{w3lc0m3_70_345yc7f_fd2e53}

Web (Intro 10)

HTMLのソースにフラグが書かれている。

easyctf{hidden_from_the_masses_072e33}

Soupreme Encoder(Cryptography 20)

16進コードをデコードするだけ

>>> '68657869745f6d6174655f3139353961643733346539386132386561363734'.decode('hex')
'hexit_mate_1959ad734e98a28ea674'
easyctf{hexit_mate_1959ad734e98a28ea674}

Netcat (Intro 20)

ncで接続するだけ。

$ nc c1.easyctf.com 12481
enter your player key: 1357494912
thanks! here's your key: easyctf{hello_there!_EdaB38f2DD98ae99}
easyctf{hello_there!_EdaB38f2DD98ae99}

Hashing (Miscellaneous 20)

ファイルのsha512を計算すればよい。

$ sha512sum ecd758d450f4158ef2276ab80aa038ca6761c7f2e1a78374c359ca1c629ded53_image.png 
c5b8c1b2173c66d0752e6f66a970a0cce42c966ef44cdb2e89b4440eb8a16b6efda199d5af9ce2e40854616005ceb41a3503fbc5c87a5bafa61a084ab80435b0  ecd758d450f4158ef2276ab80aa038ca6761c7f2e1a78374c359ca1c629ded53_image.png
easyctf{c5b8c1b2173c66d0752e6f66a970a0cce42c966ef44cdb2e89b4440eb8a16b6efda199d5af9ce2e40854616005ceb41a3503fbc5c87a5bafa61a084ab80435b0}

Exclusive (Programming 20)

2つの入力値のXORを出力するプログラムを書けばよい。

ab = raw_input()
a = int(ab.split(' ')[0])
b = int(ab.split(' ')[1])
print a ^ b

Look At Flag (Forensics 30)

$ file 4680b45d33184b3e3ad99338907d1fe7dfec8ddd4b43ac71da69154ce9a6035c_flag.txt 
4680b45d33184b3e3ad99338907d1fe7dfec8ddd4b43ac71da69154ce9a6035c_flag.txt: PNG image data, 332 x 44, 8-bit/color RGBA, non-interlaced

拡張子をpngにして開く。
f:id:satou-y:20180227204557p:plain

easyctf{FLaaaGGGGGg}

EzSteg (Forensics 30)

jpgファイルのFF D9 より後ろにフラグが入っている。

easyctf{l00k_at_fil3_sigS}

Taking Input (Programming 30)

Hello, <入力文字列>! と出力するプログラムを書けばよい。

name = raw_input()
print 'Hello, %s!' % name

Over and Over (Programming 30)

Over and Over and ... and Overと入力した値の数だけOverを出現させるようプログラムを書けばよい。

n = input()

op = ''
for i in range(n):
    op += 'over'
    if i != n - 1:
        op += ' and '

print op

Teaching Old Tricks New Dogs (Programming 40)

入力した値の数だけ、入力文字列の各文字をシフトするようプログラムを書けばよい。

rot = input()
enc = raw_input()

dec = ''
for i in range(len(enc)):
    if enc[i] == ' ':
        dec += ' '
    else:
        code = ord(enc[i]) - rot
        if code < ord('a'):
            code += 26
        dec += chr(code)

print dec

hexedit (Reverse Engineering 50)

stringsで調べるだけ。

$ strings hexedit | grep easyctf
easyctf{d93a506e}
easyctf{d93a506e}

Substitute (Cryptography 50)

https://quipqiup.com/で復号する。

YO! NICE?OWLOFSOUP JUST MADE A NEW FLAG FOR THE CTF AND IS TOTALLY PROUD OF ITS INGENUITY. THIS IS ALSO THE SECOND PRO?LEM EVER MADE FOR EASYCTF. HERE: EASYCTF{THIS_IS_AN_EASY_FLAG_TO_GUESS} USE CAPITAL LETTERS.

きちんと復号しきれていないが、フラグの部分はわかる。

EASYCTF{THIS_IS_AN_EASY_FLAG_TO_GUESS}

Markov's Bees (Linux 50)

探すべきファイルは非常に多い。grepで探す。

$ grep easyctf -rl ./                                                                 
./c/e/i/bee913.txt
$ cat ./c/e/i/bee913.txt | grep easyctf                                               
easyctf{grepping_stale_memes_is_fun}
easyctf{grepping_stale_memes_is_fun}

xor (Cryptography 50)

1つのキーで総当たりでXORし、印字可能な文字列だけになり、フラグの形式になるものを探す。

with open('8763dea5b0f7b29b0324f0a53bf9b5cccdd9762ffdb1376596b285588c461278_xor.txt', 'rb') as f:
    data = f.read()

for key in range(256):
    flag = ''
    ng = False
    for i in range(len(data)):
        code = ord(data[i]) ^ key
        if code < 32 or code > 126:
            ng = True
            break
        else:
            flag += chr(code)
    if ng == False:
        if 'easyctf' in flag or 'EASYCTF' in flag:
            print str(key) + ':' + flag

実行結果は以下の通り。

15:easyctf{nfzkkcwopxfwrklydozaoqfji}
47:EASYCTF[NFZKKCWOPXFWRKLYDOZAOQFJI]
easyctf{nfzkkcwopxfwrklydozaoqfji}

Subset Counting (Programming 55)

入力の数だけ数字が提示され、部分和が入力の値になる組み合わせの数を出力するプログラムを書けばよい。

import itertools

NS = raw_input()
N = int(NS.split(' ')[0])
S = int(NS.split(' ')[1])

elems = map(int, raw_input().split(' '))

combs = []
for n in range(1, N + 1):
    for c  in itertools.combinations(elems, n):
        if sum(c) == S:
            combs.append(c)

print len(combs)

In Plain Sight (Web 70)

DNS SPYでこのドメインを検索。https://dnsspy.io/scan/blockingthesky.comを見ると、TXTレコードにフラグが書いてある。
f:id:satou-y:20180227210137p:plain

easyctf{betcha_wish_you_could_have_used_ANY}

My Letter (Forensics 80)

docxを解凍すると、template.pngにフラグが書いてある。
f:id:satou-y:20180227210300p:plain

easyctf{r3j3ct3d_4nd_d3jected}

Nosource, Jr. (Web 80)

ソースを見る。

var flag = 'Fg4GCRoHCQ4TFh0IBxENAE4qEgwHMBsfDiwJRQImHV8GQAwBDEYvV11BCA==';

これと鍵とでXORして復号する必要がある。復号するとeasyctf{になることを想定し、XOR鍵を見つける。
keyがsoupysouとなっているので、soupyの繰り返しで使えば良さそう。

b64_enc = 'Fg4GCRoHCQ4TFh0IBxENAE4qEgwHMBsfDiwJRQImHV8GQAwBDEYvV11BCA=='

enc = b64_enc.decode('base64')

flag_head = 'easyctf{'

key = ''
for i in range(len(flag_head)):
    code = ord(enc[i]) ^ ord(flag_head[i])
    key += chr(code)

print key
## soupysou -> key = soupy
key = key[:5]

flag = ''
for i in range(len(enc)):
    code = ord(enc[i]) ^ ord(key[i%len(key)])
    flag += chr(code)

print flag
easyctf{congrats!_but_now_f0r_n0s0urc3_...}

Zippity (Miscellaneous 80)

$ nc c1.easyctf.com 12483
+======================================================================+
| Welcome to Zippy! We love US zip codes, so we'll be asking you some  |
| simple facts about them, based on the 2010 Census. Only the          |
| brightest zip-code fanatics among you will be able to succeed!       |
| You'll have 30 seconds to answer 50 questions correctly.             |
+======================================================================+

3... 2... 1...  Go!

Round  1 / 50
  What is the longitude (degrees) of the zip code 12463? 

https://www.census.gov/geo/maps-data/data/gazetteer2010.htmlからデータファイルを取ってきて、該当する情報を答えていく。

import socket
import re

def search(rows, zipcode):
    fnd_record = ''
    for row in rows:
        if row[:5] == zipcode:
            fnd_record = row.strip()
            break
    return fnd_record

def getLatitude(row):
    return row.split('\t')[7].strip()

def getLongitude(row):
    return row.split('\t')[8].strip()

def getLandArea(row):
    return row.split('\t')[3].strip()

def getWaterArea(row):
    return row.split('\t')[4].strip()

with open('Gaz_zcta_national.txt', 'r') as f:
    lines = f.readlines()[1:]

pattern1 = 'What is the (.+) \(degrees\) of the zip code (.+)\?'
pattern2 = 'What is the (.+) area \(m\^2\) of the zip code (.+)\?'

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('c1.easyctf.com', 12483))

data = s.recv(1024)
data += s.recv(1024)
data += s.recv(1024)
print data

for i in range(50):
    data = s.recv(1024)
    print data

    m = re.search(pattern1, data)
    if m is None:
        m = re.search(pattern2, data)
    attr = m.group(1)
    code = m.group(2)

    area = search(lines, code)
    ans = ''
    if attr == 'latitude':
        ans = getLatitude(area)
    elif attr == 'longitude':
        ans = getLongitude(area)
    elif attr == 'land':
        ans = getLandArea(area)
    else:
        ans = getWaterArea(area)
    print ans
    s.sendall(str(ans) + '\n')

data = s.recv(1024)
print data
easyctf{hope_you_liked_parsing_tsvs!}

Keyed Xor (Cryptography 100)

先頭がeasyctf{になることからkeyの先頭を割り出す。

with open('keyed_xor.txt', 'rb') as f:
    data = f.read()

flag_head = 'easyctf{'

key = ''
for i in range(len(flag_head)):
    code = ord(flag_head[i]) ^ ord(data[i])
    key += chr(code)

print key

この結果 tambouri とわかる。wordsから探すと、tambourineのようだ。もう一つはブルートフォースで探す。

import string

def checkFlag(s):
    for i in range(len(s)):
        if ord(s[i]) < 32 or ord(s[i]) > 126:
            return False

    if '{' in s[8:-1]:
        return False
    elif '}' in s[8:-1]:
        return False

    if s[i] != '}':
        return False

    return True

with open('keyed_xor.txt', 'rb') as f:
    data = f.read()

with open('words.txt', 'r') as f:
    words = f.readlines()

key1 = 'tambourine'

for word in words:
    key2 = word.strip()
    key = key1 + key2

    flag = ''
    for i in range(len(data)):
        code = ord(data[i]) ^ ord(key[i%len(key)])
        flag += chr(code)
    if checkFlag(flag):
        print flag

実行結果は以下の通り。

easyctf{flagflagflagflagxeavjtwkmumukhbeqsjrfytkkpsezflnfilrwchgowsfmgqmok}
easyctf{flagljac|qbpflagxeavjtwkgsmqquarqsjrfytkkpsep`lj|toewchgowsfmgqmem}
easyctf{fllldjackqhmflagxeavjtz`osmqfukoqsjrfytkkp~nx`ljktexwchgowsfmg|fmm}
easyctf{flgonjakitagflagxeavjtqcesmydpbeqsjrfytkkpumr`lbiqlrwchgowsfmgwegm}
easyctf{flllnjakmvngflagxeavjtz`esmy`rmeqsjrfytkkp~nr`lbmscrwchgowsfmg|fgm}
easyctf{fl`pxlapalhmflagxeavjtv|sumblhkoqsjrfytkkprrdflyaiexwchgowsfmgpzqk}
easyctf{flflkhanglemflagxeavjtp``qm|jhfoqsjrfytkkptnwblggihxwchgowsfmgvfbo}
easyctf{flag~lac|qbpflagxeavjtwkuumqquarqsjrfytkkpsebflj|toewchgowsfmgqmwk}

この中で英文字だけで構成されているものが一番フラグっぽい。

easyctf{flagflagflagflagxeavjtwkmumukhbeqsjrfytkkpsezflnfilrwchgowsfmgqmok}

Zipperoni (Miscellaneous 160)

bigin.zipを解凍すると、以下のファイルが展開される。

・filename.txt
・hash.txt
・pattern.txt

filename.txtの内容は次の解凍対象のファイル名、pattern.txtの内容は解凍する際のパスワードのフォーマット(0:数字、A:英大文字、a:英小文字)、hash.txtの内容は解凍する際のパスワードのsha1となっていることを前提にプログラムにする。

import zipfile
import itertools
import string
import hashlib

def applyText(text, cat, c, cnt):
    appText = text
    for i in range(cnt):
        appText = appText.replace(cat, c[i], 1)
    return appText

DIR = 'zip_files/'

for i in range(100):
    print 'Round %d' % (i+1)
    if i == 0:
        fname = DIR + 'begin.zip'
        pw = 'coolkarni'
    else:
        with open('filename.txt', 'r') as f:
            fname = f.read().strip()
        with open('hash.txt', 'r') as f:
            h = f.read().strip()
        with open('pattern.txt', 'r') as f:
            ptn = f.read().strip()

        ptn = ptn.replace('0', '#')
        ptn = ptn.replace('A', '$')
        ptn = ptn.replace('a', '%')

        cnt_0 = ptn.count('#')
        cnt_A = ptn.count('$')
        cnt_a = ptn.count('%')
        for c0 in itertools.product(string.digits, repeat=cnt_0):
            for cA in itertools.product(string.uppercase, repeat=cnt_A):
                for ca in itertools.product(string.lowercase, repeat=cnt_a):
                    text = ptn
                    text = applyText(text, '#', c0, cnt_0)
                    text = applyText(text, '$', cA, cnt_A)
                    text = applyText(text, '%', ca, cnt_a)
                    if hashlib.sha1(text).hexdigest() == h:
                        pw = text
                        print pw
                        break

    zf = zipfile.ZipFile(fname, 'r')
    zf.setpassword(pw)
    zf.extractall('.')
    zf.close()

100個目のファイルを解凍すると、flag.txtが展開され、フラグが記載されている。

easyctf{you_must_REALLY_luv_zip_files_by_now!}

RSA_v (Cryptography 200)

RSA暗号であることから、以下が言える。

pow(m, e1, n) = c1
pow(c1, e2, n) = c2
pow(c2, e3, n) = c3
pow(c3, e4, n) = c4
pow(c4, e5, n) = c
→pow(m, e1*e2*e3*e4*e5, n) = c

Wiener's attackで復号する。

from fractions import Fraction

def egcd(a, b):
    x,y, u,v = 0,1, 1,0
    while a != 0:
        q, r = b//a, b%a
        m, n = x-u*q, y-v*q
        b,a, x,y, u,v = a,r, u,v, m,n
        gcd = b
    return gcd, x, y

def decrypt(p, q, e, c):
    n = p * q
    phi = (p - 1) * (q - 1)
    gcd, a, b = egcd(e, phi)
    d = a
    pt = pow(c, d, n)
    return hex(pt)[2:-1].decode('hex')

def continued_fractions(n,e):
    cf = [0]
    while e != 0:
        cf.append(int(n/e))
        N = n
        n = e
        e = N%e
    return cf

def calcKD(cf):
    kd = list()
    for i in range(1,len(cf)+1):
        tmp = Fraction(0)
        for j in cf[1:i][::-1]:
            tmp = 1/(tmp+j)
        kd.append((tmp.numerator,tmp.denominator))
    return kd

def int_sqrt(n):
    def f(prev):
        while True:
            m = (prev + n/prev)/2
            if m >= prev:
                return prev
            prev = m
    return f(n)

def calcPQ(a,b):
    if a*a < 4*b or a < 0:
        return None
    c = int_sqrt(a*a-4*b)
    p = (a + c) /2
    q = (a - c) /2
    if p + q == a and p * q == b:
        return (p,q)
    else:
        return None

def wiener(n,e):
    kd = calcKD(continued_fractions(n,e))
    for (k,d) in kd:
        if k == 0:
            continue
        if (e*d-1) % k != 0:
            continue
        phin = (e*d-1) / k
        if phin >= n:
            continue
        ans = calcPQ(n-phin+1,n)
        if ans is None:
            continue
        return (ans[0],ans[1])

n = 9247606623523847772698953161616455664821867183571218056970099751301682205123115716089486799837447397925308887976775994817175994945760278197527909621793469
e1 = 11
e2 = 41
e3 = 67623079903
e4 = 5161910578063
e5 = 175238643578591220695210061216092361657427152135258210375005373467710731238260448371371798471959129039441888531548193154205671
c = 7117565509436551004326380884878672285722722211683863300406979545670706419248965442464045826652880670654603049188012705474321735863639519103720255725251120

e = e1 * e2 * e3 * e4 * e5
p, q = wiener(n, e)

flag = decrypt(p, q, e, c)
print flag
easyctf{keblftftzibatdsqmqotemmty}

Hidden Key (Crypto 250)

2 * d + phi(n) = A とする。
A * e = 2 * d * e + e * phi(n) mod phi(n)
→ A * e = 2 mod phi(n)
→ phi(n) = A * e - 2 と考えられる。

これを元に復号のコードを書く。

from gmpy2 import *

n = 26710688765026267138148264204920703883007904874128562799028393547554949765922542277895189518670065560334868811558510266979231934353002179757403268531715067062010281686471508752161203685149228268075021781933099554658431559853466187242611729894977296432641592421613434814603355905619394581849422912654259602509865122156934015070617275449499084493067258800113716929834658774822670480345848547269860867133466260711123975552273716095623892922027240248317967859377410150566076391929436484714414414857035650003266909701614148602709844136551594429588283632818636865431390365833016164255279255802323640923522353818320309748633
e = 65537
c = 6155261872379721610545844543378067292233506464929342294811835612600388915322721422713773519949082615325444696280961405812555399866766226739563377342138484240116517586749074812783395471895199325930180026213789355825092956319857260136986497376123985897638747818201019539905731342033491297584936188106793446672492960854016060922453033593011340995980680523319744413970189205384835729579064694334213775169787018937106526040708918216192341791478828953861227176828698102696958311016012460528271599429418317475682934164425649660020076980302878536164096467035358023176811709964428755690089778737252553786028779140848957236412

A = 53253460112850178018596037315460717011178095833855475112480704910365516369607568535550013776774464901421710311058761745335312922569418356218042111675709203846016562029353499192439190013411789736533843417778394708497184559463716663220686541213932946373526645238458498554186241197186952940378859218658633568565865387359341323473553517547778383854342692530241379613507904160499942231159638220574403240144802192330430882869766628877772376237739254238967605943185500475437018419456964233958276405329507956177859421308456900776164204921100052945746833393254101379336114249596223790020916083908846829231724794651160737011674

phi = A * e - 2
d = invert(e, phi)

m = pow(c, d, n)

flag = ('%x' % m).decode('hex')
print flag
easyctf{lbrqfhlyy8fu2bxu3z}