hxp CTF 2018 Writeup

この大会は2018/12/7 21:00(JST)~2018/12/9 21:00(JST)に開催されました。
今回もチームで参戦。結果は 385点で818チーム中57位でした。
自分で解けた問題をWriteupとして書いておきます。

daring (CRY 100)

AES暗号(CTRモード)とRSA暗号の結果とRSA暗号の公開鍵が添付されている。
AES暗号の結果は43バイト。RSA暗号の結果は128バイト。
このことから、フラグは43バイトであることがわかる。また、スクリプトからRSA暗号の場合は平文を256**95かけてから暗号化していることがわかる。

(m * 256**95) ** e = c mod n
↓
m**e * 256**95**e = c mod n
↓
m**e = inv(256**95**e, n) * c mod n

またeが3であることからmを3乗してもnを超えるか超えないかという数値になることから簡単に復号できる。

from Crypto.PublicKey import RSA
from Crypto.Util.number import *
import gmpy

with open('aes.enc', 'rb') as f:
    enc_aes = f.read()

with open('rsa.enc', 'rb') as f:
    enc_rsa = f.read()

with open('pubkey.txt', 'r') as f:
    pem = f.read()

len_pad = len(enc_rsa) - len(enc_aes)

pubkey = RSA.importKey(pem)
n = pubkey.n
e = pubkey.e
c = bytes_to_long(enc_rsa) * inverse(2**(len_pad * 8 * e), n) % n

c2 = c
while True:
    m = gmpy.root(c2, e)[0]
    if m**e == c2:
        break
    c2 += n

flag = long_to_bytes(m)
print flag
hxp{DARINGPADS_1s_4n_4n4gr4m_0f_RSAPADDING}

Pwn2Win CTF 2018 Writeup

この大会は2018/12/1 0:37(JST)~2018/12/3 0:37(JST)に開催されました。
今回もチームで参戦。結果は 530点で163チーム中44位でした。
自分で解けた問題をWriteupとして書いておきます。

A Segregated New World [Read first] (Story)

長文の中にフラグが書いてある。

CTF-BR{I_know_about_th3_r1sks!}

g00d b0y (Bonus)

