Square CTF 2018 Writeup

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

C2: flipping bits (cryptology)

共通のnとe1,c1とe2,c2がわかっているので、Common Modules Attackで復号できる。

import gmpy

def commom_modules_attack(c1, c2, e1, e2, n):
    gcd, s1, s2 = gmpy.gcdext(e1, e2)
    if s1 < 0:
        s1 = -s1
        c1 = gmpy.invert(c1, n)
    elif s2 < 0:
        s2 = -s2
        c2 = gmpy.invert(c2, n)
 
    v = pow(c1, s1, n)
    w = pow(c2, s2, n)
    x = (v*w) % n
    return x

c1 = 13981765388145083997703333682243956434148306954774120760845671024723583618341148528952063316653588928138430524040717841543528568326674293677228449651281422762216853098529425814740156575513620513245005576508982103360592761380293006244528169193632346512170599896471850340765607466109228426538780591853882736654
c2 = 79459949016924442856959059325390894723232586275925931898929445938338123216278271333902062872565058205136627757713051954083968874644581902371182266588247653857616029881453100387797111559677392017415298580136496204898016797180386402171968931958365160589774450964944023720256848731202333789801071962338635072065
e1 = 13
e2 = 15
n = 103109065902334620226101162008793963504256027939117020091876799039690801944735604259018655534860183205031069083254290258577291605287053538752280231959857465853228851714786887294961873006234153079187216285516823832102424110934062954272346111907571393964363630079343598511602013316604641904852018969178919051627
 
m = commom_modules_attack(c1, c2, e1, e2, n)
flag = ('%x' % m).decode('hex')
print flag
flag-54d3db5c1efcd7afa579c37bcb560ae0

C3: shredded (image processing)

とりあえずそのままImageMagicで連結してみる。QRコードっぽいことだけわかる。並び替えながら、正しいのを探す。

$ convert +append 0.png 12.png 13.png 5.png 6.png 25.png 16.png 2.png 15.png 26.png 3.png 20.png 10.png 21.png 4.png 19.png 7.png 8.png 1.png 22.png 23.png 24.png 18.png 14.png 11.png 17.png 9.png qr.png

f:id:satou-y:20181115195708p:plain
この場合だめだった。まだ何パターンもあるので、わかっている部分以外で総当たりする。

わからない部分:
・左のPositionの3列の黒の箇所
・真ん中5列(ただし、Timingが黒白黒白黒になることからさらに制限できる)
・右のPositionの3列の黒の箇所
from PIL import Image
import itertools
from pyzbar.pyzbar import decode

FILE_FORMAT = 'shredded/%d.png'
WIDTH = 10
HEIGHT = 297

def makeQR(ary):
    output_img = Image.new('RGB', (WIDTH*27, HEIGHT), (255, 255, 255))

    for i in range(27):
        input_img = Image.open(FILE_FORMAT % ary[i])
        output_img.paste(input_img, (WIDTH*i, 0))

    output_img.save('flag.png')

left = [2, 16, 25]
mid_black = [4, 20, 21]
mid_white = [10, 19]
right = [22, 23, 24]

found = False
for a in itertools.permutations(left):
    for b in itertools.permutations(mid_black):
        for c in itertools.permutations(mid_white):
            for d in itertools.permutations(right):
                ary = [0, 12, 13, 5, 6]
                ary += a
                ary += [15, 26, 3]
                ary += [b[0]]
                ary += [c[0]]
                ary += [b[1]]
                ary += [c[1]]
                ary += [b[2]]
                ary += [7, 8, 1]
                ary += d
                ary += [18, 14, 11, 17, 9]
                makeQR(ary)
                try:
                    data = decode(Image.open('flag.png'))
                    print data[0][0].decode('utf-8', 'ignore')
                    print ary
                    found = True
                    break
                except:
                    continue
            if found:
                break
        if found:
            break
    if found:
        break

実行結果は以下の通り。

GOOD JOB. FLAG-80AD8BCF79
[0, 12, 13, 5, 6, 16, 25, 2, 15, 26, 3, 4, 19, 21, 10, 20, 7, 8, 1, 22, 24, 23, 18, 14, 11, 17, 9]
FLAG-80AD8BCF79

C4: leaky power (cryptology)

AESの相関電力解析 (CPA : Correlation Power Analysis)の問題。
https://wiki.newae.com/Tutorial_B6_Breaking_AES_(Manual_CPA_Attack)を参考にして、鍵を推測しjweを復号するコードを実行する。

