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

HCTF 2018 Writeup

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

xor game (Crypto)

xorの鍵が不明で、その鍵を突き止める問題。
暗号化データをBase64デコードして、XOR Cracker(https://wiremask.eu/tools/xor-cracker/)にかける。
その結果を参考にして調整しながら、鍵を求める。

from Crypto.Util.strxor import strxor
import base64

def dec(data, key):
    key = (key * (len(data) / len(key) + 1))[:len(data)]
    return strxor(data, key)

with open('cipher.txt', 'r') as f:
    data = f.read()

b64enc = base64.b64decode(data)
with open('cipher', 'wb') as f:
    f.write(b64enc)

## arrange ##
print dec(b64enc, 'xor_is_interesting!@#')

これを実行すると、きちんと復号できていることが分かる。

Life, thin and light-off time and time again
Frivolous tireless
one
I heard the echo, from the valleys and the heart
Open to the lonely soul of sickle harvesting
Repeat outrightly, but also repeat the well-being of
Eventually swaying in the desert oasis
I believe I am
Born as the bright summer flowers
Do not withered undefeated fiery demon rule
Heart rate and breathing to bear the load of the cumbersome
Bored
Two
I heard the music, from the moon and carcass
Auxiliary extreme aestheticism bait to capture misty
Filling the intense life, but also filling the pure
There are always memories throughout the earth
I believe I am
Died as the quiet beauty of autumn leaves
Sheng is not chaos, smoke gesture
Even wilt also retained bone proudly Qing Feng muscle
Occult
Three
I hear love, I believe in love
Love is a pool of struggling blue-green algae
As desolate micro-burst of wind
Bleeding through my veins
Years stationed in the belief
Four
I believe that all can hear
Even anticipate discrete, I met the other their own
Some can not grasp the moment
Left to the East to go West, the dead must not return to nowhere
See, I wear Zan Flowers on my head, in full bloom along the way all the way
Frequently missed some, but also deeply moved by wind, frost, snow or rain
Five
Prajna Paramita, soon as soon as
life be beautiful like summer flowers and death like autumn leaves
Also care about what has
hctf{xor_is_interesting!@#}

xor?rsa (Crypto)

$ nc rsa.2018.hctf.io 10086
Welcome to flag getting system
give me your token > 2jrCI4AslZF63Or3g0BCLzKoe0rXGTYZ
n=25275261816080723577383625252321605813293020177515415346273626867915569138289688717413356558193996845476305528125832070604557462428746989292112808118629779995853500422163753237317584703994425361069816700082487715542167171713699666726887235382197073068733120714572524136047731843566904816054447149749558432678912765881098765528412138624603442724195067422316458776382746088522690538448161527517375561342031327658770828995835520042246472144202767031798115768655362628916139166585435054566298105440647015024307766333320215539234184515515996807842360812897141090870808136031078799184603768964924810402365848053621310874371
c1=11165085713561189003280953910551795650069641727697255665204270392356119682302194809551085381412459067137708791048033212887178854783178521082866084322294736924387003502539404006446446960913768668751546334339464672969536040183161881553997971542237143014930493145940738807334373127733697348176468962798526646137597541643569114699950367076718329916819155968155932149333397328469867074678751467552042630972042728542318490144115620371495906795551552788089985932610796897659179997445268713553678805380667254387708279651188394366215609009582176348204515365158369592961753687386325076074438007962972262423575684012468736502784
c2=3692619448894086202620380631082268435018623838407343923441472120868415709925497517010788618542797329154197746377107382855787882364201552243204454392390226061058770734934557129438642712750088115325177185876919748092262061559995643597615670605259279309223337082989077402836087239826642767218219888576126501421187433421552115165902886235999884421474474839807257919154091416829866712451600989516801908414284252064861856712338940516499148321453909303775761944595561080379704862572754567977567513085826683733515940571450314778688781004557900332788704156673505934867551765430160894036472529704734924702982507494191016739987
now give me you answer

それぞれパラメータは以下のようになっている。

p = 1024ビットランダム素数
q = 1024ビットランダム素数
n = p * q
e = 5
kbits = nのビット数 // (2*e*e)
m1 = nのビット数のランダム整数
m2 = m1 ^ kbitsのランダム整数
c1 = pow(m1, e, n)
c2 = pow(m2, e, n)

m1とm2を求める問題。kbitsが上記の定義なので、m1とm2の差は小さく、Coppersmith's Short Pad Attackでそれぞれ復号できる。

# solve.sage
import socket

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

def short_pad_attack(c1, c2, e, n):
    PRxy.<x,y> = PolynomialRing(Zmod(n))
    PRx.<xn> = PolynomialRing(Zmod(n))
    PRZZ.<xz,yz> = PolynomialRing(Zmod(n))

    g1 = x^e - c1
    g2 = (x+y)^e - c2

    q1 = g1.change_ring(PRZZ)
    q2 = g2.change_ring(PRZZ)

    h = q2.resultant(q1)
    h = h.univariate_polynomial()
    h = h.change_ring(PRx).subs(y=xn)
    h = h.monic()

    kbits = n.nbits()//(2*e*e)
    diff = h.small_roots(X=2^kbits, beta=0.5)[0]

    return diff

def related_message_attack(c1, c2, diff, e, n):
    PRx.<x> = PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (x+diff)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()

    return -gcd(g1, g2)[0]

token = '2jrCI4AslZF63Or3g0BCLzKoe0rXGTYZ'

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('rsa.2018.hctf.io', 10086))

data = recvuntil(s, '> ')
print data + token
s.sendall(token + '\n')
data = recvuntil(s, '\n').strip()
print data
n = Integer(data[2:])
e = 5
data = recvuntil(s, '\n').strip()
print data
c1 = Integer(data[3:])
data = recvuntil(s, '\n').strip()
print data
c2 = Integer(data[3:])

diff = short_pad_attack(c1, c2, e, n)
m1 = related_message_attack(c1, c2, diff, e, n)
m2 = m1 + diff

data = recvuntil(s, '\n').strip()
print data
print m1
s.sendall(str(m1) + '\n')
print m2
s.sendall(str(m2) + '\n')
data = s.recv(1024)
print data
hctf{c81d20647f1ff3d55fc25626cd29ec4d95a3d66977973569a5a7a1b2487a1e9a}

SECCON 2018 Online CTF Writeup

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

mnemonic (Crypto)

$ cat mnemonic.txt
{
    "japanese": [
	[
	    "d3a02b9706507552f0e70709f1d4921275204365b4995feae1d949fb59c663cc",
	    "ふじみ あさひ みのう いっち いがく とない はづき ますく いせえび たれんと おとしもの おどろかす ことし おくりがな ちょうし ちきゅう さんきゃく こんとん せつだん ちしき ぬいくぎ まんなか たんい そっと",
	    "338c161dbdb47c570d5d75d5936e6a32178adde370b6774d40d97a51835d7fec88f859e0a6660891fc7758d451d744d5d3b1a1ebd1123e41d62d5a1550156b1f"
	],
	[
	    "dfc9708ac4b4e7f67be6b8e33486482cb363e81967a1569c6fd888b088046f7c",
	    "ほんやく ごうきゅう おさめる たこやき ごかん れいぎ やせる ふるい まんなか てんない だんろ さうな きぼう よくぼう しのぐ よけい こんき みうち らくご いわかん いこく あたためる のはら たぶん",
	    "bdadda5bbff97eb4fda0f11c7141bc3ce3de0fef0b2e4c47900858cec639c10187aee4695b1ba462b1dd34b170b62801e68c270b93af62629f4964947a620ed9"
	],
	[
	    "c0f...",
	    "??? とかす なおす よけい ちいさい さんらん けむり ていど かがく とかす そあく きあい ぶどう こうどう ねみみ にあう ねんぐ ひねる おまいり いちじ ぎゅうにく みりょく ろしゅつ あつめる",
	    "e9a..."
	],
    ],
    "flag": "SECCON{md5(c0f...)}"
}

日本語の名称やタイトルで調べると、タイトル通り、mnemonicの問題。
日本語のリストは以下にあった。

https://github.com/bitcoin/bips/blob/master/bip-0039/japanese.txt

さらに何かよいライブラリがないか調べると、python-mnemonicがあったので、試しにこれを使ってみる。

https://github.com/trezor/python-mnemonic/tree/master/mnemonic

ここにもワードリストがある。試すときに、コード中の最後の方にある部分をenglishからjapaneseに変える。

m = Mnemonic('english')
    ↓
m = Mnemonic('japanese')
$ python mnemonic.py d3a02b9706507552f0e70709f1d4921275204365b4995feae1d949fb59c663cc
ふじみ あさひ みのう いっち いがく とない はづき ますく いせえび たれんと おとしもの おどろかす ことし おくりがな ちょうし ちきゅう さんきゃく こんとん せつだん ちしき ぬいくぎ まんなか たんい そっと
$ python mnemonic.py dfc9708ac4b4e7f67be6b8e33486482cb363e81967a1569c6fd888b088046f7c
ほんやく ごうきゅう おさめる たこやき ごかん れいぎ やせる ふるい まんなか てんない だんろ さうな きぼう よくぼう しのぐ よけい こんき みうち らくご いわかん いこく あたためる のはら たぶん

問題の結果と一致する。この中のコードを見て、逆算する。ただし、1つ目のワードがわからないので、そこはブルートフォースする。
日本語の処理が面倒だったので、リストを見ながら、インデックス(何行目か)を抽出する。その結果を含めたコードは以下の通り。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import hashlib

def decrypt(val):
    idxes = [val, 1333, 1376, 1953, 1173, 777, 570, 1262, 337, 1333, 995, 375,
        1706, 616, 1485, 1404, 1495, 1644, 297, 91, 444, 1844, 2030, 24]

    b = ''
    for idx in idxes:
        b += bin(idx)[2:].zfill(11)
    b = b[:-8]

    data = ''
    for i in range(0, len(b), 8):
        data += chr(int(b[i:i+8], 2))

    return data.encode('hex')

pre = 'c0f'

for i in range(2**11):
    h = decrypt(i)
    if h.startswith(pre):
        print 'val =', str(i)
        print 'h =', h
        break

flag = 'SECCON{' + hashlib.md5(h).hexdigest() + '}'
print flag
SECCON{cda2cb1742d1b6fc21d05c879c263eec}

でもよく考えたら、ブルートフォースする必要はなかった。11bitごとに最初の単語がきまるので、適当な単語で復号して、先頭3文字をc0fにするだけでよかった。

BSides Delhi CTF 2018 Writeup

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

Sanity Check (Welcome 1)

freenodeで#bi0s-ctfチャネルに入る。

20:44 *topic :  Official channel for BSides CTF  | Flag for sanity check: flag{w3lc0m3}  | CTF starts at 11:30 UTC and runs for 24 hours
flag{w3lc0m3}

RSA-Baby (Crypto 100)

modulusのうちそれぞれペアで公約数を出してみる。1以外の公約数があったので、素因数分解できた。素因数分解できたnに対応するeは素数ではない。
例えば、1つ目は以下のようになる。

114194 = 2 * 57097

RSA暗号はm^e%n=cなので、m^(e1*e2)%n=c、(m^e1)^e2%n=cとなる。
一旦57097で復号する。その後2乗根で、フラグが得られるかどうか試してみる。

from Crypto.Util.number import *
import itertools
import gmpy

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

modulus_list = [143786356117385195355522728814418684024129402954309769186869633376407480449846714776247533950484109173163811708549269029920405450237443197994941951104068001708682945191370596050916441792714228818475059839352105948003874426539429621408867171203559281132589926504992702401428910240117807627890055235377744541913L,
 73988726804584255779346831019194873108586184186524793132656027600961771331094234332693404730437468912329694216269372797532334390363774803642809945268154324370355113538927414351037561899998734391507272602074924837440885467211134022878597523920836541794820777951492188067045604789153534513271406458984968338509L,
 95666403279611361071535593067846981517930129087906362381453835849857496766736720885263927273295086034390557353492037703154353541274448884795437287235560639118986397838850340017834752502157881329960725771502503917735194236743345777337851076649842634506339513864285786698870866229339372558162315435127197444193L, 
 119235191922699211973494433973985286182951917872084464216722572875998345005104112625024274855529546680909781406076412741844254205002739352725207590519921992295941563460138887173402493503653397592300336588721082590464192875253265214253650991510709511154297580284525736720396804660126786258245028204861220690641L]

e = [114194L, 130478L, 122694L, 79874L]

ciphertext = ['0c55bc89e3773d8e378121eced4f9300103a8696bc3f9a1542c5b1539442ca5de03a40ad564ab5c2e764b2f946058ec220abf20afc271896ff4ca1f4a2dd227405f221de51e097d6b9f270c4561cd25596e96efd7de1a0e65d37cbf6a73c62a7e323f48450b9dc75e3e738ec1c7e1ae9fc808da8c476e72aea9155125b815653', '67caf9720696b1d0d589f053bb00ebe42b7b26fed38acb4012d29ddc55cd53da8398f042f22987453bdfa2ee8fb35ff121f81e96137995a8ca4daa1fbd88af3fd29138853d5fe98f9b983f67d6fd2b7ff6650228479ca6cac1d49572d28f01a659892b0799ca8202031a1ab37656331470d3ea5f221cc948636c1027bb6dd10f', '65e1cffe93ebccd49a9d14c01b2583a5d5e3140bf38a768833aa494d2d879a2934dbc10a843ec834e9ade286824e68879cb09ac9bd67afd7318b74955e9aa66df5740e6dcc26ccc787f0b415bdc80c6468421c4d4ce615fa3d25350940c5004e9b480c86faebc31e809725a9a868c94e9f1eaac567b4672fe395a7b205775883', '23108bb7f35d12b69bbe5e649ff47fb802b68f22045c484805040a3f4f8669acde8b04daba71190154aef4be9a0eafdebe31b5f96e8b01b5085f502fc0e12a326cc4d867f5317ac12bf16607765d99708934c35c4b9404747f69988ea7d3f4d8022cdfd81ada3aedb22d110db4aa81038aa151c9a4dbb5651757dc092b70b84d']

for ns in list(itertools.combinations(modulus_list, 2)):
    gcd = egcd(ns[0], ns[1])
    if gcd[0] > 1:
        n = ns[0]
        p = gcd[0]
        q = n / p

idx = modulus_list.index(n)
print 'index =', idx

c = int(ciphertext[idx], 16)
phi = (p - 1) * (q - 1)
d = inverse(e[idx]/2, phi)
m_tmp = pow(c, d, n)

m = gmpy.root(m_tmp, 2)[0]
flag = long_to_bytes(m)
print flag
flag{Congratzzz_y0u_kn0w_ext3nded_GCD_WOw!!}

pyQueue (Crypto 150)

keyが少しずつ後ろにずれて、暗号化していて、以下のようなイメージになっている。

1ブロック目:key[1:] + ?
2ブロック目:key[2:] + ? + ?
3ブロック目:key[3:] + ? + ? + ?

MAC: C1 ^ C2 ^ C3 ^ (key[4:] + ? + ? + ? + ?)

最終的なMACがわかっているので、逆算して鍵を割り出していけば、復号できる。鍵の最初の1バイトに対して、ブルートフォース

from Crypto.Util.number import *
from Crypto.Cipher import AES

def is_printable(s):
    for c in s:
        code = ord(c)
        if code > 126:
             return False
    return True

def unpad(s):
    return s[:-ord(s[-1])]

with open('ci.pher.text', 'r') as f:
    data = f.read().split(':')

MAC = int(data[0])
ct = data[1]

ct1 = ct[:32]
ct2 = ct[32:64]
ct3 = ct[64:]
ct = [ct1, ct2, ct3]
key = int(ct1, 16) ^ int(ct2, 16) ^ int(ct3, 16) ^ MAC
key = long_to_bytes(key)

flag = ''
for i in range(len(ct)):
    for code in range(256):
        try_key = chr(code) + key[:-1]
        cipher = AES.new(try_key, AES.MODE_ECB)
        pt = cipher.decrypt(ct[2-i].decode('hex'))
        if is_printable(pt):
            flag = pt + flag
            key = try_key
            break

flag = unpad(flag)
print flag
flag{H4il_bUggi3s!_qu3u3_k3y_2_h0t_4_w4rmUp}

RSA-reloaded (Crypto 200)

rとsは比較的近い素数と考えられるため、Fermat法で素因数分解する。さらにx+f1の次の素数がrになることからxの範囲を探し、2次方程式を解くことによって、p,qを求め復号する。

q%p = x
q = p*k+x

まずはk=1で考える。
⇒p * (p+x) = N1 ⇒ p**2 + x*p = N1
⇒該当するpが見つかる。

最終的なコードは以下の通り。

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

def isqrt(n):
    x = n
    y = (x + n // x) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

def fermat(n):
    x = isqrt(n) + 1
    y = isqrt(x * x - n)
    while True:
        w = x * x - n - y * y
        if w == 0:
            break
        elif w > 0:
            y += 1
        else:
            x += 1
    return x - y, x + y

with open('publickey2.pem', 'r') as f:
    pub_data2 = f.read()

pubkey2 = RSA.importKey(pub_data2)
N2 = pubkey2.n
e = pubkey2.e

r, s = fermat(N2)

with open('ciphertext2.txt', 'r') as f:
    enc_f2 = int(f.read())

phi2 = (r - 1) * (s - 1)
d2 = inverse(e, phi2)
f1 = pow(enc_f2, d2, N2)

flag1 = long_to_bytes(f1)

x = r - f1 - 1

with open('publickey1.pem', 'r') as f:
    pub_data1 = f.read()

pubkey1 = RSA.importKey(pub_data1)
N1 = pubkey1.n
e = pubkey1.e

p = Int('p')
while True:
    np = gmpy.next_prime(x + f1)
    if np != r:
        break
    s = Solver()
    s.add(p**2 + x*p == N1, p > 0)
    if s.check() == sat:
        p = s.model()[p].as_long()
        q = p + x
        break
    x -= 1

with open('ciphertext1.txt', 'r') as f:
    enc_f1 = int(f.read())

phi1 = (p - 1) * (q - 1)
d1 = inverse(e, phi1)
f2 = pow(enc_f1, d1, N1)

flag2 = long_to_bytes(f2)

flag = flag1 + flag2
print flag
flag{F3rm@t_&_s0me_t1nk3r1ng_can_d0_w0nd3rs!!!!}

FuzzY (Forensics 200)

httpでフィルタリングし、success.txtを取得すると、PGP MESSAGEが含まれていた。

-----BEGIN PGP MESSAGE-----
Version: OpenPGP v2.0.8
Comment: https://sela.io/pgp/

wcBMA8fXP+32fyviAQf/T+NzsOgQ+ejW16GeK6h9WS9IDelAN9GLY5x5o9ilBlEL
G4IPati4/zqd+kyV5mmA7k2eKnNByRnxElpp0PoGULX0ykjBTcXuLtNXzGWcDsFF
xAkH8PduoPCcnNGWrCU6D8ZuWNtp7oeZ1krUZP+Kg9sfjjKfx0aUFhWs9SQH6mif
AlbJQwxKi2xXv0UsHvg4Mz4TpVstoO5XcN9d4V+gygc+wx0K61JwAFw96xptNi9y
hdMz/c7yW56JwBfwyiHvYmgLdWYJW9OEoQIj7Rwh1v8mD846vbvEDmagQ0Ra/K6q
lnxa37gBFE+4kYpSXP7yqr8QMhmGDpMROJoJqxYyY9JxAe6317HZ+UUEOmNR+0tB
EmPl/VVaoPc5q6RQ/cxwY4VhR4DtPsG9Gw237Sx+xSTAG5JbmtBf4KfQdVbeaXn1
PYPYBeCVL6nb6uPz6ZHBJ2SODWg9+Ssas+Gd5P7Q0zSA/35qYdamnAqUM/ujM2nN
k2U=
=+x+V
-----END PGP MESSAGE-----

これをsuccess.gpgとして保存する。秘密鍵もどこかにあるはずなので、探してみる。DNSの通信の中にPNGが含まれていたが、分散していたので、スクリプトで抽出する。

from scapy.all import *

packets = rdpcap('final_fuzz.pcap')

png = ''
for p in packets:
    if p.haslayer(IP) and p.haslayer(UDP):
        if p[IP].src == '192.168.42.129' and p[UDP].sport == 53:
            pad = p[Padding].load.lstrip('\x00')
            png += pad

with open('out.png', 'wb') as f:
    f.write(png)

この結果、QRコードの画像が抽出できる。
f:id:satou-y:20181028202214p:plain
このQRコードを読み取ると、以下の秘密鍵が取得できる。

-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: BCPG C# v1.6.1.0

lQOsBFvO/9wBCACgT4fK4dJm+M14jotXPUeKueo8xfFDunNUx/ZaSQbp5Y0i64OZ
dPkQk4E2zCgXaYKNRhiIx2RUy27GBf7xjtDb0gh/HNhC41f5ZzYrNQBEcabcr0hn
VfwiEzAqmTg+5TNsG26ZD2kuO1/J5zbKxI1D3g/9//fe5Nw8GucDiOntKgvFEXeV
ETZ0llbP/mh8SAn5+naJiJJri9y3GF+QhX7wYP+W6mBkano8X/Yk2B2qWIRT6wRU
DMQy1ptavyv5EJhYbsQGeAMu7WPJN+mLutAE2E1Xj03Sevsx2ynN8b/jF/HYp/mZ
SzZ+TbHlRUoMC4+hYh5XfXy7Cx9HSI0uIDShABEBAAH/AwMCEv0ZoXbeXxxg3ioH
/Y0lUhYOormsNzbrBjl1ipyWTDmRAf9BhmAPrX9K5GPAFAurGOj8QOQEWGrOyXfk
gYtHXzGk1K6ItCitgxdBqHgbti23Ht8SmVWw3/pijPXXerXXMqj6NQ95ma6bYPsU
PRtE1qtiEs+T8ln6ZBU9BCNyuZDceBY6btZS0cp88wB1xEPorhXVtjiV1cjDRSFG
licqXh4fr4Qe0TUEeZK1uTqlhdj6YvKoFP94OKGxeM0eR1R/H/zyOtJVMMsEZLGr
GNSVBZBN0B6l9wAMa+DGpuHIX25I197vP3x0v0gvP/57bF9og9mj2JzntM9NJsR1
2zAgplgX4IUp4SGPvcNbLE5c9yIEj77SAOBumrF3IcUYNN9IXvIHQh8qzOWmI5q+
NFCKin0tQNCAx4ef+4ThkyPezRovlFxG6T4HMF1YjYrlVMgiN034opaCKoFXd3EF
4UufN3vV0IYB7AfxWLeNAJyPCreDSyYyLFGx+ONpM5JKk/1cwH8H0XQuLXY+TuGj
iF6QWkRkVcAYv0F7r2wPaVOVa8465s34fY94Rv1+KCpsjNFc3FrAJhz84jETxxqr
s44U/zmGh0/tixjs9vB1C/i7csYWXYJYiPsPmcp5sOE4M1PtYsIfuOlaJ12e/IV9
YnNK+RLghYQ0MghUMHZeg8aqKY7SATDB1SuK+YKmhXte/E/VhTBUy+3RautMIUwS
w9R1z2Hh4POZ4kp8yj9PnEujoQ5XtZuruhNyiWEwWYf1GPuDSoeEYcIRj18h8dL+
OSvsS1DqgPxH9hy8iidLDq1ZkrQ0w08Du9zjVY02f4OoRunzXbis6P3Y0mRf4iif
bYdqVg3snMwY5u9lEaIYqmtcGibgybah394CgTt0xrQTa2FydGlrOTk3QGdtYWls
LmNvbYkBHAQQAQIABgUCW87/3AAKCRDH1z/t9n8r4qnlB/9N1BBWSf6lfmejPh3R
DZ+QrBsCELm8qBeawlsY9To6UUdrIoC9vzIwKAgil2K2MC9z/laZQcep0WepnOar
5KSUyhPI50/aE97yfA0v4lKkylb0OPt8E0S4gIxTlRhpht2K4lsRaD+2wyRvMRuU
/Grgxd5TVVm9KfXQBCAxgFgX2OdZ2/Yb2GJQ4M6DquISIBar+i39a9bdZ9kP70ox
jfgG8SLXPxzBiHIULUy4X+80VafKWw1/AzN2t4CTRtIMHu7jeUqpws+MB6TxTLBA
G/JSdb+W3ceHseJ9YXqVIhfrlKt8T3QAqErjQjPN0YB9KDaELwDM1rxFryy8zuAB
zdZ/
=rb5z
-----END PGP PRIVATE KEY BLOCK-----

これをprivate.ascとして保存する。
復号してみる。

$ gpg --import private.asc
gpg: 鍵F67F2BE2: 秘密鍵をインポートしました
gpg: /home/ctf/.gnupg/trustdb.gpg: 信用データベースができました
gpg: 鍵F67F2BE2: 公開鍵"kartik997@gmail.com"をインポートしました
gpg: 処理数の合計: 1
gpg:               インポート: 1  (RSA: 1)
gpg:       秘密鍵の読み込み: 1
gpg:   秘密鍵のインポート: 1
$ gpg success.gpg

次のユーザの秘密鍵のロックを解除するには
パスフレーズがいります

パスフレーズが必要なので、探してみる。先ほどのQRコードPNGファイルのzTXtチャンクに怪しいデータがある。

4578696600004d4d002a000000080003010e00020000000b000000320128000300000001
000200000213000300000001000100000000000068656c6c6f776f726c640000

末尾のデータをhexデコードする。

$ echo 68656c6c6f776f726c64 | xxd -r -p
helloworld

これをパスフレーズにして復号する。

$ gpg success.gpg

次のユーザの秘密鍵のロックを解除するには
パスフレーズがいります:"kartik997@gmail.com"
2048ビットRSA鍵, ID F67F2BE2作成日付は2018-10-23

gpg: *警告*: 暗号アルゴリズムAES256は受取人の優先指定に入っていません
gpg: 2048-ビットRSA鍵, ID F67F2BE2, 日付2018-10-23に暗号化されました
      "kartik997@gmail.com"
$ cat success
flag{eNcryP7!ng_t0_PgP_1s_r34LLy_Pre3tY_g00D_pr1V4cY}
flag{eNcryP7!ng_t0_PgP_1s_r34LLy_Pre3tY_g00D_pr1V4cY}

HITCON CTF 2018 Writeup

この大会は2018/10/20 11:00(JST)~2018/10/22 11:00(JST)に開催されました。
今回もチームで参戦。結果は 330点で1816チーム中116位でした。
Welcome問題だけでしたが、自分で解けた問題をWriteupとして書いておきます。

Lumosity (Welcome)

プレースホルダにフラグが書いてある。

hitcon{Th1s_f13ld-1s^r3qu1r3d}

Hack.lu CTF 2018 Writeup

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

Relations (Crypto)

いろいろ試してみる。

$ nc arcade.fluxfingers.net 1821
------------------------------
Welcome to theory world
------------------------------

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
XOR

Please choose the operand in hex >>> 01
Ciphertext is  sY4v3laEDC9hcW7OLMoOkxrz7SVVioYl+4ni7XawrypvCKuodcSPtwpvEJyvNGxDQgEM+qCeKcaq
xk1a7I65qQ==

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
XOR

Please choose the operand in hex >>> 0001
Ciphertext is  sY4v3laEDC9hcW7OLMoOkxrz7SVVioYl+4ni7XawrypvCKuodcSPtwpvEJyvNGxDQgEM+qCeKcaq
xk1a7I65qQ==

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
XOR

Please choose the operand in hex >>> 56
Ciphertext is  NWwcbySi44ud+TDvirbaZbwH0P5iXI0DIce9fPsp1PAapdofgXXHF2Up8cVdgmQZff4z9IUxemHh
7vHVQ6pz+w==

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
ADD

Please choose the operand in hex >>> 56
Ciphertext is  YGuF1Rl5Xj44kM+21Fazt7IFH0pDb5VV9QhnWghpIfCQr6fjwi2ko9RHEdw+s7dgEMKTWaf3eum/
QeqPcV6Vng==

$ nc arcade.fluxfingers.net 1821
------------------------------
Welcome to theory world
------------------------------

------------------------------
Possible Oracles
(XOR) Choose XOR Oracle
(ADD) Choose ADD Oracle
(DEC) For trying to decrypt
-----------------------------*
DEC

Enter the key base64 encoded >>> MDEy                                    
Traceback (most recent call last):
  File "/home/chall/rka.py", line 113, in <module>
    main()
  File "/home/chall/rka.py", line 107, in main
    aes = pyaes.AESModeOfOperationECB(key)
  File "/home/chall/pyaes/aes.py", line 304, in __init__
    self._aes = AES(key)
  File "/home/chall/pyaes/aes.py", line 134, in __init__
    raise ValueError('Invalid key size')
ValueError: Invalid key size

どうやらAESの鍵にXORやADDして暗号化した結果を表示していると推定できる。各ビットに対して、XORした鍵とADDした鍵が同じ場合は、ビットが立っていないことになることを利用して、鍵を1ビットずつ割り出し、128ビット分判明したら復号する。

import socket
import re
import base64

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(('arcade.fluxfingers.net', 1821))

b_key = ''
for i in range(128):
    print 'Round %d' % (i + 1)
    print b_key
    send_data = hex(2**i)[2:].rstrip('L')

    data = recvuntil(s, '*\n')
    print data + 'XOR'
    s.sendall('XOR\n')
    data = recvuntil(s, '>>> ')
    print data + send_data
    s.sendall(send_data + '\n')

    data = recvuntil(s, '\n\n')
    pattern = 'Ciphertext is  (.+)\n(.+)\n'
    m = re.search(pattern, data)
    ciphertext = m.group(1) + m.group(2)
    print data

    data = recvuntil(s, '*\n')
    print data + 'ADD'
    s.sendall('ADD\n')
    data = recvuntil(s, '>>> ')
    print data + send_data
    s.sendall(send_data + '\n')

    data = recvuntil(s, '\n\n')
    pattern = 'Ciphertext is  (.+)\n(.+)\n'
    m = re.search(pattern, data)
    ciphertext2 = m.group(1) + m.group(2)
    print data

    if ciphertext == ciphertext2:
        b_key = '0' + b_key
    else:
        b_key = '1' + b_key

key = ''
for i in range(0, 128, 8):
    key += chr(int(b_key[i:i+8], 2))

print key.encode('hex')
b64_key = base64.b64encode(key)

data = recvuntil(s, '*\n')
print data + 'DEC'
s.sendall('DEC\n')
data = recvuntil(s, '>>> ')
print data + b64_key
s.sendall(b64_key + '\n')

data = recvuntil(s, '\n')
print data
flag{r3l4t3d_k3y_der1iviNg_fuNct1on5_h4ve_to_be_a_l1mit3d_cla55}

Multiplayer Part 1 (Crypto)

jsonデータ入力。パラメータは x, y, c, d, groupIDで、以下の形式。

{"x": val, "y": val, "c": val, "d": val, "groupID": val}

X = E*1
X == c*P + d*Q
response = get_response(data["x"], data["y"], data["c"], data["d"], data["groupID"])

DBにない場合は登録。指定GroupIDのデータを201個以上登録できたら、フラグが表示される。c, dをランダムな値にしてXを算出。x, yを指定すればよい。

import socket
import re
from gmpy2 import invert
from random import randint

class EllipticCurve(object):
    def __init__(self, a, b, p):
        self.a = a
        self.b = b
        self.p = p

        self.discriminant = -16 * (4 * a * a * a + 27 * b * b)
        if not self.isSmooth():
            raise Exception("The curve %s is not smooth!" % self)

    def isSmooth(self):
        return self.discriminant != 0

    def testPoint(self, x, y, p):
        return (y ** 2) % p == (x ** 3 + self.a * x + self.b) % p

    def __str__(self):
        return 'y^2 = x^3 + %Gx + %G (mod %G)' % (self.a, self.b, self.p)

    def __eq__(self, other):
        return (self.a, self.b, self.p) == (other.a, other.b, other.p)

class Point(object):
    def __init__(self, curve, x, y):
        self.curve = curve
        self.x = x
        self.y = y
        if not curve.testPoint(x, y, curve.p):
            raise Exception("The point %s is not on the given curve %s" % (self, curve))

    def __neg__(self):
        return Point(self.curve, self.x, -self.y)

    def __add__(self, Q):
        if isinstance(Q, Ideal):
            return self
        x_1, y_1, x_2, y_2 = self.x, self.y, Q.x, Q.y
        if (x_1, y_1) == (x_2, y_2):
            if y_1 == 0:
                return Ideal(self.curve)
            s = (3 * x_1 * x_1 + self.curve.a) * int(invert(2 * y_1, self.curve.p))
        else:
            if x_1 == x_2:
                return Ideal(self.curve)
            s = (y_2 - y_1) * int(invert(x_2 - x_1, self.curve.p))
        x_3 = (s * s - x_2 - x_1) % self.curve.p
        y_3 = (s * (x_3 - x_1) + y_1) % self.curve.p
        return Point(self.curve, x_3, self.curve.p - y_3)

    def __sub__(self, Q):
        return self + -Q

    def __mul__(self, n):
        if not (isinstance(n, int) or isinstance(n, long)):
            raise Exception("Can't scale a point by something which isn't an int!")
        else:
            if n < 0:
                return -self * -n
            if n == 0:
                return Ideal(self.curve)
            else:
                Q = self
                R = self if n & 1 == 1 else Ideal(self.curve)
                i = 2
                while i <= n:
                    Q = Q + Q
                    if n & i == i:
                        R = Q + R
                    i = i << 1
        return R

    def __rmul__(self, n):
        return self * n

class Ideal(Point):

    def __init__(self, curve):
        self.curve = curve

    def __str__(self):
        return "Ideal"

    def __neg__(self):
        return self

    def __add__(self, Q):
        return Q

    def __mul__(self, n):
        if not isinstance(n, int):
            raise Exception("Can't scale a point by something which isn't an int!")
        else:
            return self

p = 889774351128949770355298446172353873
a = 12345
b = 67890
px = 238266381988261346751878607720968495
py = 591153005086204165523829267245014771
qx = 341454032985370081366658659122300896
qy = 775807209463167910095539163959068826

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

def make_json(x, y, c, d):
    groupID = 'n0r4n3c0_m1k3n3c0'
    json_format = '{"x": "%d", "y": "%d", "c": "%d", "d": "%d", "groupID": "%s"}'
    json = json_format % (x, y, c, d, groupID)
    return json

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('arcade.fluxfingers.net', 1822))

E = EllipticCurve(a, b, p)
P = Point(E, px, py)
Q = Point(E, qx, qy)

while True:
    c = randint(1, p - 1)
    d = randint(1, p - 1)
    X = c * P + d * Q
    x = X.x
    y = X.y
    json = make_json(x, y, c, d)
    print json
    s.sendall(json + '\n')
    data = recvuntil(s, '}')
    if 'flag1' in data:
        data += recvuntil(s, '}')
        print data
        break
    else:
        print data
flag{d1str1but3_the_rh0w_p0W3r}

*1:x, y

HumanCTF Writeup

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

Disgruntled (Misc 10)

bijereg@gmail.comに次の文面でメールをしたら、フラグを返してくれた。

Please tell me your secrets!
Best Regards
HumanCTF{nin3_to_fiv3_j0b}

Lottery (Misc 10)

ヒントのメニューが10個あり、すべて-1点。どうやらくじ引きでこのうち1個にフラグが書いてあるらしい。なんとなく7つ目を開いたら、フラグが表示された。

HumanCTF{WINNER!}

Meet the Überbank (Web 20)

HTMLソースのコメントにフラグが書いてあった。

    <!--
    Developed by Sergey Belousov (bijereg@gmail.com) for HumanCTF.
    Telegram: @bijereg
    https://vk.com/bijereg


    FLAG:
    HumanCTF{ha57_du_3in3n_V3r7rag}
    -->
HumanCTF{ha57_du_3in3n_V3r7rag}

What you have tamed? (Web 50)

robotsの話が問題文にあるので、http://ctf.knastu.ru/webch/robots.txt にアクセスすると、フラグが書いてあった。

User-Agent: *
Allow: /
Allow: /jobs
Flag: HumanCTF{skynet_d035n7_pa55}
Disallow: /jobs/manage
Allow: /about
Allow: /cards
Allow: /deposits
HumanCTF{skynet_d035n7_pa55}

More than privacy (Web 80)

Privacy Policyをクリックすると、以下のページにアクセスできる。

http://ctf.knastu.ru/webch/read?file=privacy.txt

http://ctf.knastu.ru/webch/read?file=flag.txt にアクセスしてみると、フラグが表示された。

HumanCTF{n0b0dy_r3ad5_privacy}

Stealer (Stego 90)

そのまま解凍すると、flag.txtが展開されるが、「It is not the flag」と書いてあるだけでフラグは書かれていない。
rarファイルなので、ADSにフラグが隠されていると推測して、WinRARで解凍する。解凍したディレクトリで、以下を実行する。

>dir /R
 ドライブ C のボリューム ラベルは S3A8244D001 です
 ボリューム シリアル番号は 50D2-38C8 です

 C:\CTF\work\Flag のディレクトリ

2018/10/13  11:34    <DIR>          .
2018/10/13  11:34    <DIR>          ..
2018/10/10  22:46                18 Flag.txt
                                 28 Flag.txt:Flag.txt:$DATA
                                345 Flag.txt:Zone.Identifier:$DATA
               1 個のファイル                  18 バイト
               2 個のディレクトリ  1,242,034,556,928 バイトの空き領域

>more < Flag.txt:Flag.txt:$DATA
HumanCTF{d0ub13_fak3_f1ag}
HumanCTF{d0ub13_fak3_f1ag}