ルールのページ(https://pwn2win.party/rules/?lang=br)の最下部にフラグが書いてある。

CTF-BR{RTFM_1s_4_g00d_3xpr3ss10n_v4.0}

Sum [Hello World Platform] (PPC-M, Platform)

SSL接続するサンプルプログラムがついているので、実行してみる。

received: 5 7 3 5 6 3 6 1 5 3 9 2 0
sent: 55
received: 2 2 5 3 8 0
sent: 20
received: 9 6 9 3 4 2 4 0
sent: 37
received: 3 4 1 7 3 9 3 3 7 8 9 2 8 8 6 6 2 8 0
sent: 97
received: 5 3 4 2 1 5 5 0
sent: 25
received: 2 4 8 3 4 6 2 4 9 8 0
sent: 50
received: 3 9 9 4 7 3 4 8 0
sent: 47
received: 5 4 2 9 8 9 4 1 5 6 3 4 8 9 5 7 8 0
sent: 97
received: 4 8 4 8 1 3 4 2 2 6 3 2 3 7 0
sent: 57
received: 6 8 2 9 7 8 4 9 8 5 5 7 3 6 3 3 7 0
sent: 100
received: 2 7 5 6 9 3 6 0
sent: 38
received: 4 5 7 1 9 1 6 4 1 3 7 5 1 3 5 0
sent: 62
received: 6 5 3 3 7 4 9 0
sent: 37
received: 3 1 8 6 1 1 1 6 4 7 5 7 0
sent: 50
received: 6 2 2 5 9 2 3 5 3 0
sent: 37
received: 6 4 7 6 8 1 8 1 8 6 2 8 3 3 0
sent: 71
received: 3 6 9 5 9 4 1 5 6 2 9 6 4 6 8 7 1 6 0
sent: 97
received: 7 6 5 1 7 9 6 8 0
sent: 49
received: 9 3 5 1 6 1 5 7 5 5 0
sent: 47
received: 6 3 7 7 1 8 7 9 2 5 9 3 7 1 8 8 5 0
sent: 96
received: CTF-BR{Congrats!_you_know_how_to_sum!}
CTF-BR{Congrats!_you_know_how_to_sum!}

The Bavarian Hierarchy (PPC-M, Platform)

最初の1行はNとMを表している。Nは上司、部下の関係みたいな組み合わせの数。Mはある2名の従業員番号の情報の数。
次のN行が上記の組み合わせ。次のM行が上記2名で問題となっている。

N行は左が最も上位で、右の番号に別のデータの左の番号を連結させていくと階層になる。問題となっている2名で共通の上司の数を求める問題になっていることを考慮して、コードを書く。テンプレートは与えられているので、solve関数の中だけ追記すればよい。

def solve(N,M,edges,queries):
    # Solution here, ans = array of answers to each of the queries
    trees = []
    for edge in edges:
        found = False
        for i in range(len(trees)):
            if trees[i][-1] == edge[0]:
                found = True
                trees[i].append(edge[1])
                break
            elif edge[0] in trees[i]:
                found = True
                idx = trees[i].index(edge[0])
                new_tree = [trees[i][j] for j in range(idx + 1)]
                new_tree.append(edge[1])
                trees.append(new_tree)
                break
        if not found:
            trees.append(list(edge))

    ans = []
    for query in queries:
        q = list(query)
        paths = []
        found1 = False
        found2 = False
        for i in range(len(trees)):
            if found1 == False and q[0] in trees[i]:
                found1 = True
                paths.append(trees[i])
            if found2 == False and q[1] in trees[i]:
                found2 = True
                paths.append(trees[i])

        if len(paths[0]) < len(paths[1]):
            min_path = len(paths[0])
        else:
            min_path = len(paths[1])

        count = 0
        for i in range(min_path):
            if paths[0][i] == paths[1][i]:
                count += 1
            else:
                break
        ans.append(count)

    return '\n'.join(map(str,ans)).strip()

    
import ssl, socket

class Connect(object):
    def __init__(self, host, port):
        self.context = ssl.create_default_context()
        self.conn = self.context.wrap_socket(
            socket.socket(socket.AF_INET),
            server_hostname=host)
        self.conn.connect((host, port))
        self.f = self.conn.makefile('rwb', 0)
    def __enter__(self):
        return self.f
    def __exit__(self, type, value, traceback):
        self.f.close()

with Connect('programming.pwn2.win', 9003) as f:
    inew,M = 0,0
    edges = list()
    queries = list()
    for il,line in enumerate(f):
        line = line.strip()
        print('received line %d: %s' % (il,line))
        
        if b'CTF-BR{' in line or b'WRONG' in line: break
            
        if il==inew:
            N,M = map(int,line.split())
            edges = list()
            queries = list()
        elif il<=inew+N:
            a,b = line.split()
            edges.append((a.strip(),b.strip()))
        elif il<=inew+N+M:
            a,b = line.split()
            queries.append((a.strip(),b.strip()))
        
        if il==inew+N+M:
            ans = solve(N,M,edges,queries)

            f.write((ans+'\n').encode('utf-8'))
            print(ans)  # for debugging purposes
            
            inew = il+1

実行すると、15ラウンドほどでフラグが表示された。

CTF-BR{L0w357_C0mMoN_4ncE57Or_1s_u53fu1l_f0r_8i3r4rch13S}

BCTF 2018 Writeup

この大会は2018/11/27 15:00(JST)~2018/11/29 3:00(JST)に開催されました。
今回もチームで参戦。結果は 876点で318チーム中41位でした。
自分で解けた問題をWriteupとして書いておきます。

IRC checkin (Misc)

freenodeで#BCTFチャネルに入る。

21:48 *topic :  BCTF 2018 will start at Nov 27 10:00(UTC+4), Beijing Time 14:00(UTC+8) at https://bctf.xctf.org.cn (if you had registered xctf account, just use it, otherwise please register one and create a team to play) | Flag format: bctf{.+}, if not will be clarified in challenge description. The flag is BCTF{Welcome_to_BCTF2018}
BCTF{Welcome_to_BCTF2018}

guess_polynomial (Crypto)

n: 100以上120以下のランダム整数
coeffリスト: n個の配列(0以上[1をnビットシフトした値]以下のランダム整数)
x: 入力

calcでxを掛けながら配列の各要素に対して和を取る。この和の値を表示する。coeffを推測できればフラグが表示される。
x を2**120より大きい値を指定すれば、簡単に推測できそう。

import socket

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def guess_nums(num, x):
    guess = []
    while True:
        guess.append(str(num % x))
        num /= x
        if num == 0:
            break
    guess.reverse()
    return guess

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('39.96.8.114', 9999))

coeff = 2**128

for i in range(10):
    data = recvuntil(s, 'coeff: ')
    print data + str(coeff)
    s.sendall(str(coeff) + '\n')
    data = recvuntil(s, '\n').strip()
    print data

    num = int(data[17:])
    guess = guess_nums(num, coeff)
    guess = ' '.join(guess)
    data = recvuntil(s, 'coeff!')
    print data + guess
    s.sendall(guess + '\n')

data = recvuntil(s, '\n').strip()
print data
BCTF{One_T1m3_10_Gue33_Coeff_1s_0K!}

ASIS CTF Finals 2018 Writeup

この大会は2018/11/24 15:00(JST)~2018/11/26 15:00(JST)に開催されました。
今回もチームで参戦。結果は 811点で182チーム中28位でした。
自分で解けた問題をWriteupとして書いておきます。

Mic check! (Trivia, Warmup)

問題にフラグが書いてあった。

ASIS{w3lc0m3_b4ck!_7h1s_p14c3_h45n7_b33n_753_s4m3_w1750u7_y0u}

John-Bull (Crypto)

pubkeyを因数分解する。