import numpy as np
from jwcrypto.common import base64url_decode, base64url_encode
from jwcrypto import jwk, jwe

HW = [bin(n).count("1") for n in range(0,256)]

sbox=(
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)

def intermediate(pt, keyguess):
    return sbox[pt ^ keyguess]

traces = np.load('powertraces.npy')
pt = np.load('plaintexts.npy')

numtraces = np.shape(traces)[0]-1
numpoint = np.shape(traces)[1]

#Use less than the maximum traces by setting numtraces to something
#numtraces = 15

bestguess = [0]*16
#Set 16 to something lower (like 1) to only go through a single subkey & save time!
for bnum in range(0, 16):
    cpaoutput = [0]*256
    maxcpa = [0]*256
    for kguess in range(0, 256):
        print "Subkey %2d, hyp = %02x: "%(bnum, kguess),


        #Initialize arrays & variables to zero
        sumnum = np.zeros(numpoint)
        sumden1 = np.zeros(numpoint)
        sumden2 = np.zeros(numpoint)

        hyp = np.zeros(numtraces)
        for tnum in range(0, numtraces):
            hyp[tnum] = HW[intermediate(pt[tnum][bnum], kguess)]


        #Mean of hypothesis
        meanh = np.mean(hyp, dtype=np.float64)

        #Mean of all points in trace
        meant = np.mean(traces, axis=0, dtype=np.float64)

        #For each trace, do the following
        for tnum in range(0, numtraces):
            hdiff = (hyp[tnum] - meanh)
            tdiff = traces[tnum,:] - meant

            sumnum = sumnum + (hdiff*tdiff)
            sumden1 = sumden1 + hdiff*hdiff 
            sumden2 = sumden2 + tdiff*tdiff

        cpaoutput[kguess] = sumnum / np.sqrt( sumden1 * sumden2 )
        maxcpa[kguess] = max(abs(cpaoutput[kguess]))

        print maxcpa[kguess]

    #Find maximum value of key
    bestguess[bnum] = np.argmax(maxcpa)

print "Best Key Guess: "
key = ''
for b in bestguess:
    key += "%02x" % b
    print "%02x" % b,
print ''

with open('instructions_corrected.jwe', 'r') as f:
    enc = f.read()

key = jwk.JWK(kty = 'oct', k = base64url_encode(key.decode('hex')))

jwetoken = jwe.JWE()
jwetoken.deserialize(enc)
jwetoken.decrypt(key)
payload = jwetoken.payload
print payload

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

    :
Best Key Guess:
d2 de a0 57 d1 14 5f 45 67 96 96 60 24 a7 03 b2
CONFIDENTIAL

To disable C4, you will need:
- 6 bits of Dragon Sumac
- 1 nibble of Winter Spice
- 1 byte of Drake Cardamom
- 1 flag with value flag-e2f27bac480a7857de45
- 2 diskfulls of Tundra Chives
- 5 forks

Grind the Dragon Sumac in a cup, making sure you don't break the cup as it's probably a delicate cup. Add a sprinkle of
liquid ice to turn it into a cream-like paste, then add the Winter Spice, first almost everything, then the last tiny
remnants.

Fill a pan with elemental water, add the mixture and cool it down with how cool you are, then bring the mixture
to a boil. Let it cool down to the body temperature of a reptile before adding the Drake Cardamom and Tundra Chives,
all at once of one, then half at a time of the other.

Bring everything back to a boil, turn of the heat, mix with the forks and let everything cool down. If you
touch the liquid and it burns you, it hasn't cooled down enough.

Whisk the mixture heavily to aerate it. Stop when it's frothy.

Drinking the potion will disable C4.

note: A small, but very cold amount is needed for the potion to be effective. Mixing it in a milkshake could work, but
be wary of brain freeze.
flag-e2f27bac480a7857de45

C5: de-anonymization (gdpr)

datasetからYakubovicsに関する情報を探す。

■Yakubovicsで検索
1.csv
email,job_title,income
eyakubovics9t@nih.gov,Captain,96605

2.csv
email,state
eyakubovics9t@nih.gov,Florida

■96605で検索
4.csv
income,state,street,postal
96605,Florida,4 Magdeline,33421

■"4 Magdeline"で検索
3.csv
ssn_last_four,state,street
4484,Florida,4 Magdeline