r**6 + 5*r**5 + 10*r**4 + 13*r**3 + 10*r**2 + 5*r + 1
= (r**2 + r + 1)**2 * (r**2 + 3*r + 1)
= p**2 * q

rは6次方程式になるが、sympyで求められる。上記からp, qがわかり、pubkeyを素因数分解できる。ここでの暗号は以下のようになっている。

enc = pow(m, pubkey, pubkey)
pubkey = p * p * q
phi = p * (p-1) * (q-1)

p * p * q は p * (p-1) * (q-1)と互いに素にならないので、そのまま復号できない。暗号の式を変えてみる。

enc = pow(m, p * p * q, p * p * q)
↓
c = enc % (p * q)とする。
c = pow(m, p * p * q, p * q)

p * p * q は (p-1) * (q-1)と互いに素。これで通常のRSAの復号方法が使える。

from sympy import *
from Crypto.Util.number import *

pubkey = 3415775651990117231114868059991823731694168391465118261123541073986397702947056759501589697018682285283905893102019391953165129250445987511496328390478214156138550568081360884795196720007402795178414072586445084589188812271144227913976270609786532206307549154139514246177504313696905220271023590900584622193476455815728425827517096143262953674043805121028581660274394493861460258597130188538332977679416970808454282017991307383835356188698891323239771333178860346825972405652914210954631134409600833327693593543421410732434281694454355747008933885889869077937880862749049074740126067215284910788706518425606114203333939656875871818894784079170292840540681948732880660003000926906333894065117345867196506856521542472349855590932301830372695420851264943795112040150205561483289746364835891125359307397506516272039186039783992620965800450343112765502550149168357851547665186618429181796721954012847077634388652598794182315250366936611355658686688939934516900009808518223359241944137277154786476218874224865037819222158865245588353031015122185374014406127446401298766736266831637852985756300017995390160761028057020573055543615912481389851812757348379419397130083208775789655825117981028241260930861007152057766814139170496584713321278626253968883276653358428036897577768739458725693447122759791961361097160265922640311146274535842798727318743122276126487545827596583971543880517021741131581309905790220398409615820785382645469996656013188425862658824568438227653902664968157149269346732859000330582545267782235066499139875211275390674091559851875853548905976806413521230016513322214240509217605309858575530246251875145909490471112222194075412324792050366838359937779806344187239056856471058353548936427916942194109609165854034767323294935701500110192365307711393371247166567444590592355257900093259599574780053937287600294098393324949090038950101

enc = 172254401616728337848224556256193294668254066768665624620573955921904663415844987360305683044269987528418379305124320147209588306619600641931717574384801086412717165043339754089854945269488039157031822759552038220243667748187712406870604130874288848961422658181035116276222087495309539815379227353704980160563111860011813283093207521575802100014408033039719223557618408913808906098293389776713037943338000210957134248117986210892735617670398002934139712508431588063592442750666131635721232279579114412057382482634199793027886476451072132799009262126068858578298283046601000880559403008084425323306037979617803443696059413247270929031458924282241105950888791411422687779723342754994324724737283365077742875322607687106058784350376580745162809296384205316865766883773721914203935128431492247985755204159286761485776182149007712492892483933270120585324692872062057327390303613634093538988078627329297217605056348331910353384255935457914956221810806802338940927281542361188610818936740209620052730118914762676848436107226923154169182628394200511074094416135594725057729803618271672713265182266181460723433237689954353512385555171399460870707449024037962690482864822971633650492835486036834553508571232609988066578567702464349018675580536990329927725573108328721614238899767792645665535623446031410358844459259283846182131541439033837736746980370117933542817231688533759434901524622912254499686908606070397662874360407075300248727974071742299708009048853008697832021516188431289704110567432234451743516365186915321744582268642673156685090283640357282428092579506635927396169516769103184205482482269342855874163597222301484981530193824960936155581252590505096646005478378577133665334258562017564658379524993172647857018916805201682065829122503078635804169426240558643597061660475712949155431727872200202869508621492240387907991955060578185917725771

var('r')
eq = Eq(r**6 + 5*r**5 + 10*r**4 + 13*r**3 + 10*r**2 + 5*r + 1 - pubkey)
ans = solve(eq)

r = ans[0]
p, q = r**2+r+1, r**2+3*r+1

assert p * p * q == pubkey

c = enc % (p * q)
phi = (p - 1) * (q - 1)
d = inverse(pubkey, phi)

m = pow(int(c), int(d), int(p * q))

flag = long_to_bytes(m)
print flag
ASIS{_Wo0W_Y0u_4r3_Mas73R_In____Schmidt-Samoa___}

TUCTF 2018 Writeup

この大会は2018/11/24 8:00(JST)~2018/11/26 8:00(JST)に開催されました。
今回もチームで参戦。結果は 7219点で764チーム中65位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (Welcome)

Discordの#welcomeチャネルにフラグが書いてあった。

TUCTF{my_f1r57_fl46}

Mrs. White's <br> Messy Maids (Web)

ソースのコメントに以下が書いてある。