5.csv
first_name,street
Elyssa,4 Magdeline

Reset Passwordで以下入力

- First Name: Elyssa
- Last Name: Yakubovics
- Email Address: eyakubovics9t@nih.gov
- What are the last four digits of your SSN?: 4484
- What is the name of the street you grew up on?: 4 Magdeline

Reset Passwordの画面に移るので、HTMLソースを見ると、Current Passwordがフラグになっていた。

<input name='current_password' type='password' value='flag-5ed43dc6356b2b68c689422769952b82' readonly />
flag-5ed43dc6356b2b68c689422769952b82

C8: captcha (math)

HTMLソースを見ると、数式は以下のようになっている。

ggggmt o CW j gggs j gZ j ZWW I ggm o ggp o gn I mtWW o gR I hWWW I EWW o sWW I ggggR o mW j gn o mWW o ggZ j pW I ZWW I gggggp I RW o ggm I sW j gm I mtWWW o ggs o ggs o ZW o gE j gmt j pWWWW o sWW o hW j gC j ggC j EW I gs I gR o pWWWWWWW o gggh o mtW o gggE I EW I EW o RWW j gs I gp j gm o mtWWWWW

表示されている文字はフォント定義により異なる文字を表している。

1回アクセスしたときのbase64文字列からttfを作成しておき、対応する数字のフォントデータを辞書として記憶しておく。それを元に対応する数式に変換し、計算して答える。

import requests
import re
from fontTools.ttLib import TTFont
from fontTools.pens.recordingPen import RecordingPen

def make_formula(records_pen, cmap, s):
    f = s
    for code in cmap:
        glyph_name = cmap[code]
        char = glyph_set[glyph_name]
        recording_pen = RecordingPen()
        char.draw(recording_pen)
        idx = records_pen.index(recording_pen.value)
        f = f.replace(chr(code), chars[idx])
    return f

r = requests.get('https://hidden-island-93990.squarectf.com/ea6c95c6d0ff24545cad')
body = r.text

pattern = 'base64,(.+)\'\);\}.+<p>(.+)</p>.+\"token\" value=\"(\d+)\"'
m = re.search(pattern, body)
b64_ttf = m.group(1)
sign = m.group(2)
token = m.group(3)

with open('tmp.ttf', 'wb') as f:
    f.write(b64_ttf.decode('base64'))

chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '(', ')', '+', '-', '*']

f = TTFont('base.ttf')
glyph_set = f.getGlyphSet()
cmap = f.getBestCmap()

base_list = [102, 120, 87, 111, 113, 65, 70, 89, 119, 107, 78, 98, 115, 81, 100]
records_pen = []
for code in base_list:
    glyph_name = cmap[code]
    char = glyph_set[glyph_name]
    recording_pen = RecordingPen()
    char.draw(recording_pen)
    records_pen.append(recording_pen.value)

f = TTFont('tmp.ttf')
glyph_set = f.getGlyphSet()
cmap = f.getBestCmap()
formula = make_formula(records_pen, cmap, sign)
print formula
ans = eval(formula)
print ans

param = {'token': token, 'answer': str(ans)}
r = requests.post('https://hidden-island-93990.squarectf.com/ea6c95c6d0ff24545cad', data = param)
print r.text

実行結果は以下の通り。

(((((3 * (2 - 10)) * 8) + ((((3 - (10 - 9)) * 6) * ((7 + 2) - 5)) + (((9 + ((6 - 7) * (8 - (2 + (9 + 4))))) * ((2 - 6) * ((4 - 3) * 8))) - (1 - 1)))) * (((7 + 1) - 10) * (3 + 3))) - ((2 + 8) * ((((1 - 4) + (4 * (7 - (4 - 6)))) * ((3 * ((1 - 1) + 5)) + ((2 * (5 * 2)) + 1))) - ((1 * (7 * 10)) * 9))))
2292
<html><body><p>If you can see this then you have solved the CAPTCHA.</p><p>If you have solved the CAPTCHA then you are a Charvise.</p><p>If you are a Charvise then you are welcome on Charvis.</p><p>If you are welcome on Charvis then:</p><ol><li>You can disable this system using flag-a76013167fd4c04e3134</li><li><p>You should be given useful information.</p><p>If you should be given useful information then this informs you that there are two layers of defense left, and the last one is trivial.</p></body></html>
flag-a76013167fd4c04e3134