<!-- I might kill if I could find him. Stupid Mr. /Boddy -->

http://18.218.152.56/Boddy/にアクセスすると、フラグが書いてあった。

TUCTF{1_4ccu53_Mr5._Wh173_w17h_7h3_c4ndl3571ck_1n_7h3_c0mm3n75}

Literal (Misc)

$ curl http://18.222.124.7
<html>

<head>
	<meta http-equiv="refresh" content="0; URL='Literal.html'" />
</head>

</html>
$ curl http://18.222.124.7/Literal.html
<html>
  <head>
  <meta http-equiv="Refresh" content="1; url=https://en.wikipedia.org/wiki/Fork_bomb">
  </head>
  <body>
  <!--
        *   *                     f   f   f
      *  ** *                   ff  ff  ff
      * * ** ||                ff  ff  ff
    **   ||||T||              fUffffffff
      *   |C|||T| oooooooooooo fFff
           |||||||{ooooooooooRfff3o
          ooo4ooooooooooooooLff.ooooo0
        oooNooooooooooooooo3ooooooo5ooo.
        oooo4oooooRoooooooooo3oooooooooo.
        oooDooooo4oooooNooooooooooooooooo
        ooooooooooooooGoooooooooooooooooo
        ooooooooooooooooooooo3oooooooooRo
         oooooooo0oooooooooooooooooooooo
          oooooooffUoooooooooooooooooo
            ooofff5ooooooooooooooooo
             fff }ooooooooooooooo
            fff
  -->
  Redirecting to Wikipedia...!
  </body>
</html>

英大文字、数字、'{', '}', '.'を抜き出す。

import string
comment = '''
        *   *                     f   f   f
      *  ** *                   ff  ff  ff
      * * ** ||                ff  ff  ff
    **   ||||T||              fUffffffff
      *   |C|||T| oooooooooooo fFff
           |||||||{ooooooooooRfff3o
          ooo4ooooooooooooooLff.ooooo0
        oooNooooooooooooooo3ooooooo5ooo.
        oooo4oooooRoooooooooo3oooooooooo.
        oooDooooo4oooooNooooooooooooooooo
        ooooooooooooooGoooooooooooooooooo
        ooooooooooooooooooooo3oooooooooRo
         oooooooo0oooooooooooooooooooooo
          oooooooffUoooooooooooooooooo
            ooofff5ooooooooooooooooo
             fff }ooooooooooooooo
            fff
'''

chars = string.uppercase + string.digits + '{}.'

flag = ''
for c in comment:
    if c in chars:
        flag += c

print flag
TUCTF{R34L.0N35.4R3.D4NG3R0U5}

RSAyyyy (Crypto)

RSAの基本的な計算をして答えていく。

import socket
import re
from Crypto.Util.number import *
import sympy

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('3.16.57.250', 12345))

## Level 1 ##
data = recvuntil(s, 'n?\n').strip()
print data
pattern = 'p = (.+)\nq = (.+)\n'
m = re.search(pattern, data)
p = int(m.group(1))
q = int(m.group(2))
n = p * q
print n
s.sendall(str(n) + '\n')

## Level 2 ##
data = recvuntil(s, 'm?\n').strip()
print data
pattern = 'message = \"(.+)\"'
m = re.search(pattern, data)
message = m.group(1)
m = bytes_to_long(message)
print m
s.sendall(str(m) + '\n')

## Level 3 ##
data = recvuntil(s, 'n?\n').strip()
print data
pattern = 'p = (.+)\nq = (.+)\n'
m = re.search(pattern, data)
p = int(m.group(1))
q = int(m.group(2))
n = p * q
print n
s.sendall(str(n) + '\n')

data = recvuntil(s, 'c?\n').strip()
print data
pattern = 'e = (.+)\nm = (.+)\n'
m = re.search(pattern, data)
e = int(m.group(1))
m = int(m.group(2))
c = pow(m, e, p * q)
print c
s.sendall(str(c) + '\n')

## Level 4 ##
data = recvuntil(s, '(n)?\n').strip()
print data
pattern = 'p = (.+)\nq = (.+)\n'
m = re.search(pattern, data)
p = int(m.group(1))
q = int(m.group(2))
phi = (p - 1) * (q - 1)
print phi
s.sendall(str(phi) + '\n')

data = recvuntil(s, 'd?\n').strip()
print data
pattern = 'e = (.+)\n'
m = re.search(pattern, data)
e = int(m.group(1))
d = inverse(e, phi)
print d
s.sendall(str(d) + '\n')

## Level 5 ##
data = recvuntil(s, 'p?\n').strip()
print data
pattern = 'n = (.+)\n'
m = re.search(pattern, data)
n = int(m.group(1))
fac = sympy.factorint(n)
p = fac.keys()[0]
print p
s.sendall(str(p) + '\n')

data = recvuntil(s, 'q?\n').strip()
print data
q = n / p
print q
s.sendall(str(q) + '\n')

## Level 6 ##
data = recvuntil(s, 'p?\n').strip()
print data
pattern = 'c = (.+)\nn = (.+)\ne = (.+)\n'
m = re.search(pattern, data)
c = int(m.group(1))
n = int(m.group(2))
e = int(m.group(3))
fac = sympy.factorint(n)
p = fac.keys()[0]
print p
s.sendall(str(p) + '\n')

data = recvuntil(s, 'q?\n').strip()
print data
q = n / p
print q
s.sendall(str(q) + '\n')

data = recvuntil(s, '(n)?\n').strip()
print data
phi = (p - 1) * (q - 1)
print phi
s.sendall(str(phi) + '\n')

data = recvuntil(s, 'd?\n').strip()
print data
d = inverse(e, phi)
print d
s.sendall(str(d) + '\n')

data = recvuntil(s, 'm?\n').strip()
print data
m = pow(c, d, n)
print m
s.sendall(str(m) + '\n')

data = s.recv(2048)
print data

実行結果は以下のとおり。

$ python solve.py
               AAA               YYYYYYY       YYYYYYYYYYYYYY       YYYYYYYYYYYYYY       YYYYYYY
              A:::A              Y:::::Y       Y:::::YY:::::Y       Y:::::YY:::::Y       Y:::::Y
             A:::::A             Y:::::Y       Y:::::YY:::::Y       Y:::::YY:::::Y       Y:::::Y
            A:::::::A            Y::::::Y     Y::::::YY::::::Y     Y::::::YY::::::Y     Y::::::Y
           A:::::::::A           YYY:::::Y   Y:::::YYYYYY:::::Y   Y:::::YYYYYY:::::Y   Y:::::YYY
          A:::::A:::::A             Y:::::Y Y:::::Y      Y:::::Y Y:::::Y      Y:::::Y Y:::::Y
         A:::::A A:::::A             Y:::::Y:::::Y        Y:::::Y:::::Y        Y:::::Y:::::Y
        A:::::A   A:::::A             Y:::::::::Y          Y:::::::::Y          Y:::::::::Y
       A:::::A     A:::::A             Y:::::::Y            Y:::::::Y            Y:::::::Y
      A:::::AAAAAAAAA:::::A             Y:::::Y              Y:::::Y              Y:::::Y
     A:::::::::::::::::::::A            Y:::::Y              Y:::::Y              Y:::::Y
    A:::::AAAAAAAAAAAAA:::::A           Y:::::Y              Y:::::Y              Y:::::Y
   A:::::A             A:::::A          Y:::::Y              Y:::::Y              Y:::::Y
  A:::::A               A:::::A      YYYY:::::YYYY        YYYY:::::YYYY        YYYY:::::YYYY
 A:::::A                 A:::::A     Y:::::::::::Y        Y:::::::::::Y        Y:::::::::::Y
AAAAAAA                   AAAAAAA    YYYYYYYYYYYYY        YYYYYYYYYYYYY        YYYYYYYYYYYYY

This challenge is designed to act as an introduction to RSA.
If you have a team member that is not already familiar with RSA,
then give this challenge to them.

For the first level, I recommend looking at
https://simple.wikipedia.org/wiki/RSA_algorithm
but any description of the RSA algorithm will do.

Later levels will probably require further research.

Let's get started!



Level 1: Calculating n

p = 4069127677
q = 2789804453
What is n?
11352070513120145681
Whoop whoop!

Congratulations! You beat Level 1!

In order to calculate the ciphertext,
the message needs to be converted to an integer.


Level 2: Calculating m

message = "epidemiology ruminant posy provident condemnatory"
What is m?
3996904368156142538059084847112340597446843790219386982825830622566068551003043414749611727228669241263353749935452793
Yeah! You do that RSA!

Congratulations! You beat Level 2!

Now, we are going to actually calculate ciphertext.


Level 3: Calculating c

p = 2443367911
q = 2356648727
What is n?
5758159877050799297
Whoop whoop!

e = 65537
m = 113667960107631
What is c?
1146535438296311460
Nice job!

Congratulations! You beat Level 3!

In order for RSA to be asymmetrical,
the private exponent, d, needs to be calculated.


Level 4: Calculating d

p = 3288422173
q = 4282201841
What is tot(n)?
14081687475635196480
Ayyyyy

e = 65537
What is d?
4110390793122988673
Way to go!

Congratulations! You beat Level 4!

The easiest way to break RSA is factoring n, if it is possible.


Level 5: Factoring n

n = 6158577329169411137
What is p?
2316690071
Nice job!

What is q?
2658351847
Yeah! You do that RSA!

Congratulations! You beat Level 5!

Now, let's put everything together and break RSA!


Level 6: Breaking simple RSA

c = 5755984274299477791
n = 13316707131108976583
e = 65537
What is p?
3710062907
Way to go!

What is q?
3589348069
Yeah! You do that RSA!

What is tot(n)?
13316707123809565608
Way to go!

What is d?
4877462163356955809
Nice job!

Finally, what is m?
418548049262

That was adequate.

Congratulations! You beat Level 6!


Congratulations on finishing this introduction to RSA!
I hope this was fun and informative.

Here's your flag:
TUCTF{RSA_1$_R34LLY_C00L_4ND_1MP0RT4NT_CRYPT0}
TUCTF{RSA_1$_R34LLY_C00L_4ND_1MP0RT4NT_CRYPT0}

AESential Lesson (Crypto)

$ nc 18.218.238.95 12345

Lol. You think you can steal my flag?
I'll even encrypt your input for you,
but you can't get my secrets!

Enter your text here: 11
Here's your encrypted text:
61537177fe7ceb9cc7eb9e57c64fc9a5492544386adac7645752ed14e253dc501c2da954187caa36ed893ca14c05e767ae1c6abac2df0bbffea785521c616e54

フラグを1文字ずつはみ出させ、ブルートフォースでブロック単位で暗号が一致するものを求めていく。

0123456789abcdef
1111111111111111
111111111111111A
1111111111111111
111111111111111F
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFP

※F: フラグ(32文字)
import socket

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('18.218.238.95', 12345))

flag = ''
for i in range(32):
    for code in range(32, 127):
        plain = '#' * (31 - i) + flag + chr(code) + '#' * (31 - i)
        data = recvuntil(s, 'here: ')
        print data + plain
        s.sendall(plain + '\n')
        data = recvuntil(s, '\n')
        cipher = recvuntil(s, '\n').strip()
        print data + cipher
        if cipher[32:64] == cipher[96:128]:
            flag += chr(code)
            break

print flag
TUCTF{A3S_3CB_1S_VULN3R4BL3!!!!}

Jimmy's Crypto (Crypto)

2つのファイルが同じXOR鍵で暗号化されている。https://github.com/SpiderLabs/cribdragのツールを使って、英単語を推測しながら復号していく。
まず、2つの暗号ファイルのXORを16進数で取得する。

def str_xor(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

with open('secret', 'rb') as f:
    enc_secret = f.read()

with open('flag', 'rb') as f:
    enc_flag = f.read()

xor_str = str_xor(enc_secret, enc_flag)
print xor_str.encode('hex')

'secret' や'this'などよく出てきそうな英単語から推測していく。フラグより後は復号していないが、最終的には以下の結果。

>cribdrag.py 2a1106060001125604001100061c45155512010317475a310b50461c1743060d1d5d00370a0d5511540d2030223866163d103d313c2736472c7a687a39734d22467f2a413c7f2a5f3d343d26547f32406d4737244073355d2b56515e0f00010c5200031415411a06184f532765
                                               :
Your message is currently:
0       This is a secret file. Top secret. Don't
40       steal my secrets. If you are looking at
80       this file,__________________
Your key is currently:
0       ~you have been authorized for this secre
40      t~TUCTF{D0NT_US3_TH3_S4M3_K3Y_F0R_TH3_S4
80      M3_M3SS4G3}__________________
TUCTF{D0NT_US3_TH3_S4M3_K3Y_F0R_TH3_S4M3_M3SS4G3}

Kaspersky Industrial CTF 2018 参戦

この大会は2018/11/23 23:00(JST)~2018/11/24 23:00(JST)に開催されました。
今回もチームで参戦。結果は1272点で130チーム中18位でした。
今回は自分が得点した問題は1問もありませんでした。
Miscの問題くらいは解きたかったけど、残念!

RITSEC CTF 2018 Writeup

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

Litness Test (Misc 1)

問題にフラグが書いてある。

RITSEC{welc0me_t0_th3_CTF!}

Talk to me (Misc 10)

Discordの#announcementsチャネルでフラグを見つけた。

RITSEC{its_like_irc-but_with_2_much_javascript}

Check out this cool filter (Misc 200)

Youtubeのタイトルは「Eiffel 65 - Blue (Da Ba Dee)」。Blueが関係あるのかもしれない。
Blueの値を取得してみると、同じ数値の並びが繰り返されている。
繰り返しを外し、シフトするとフラグになりそうだったので試してみる。

from PIL import Image

img = Image.open('CheckOutThisFilter.png').convert('RGB')
w, h = img.size

codes = []
for y in range(0, h):
    for x in range(0, w):
        r, g, b = img.getpixel((x, y))
        codes.append(b)

codes = codes[:51]

flag = ''
for code in codes:
    flag += chr(code - 13)

print flag
RITSEC{TIL_JPEG_COMPRESSION_MESSES_WITH_RGB_VALUES}

music.png (Misc 300)

赤のみで値を文字にすると、同じ文字列の繰り返しになっている。
緑のみ、青のみも同様。繰り返しなくして、連結してみる。

from PIL import Image

img = Image.open('music.png').convert('RGB')
w, h = img.size

r_str = ''
g_str = ''
b_str = ''
for y in range(0, h):
    for x in range(0, w):
        r, g, b = img.getpixel((x, y))
        r_str += chr(r)
        g_str += chr(g)
        b_str += chr(b)

s = r_str[:32] + g_str[:38] + b_str[:66]
print s

結果は以下の通り。

(t<<3)*[8/9,1,9/8,6/5,4/3,3/2,0][[0xd2d2c7,0xce4087,0xca32c7,0x8e4008][t>>14&3.1]>>(0x3dbe4687>>((t>>10&15)>9?18:t>>10&15)*3&7.1)*3&7.1]

これで検索してみると、以下のページが見つかった。

https://gist.github.com/djcsdy/2875542

値はほぼ同じ。値を入れ替え、ブラウザのデベロッパーツールでConsoleから実行してみたが、うまくいかない。上記のページの値で実行し直し、BASE64データを取得する。デコードして保存するとwavファイルになった。
この曲名当ての問題のようなので、アプリに音楽を聞かせ、曲名が「Never Gonna Give You Up」であることがわかった。

RITSEC{never_gonna_give_you_up}

I am a Stegosaurus (Forensics 250)

TweakPNGで開こうとすると、以下のようなメッセージあり。

Incorrect crc for IHDR chunk (is 93cf1eca, should be 01aae416)

IHDRチャンクのCRCを正しく修正する。画像の下の方にフラグが書いてある。

RITSEC{th1nk_0uts1d3_th3_b0x}

Space Force (Web 100)

' or 1=1 # と入力すると、全データが表示される。Shipname「LbtebKe6yrU8vEnx」のCaptainがフラグだった。

RITSEC{hey_there_h4v3_s0me_point$_3ny2Lx}

CictroHash (Crypto 150)

PDFを読み、ハッシュ処理をスクリプトにする。4バイトのハッシュということもあり、短い文字で衝突しそうなので、ブルートフォースする。

import itertools

def lol(val):
    s = bin(val)[2:].zfill(8)
    s = s[1:] + s[0]
    return int(s, 2)

def rol(val):
    s = bin(val)[2:].zfill(8)
    s = s[-1] + s[:-1]
    return int(s, 2)

def alpha(w):
    return [w[1], w[0]]

def beta(w):
    w[0][0] ^= w[1][3]
    w[0][1] ^= w[1][2]
    w[0][2] ^= w[1][1]
    w[0][3] ^= w[1][0]
    return w

def gamma(w):
    new_w = [[0, 0, 0, 0], [0, 0, 0, 0]]
    new_w[0][3] = w[0][0]
    new_w[1][2] = w[0][1]
    new_w[1][3] = w[0][2]
    new_w[1][1] = w[0][3]
    new_w[0][1] = w[1][0]
    new_w[1][0] = w[1][1]
    new_w[0][2] = w[1][2]
    new_w[0][0] = w[1][3]
    return new_w

def delta(w):
    w[0][0] = lol(w[0][0])
    w[1][0] = lol(w[1][0])
    w[0][2] = lol(w[0][2])
    w[1][2] = lol(w[1][2])
    w[0][1] = rol(w[0][1])
    w[1][1] = rol(w[1][1])
    w[0][3] = rol(w[0][3])
    w[1][3] = rol(w[1][3])
    return w

def round(w):
    w = alpha(w)
    w = beta(w)
    w = gamma(w)
    w = delta(w)
    return w

def f(w):
    for i in range(50):
        w = round(w)
    return w

def xor_pre(w, p):
    for i in  range(4):
        w[0][i] ^= p[i]
    return w

def str_to_blocks(s):
    while True:
        if len(s) % 4 == 0:
            break
        s += '\x00'
    blocks = []
    for i in range(len(s) / 4):
        block = []
        for j in range(4):
            block.append(ord(s[i*4+j]))
        blocks.append(block)
    return blocks

def block_to_hex(block):
    h = ''
    for code in block:
        h += ('%x' % code).zfill(2)
    return h

def get_hash(msg):
    w = [[31, 56, 156, 167], [38, 240, 174, 248]]
    blocks = str_to_blocks(msg)
    for block in blocks:
        w = xor_pre(w, block)
        w = f(w)
    return block_to_hex(w[1])

chars = ''.join([chr(i) for i in range(33, 127)])
dic = {}
found = False
for i in range(1, 6):
    for c in itertools.product(chars, repeat=i):
        text = ''.join(c)
        print text
        h = get_hash(text)
        if h in dic:
            found = True
            print 'Found!'
            print '1:', text
            print '2:', dic[h]
            print 'hash:', h
            break
        else:
            dic[h] = text
    if found:
        break

実行結果は以下の通り。

Found!
1: !!!U
2: tt!
hash: 1ffb77c0
$ curl -X POST http://fun.ritsec.club:8003/checkCollision --header "Content-Type: application/json" --data '{"str1": "!!!U", "str2": "tt!"}'
{
  "flag": "RITSEC{I_am_th3_gr3@t3st_h@XOR_3v@}", 
  "success": true
}
RITSEC{I_am_th3_gr3@t3st_h@XOR_3v@}

Nobody uses the eggplant emoji (Crypto 200)

絵文字を使った換字式暗号だとわかった。まず絵文字を半角英字に置き換える。

ABCDEFCGHAIJCDEFCKLMCNELGHDCEBCGHMCBOKPCBALQGCDEFCRFQGCKIQNMLCGHMQMCGHLMMCSFMQGAEIQTCNHKGCAQCDEFCIKRMUCNHKGCAQCDEFLCSFMQGUCNHKGCAQCGHMCKALCQVMMWCXMOEYAGDCEBCKICFIOKWMICQNKOOENTCDEFLCBOKPCAQZCKBLAYKI_EL_MFLEVMKI_QNKOOEN_NEN_GHMLMQ_K_WABBMLMIYMC

quipqiup(https://quipqiup.com/)で復号してみるが、ダメだった。以下のように最後の_をスペースに置き換えて試す。

ABCDEFCGHAIJCDEFCKLMCNELGHDCEBCGHMCBOKPCBALQGCDEFCRFQGCKIQNMLCGHMQMCGHLMMCSFMQGAEIQTCNHKGCAQCDEFCIKRMUCNHKGCAQCDEFLCSFMQGUCNHKGCAQCGHMCKALCQVMMWCXMOEYAGDCEBCKICFIOKWMICQNKOOENTCDEFLCBOKPCAQZCKBLAYKI EL MFLEVMKI QNKOOEN NEN GHMLMQ K WABBMLMIYMC

結果以下のようになった。

IFS?OUSJHIN?S?OUSARESBORJH?SOFSJHESF?A?SFIRKJS?OUS?UKJSANKBERSJHEKESJHREES?UEKJIONK?SBHAJSIKS?OUSNA?E?SBHAJSIKS?OURS?UEKJ?SBHAJSIKSJHESAIRSKPEEDS?E?OCIJ?SOFSANSUN?ADENSKBA??OB?S?OURSF?A?SIK?SAFRICAN OR EUROPEAN KBA??OB BOB JHEREK A DIFFERENCES

"AFRICAN", "EUROPEAN"や"A DIFFERNCE"は合っていそう。最後は複数形はおかしいので、Sは保留。ここから変換を試しながら、穴を埋めていく。最終コードは以下の通り。

import string

enc = 'ABCDEFCGHAIJCDEFCKLMCNELGHDCEBCGHMCBOKPCBALQGCDEFCRFQGCKIQNMLCGHMQMCGHLMMCSFMQGAEIQTCNHKGCAQCDEFCIKRMUCNHKGCAQCDEFLCSFMQGUCNHKGCAQCGHMCKALCQVMMWCXMOEYAGDCEBCKICFIOKWMICQNKOOENTCDEFLCBOKPCAQZCKBLAYKI_EL_MFLEVMKI_QNKOOEN_NEN_GHMLMQ_K_WABBMLMIYMC'

cipher = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
plain  = 'if youthnkarewlgsmq.,pdvc:'

table = string.maketrans(cipher, plain)
msg = enc.translate(table)
print msg

復号結果は以下の通り。

if you think you are worthy of the flag first you must answer these three questions. what is you name, what is your quest, what is the air speed velocity of an unladen swallow. your flag is: african_or_european_swallow_wow_theres_a_difference
RITSEC{african_or_european_swallow_wow_theres_a_difference}

Who drew on my program? (Crypto 350)

AES CBCモードなので、以下のようになる。

[平文1ブロック目] ^ IV                  --(暗号化)--> [暗号文1ブロック目]
[平文2ブロック目] ^ [暗号文1ブロック目] --(暗号化)--> [暗号文2ブロック目]

平文全体、暗号文1ブロック目の一部と暗号文2ブロック目はわかっている。鍵が14バイトわかっているので、この条件を満たすよう残りはブルートフォースで一致するものを求める。最終的にIVを求めると、フラグがわかる。

from Crypto.Cipher import AES
import binascii
import string

def str_xor(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

def decrypt(ciphertext, passphrase, IV):
    aes = AES.new(passphrase, AES.MODE_CBC, IV)
    return aes.decrypt(ciphertext)

pre_KEY = '9aF738g9AkI112'
plain = 'The message is protected by AES!'
cipher = '9exxxxxxxxxxxxxxxxxxxxxxxxxx436a808e200a54806b0e94fb9633db9d67f0'

found = False
for i in range(32, 127):
    for j in range(32, 127):
        KEY = pre_KEY + chr(i) + chr(j)
        tmp_cipher = cipher.replace('x', '0')
        tmp_cipher = binascii.unhexlify(tmp_cipher)
        dec = decrypt(tmp_cipher, KEY, '0' * 16)
        if dec[16] == plain[16] and dec[30:] == plain[30:]:
            found = True
            break
    if found:
        break

aes = AES.new(KEY, AES.MODE_ECB)
cipher2 = binascii.unhexlify(cipher[32:])
cipher1 = str_xor(aes.decrypt(cipher2), plain[16:])
IV = str_xor(aes.decrypt(cipher1), plain[:16])
print IV
RITSEC{b4dcbc#g}